diff --git a/arbor/include/arbor/mechcat.hpp b/arbor/include/arbor/mechcat.hpp index 17cd7ef962917e55e913af6d66c154a4a7c8ef56..0018791f32818ef657a5ac2df0c74ed805e6ba70 100644 --- a/arbor/include/arbor/mechcat.hpp +++ b/arbor/include/arbor/mechcat.hpp @@ -106,7 +106,10 @@ public: register_impl(std::type_index(typeid(B)), name, std::move(generic_proto)); } - ~mechanism_catalogue(); + // Copy over another catalogue's mechanism and attach a -- possibly empty -- prefix + void import(const mechanism_catalogue& other, const std::string& prefix); + + ~mechanism_catalogue(); private: std::unique_ptr<catalogue_state> state_; @@ -119,5 +122,6 @@ private: // Reference to global default mechanism catalogue. const mechanism_catalogue& global_default_catalogue(); +const mechanism_catalogue& global_allen_catalogue(); } // namespace arb diff --git a/arbor/mechcat.cpp b/arbor/mechcat.cpp index 59b45df7943c0f666c925ce7d0bd63d3fa4ff468..4be6af12198e7debd9f861d613632e190e2b6703 100644 --- a/arbor/mechcat.cpp +++ b/arbor/mechcat.cpp @@ -123,25 +123,44 @@ struct catalogue_state { catalogue_state() = default; catalogue_state(const catalogue_state& other) { - info_map_.clear(); + import(other, ""); + } + + void import(const catalogue_state& other, const std::string& prefix) { + // Do all checks before adding anything, otherwise we might get inconsistent state. + auto assert_undefined = [&](const std::string& key) { + auto pkey = prefix+key; + if (defined(pkey)) { + throw duplicate_mechanism(pkey); + } + }; + + for (const auto& kv: other.info_map_) { + assert_undefined(kv.first); + } + + for (const auto& kv: other.derived_map_) { + assert_undefined(kv.first); + } + for (const auto& kv: other.info_map_) { - info_map_[kv.first] = make_unique<mechanism_info>(*kv.second); + auto key = prefix + kv.first; + info_map_[key] = make_unique<mechanism_info>(*kv.second); } - derived_map_.clear(); for (const auto& kv: other.derived_map_) { + auto key = prefix + kv.first; const derivation& v = kv.second; - derived_map_[kv.first] = {v.parent, v.globals, v.ion_remap, make_unique<mechanism_info>(*v.derived_info)}; + derived_map_[key] = {prefix + v.parent, v.globals, v.ion_remap, make_unique<mechanism_info>(*v.derived_info)}; } - impl_map_.clear(); for (const auto& name_impls: other.impl_map_) { std::unordered_map<std::type_index, std::unique_ptr<mechanism>> impls; for (const auto& tidx_mptr: name_impls.second) { impls[tidx_mptr.first] = tidx_mptr.second->clone(); } - - impl_map_[name_impls.first] = std::move(impls); + auto key = prefix + name_impls.first; + impl_map_[key] = std::move(impls); } } @@ -540,6 +559,10 @@ void mechanism_catalogue::derive(const std::string& name, const std::string& par state_->bind(name, value(state_->derive(parent))); } +void mechanism_catalogue::import(const mechanism_catalogue& other, const std::string& prefix) { + state_->import(*other.state_, prefix); +} + void mechanism_catalogue::remove(const std::string& name) { if (!has(name)) { throw no_such_mechanism(name); diff --git a/mechanisms/CMakeLists.txt b/mechanisms/CMakeLists.txt index a751dd25a97249bd71a3df5c67738059e96ae380..f72d7399874da1b2b2e070ba8c87619b4c66007d 100644 --- a/mechanisms/CMakeLists.txt +++ b/mechanisms/CMakeLists.txt @@ -1,8 +1,5 @@ include(BuildModules.cmake) -set(mech_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") -file(MAKE_DIRECTORY "${mech_dir}") - set(external_modcc) if(ARB_WITH_EXTERNAL_MODCC) set(external_modcc MODCC ${modcc}) @@ -15,6 +12,8 @@ set(mech_sources "") # ALLEN set(allen_mechanisms CaDynamics Ca_HVA Ca_LVA Ih Im Im_v2 K_P K_T Kd Kv2like Kv3_1 NaTa NaTs NaV Nap SK) set(allen_mod_srcdir "${CMAKE_CURRENT_SOURCE_DIR}/allen") +set(mech_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/allen") +file(MAKE_DIRECTORY "${mech_dir}") build_modules( ${allen_mechanisms} @@ -53,6 +52,8 @@ endforeach() # DEFAULT set(default_mechanisms exp2syn expsyn hh kamt kdrmt nax nernst pas) set(default_mod_srcdir "${CMAKE_CURRENT_SOURCE_DIR}/default") +set(mech_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/default") +file(MAKE_DIRECTORY "${mech_dir}") build_modules( ${default_mechanisms} diff --git a/python/mechanism.cpp b/python/mechanism.cpp index 0df5d95da1394ca7712f3a1e91075ee6d26e24d2..92515ef1e0e5a11e3fec446d12da29e6bb4196db 100644 --- a/python/mechanism.cpp +++ b/python/mechanism.cpp @@ -118,6 +118,10 @@ void register_mechanisms(pybind11::module& m) { throw std::runtime_error(util::pprintf("\nKeyError: '{}'", name)); } }) + .def("import", &arb::mechanism_catalogue::import, + "other"_a, "Catalogue to import into self", + "prefix"_a, "Prefix for names in other", + "Import another catalogue, possibly with a prefix. Will overwrite in case of name collisions.") .def("derive", &apply_derive, "name"_a, "parent"_a, "globals"_a=std::unordered_map<std::string, double>{}, @@ -130,6 +134,7 @@ void register_mechanisms(pybind11::module& m) { return util::pprintf("<arbor.mechanism_catalogue>"); }); m.def("default_catalogue", [](){return arb::global_default_catalogue();}); + m.def("allen_catalogue", [](){return arb::global_allen_catalogue();}); // arb::mechanism_desc // For specifying a mechanism in the cable_cell interface. diff --git a/test/unit/test_mechcat.cpp b/test/unit/test_mechcat.cpp index 2d7ccac9432538b5f319bd9a4a30e2fe19945ab9..31526aa2c8e840861ace53879666658d94129ee5 100644 --- a/test/unit/test_mechcat.cpp +++ b/test/unit/test_mechcat.cpp @@ -443,4 +443,95 @@ TEST(mechcat, copy) { EXPECT_EQ(typeid(*fleeb2_inst.mech.get()), typeid(*fleeb2_inst2.mech.get())); } +TEST(mechcat, import) { + auto cat = build_fake_catalogue(); + mechanism_catalogue cat2; + cat2.import(cat, "fake_"); + + EXPECT_TRUE(cat.has("fleeb2")); + EXPECT_FALSE(cat.has("fake_fleeb2")); + + EXPECT_TRUE(cat2.has("fake_fleeb2")); + EXPECT_FALSE(cat2.has("fleeb2")); + + EXPECT_EQ(cat["fleeb2"], cat2["fake_fleeb2"]); + + auto fleeb2_inst = cat.instance<foo_backend>("fleeb2"); + auto fleeb2_inst2 = cat2.instance<foo_backend>("fake_fleeb2"); + + EXPECT_EQ(typeid(*fleeb2_inst.mech.get()), typeid(*fleeb2_inst2.mech.get())); +} + +TEST(mechcat, import_collisions) { + { + auto cat = build_fake_catalogue(); + + mechanism_catalogue cat2; + EXPECT_NO_THROW(cat2.import(cat, "prefix:")); // Should have no collisions. + EXPECT_NO_THROW(cat.import(cat2, "prefix:")); // Should have no collisions here either. + + // cat should have both original entries and copies with 'prefix:prefix:' prefixed. + ASSERT_TRUE(cat.has("fleeb2")); + ASSERT_TRUE(cat.has("prefix:prefix:fleeb2")); + } + + // We should throw if there any collisions between base or derived mechanism + // names between the catalogues. If the import fails, the catalogue should + // remain unchanged. + { + // Collision between two base mechanisms. + { + auto cat = build_fake_catalogue(); + mechanism_catalogue other; + other.add("fleeb", burble_info); // Note different mechanism info! + + EXPECT_THROW(cat.import(other, ""), arb::duplicate_mechanism); + ASSERT_EQ(cat["fleeb"], fleeb_info); + } + + // Collision derived vs base. + { + auto cat = build_fake_catalogue(); + + mechanism_catalogue other; + other.add("fleeb2", burble_info); + + auto fleeb2_info = cat["fleeb2"]; + EXPECT_THROW(cat.import(other, ""), arb::duplicate_mechanism); + EXPECT_EQ(cat["fleeb2"], fleeb2_info); + } + + // Collision base vs derived. + { + auto cat = build_fake_catalogue(); + + mechanism_catalogue other; + other.add("zonkers", fleeb_info); + other.derive("fleeb", "zonkers", {{"plugh", 8.}}); + ASSERT_FALSE(other["fleeb"]==fleeb_info); + + ASSERT_FALSE(cat.has("zonkers")); + EXPECT_THROW(cat.import(other, ""), arb::duplicate_mechanism); + EXPECT_EQ(cat["fleeb"], fleeb_info); + EXPECT_FALSE(cat.has("zonkers")); + } + + // Collision derived vs derived. + { + auto cat = build_fake_catalogue(); + + mechanism_catalogue other; + other.add("zonkers", fleeb_info); + other.derive("fleeb2", "zonkers", {{"plugh", 8.}}); + + auto fleeb2_info = cat["fleeb2"]; + ASSERT_FALSE(other["fleeb2"]==fleeb2_info); + + ASSERT_FALSE(cat.has("zonkers")); + EXPECT_THROW(cat.import(other, ""), arb::duplicate_mechanism); + EXPECT_EQ(cat["fleeb2"], fleeb2_info); + EXPECT_FALSE(cat.has("zonkers")); + } + } +}