Skip to content
Snippets Groups Projects
Unverified Commit d06d74a6 authored by Jannik Luboeinski's avatar Jannik Luboeinski Committed by GitHub
Browse files

New policy 'round_robin_halt' (#1868)

Introduces the new policy `round_robin_halt`. This enables to halt at the current item until the policy `round_robin` is called again.

Related to issue #1749.
parent 71f76bd6
No related branches found
No related tags found
No related merge requests found
......@@ -8,6 +8,8 @@ ARB_ARBOR_API std::ostream& operator<<(std::ostream& o, lid_selection_policy pol
switch (policy) {
case lid_selection_policy::round_robin:
return o << "round_robin";
case lid_selection_policy::round_robin_halt:
return o << "round_robin_halt";
case lid_selection_policy::assert_univalent:
return o << "univalent";
}
......
......@@ -70,6 +70,7 @@ struct lid_range {
enum class lid_selection_policy {
round_robin,
round_robin_halt,
assert_univalent // throw if the range of possible lids is wider than 1
};
......
......@@ -78,7 +78,6 @@ lid_hopefully label_resolution_map::range_set::at(unsigned idx) const {
// Offset into the range containing idx.
const auto& range_part = part.at(ridx);
auto offset = idx - range_part.first;
return start + offset;
}
......@@ -126,6 +125,19 @@ lid_hopefully round_robin_state::update(const label_resolution_map::range_set& r
return lid;
}
cell_lid_type round_robin_state::get() {
return state;
}
lid_hopefully round_robin_halt_state::update(const label_resolution_map::range_set& range_set) {
auto lid = range_set.at(state);
return lid;
}
cell_lid_type round_robin_halt_state::get() {
return state;
}
lid_hopefully assert_univalent_state::update(const label_resolution_map::range_set& range_set) {
if (range_set.size() != 1) {
return util::unexpected("range is not univalent");
......@@ -134,11 +146,29 @@ lid_hopefully assert_univalent_state::update(const label_resolution_map::range_s
return range_set.at(0);
}
cell_lid_type assert_univalent_state::get() {
return 0;
}
// resolver methods
resolver::state_variant resolver::construct_state(lid_selection_policy pol) {
switch (pol) {
case lid_selection_policy::round_robin:
return round_robin_state();
case lid_selection_policy::round_robin_halt:
return round_robin_halt_state();
case lid_selection_policy::assert_univalent:
return assert_univalent_state();
default: return assert_univalent_state();
}
}
resolver::state_variant resolver::construct_state(lid_selection_policy pol, cell_lid_type state) {
switch (pol) {
case lid_selection_policy::round_robin:
return round_robin_state(state);
case lid_selection_policy::round_robin_halt:
return round_robin_halt_state(state);
case lid_selection_policy::assert_univalent:
return assert_univalent_state();
default: return assert_univalent_state();
......@@ -151,15 +181,24 @@ cell_lid_type resolver::resolve(const cell_global_label_type& iden) {
}
const auto& range_set = label_map_->at(iden.gid, iden.label.tag);
// Construct state if if doesn't exist
if (!state_map_[iden.gid][iden.label.tag].count(iden.label.policy)) {
state_map_[iden.gid][iden.label.tag][iden.label.policy] = construct_state(iden.label.policy);
}
// Policy round_robin_halt: use previous state of round_robin policy, if existent
if (iden.label.policy == lid_selection_policy::round_robin_halt
&& state_map_[iden.gid][iden.label.tag].count(lid_selection_policy::round_robin)) {
cell_lid_type prev_state_rr = std::visit([range_set](auto& state) { return state.get(); }, state_map_[iden.gid][iden.label.tag][lid_selection_policy::round_robin]);
state_map_[iden.gid][iden.label.tag][lid_selection_policy::round_robin_halt] = construct_state(lid_selection_policy::round_robin_halt, prev_state_rr);
}
// Construct state if it doesn't exist
if (!state_map_[iden.gid][iden.label.tag].count(iden.label.policy)) {
state_map_[iden.gid][iden.label.tag][iden.label.policy] = construct_state(iden.label.policy);
}
// Update state
auto lid = std::visit([range_set](auto& state) { return state.update(range_set); }, state_map_[iden.gid][iden.label.tag][iden.label.policy]);
if (!lid) {
throw arb::bad_connection_label(iden.gid, iden.label.tag, lid.error());
}
return lid.value();
}
......
......@@ -88,13 +88,23 @@ private:
};
struct ARB_ARBOR_API round_robin_state {
cell_size_type state = 0;
cell_lid_type state = 0;
round_robin_state() : state(0) {};
round_robin_state(cell_lid_type state) : state(state) {};
cell_lid_type get();
lid_hopefully update(const label_resolution_map::range_set& range);
};
struct ARB_ARBOR_API round_robin_halt_state {
cell_lid_type state = 0;
round_robin_halt_state() : state(0) {};
round_robin_halt_state(cell_lid_type state) : state(state) {};
cell_lid_type get();
lid_hopefully update(const label_resolution_map::range_set& range);
};
struct ARB_ARBOR_API assert_univalent_state {
cell_lid_type get();
lid_hopefully update(const label_resolution_map::range_set& range);
};
......@@ -105,9 +115,10 @@ struct ARB_ARBOR_API resolver {
cell_lid_type resolve(const cell_global_label_type& iden);
private:
using state_variant = std::variant<round_robin_state, assert_univalent_state>;
using state_variant = std::variant<round_robin_state, round_robin_halt_state, assert_univalent_state>;
state_variant construct_state(lid_selection_policy pol);
state_variant construct_state(lid_selection_policy pol, cell_lid_type state);
const label_resolution_map* label_map_;
std::unordered_map<cell_gid_type, std::unordered_map<cell_tag_type, std::unordered_map <lid_selection_policy, state_variant>>> state_map_;
......
......@@ -54,6 +54,10 @@ cells and members of cell-local collections.
Iterate over the items of the group in a round-robin fashion.
.. cpp:enumerator:: round_robin_halt
Halts at the current item of the group until the round_robin policy is called (again).
.. cpp:enumerator:: assert_univalent
Assert that ony one item is available in the group. Throws an exception if the assertion
......
......@@ -18,6 +18,10 @@ The types defined below are used as identifiers for cells and members of cell-lo
Iterate over the items of the group in a round-robin fashion.
.. attribute:: round_robin_halt
Halts at the current item of the group until the round_robin policy is called (again).
.. attribute:: univalent
Assert that only one item is available in the group. Throws an exception if the assertion
......
......@@ -27,7 +27,7 @@ make_catalogue(
NAME default
SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/default"
OUTPUT "CAT_DEFAULT_SOURCES"
MOD exp2syn expsyn expsyn_stdp hh kamt kdrmt nax nernst pas gj
MOD exp2syn expsyn expsyn_curr expsyn_stdp hh kamt kdrmt nax nernst pas gj
CXX
PREFIX "${PROJECT_SOURCE_DIR}/mechanisms"
CXX_FLAGS_TARGET "${ARB_CXX_FLAGS_TARGET_FULL}"
......
: Exponential current-based synapse
NEURON {
POINT_PROCESS expsyn_curr
RANGE w, tau, R_mem
NONSPECIFIC_CURRENT I
}
UNITS {
(ms) = (milliseconds)
(mV) = (millivolt)
(MOhm) = (megaohm)
}
PARAMETER {
R_mem = 10.0 (MOhm) : membrane resistance
tau = 5.0 (ms) : synaptic time constant
w = 4.20075 (mV) : weight
}
STATE {
g (mV) : instantaneous synaptic conductance
}
INITIAL {
g = 0
}
BREAKPOINT {
:SOLVE state METHOD cnexp
SOLVE state METHOD sparse : to match with expsyn_curr_calcium_plasticity
I = -g / R_mem
}
DERIVATIVE state {
: Exponential decay of postsynaptic potential
g' = -g / tau
}
NET_RECEIVE(weight) {
if (weight >= 0) {
: Start of postsynaptic potential
g = g + w
}
}
......@@ -18,7 +18,9 @@ void register_identifiers(py::module& m) {
py::enum_<arb::lid_selection_policy>(m, "selection_policy",
"Enumeration used to identify a selection policy, used by the model for selecting one of possibly multiple locations on the cell associated with a labeled item.")
.value("round_robin", arb::lid_selection_policy::round_robin,
"iterate round-robin over all possible locations.")
"Iterate round-robin over all possible locations.")
.value("round_robin_halt", arb::lid_selection_policy::round_robin_halt,
"Halts at the current location until the round_robin policy is called (again).")
.value("univalent", arb::lid_selection_policy::assert_univalent,
"Assert that there is only one possible location associated with a labeled item on the cell. The model throws an exception if the assertion fails.");
......
......@@ -140,34 +140,10 @@ class empty_recipe(arbor.recipe):
"""
pass
@_fixture
def cable_cell():
# (1) Create a morphology with a single (cylindrical) segment of length=diameter=6 μm
tree = arbor.segment_tree()
tree.append(
arbor.mnpos,
arbor.mpoint(-3, 0, 0, 3),
arbor.mpoint(3, 0, 0, 3),
tag=1,
)
# (2) Define the soma and its midpoint
labels = arbor.label_dict({'soma': '(tag 1)',
'midpoint': '(location 0 0.5)'})
# (3) Create cell and set properties
decor = arbor.decor()
decor.set_property(Vm=-40)
decor.paint('"soma"', arbor.density('hh'))
decor.place('"midpoint"', arbor.iclamp( 10, 2, 0.8), "iclamp")
decor.place('"midpoint"', arbor.spike_detector(-10), "detector")
return arbor.cable_cell(tree, labels, decor)
@_fixture
class art_spiker_recipe(arbor.recipe):
"""
Recipe fixture with 3 artificial spiking cells.
Recipe fixture with 3 artificial spiking cells and one cable cell.
"""
def __init__(self):
super().__init__()
......@@ -201,15 +177,52 @@ class art_spiker_recipe(arbor.recipe):
else:
return [arbor.cable_probe_membrane_voltage('"midpoint"')]
@cable_cell
def _cable_cell(self, cable_cell):
return cable_cell
def _cable_cell_elements(self):
# (1) Create a morphology with a single (cylindrical) segment of length=diameter=6 μm
tree = arbor.segment_tree()
tree.append(
arbor.mnpos,
arbor.mpoint(-3, 0, 0, 3),
arbor.mpoint(3, 0, 0, 3),
tag=1,
)
# (2) Define the soma and its midpoint
labels = arbor.label_dict({'soma': '(tag 1)',
'midpoint': '(location 0 0.5)'})
# (3) Create cell and set properties
decor = arbor.decor()
decor.set_property(Vm=-40)
decor.paint('"soma"', arbor.density('hh'))
decor.place('"midpoint"', arbor.iclamp( 10, 2, 0.8), "iclamp")
decor.place('"midpoint"', arbor.spike_detector(-10), "detector")
# return tuple of tree, labels, and decor for creating a cable cell (can still be modified before calling arbor.cable_cell())
return tree, labels, decor
def cell_description(self, gid):
if gid < 3:
return arbor.spike_source_cell("src", arbor.explicit_schedule(self.trains[gid]))
else:
return self._cable_cell()
tree, labels, decor = self._cable_cell_elements()
return arbor.cable_cell(tree, labels, decor)
@_fixture
def sum_weight_hh_spike():
"""
Fixture returning connection weight for 'expsyn_stdp' mechanism which is just enough to evoke an immediate spike
at t=1ms in the 'hh' neuron in 'art_spiker_recipe'
"""
return 0.4
@_fixture
def sum_weight_hh_spike_2():
"""
Fixture returning connection weight for 'expsyn_stdp' mechanism which is just enough to evoke an immediate spike
at t=1.8ms in the 'hh' neuron in 'art_spiker_recipe'
"""
return 0.36
@_fixture
@context
......
# -*- coding: utf-8 -*-
#
# test_multiple_connections.py
import unittest
import types
import numpy as np
import arbor as arb
from .. import fixtures
"""
tests for multiple connections onto the same postsynaptic label and for one connection that has the same net impact as the multiple-connection paradigm,
thereby testing the selection policies 'round_robin', 'round_robin_halt', and 'univalent'
NOTE: In principle, a plasticity (STDP) mechanism is employed here to test if a selected connection uses the correct instance of the mechanism.
Thus, the scenario in Test #1 is intentionally "a wrong one", as opposed to the scenario in Test #2. In Test #1, one presynaptic neuron effectively connects _via one synapse_ to two postsynaptic neurons,
and the spike at t=0.8ms in presynaptic neuron 0 will enhance potentiation in both the first and the second synapse mechanism. In Test #2, this is prevented by the 'round_robin_halt' policy, whereby the
potentiation in the second synapse mechanism is only enhanced by spikes of presynaptic neuron 1.
"""
class TestMultipleConnections(unittest.TestCase):
# Constructor (overridden)
def __init__(self, args):
super(TestMultipleConnections, self).__init__(args)
self.runtime = 2 # ms
self.dt = 0.01 # ms
# Method creating a new mechanism for a synapse with STDP
def create_syn_mechanism(self, scale_contrib = 1):
# create new synapse mechanism
syn_mechanism = arb.mechanism("expsyn_stdp")
# set pre- and postsynaptic contributions for STDP
syn_mechanism.set("Apre", 0.01 * scale_contrib)
syn_mechanism.set("Apost", -0.01 * scale_contrib)
# set minimal decay time
syn_mechanism.set("tau", self.dt)
return syn_mechanism
# Method that does the final evaluation for all tests
def evaluate_outcome(self, sim, handle_mem):
# membrane potential should temporarily be above the spiking threshold at around 1.0 ms (only testing this if the current node keeps the data, cf. GitHub issue #1892)
if len(sim.samples(handle_mem)) > 0:
data_mem, _ = sim.samples(handle_mem)[0]
#print(data_mem[(data_mem[:, 0] >= 1.0), 1])
self.assertGreater(data_mem[(np.round(data_mem[:, 0], 2) == 1.02), 1], -10)
self.assertLess(data_mem[(np.round(data_mem[:, 0], 2) == 1.05), 1], -10)
# neuron 3 should spike at around 1.0 ms, when the added input from all connections will cause threshold crossing
spike_times = sim.spikes()["time"]
spike_gids = sim.spikes()["source"]["gid"]
#print(list(zip(*[spike_times, spike_gids])))
self.assertGreater(sum(spike_gids == 3), 0)
self.assertAlmostEqual(spike_times[(spike_gids == 3)][0], 1.00, delta=0.04)
# Method that does additional evaluation for Test #1
def evaluate_additional_outcome_1(self, sim, handle_mem):
# order of spiking neurons (also cf. 'test_spikes.py')
spike_gids = sim.spikes()["source"]["gid"]
self.assertEqual([2, 1, 0, 3, 3], spike_gids.tolist())
# neuron 3 should spike again at around 1.8 ms, when the added input from all connections will cause threshold crossing
spike_times = sim.spikes()["time"]
self.assertAlmostEqual(spike_times[(spike_gids == 3)][1], 1.80, delta=0.04)
# Method that does additional evaluation for Test #2 and Test #3
def evaluate_additional_outcome_2_3(self, sim, handle_mem):
# order of spiking neurons (also cf. 'test_spikes.py')
spike_gids = sim.spikes()["source"]["gid"]
self.assertEqual([2, 1, 0, 3], spike_gids.tolist())
# Method that runs the main part of Test #1 and Test #2
def rr_main(self, context, art_spiker_recipe, weight, weight2):
# define new method 'cell_description()' and overwrite the original one in the 'art_spiker_recipe' object
create_syn_mechanism = self.create_syn_mechanism
def cell_description(self, gid):
# spike source neuron
if gid < 3:
return arb.spike_source_cell("spike_source", arb.explicit_schedule(self.trains[gid]))
# spike-receiving cable neuron
elif gid == 3:
tree, labels, decor = self._cable_cell_elements()
scale_stdp = 0.5 # use only half of the original magnitude for STDP because two connections will come into play
decor.place('"midpoint"', arb.synapse(create_syn_mechanism(scale_stdp)), "postsyn_target") # place synapse for input from one presynaptic neuron at the center of the soma
decor.place('"midpoint"', arb.synapse(create_syn_mechanism(scale_stdp)), "postsyn_target") # place synapse for input from another presynaptic neuron at the center of the soma
# (using the same label as above!)
return arb.cable_cell(tree, labels, decor)
art_spiker_recipe.cell_description = types.MethodType(cell_description, art_spiker_recipe)
# read connections from recipe for testing
connections_from_recipe = art_spiker_recipe.connections_on(3)
# connection #1 from neuron 0 to 3
self.assertEqual(connections_from_recipe[0].dest.label, "postsyn_target")
self.assertAlmostEqual(connections_from_recipe[0].weight, weight)
self.assertAlmostEqual(connections_from_recipe[0].delay, 0.2)
# connection #2 from neuron 0 to 3
self.assertEqual(connections_from_recipe[1].dest.label, "postsyn_target")
self.assertAlmostEqual(connections_from_recipe[1].weight, weight)
self.assertAlmostEqual(connections_from_recipe[1].delay, 0.2)
# connection #1 from neuron 1 to 3
self.assertEqual(connections_from_recipe[2].dest.label, "postsyn_target")
self.assertAlmostEqual(connections_from_recipe[2].weight, weight2)
self.assertAlmostEqual(connections_from_recipe[2].delay, 1.4)
# connection #2 from neuron 1 to 3
self.assertEqual(connections_from_recipe[3].dest.label, "postsyn_target")
self.assertAlmostEqual(connections_from_recipe[3].weight, weight2)
self.assertAlmostEqual(connections_from_recipe[3].delay, 1.4)
# construct domain_decomposition and simulation object
dd = arb.partition_load_balance(art_spiker_recipe, context)
sim = arb.simulation(art_spiker_recipe, dd, context)
sim.record(arb.spike_recording.all)
# create schedule and handle to record the membrane potential of neuron 3
reg_sched = arb.regular_schedule(0, self.dt, self.runtime)
handle_mem = sim.sample((3, 0), reg_sched)
# run the simulation
sim.run(self.runtime, self.dt)
return sim, handle_mem
# Test #1 (for 'round_robin')
@fixtures.context
@fixtures.art_spiker_recipe
@fixtures.sum_weight_hh_spike
@fixtures.sum_weight_hh_spike_2
def test_multiple_connections_rr_no_halt(self, context, art_spiker_recipe, sum_weight_hh_spike, sum_weight_hh_spike_2):
weight = sum_weight_hh_spike/2 # connection strength which is, summed over two connections, just enough to evoke an immediate spike at t=1ms
weight2 = 0.97*sum_weight_hh_spike_2/2 # connection strength which is, summed over two connections, just NOT enough to evoke an immediate spike at t=1.8ms
# define new method 'connections_on()' and overwrite the original one in the 'art_spiker_recipe' object
def connections_on(self, gid):
# incoming to neurons 0--2
if gid < 3:
return []
# incoming to neuron 3
elif gid == 3:
source_label_0 = arb.cell_global_label(0, "spike_source") # referring to the "spike_source" label of neuron 0
source_label_1 = arb.cell_global_label(1, "spike_source") # referring to the "spike_source" label of neuron 1
target_label_rr = arb.cell_local_label("postsyn_target", arb.selection_policy.round_robin) # referring to the current item in the "postsyn_target" label group of neuron 3, moving to the next item afterwards
conn_0_3_n1 = arb.connection(source_label_0, target_label_rr, weight, 0.2) # first connection from neuron 0 to 3
conn_0_3_n2 = arb.connection(source_label_0, target_label_rr, weight, 0.2) # second connection from neuron 0 to 3
# NOTE: this is not connecting to the same target label item as 'conn_0_3_n1' because 'round_robin' has been used before!
conn_1_3_n1 = arb.connection(source_label_1, target_label_rr, weight2, 1.4) # first connection from neuron 1 to 3
conn_1_3_n2 = arb.connection(source_label_1, target_label_rr, weight2, 1.4) # second connection from neuron 1 to 3
# NOTE: this is not connecting to the same target label item as 'conn_1_3_n1' because 'round_robin' has been used before!
return [conn_0_3_n1, conn_0_3_n2, conn_1_3_n1, conn_1_3_n2]
art_spiker_recipe.connections_on = types.MethodType(connections_on, art_spiker_recipe)
# run the main part of this test
sim, handle_mem = self.rr_main(context, art_spiker_recipe, weight, weight2)
# evaluate the outcome
self.evaluate_outcome(sim, handle_mem)
self.evaluate_additional_outcome_1(sim, handle_mem)
# Test #2 (for the combination of 'round_robin_halt' and 'round_robin')
@fixtures.context
@fixtures.art_spiker_recipe
@fixtures.sum_weight_hh_spike
@fixtures.sum_weight_hh_spike_2
def test_multiple_connections_rr_halt(self, context, art_spiker_recipe, sum_weight_hh_spike, sum_weight_hh_spike_2):
weight = sum_weight_hh_spike/2 # connection strength which is, summed over two connections, just enough to evoke an immediate spike at t=1ms
weight2 = 0.97*sum_weight_hh_spike_2/2 # connection strength which is, summed over two connections, just NOT enough to evoke an immediate spike at t=1.8ms
# define new method 'connections_on()' and overwrite the original one in the 'art_spiker_recipe' object
def connections_on(self, gid):
# incoming to neurons 0--2
if gid < 3:
return []
# incoming to neuron 3
elif gid == 3:
source_label_0 = arb.cell_global_label(0, "spike_source") # referring to the "spike_source" label of neuron 0
source_label_1 = arb.cell_global_label(1, "spike_source") # referring to the "spike_source" label of neuron 1
target_label_rr_halt = arb.cell_local_label("postsyn_target", arb.selection_policy.round_robin_halt) # referring to the current item in the "postsyn_target" label group of neuron 3
target_label_rr = arb.cell_local_label("postsyn_target", arb.selection_policy.round_robin) # referring to the current item in the "postsyn_target" label group of neuron 3, moving to the next item afterwards
conn_0_3_n1 = arb.connection(source_label_0, target_label_rr_halt, weight, 0.2) # first connection from neuron 0 to 3
conn_0_3_n2 = arb.connection(source_label_0, target_label_rr, weight, 0.2) # second connection from neuron 0 to 3
conn_1_3_n1 = arb.connection(source_label_1, target_label_rr_halt, weight2, 1.4) # first connection from neuron 1 to 3
conn_1_3_n2 = arb.connection(source_label_1, target_label_rr, weight2, 1.4) # second connection from neuron 1 to 3
return [conn_0_3_n1, conn_0_3_n2, conn_1_3_n1, conn_1_3_n2]
art_spiker_recipe.connections_on = types.MethodType(connections_on, art_spiker_recipe)
# run the main part of this test
sim, handle_mem = self.rr_main(context, art_spiker_recipe, weight, weight2)
# evaluate the outcome
self.evaluate_outcome(sim, handle_mem)
self.evaluate_additional_outcome_2_3(sim, handle_mem)
# Test #3 (for 'univalent')
@fixtures.context
@fixtures.art_spiker_recipe
@fixtures.sum_weight_hh_spike
@fixtures.sum_weight_hh_spike_2
def test_multiple_connections_uni(self, context, art_spiker_recipe, sum_weight_hh_spike, sum_weight_hh_spike_2):
weight = sum_weight_hh_spike # connection strength which is just enough to evoke an immediate spike at t=1ms (equaling the sum of two connections in Test #2)
weight2 = 0.97*sum_weight_hh_spike_2 # connection strength which is just NOT enough to evoke an immediate spike at t=1.8ms (equaling the sum of two connections in Test #2)
# define new method 'connections_on()' and overwrite the original one in the 'art_spiker_recipe' object
def connections_on(self, gid):
# incoming to neurons 0--2
if gid < 3:
return []
# incoming to neuron 3
elif gid == 3:
source_label_0 = arb.cell_global_label(0, "spike_source") # referring to the "spike_source" label of neuron 0
source_label_1 = arb.cell_global_label(1, "spike_source") # referring to the "spike_source" label of neuron 1
target_label_uni_n1 = arb.cell_local_label("postsyn_target_1", arb.selection_policy.univalent) # referring to an only item in the "postsyn_target_1" label group of neuron 3
target_label_uni_n2 = arb.cell_local_label("postsyn_target_2", arb.selection_policy.univalent) # referring to an only item in the "postsyn_target_2" label group of neuron 3
conn_0_3 = arb.connection(source_label_0, target_label_uni_n1, weight, 0.2) # connection from neuron 0 to 3
conn_1_3 = arb.connection(source_label_1, target_label_uni_n2, weight2, 1.4) # connection from neuron 1 to 3
return [conn_0_3, conn_1_3]
art_spiker_recipe.connections_on = types.MethodType(connections_on, art_spiker_recipe)
# define new method 'cell_description()' and overwrite the original one in the 'art_spiker_recipe' object
create_syn_mechanism = self.create_syn_mechanism
def cell_description(self, gid):
# spike source neuron
if gid < 3:
return arb.spike_source_cell("spike_source", arb.explicit_schedule(self.trains[gid]))
# spike-receiving cable neuron
elif gid == 3:
tree, labels, decor = self._cable_cell_elements()
decor.place('"midpoint"', arb.synapse(create_syn_mechanism()), "postsyn_target_1") # place synapse for input from one presynaptic neuron at the center of the soma
decor.place('"midpoint"', arb.synapse(create_syn_mechanism()), "postsyn_target_2") # place synapse for input from another presynaptic neuron at the center of the soma
# (using another label as above!)
return arb.cable_cell(tree, labels, decor)
art_spiker_recipe.cell_description = types.MethodType(cell_description, art_spiker_recipe)
# read connections from recipe for testing
connections_from_recipe = art_spiker_recipe.connections_on(3)
# connection from neuron 0 to 3
self.assertEqual(connections_from_recipe[0].dest.label, "postsyn_target_1")
self.assertAlmostEqual(connections_from_recipe[0].weight, weight)
self.assertAlmostEqual(connections_from_recipe[0].delay, 0.2)
# connection from neuron 1 to 3
self.assertEqual(connections_from_recipe[1].dest.label, "postsyn_target_2")
self.assertAlmostEqual(connections_from_recipe[1].weight, weight2)
self.assertAlmostEqual(connections_from_recipe[1].delay, 1.4)
# construct domain_decomposition and simulation object
dd = arb.partition_load_balance(art_spiker_recipe, context)
sim = arb.simulation(art_spiker_recipe, dd, context)
sim.record(arb.spike_recording.all)
# create schedule and handle to record the membrane potential of neuron 3
reg_sched = arb.regular_schedule(0, self.dt, self.runtime)
handle_mem = sim.sample((3, 0), reg_sched)
# run the simulation
sim.run(self.runtime, self.dt)
# evaluate the outcome
self.evaluate_outcome(sim, handle_mem)
self.evaluate_additional_outcome_2_3(sim, handle_mem)
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment