Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,7 @@ void {{ neuronName }}::handle(nest::CurrentEvent& e)
{%- for blk in neuron.get_on_receive_blocks() %}
{%- set ast = blk.get_block() %}
void
{{ neuronName }}::on_receive_block_{{ blk.get_port_name() }}()
{{ neuronName }}::on_receive_block_{{ blk.get_port_name() }}{% if blk.has_port_index() %}_VEC_{{ blk.get_port_index() }}{% endif %} ()
{
{%- filter indent(2, True) -%}
{%- include "directives_cpp/Block.jinja2" %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ public:

{% filter indent(2, True) -%}
{%- for blk in neuron.get_on_receive_blocks() %}
void on_receive_block_{{ blk.get_port_name() }}();
void on_receive_block_{{ blk.get_port_name() }}{% if blk.has_port_index() %}_VEC_{{ blk.get_port_index() }}{% endif %}();
{%- endfor %}
{%- endfilter %}

Expand Down
593 changes: 309 additions & 284 deletions pynestml/generated/PyNestMLParser.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pynestml/grammars/PyNestMLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ parser grammar PyNestMLParser;
/** ASTOnReceiveBlock
@attribute block implementation of the dynamics
*/
onReceiveBlock: ON_RECEIVE_KEYWORD LEFT_PAREN inputPortName=NAME (COMMA constParameter)* RIGHT_PAREN COLON
onReceiveBlock: ON_RECEIVE_KEYWORD LEFT_PAREN inputPortName=NAME (LEFT_SQUARE_BRACKET vectorParameter=expression RIGHT_SQUARE_BRACKET)? (COMMA constParameter)* RIGHT_PAREN COLON
block;

/** ASTOnConditionBlock
Expand Down
4 changes: 2 additions & 2 deletions pynestml/meta_model/ast_node_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ def create_ast_namespace_decorator(cls, namespace=None, name=None, source_positi
return ASTNamespaceDecorator(namespace, name, source_position=source_position)

@classmethod
def create_ast_on_receive_block(cls, block=None, port_name=None, const_parameters=None, source_position=None):
return ASTOnReceiveBlock(block, port_name, const_parameters, source_position=source_position)
def create_ast_on_receive_block(cls, block=None, port_name=None, port_index=None, const_parameters=None, source_position=None):
return ASTOnReceiveBlock(block, port_name, port_index, const_parameters, source_position=source_position)

@classmethod
def create_ast_on_condition_block(cls, block=None, cond_expr=None, const_parameters=None, source_position=None):
Expand Down
18 changes: 17 additions & 1 deletion pynestml/meta_model/ast_on_receive_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class ASTOnReceiveBlock(ASTNode):

"""

def __init__(self, block: ASTBlock, port_name: str, const_parameters: Optional[Mapping] = None, *args, **kwargs):
def __init__(self, block: ASTBlock, port_name: str, port_index: Optional[int], const_parameters: Optional[Mapping] = None, *args, **kwargs):
r"""
Standard constructor.
:param block: a block of definitions.
Expand All @@ -47,6 +47,7 @@ def __init__(self, block: ASTBlock, port_name: str, const_parameters: Optional[M
super(ASTOnReceiveBlock, self).__init__(*args, **kwargs)
self.block = block
self.port_name = port_name
self.port_index = port_index
self.const_parameters = const_parameters
if self.const_parameters is None:
self.const_parameters = {}
Expand All @@ -59,6 +60,7 @@ def clone(self) -> ASTOnReceiveBlock:
"""
dup = ASTOnReceiveBlock(block=self.block.clone(),
port_name=self.port_name,
port_index=self.port_index,
const_parameters=self.const_parameters,
# ASTNode common attributes:
source_position=self.source_position,
Expand Down Expand Up @@ -87,6 +89,20 @@ def get_port_name(self) -> str:
"""
return self.port_name

def has_port_index(self) -> bool:
r"""
Returns whether there is a port vector index
:return: the port index
"""
return self.port_index is not None

def get_port_index(self) -> Optional[int]:
r"""
Returns the port vector index if there is one, otherwise None
:return: the port index
"""
return self.port_index

def get_children(self) -> List[ASTNode]:
r"""
Returns the children of this node, if any.
Expand Down
8 changes: 7 additions & 1 deletion pynestml/visitors/ast_builder_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,13 @@ def visitOnReceiveBlock(self, ctx):
const_parameters = {}
for el in ctx.constParameter():
const_parameters[el.name.text] = el.value.text
ret = ASTNodeFactory.create_ast_on_receive_block(block=block, port_name=port_name, const_parameters=const_parameters, source_position=create_source_pos(ctx))

port_index = None
if ctx.vectorParameter:
vector_parameter = self.visit(ctx.vectorParameter)
port_index = int(vector_parameter.get_numeric_literal())

ret = ASTNodeFactory.create_ast_on_receive_block(block=block, port_name=port_name, port_index=port_index, const_parameters=const_parameters, source_position=create_source_pos(ctx))
update_node_comments(ret, self.__comments.visit(ctx))
return ret

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
onreceive_vector_input_ports_neuron
###################################

Description
+++++++++++

Used in NESTML unit testing.

"""
model onreceive_vector_input_ports_neuron:
state:
x real = 0
y real = 0
z real = 0

input:
spikes[2] <- spike

output:
spike

onReceive(spikes):
x += 1

onReceive(spikes[0]):
y += 1

onReceive(spikes[1]):
z += 1
109 changes: 109 additions & 0 deletions tests/nest_tests/test_onreceive_vector_input_ports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
#
# test_on_receive_vector_input_ports.py
#
# This file is part of NEST.
#
# Copyright (C) 2004 The NEST Initiative
#
# NEST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# NEST is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with NEST. If not, see <http://www.gnu.org/licenses/>.

import numpy as np
import os
import pytest

import nest

from pynestml.codegeneration.nest_tools import NESTTools
from pynestml.frontend.pynestml_frontend import generate_nest_target

try:
import matplotlib
import matplotlib.pyplot as plt
TEST_PLOTS = True
except BaseException:
TEST_PLOTS = False


@pytest.mark.skipif(NESTTools.detect_nest_version().startswith("v2"),
reason="This test does not support NEST 2")
class TestOnReceiveVectorInputPorts:

def test_multisynapse_with_vector_input_ports(self):
input_path = os.path.join(os.path.realpath(os.path.join(
os.path.dirname(__file__), "resources", "onreceive_vector_input_ports_neuron.nestml")))
target_path = "target"
logging_level = "DEBUG"
suffix = "_nestml"

generate_nest_target(input_path,
target_path=target_path,
logging_level=logging_level,
suffix=suffix)

nest.ResetKernel()
nest.set_verbosity("M_ALL")
nest.resolution = 0.1
try:
nest.Install("nestmlmodule")
except Exception:
# ResetKernel() does not unload modules for NEST Simulator < v3.7; ignore exception if module is already loaded on earlier versions
pass

# network construction
neuron = nest.Create("onreceive_vector_input_ports_neuron_nestml")

# List of receptor types for the spiking input ports
receptor_types = nest.GetStatus(neuron, "receptor_types")[0]

sg = nest.Create("spike_generator", params={"spike_times": [20., 80.]})
nest.Connect(sg, neuron, syn_spec={"receptor_type": receptor_types["SPIKES_0"], "weight": 1000., "delay": 0.1})

sg2 = nest.Create("spike_generator", params={"spike_times": [40., 60.]})
nest.Connect(sg2, neuron, syn_spec={"receptor_type": receptor_types["SPIKES_1"], "weight": 1000., "delay": 0.1})

mm = nest.Create("multimeter", params={"record_from": ["x", "y", "z"], "interval": nest.resolution})
nest.Connect(mm, neuron)

# simulate
nest.Simulate(125.)

if TEST_PLOTS:
fig, ax = plt.subplots(nrows=3)

ax[0].plot(mm.events["times"], mm.events["x"], label="x")
ax[0].set_ylabel("voltage")

ax[1].plot(mm.events["times"], mm.events["y"], label="y")
ax[1].set_ylabel("current")

ax[2].plot(mm.events["times"], mm.events["z"], label="z")
ax[2].set_ylabel("current")

for _ax in ax:
_ax.legend(loc="upper right")
_ax.set_xlim(0., 125.)
_ax.grid(True)

for _ax in ax[:-1]:
_ax.set_xticklabels([])

ax[-1].set_xlabel("time")

fig.savefig("/tmp/test_onreceive_vector_input_ports.png")

# testing
np.testing.assert_almost_equal(mm.events["x"][-1], 4)
np.testing.assert_almost_equal(mm.events["y"][-1], 2)
np.testing.assert_almost_equal(mm.events["z"][-1], 2)
Loading