diff --git a/arbor/arbexcept.cpp b/arbor/arbexcept.cpp index 76dfdd580e2328f128832ba45aaf145b1afe784d..f03cfae162257379d7b0b3eedeb456d768b30c50 100644 --- a/arbor/arbexcept.cpp +++ b/arbor/arbexcept.cpp @@ -15,6 +15,35 @@ bad_cell_description::bad_cell_description(cell_kind kind, cell_gid_type gid): gid(gid), kind(kind) {} +bad_target_description::bad_target_description(cell_gid_type gid, cell_size_type rec_val, cell_size_type cell_val): + arbor_exception(pprintf("Model building error on cell {}: recipe::num_targets(gid={}) = {} is greater than the number of synapses on the cell = {}", gid, gid, rec_val, cell_val)), + gid(gid), rec_val(rec_val), cell_val(cell_val) +{} + +bad_source_description::bad_source_description(cell_gid_type gid, cell_size_type rec_val, cell_size_type cell_val): + arbor_exception(pprintf("Model building error on cell {}: recipe::num_sources(gid={}) = {} is greater than the number of detectors on the cell = {}", gid, gid, rec_val, cell_val)), + gid(gid), rec_val(rec_val), cell_val(cell_val) +{} + +bad_connection_source_gid::bad_connection_source_gid(cell_gid_type gid, cell_gid_type src_gid, cell_size_type num_cells): + arbor_exception(pprintf("Model building error on cell {}: connection source gid {} is out of range: there are only {} cells in the model, in the range [{}:{}].", gid, src_gid, num_cells, 0, num_cells-1)), + gid(gid), src_gid(src_gid), num_cells(num_cells) +{} + +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)), + gid(gid), src_lid(src_lid), num_sources(num_sources) +{} + +bad_connection_target_gid::bad_connection_target_gid(cell_gid_type gid, cell_gid_type tgt_gid): + arbor_exception(pprintf("Model building error on cell {}: connection target gid {} has to match cell gid {}].", gid, tgt_gid, gid)), + gid(gid), tgt_gid(tgt_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)), + gid(gid), tgt_lid(tgt_lid), num_targets(num_targets) +{} bad_global_property::bad_global_property(cell_kind kind): arbor_exception(pprintf("bad global property for cell kind {}", kind)), @@ -32,6 +61,19 @@ gj_unsupported_domain_decomposition::gj_unsupported_domain_decomposition(cell_gi gid_1(gid_1) {} +bad_gj_connection_gid::bad_gj_connection_gid(cell_gid_type gid, cell_gid_type site_0, cell_gid_type site_1): + arbor_exception(pprintf("Model building error on cell {}: recipe::gap_junctions_on(gid={}) -> cell {} <-> cell{}: one of the sites must be on the cell with gid = {})", gid, gid, site_0, site_1, gid)), + gid(gid), + site_0(site_0), + site_1(site_1) +{} + +bad_gj_connection_lid::bad_gj_connection_lid(cell_gid_type gid, cell_member_type site): + arbor_exception(pprintf("Model building error on cell {}: gap junction index {} on cell {} does not exist)", gid, site.gid, site.index)), + gid(gid), + site(site) +{} + gj_kind_mismatch::gj_kind_mismatch(cell_gid_type gid_0, cell_gid_type gid_1): arbor_exception(pprintf("Cells on gid {} and {} connected via gap junction have different cell kinds", gid_0, gid_1)), gid_0(gid_0), diff --git a/arbor/communication/communicator.cpp b/arbor/communication/communicator.cpp index 50acd362c085bf3237c5a0e710fa028bbb96521b..638ad093f4f6dc1b25597e6333ee2c1e9560dca6 100644 --- a/arbor/communication/communicator.cpp +++ b/arbor/communication/communicator.cpp @@ -6,6 +6,7 @@ #include <arbor/domain_decomposition.hpp> #include <arbor/recipe.hpp> #include <arbor/spike.hpp> +#include <include/arbor/arbexcept.hpp> #include "algorithms.hpp" #include "communication/gathered_vector.hpp" @@ -32,6 +33,7 @@ communicator::communicator(const recipe& rec, num_domains_ = distributed_->size(); num_local_groups_ = dom_dec.groups.size(); num_local_cells_ = dom_dec.num_local_cells; + auto num_total_cells = rec.num_cells(); // For caching information about each cell struct gid_info { @@ -75,9 +77,24 @@ communicator::communicator(const recipe& rec, std::vector<unsigned> src_domains; src_domains.reserve(n_cons); std::vector<cell_size_type> src_counts(num_domains_); - for (const auto& g: gid_infos) { - for (auto con: g.conns) { - const auto src = dom_dec.gid_domain(con.source.gid); + + for (const auto& cell: gid_infos) { + auto num_targets = rec.num_targets(cell.gid); + for (auto c: cell.conns) { + auto num_sources = rec.num_sources(c.source.gid); + if (c.source.gid >= num_total_cells) { + throw arb::bad_connection_source_gid(cell.gid, c.source.gid, num_total_cells); + } + if (c.source.index >= num_sources) { + throw arb::bad_connection_source_lid(cell.gid, c.source.index, num_sources); + } + if (c.dest.gid != cell.gid) { + throw arb::bad_connection_target_gid(cell.gid, c.dest.gid); + } + if (c.dest.index >= num_targets) { + throw arb::bad_connection_target_lid(cell.gid, c.dest.index, num_targets); + } + const auto src = dom_dec.gid_domain(c.source.gid); src_domains.push_back(src); src_counts[src]++; } diff --git a/arbor/fvm_lowered_cell_impl.hpp b/arbor/fvm_lowered_cell_impl.hpp index 26e3a6458db352f499fe6e57d0893cea782ee007..3851ef02835955532520dc3fe39a4297d40ef216 100644 --- a/arbor/fvm_lowered_cell_impl.hpp +++ b/arbor/fvm_lowered_cell_impl.hpp @@ -526,7 +526,6 @@ void fvm_lowered_cell_impl<Backend>::initialize( } } - // Collect detectors, probe handles. std::vector<index_type> detector_cv; std::vector<value_type> detector_threshold; @@ -535,6 +534,17 @@ void fvm_lowered_cell_impl<Backend>::initialize( for (auto cell_idx: make_span(ncell)) { cell_gid_type gid = gids[cell_idx]; + // Sanity check recipe + auto& cell = cells[cell_idx]; + if (rec.num_sources(gid) > cell.detectors().size()) { + throw arb::bad_source_description(gid, rec.num_sources(gid), cell.detectors().size());; + } + auto cell_targets = util::sum_by(cell.synapses(), [](auto& syn) {return syn.second.size();}); + if (cell_targets > rec.num_targets(gid)) { + throw arb::bad_target_description(gid, rec.num_targets(gid), cell_targets); + } + + // Collect detectors, probe handles. for (auto entry: cells[cell_idx].detectors()) { detector_cv.push_back(D.geometry.location_cv(cell_idx, entry.loc, cv_prefer::cv_empty)); detector_threshold.push_back(entry.item.threshold); @@ -588,16 +598,16 @@ std::vector<fvm_gap_junction> fvm_lowered_cell_impl<Backend>::fvm_gap_junctions( auto gj_list = rec.gap_junctions_on(gid); for (auto g: gj_list) { if (gid != g.local.gid && gid != g.peer.gid) { - throw arb::bad_cell_description(cell_kind::cable, gid); + throw arb::bad_gj_connection_gid(gid, g.local.gid, g.peer.gid); } - cell_gid_type cv0, cv1; - try { - cv0 = gid_to_cvs[g.local.gid].at(g.local.index); - cv1 = gid_to_cvs[g.peer.gid].at(g.peer.index); + if (g.local.index >= gid_to_cvs[g.local.gid].size()) { + throw arb::bad_gj_connection_lid(gid, g.local); } - catch (std::out_of_range&) { - throw arb::bad_cell_description(cell_kind::cable, gid); + if (g.peer.index >= gid_to_cvs[g.peer.gid].size()) { + throw arb::bad_gj_connection_lid(gid, g.peer); } + auto cv0 = gid_to_cvs[g.local.gid][g.local.index]; + auto cv1 = gid_to_cvs[g.peer.gid][g.peer.index]; if (gid != g.local.gid) { std::swap(cv0, cv1); } diff --git a/arbor/include/arbor/arbexcept.hpp b/arbor/include/arbor/arbexcept.hpp index 6eb4a4bfd8016ae65d3a48db1a7f361f02592e45..e1dcf8b1c4338a38b92705a447e69bdcccad10e7 100644 --- a/arbor/include/arbor/arbexcept.hpp +++ b/arbor/include/arbor/arbexcept.hpp @@ -35,6 +35,43 @@ struct bad_cell_description: arbor_exception { cell_kind kind; }; +struct bad_target_description: arbor_exception { + bad_target_description(cell_gid_type gid, cell_size_type rec_val, cell_size_type cell_val); + cell_gid_type gid; + cell_size_type rec_val, cell_val; +}; + +struct bad_source_description: arbor_exception { + bad_source_description(cell_gid_type gid, cell_size_type rec_val, cell_size_type cell_val); + cell_gid_type gid; + cell_size_type rec_val, cell_val; +}; + +struct bad_connection_source_gid: arbor_exception { + bad_connection_source_gid(cell_gid_type gid, cell_gid_type src_gid, cell_size_type num_cells); + cell_gid_type gid, src_gid; + cell_size_type num_cells; +}; + +struct bad_connection_source_lid: arbor_exception { + bad_connection_source_lid(cell_gid_type gid, cell_lid_type src_lid, cell_size_type num_sources); + cell_gid_type gid; + cell_lid_type src_lid; + cell_size_type num_sources; +}; + +struct bad_connection_target_gid: arbor_exception { + bad_connection_target_gid(cell_gid_type gid, cell_gid_type tgt_gid); + cell_gid_type gid, tgt_gid; +}; + +struct bad_connection_target_lid: arbor_exception { + bad_connection_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; @@ -50,6 +87,17 @@ struct gj_kind_mismatch: arbor_exception { cell_gid_type gid_0, gid_1; }; +struct bad_gj_connection_gid: arbor_exception { + bad_gj_connection_gid(cell_gid_type gid, cell_gid_type site_0, cell_gid_type site_1); + cell_gid_type gid, site_0, site_1; +}; + +struct bad_gj_connection_lid: arbor_exception { + bad_gj_connection_lid(cell_gid_type gid, cell_member_type site); + cell_gid_type gid; + cell_member_type site; +}; + // Domain decomposition errors: struct gj_unsupported_domain_decomposition: arbor_exception { diff --git a/arbor/partition_load_balance.cpp b/arbor/partition_load_balance.cpp index 5236658e2ba27cb3bd098ff534344af0ddc69b6f..e8fd413557355138f01db42c29b9d7e51f1b2644 100644 --- a/arbor/partition_load_balance.cpp +++ b/arbor/partition_load_balance.cpp @@ -93,7 +93,7 @@ domain_decomposition partition_load_balance( auto conns = rec.gap_junctions_on(element); for (auto c: conns) { if (element != c.local.gid && element != c.peer.gid) { - throw bad_cell_description(cell_kind::cable, element); + throw bad_gj_connection_gid(element, c.local.gid, c.peer.gid); } cell_member_type other = c.local.gid == element ? c.peer : c.local; diff --git a/test/simple_recipes.hpp b/test/simple_recipes.hpp index 9a1c1dad7d324d8792b8cc78935ca3881043b7ce..5344a1361bde35598945793894f24c37b15c4a5a 100644 --- a/test/simple_recipes.hpp +++ b/test/simple_recipes.hpp @@ -12,6 +12,8 @@ #include <arbor/recipe.hpp> #include <arbor/util/unique_any.hpp> +#include "util/rangeutil.hpp" + namespace arb { // Common functionality: maintain an unordered map of probe data @@ -119,7 +121,7 @@ public: } cell_size_type num_targets(cell_gid_type i) const override { - return cells_.at(i).synapses().size(); + return util::sum_by(cells_.at(i).synapses(), [](auto& syn) {return syn.second.size();}); } util::unique_any get_cell_description(cell_gid_type i) const override { diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index eb3ef8d6476ae54cc0b3bcd512c037bb1502dd72..ba08e6c53ae8a867058b594f8c1f3da8aaa0b5d7 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -138,6 +138,7 @@ set(unit_sources test_pp_util.cpp test_probe.cpp test_range.cpp + test_recipe.cpp test_ratelem.cpp test_schedule.cpp test_scope_exit.cpp diff --git a/test/unit/test_recipe.cpp b/test/unit/test_recipe.cpp new file mode 100644 index 0000000000000000000000000000000000000000..958b66e967e0aa77bcfadf886ea3c027c05f3da1 --- /dev/null +++ b/test/unit/test_recipe.cpp @@ -0,0 +1,293 @@ +#include "../gtest.h" + +#include <arbor/common_types.hpp> +#include <arbor/domain_decomposition.hpp> +#include <arbor/load_balance.hpp> +#include <arbor/cable_cell.hpp> +#include <arbor/recipe.hpp> +#include <arbor/simulation.hpp> + +#include <arborenv/concurrency.hpp> + +#include "util/span.hpp" + +#include "../common_cells.hpp" + +using namespace arb; +using arb::util::make_span; + +namespace { + class custom_recipe: public recipe { + public: + custom_recipe(std::vector<cable_cell> cells, + 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): + num_cells_(cells.size()), + num_sources_(num_sources), + num_targets_(num_targets), + connections_(conns), + gap_junctions_(gjs), + cells_(cells) {} + + cell_size_type num_cells() const override { + return num_cells_; + } + arb::util::unique_any get_cell_description(cell_gid_type gid) const override { + return cells_[gid]; + } + cell_kind get_cell_kind(cell_gid_type gid) const override { + return cell_kind::cable; + } + std::vector<gap_junction_connection> gap_junctions_on(cell_gid_type gid) const override { + return gap_junctions_[gid]; + } + std::vector<cell_connection> connections_on(cell_gid_type gid) const override { + return connections_[gid]; + } + cell_size_type num_sources(cell_gid_type gid) const override { + return num_sources_[gid]; + } + cell_size_type num_targets(cell_gid_type gid) const override { + return num_targets_[gid]; + } + arb::util::any get_global_properties(cell_kind) const override { + arb::cable_cell_global_properties a; + a.default_parameters = arb::neuron_parameter_defaults; + return a; + } + + private: + cell_size_type num_cells_; + 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<cable_cell> cells_; + }; + + cable_cell custom_cell(cell_size_type num_detectors, cell_size_type num_synapses, cell_size_type num_gj) { + arb::segment_tree tree; + tree.append(arb::mnpos, {0,0,0,10}, {0,0,20,10}, 1); // soma + tree.append(0, {0,0, 20, 2}, {0,0, 320, 2}, 3); // dendrite + + arb::cable_cell cell(tree, {}); + + // Add a num_detectors detectors to the cell. + for (auto i: util::make_span(num_detectors)) { + cell.place(arb::mlocation{0,(double)i/num_detectors}, arb::threshold_detector{10}); + } + + // Add a num_synapses synapses to the cell. + for (auto i: util::make_span(num_synapses)) { + cell.place(arb::mlocation{0,(double)i/num_synapses}, "expsyn"); + } + + // Add a num_gj gap_junctions to the cell. + for (auto i: util::make_span(num_gj)) { + cell.place(arb::mlocation{0,(double)i/num_gj}, arb::gap_junction_site{}); + } + + return cell; + } +} + +// test assumes one domain +TEST(recipe, num_sources) +{ + 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 = custom_cell(1, 0, 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 decomp_1 = partition_load_balance(recipe_1, context); + + EXPECT_THROW(simulation(recipe_1, decomp_1, context), arb::bad_source_description); + } +} + +TEST(recipe, num_targets) +{ + 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 = custom_cell(0, 2, 0); + + { + 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}, {1}, {{}}, {{}}); + auto decomp_1 = partition_load_balance(recipe_1, context); + + EXPECT_THROW(simulation(recipe_1, decomp_1, context), arb::bad_target_description); + } +} + +TEST(recipe, gap_junctions) +{ + 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(0, 0, 3); + auto cell_1 = custom_cell(0, 0, 3); + + { + std::vector<arb::gap_junction_connection> gjs_0 = {{{0, 0}, {1, 1}, 0.1}, + {{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 decomp_0 = partition_load_balance(recipe_0, context); + + EXPECT_NO_THROW(simulation(recipe_0, decomp_0, context)); + } + { + std::vector<arb::gap_junction_connection> gjs_1 = {{{0, 0}, {1, 1}, 0.1}, + {{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 decomp_1 = partition_load_balance(recipe_1, context); + + EXPECT_THROW(simulation(recipe_1, decomp_1, context), arb::bad_gj_connection_lid); + + } + { + std::vector<arb::gap_junction_connection> gjs_2 = {{{0, 0}, {1, 1}, 0.1}, + {{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 context = make_context(resources); + + EXPECT_THROW(partition_load_balance(recipe_2, context), arb::bad_gj_connection_gid); + } +} + +TEST(recipe, connections) +{ + 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::cell_connection> conns_0, conns_1; + { + conns_0 = {{{1, 0}, {0, 0}, 0.1, 0.1}, + {{1, 1}, {0, 0}, 0.1, 0.1}, + {{1, 0}, {0, 1}, 0.2, 0.4}}; + + conns_1 = {{{0, 0}, {1, 0}, 0.1, 0.2}, + {{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 decomp_0 = partition_load_balance(recipe_0, context); + + EXPECT_NO_THROW(simulation(recipe_0, decomp_0, context)); + } + { + conns_0 = {{{1, 0}, {0, 0}, 0.1, 0.1}, + {{2, 1}, {0, 0}, 0.1, 0.1}, + {{1, 0}, {0, 1}, 0.2, 0.4}}; + + conns_1 = {{{0, 0}, {1, 0}, 0.1, 0.2}, + {{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 decomp_1 = partition_load_balance(recipe_1, context); + + EXPECT_THROW(simulation(recipe_1, decomp_1, context), arb::bad_connection_source_gid); + } + { + conns_0 = {{{1, 0}, {0, 0}, 0.1, 0.1}, + {{1, 1}, {0, 0}, 0.1, 0.1}, + {{1, 3}, {0, 1}, 0.2, 0.4}}; + + conns_1 = {{{0, 0}, {1, 0}, 0.1, 0.2}, + {{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 decomp_2 = partition_load_balance(recipe_2, context); + + EXPECT_THROW(simulation(recipe_2, decomp_2, context), arb::bad_connection_source_lid); + } + { + conns_0 = {{{1, 0}, {0, 0}, 0.1, 0.1}, + {{1, 1}, {0, 0}, 0.1, 0.1}, + {{1, 0}, {0, 1}, 0.2, 0.4}}; + + conns_1 = {{{0, 0}, {1, 0}, 0.1, 0.2}, + {{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 decomp_3 = partition_load_balance(recipe_3, context); + + EXPECT_THROW(simulation(recipe_3, decomp_3, context), arb::bad_connection_target_gid); + } + { + conns_0 = {{{1, 0}, {0, 0}, 0.1, 0.1}, + {{1, 1}, {0, 0}, 0.1, 0.1}, + {{1, 0}, {0, 1}, 0.2, 0.4}}; + + conns_1 = {{{0, 0}, {1, 0}, 0.1, 0.2}, + {{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 decomp_5 = partition_load_balance(recipe_5, context); + + EXPECT_THROW(simulation(recipe_5, decomp_5, context), arb::bad_connection_target_gid); + } + { + conns_0 = {{{1, 0}, {0, 0}, 0.1, 0.1}, + {{1, 1}, {0, 0}, 0.1, 0.1}, + {{1, 0}, {0, 1}, 0.2, 0.4}}; + + conns_1 = {{{0, 0}, {1, 0}, 0.1, 0.2}, + {{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 decomp_4 = partition_load_balance(recipe_4, context); + + EXPECT_THROW(simulation(recipe_4, decomp_4, context), arb::bad_connection_target_lid); + } +} \ No newline at end of file