diff --git a/arbor/arbexcept.cpp b/arbor/arbexcept.cpp index d12883b5fcb1d6f0ad853a6a0dbf308fbdc28f7e..e27f04fad7501d7e5358eca41fc10e2c7303c602 100644 --- a/arbor/arbexcept.cpp +++ b/arbor/arbexcept.cpp @@ -38,7 +38,8 @@ bad_connection_source_gid::bad_connection_source_gid(cell_gid_type gid, cell_gid {} bad_connection_source_lid::bad_connection_source_lid(cell_gid_type gid, cell_lid_type src_lid, cell_size_type num_sources): - arbor_exception(pprintf("Model building error on cell {}: connection source index {} is out of range. Cell {} has {} sources, in the range [{}:{}].", gid, src_lid, gid, num_sources, 0, num_sources-1)), + arbor_exception(pprintf("Model building error on cell {}: connection source index {} is out of range. Cell {} has {} sources", gid, src_lid, gid, num_sources) + + (num_sources? pprintf(", in the range [{}:{}].", 0, num_sources-1) : ".")), gid(gid), src_lid(src_lid), num_sources(num_sources) {} @@ -48,7 +49,19 @@ bad_connection_target_gid::bad_connection_target_gid(cell_gid_type gid, cell_gid {} bad_connection_target_lid::bad_connection_target_lid(cell_gid_type gid, cell_lid_type tgt_lid, cell_size_type num_targets): - arbor_exception(pprintf("Model building error on cell {}: connection target index {} is out of range. Cell {} has {} targets, in the range [{}:{}].", gid, tgt_lid, gid, num_targets, 0, num_targets-1)), + arbor_exception(pprintf("Model building error on cell {}: connection target index {} is out of range. Cell {} has {} targets", gid, tgt_lid, gid, num_targets) + + (num_targets ? pprintf(", in the range [{}:{}].", 0, num_targets-1) : ".")), + gid(gid), tgt_lid(tgt_lid), num_targets(num_targets) +{} + +bad_event_generator_target_gid::bad_event_generator_target_gid(cell_gid_type gid, cell_gid_type tgt_gid): + arbor_exception(pprintf("Model building error on cell {}: event_generator target gid {} has to match cell gid {}].", gid, tgt_gid, gid)), + gid(gid), tgt_gid(tgt_gid) +{} + +bad_event_generator_target_lid::bad_event_generator_target_lid(cell_gid_type gid, cell_lid_type tgt_lid, cell_size_type num_targets): + arbor_exception(pprintf("Model building error on cell {}: event_generator target index {} is out of range. Cell {} has {} targets", gid, tgt_lid, gid, num_targets) + + (num_targets ? pprintf(", in the range [{}:{}].", 0, num_targets-1) : ".")), gid(gid), tgt_lid(tgt_lid), num_targets(num_targets) {} diff --git a/arbor/include/arbor/arbexcept.hpp b/arbor/include/arbor/arbexcept.hpp index f2c49675604bc038267c01ec3652cb70431057e4..814c628fc98e96e405727cf2ff680f95eb627ff7 100644 --- a/arbor/include/arbor/arbexcept.hpp +++ b/arbor/include/arbor/arbexcept.hpp @@ -79,6 +79,18 @@ struct bad_connection_target_lid: arbor_exception { cell_size_type num_targets; }; +struct bad_event_generator_target_gid: arbor_exception { + bad_event_generator_target_gid(cell_gid_type gid, cell_gid_type tgt_gid); + cell_gid_type gid, tgt_gid; +}; + +struct bad_event_generator_target_lid: arbor_exception { + bad_event_generator_target_lid(cell_gid_type gid, cell_lid_type tgt_lid, cell_size_type num_targets); + cell_gid_type gid; + cell_lid_type tgt_lid; + cell_size_type num_targets; +}; + struct bad_global_property: arbor_exception { explicit bad_global_property(cell_kind kind); cell_kind kind; diff --git a/arbor/include/arbor/event_generator.hpp b/arbor/include/arbor/event_generator.hpp index 8d12a21817ad54e2579dfea9625d052a3cbea77d..4e526b58eb7c8f3460b724cb4c114fe148cd3757 100644 --- a/arbor/include/arbor/event_generator.hpp +++ b/arbor/include/arbor/event_generator.hpp @@ -16,12 +16,12 @@ namespace arb { // An `event_generator` generates a sequence of events to be delivered to a cell. // The sequence of events is always in ascending order, i.e. each event will be -// greater than the event that proceded it, where events are ordered by: +// greater than the event that proceeded it, where events are ordered by: // - delivery time; // - then target id for events with the same delivery time; // - then weight for events with the same delivery time and target. // -// An `event_generator` supports two operations: +// An `event_generator` supports three operations: // // `void event_generator::reset()` // @@ -32,6 +32,10 @@ namespace arb { // Provide a non-owning view on to the events in the time interval // [to, from). // +// `std::vector<cell_member_type> targets()` +// +// Return a vector of all the targets of the generator. +// // The `event_seq` type is a pair of `spike_event` pointers that // provide a view onto an internally-maintained contiguous sequence // of generated spike event objects. This view is valid only for @@ -64,6 +68,9 @@ struct empty_generator { event_seq events(time_type, time_type) { return {nullptr, nullptr}; } + std::vector<cell_member_type> targets() { + return {}; + }; }; class event_generator { @@ -95,10 +102,15 @@ public: return impl_->events(t0, t1); } + std::vector<cell_member_type> targets() const { + return impl_->targets(); + } + private: struct interface { virtual void reset() = 0; virtual event_seq events(time_type, time_type) = 0; + virtual std::vector<cell_member_type> targets() = 0; virtual std::unique_ptr<interface> clone() = 0; virtual ~interface() {} }; @@ -114,6 +126,10 @@ private: return wrapped.events(t0, t1); } + std::vector<cell_member_type> targets() override { + return wrapped.targets(); + } + void reset() override { wrapped.reset(); } @@ -153,6 +169,10 @@ struct schedule_generator { return {events_.data(), events_.data()+events_.size()}; } + std::vector<cell_member_type> targets() { + return {target_}; + } + private: pse_vector events_; cell_member_type target_; @@ -217,6 +237,12 @@ struct explicit_generator { return {lb, ub}; } + std::vector<cell_member_type> targets() { + std::vector<cell_member_type> tgts; + std::transform(events_.begin(), events_.end(), std::back_inserter(tgts), [](auto&& e){ return e.target;}); + return tgts; + } + private: pse_vector events_; std::size_t start_index_ = 0; diff --git a/arbor/simulation.cpp b/arbor/simulation.cpp index 9dae450c4f51cc610d647e752ead57222ad7f9ab..4564487df80ffc400a6c50a04a8d76474cf3b8f5 100644 --- a/arbor/simulation.cpp +++ b/arbor/simulation.cpp @@ -203,8 +203,23 @@ simulation_state::simulation_state( // Store mapping of gid to local cell index. gid_to_local_[gid] = gid_local_info{lidx, grpidx}; + // Check validity of event_generator targets + auto event_gens = rec.event_generators(gid); + auto num_targets = rec.num_targets(gid); + for (const auto& g: event_gens) { + for (const auto& t: g.targets()) { + if (t.gid != gid) { + throw arb::bad_event_generator_target_gid(gid, t.gid); + } + if (t.index >= num_targets) { + throw arb::bad_event_generator_target_lid(gid, t.index, num_targets); + } + } + } + // Set up the event generators for cell gid. - event_generators_[lidx] = rec.event_generators(gid); + event_generators_[lidx] = event_gens; + ++lidx; } ++grpidx; diff --git a/python/example/single_cell_stdp.py b/python/example/single_cell_stdp.py index 92816a210d68548cb237a6f5bb4524fb49533ffc..84fa6550fa2e02acc721717f129a3845fb69a714 100755 --- a/python/example/single_cell_stdp.py +++ b/python/example/single_cell_stdp.py @@ -22,6 +22,9 @@ class single_recipe(arbor.recipe): def num_sources(self, gid): return 1 + def num_targets(self, gid): + return 2 + def cell_kind(self, gid): return arbor.cell_kind.cable diff --git a/test/unit/test_recipe.cpp b/test/unit/test_recipe.cpp index 13abf4fb3a0a958e69b9f897977cb632776bfe3a..0d2a648985bf268c4dae50e707513224931b26a4 100644 --- a/test/unit/test_recipe.cpp +++ b/test/unit/test_recipe.cpp @@ -26,12 +26,14 @@ namespace { std::vector<cell_size_type> num_sources, std::vector<cell_size_type> num_targets, std::vector<std::vector<cell_connection>> conns, - std::vector<std::vector<gap_junction_connection>> gjs): + std::vector<std::vector<gap_junction_connection>> gjs, + std::vector<std::vector<arb::event_generator>> gens): num_cells_(cells.size()), num_sources_(num_sources), num_targets_(num_targets), connections_(conns), gap_junctions_(gjs), + event_generators_(gens), cells_(cells) {} cell_size_type num_cells() const override { @@ -55,6 +57,9 @@ namespace { cell_size_type num_targets(cell_gid_type gid) const override { return num_targets_[gid]; } + std::vector<arb::event_generator> event_generators(cell_gid_type gid) const override { + return event_generators_[gid]; + } std::any get_global_properties(cell_kind) const override { arb::cable_cell_global_properties a; a.default_parameters = arb::neuron_parameter_defaults; @@ -66,6 +71,7 @@ namespace { std::vector<cell_size_type> num_sources_, num_targets_; std::vector<std::vector<cell_connection>> connections_; std::vector<std::vector<gap_junction_connection>> gap_junctions_; + std::vector<std::vector<arb::event_generator>> event_generators_; std::vector<cable_cell> cells_; }; @@ -111,13 +117,13 @@ TEST(recipe, num_sources) auto cell = custom_cell(1, 0, 0); { - auto recipe_0 = custom_recipe({cell}, {1}, {0}, {{}}, {{}}); + auto recipe_0 = custom_recipe({cell}, {1}, {0}, {{}}, {{}}, {{}}); auto decomp_0 = partition_load_balance(recipe_0, context); EXPECT_NO_THROW(simulation(recipe_0, decomp_0, context)); } { - auto recipe_1 = custom_recipe({cell}, {2}, {0}, {{}}, {{}}); + auto recipe_1 = custom_recipe({cell}, {2}, {0}, {{}}, {{}}, {{}}); auto decomp_1 = partition_load_balance(recipe_1, context); EXPECT_THROW(simulation(recipe_1, decomp_1, context), arb::bad_source_description); @@ -137,13 +143,13 @@ TEST(recipe, num_targets) auto cell = custom_cell(0, 2, 0); { - auto recipe_0 = custom_recipe({cell}, {0}, {2}, {{}}, {{}}); + auto recipe_0 = custom_recipe({cell}, {0}, {2}, {{}}, {{}}, {{}}); auto decomp_0 = partition_load_balance(recipe_0, context); EXPECT_NO_THROW(simulation(recipe_0, decomp_0, context)); } { - auto recipe_1 = custom_recipe({cell}, {0}, {3}, {{}}, {{}}); + auto recipe_1 = custom_recipe({cell}, {0}, {3}, {{}}, {{}}, {{}}); auto decomp_1 = partition_load_balance(recipe_1, context); EXPECT_THROW(simulation(recipe_1, decomp_1, context), arb::bad_target_description); @@ -169,7 +175,7 @@ TEST(recipe, gap_junctions) {{0, 1}, {1, 2}, 0.1}, {{0, 2}, {1, 0}, 0.1}}; - auto recipe_0 = custom_recipe({cell_0, cell_1}, {0, 0}, {0, 0}, {{}, {}}, {gjs_0, gjs_0}); + auto recipe_0 = custom_recipe({cell_0, cell_1}, {0, 0}, {0, 0}, {{}, {}}, {gjs_0, gjs_0}, {{}, {}}); auto decomp_0 = partition_load_balance(recipe_0, context); EXPECT_NO_THROW(simulation(recipe_0, decomp_0, context)); @@ -179,7 +185,7 @@ TEST(recipe, gap_junctions) {{0, 1}, {1, 2}, 0.1}, {{0, 2}, {1, 5}, 0.1}}; - auto recipe_1 = custom_recipe({cell_0, cell_1}, {0, 0}, {0, 0}, {{}, {}}, {gjs_1, gjs_1}); + auto recipe_1 = custom_recipe({cell_0, cell_1}, {0, 0}, {0, 0}, {{}, {}}, {gjs_1, gjs_1}, {{}, {}}); auto decomp_1 = partition_load_balance(recipe_1, context); EXPECT_THROW(simulation(recipe_1, decomp_1, context), arb::bad_gj_connection_lid); @@ -190,7 +196,7 @@ TEST(recipe, gap_junctions) {{0, 1}, {1, 2}, 0.1}, {{0, 2}, {3, 0}, 0.1}}; - auto recipe_2 = custom_recipe({cell_0, cell_1}, {0, 0}, {0, 0}, {{}, {}}, {gjs_2, gjs_2}); + auto recipe_2 = custom_recipe({cell_0, cell_1}, {0, 0}, {0, 0}, {{}, {}}, {gjs_2, gjs_2}, {{}, {}}); auto context = make_context(resources); EXPECT_THROW(partition_load_balance(recipe_2, context), arb::bad_gj_connection_gid); @@ -220,7 +226,7 @@ TEST(recipe, connections) {{0, 0}, {1, 0}, 0.3, 0.1}, {{0, 0}, {1, 0}, 0.1, 0.8}}; - auto recipe_0 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}); + auto recipe_0 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}, {{}, {}}); auto decomp_0 = partition_load_balance(recipe_0, context); EXPECT_NO_THROW(simulation(recipe_0, decomp_0, context)); @@ -234,7 +240,7 @@ TEST(recipe, connections) {{0, 0}, {1, 0}, 0.3, 0.1}, {{0, 0}, {1, 0}, 0.1, 0.8}}; - auto recipe_1 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}); + auto recipe_1 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}, {{}, {}}); auto decomp_1 = partition_load_balance(recipe_1, context); EXPECT_THROW(simulation(recipe_1, decomp_1, context), arb::bad_connection_source_gid); @@ -248,7 +254,7 @@ TEST(recipe, connections) {{0, 0}, {1, 0}, 0.3, 0.1}, {{0, 0}, {1, 0}, 0.1, 0.8}}; - auto recipe_2 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}); + auto recipe_2 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}, {{}, {}}); auto decomp_2 = partition_load_balance(recipe_2, context); EXPECT_THROW(simulation(recipe_2, decomp_2, context), arb::bad_connection_source_lid); @@ -262,7 +268,7 @@ TEST(recipe, connections) {{0, 0}, {7, 0}, 0.3, 0.1}, {{0, 0}, {1, 0}, 0.1, 0.8}}; - auto recipe_3 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}); + auto recipe_3 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}, {{}, {}}); auto decomp_3 = partition_load_balance(recipe_3, context); EXPECT_THROW(simulation(recipe_3, decomp_3, context), arb::bad_connection_target_gid); @@ -276,7 +282,7 @@ TEST(recipe, connections) {{0, 0}, {0, 0}, 0.3, 0.1}, {{0, 0}, {1, 0}, 0.1, 0.8}}; - auto recipe_5 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}); + auto recipe_5 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}, {{}, {}}); auto decomp_5 = partition_load_balance(recipe_5, context); EXPECT_THROW(simulation(recipe_5, decomp_5, context), arb::bad_connection_target_gid); @@ -290,9 +296,52 @@ TEST(recipe, connections) {{0, 0}, {1, 9}, 0.3, 0.1}, {{0, 0}, {1, 0}, 0.1, 0.8}}; - auto recipe_4 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}); + auto recipe_4 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {conns_0, conns_1}, {{}, {}}, {{}, {}}); auto decomp_4 = partition_load_balance(recipe_4, context); EXPECT_THROW(simulation(recipe_4, decomp_4, context), arb::bad_connection_target_lid); } } + +TEST(recipe, event_generators) { + arb::proc_allocation resources; + if (auto nt = arbenv::get_env_num_threads()) { + resources.num_threads = nt; + } + else { + resources.num_threads = arbenv::thread_concurrency(); + } + auto context = make_context(resources); + + auto cell_0 = custom_cell(1, 2, 0); + auto cell_1 = custom_cell(2, 1, 0); + std::vector<arb::event_generator> gens_0, gens_1; + { + gens_0 = {arb::explicit_generator(arb::pse_vector{{{0, 0}, 1.0, 0.1}, {{0, 1}, 2.0, 0.1}})}; + + gens_1 = {arb::explicit_generator(arb::pse_vector{{{1, 0}, 1.0, 0.1}})}; + + auto recipe_0 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {{}, {}}, {{}, {}}, {gens_0, gens_1}); + auto decomp_0 = partition_load_balance(recipe_0, context); + + EXPECT_NO_THROW(simulation(recipe_0, decomp_0, context)); + } + { + gens_0.clear(); + gens_1 = {arb::explicit_generator(arb::pse_vector{{{0, 0}, 1.0, 0.1}})}; + + auto recipe_0 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {{}, {}}, {{}, {}}, {gens_0, gens_1}); + auto decomp_0 = partition_load_balance(recipe_0, context); + + EXPECT_THROW(simulation(recipe_0, decomp_0, context), arb::bad_event_generator_target_gid); + } + { + gens_0 = {arb::explicit_generator(arb::pse_vector{{{0, 0}, 1.0, 0.1}, {{0, 3}, 2.0, 0.1}})}; + gens_1.clear(); + + auto recipe_0 = custom_recipe({cell_0, cell_1}, {1, 2}, {2, 1}, {{}, {}}, {{}, {}}, {gens_0, gens_1}); + auto decomp_0 = partition_load_balance(recipe_0, context); + + EXPECT_THROW(simulation(recipe_0, decomp_0, context), arb::bad_event_generator_target_lid); + } +}