diff --git a/arbor/arbexcept.cpp b/arbor/arbexcept.cpp index 0a3a382c7fd0cb2e36c3c54d798c3a4659347aa7..76dfdd580e2328f128832ba45aaf145b1afe784d 100644 --- a/arbor/arbexcept.cpp +++ b/arbor/arbexcept.cpp @@ -69,9 +69,18 @@ invalid_parameter_value::invalid_parameter_value(const std::string& mech_name, c arbor_exception(pprintf("invalid parameter value for mechanism {} parameter {}: {}", mech_name, param_name, value)), mech_name(mech_name), param_name(param_name), + value_str(), value(value) {} +invalid_parameter_value::invalid_parameter_value(const std::string& mech_name, const std::string& param_name, const std::string& value_str): + arbor_exception(pprintf("invalid parameter value for mechanism {} parameter {}: {}", mech_name, param_name, value_str)), + mech_name(mech_name), + param_name(param_name), + value_str(value_str), + value(0) +{} + invalid_ion_remap::invalid_ion_remap(const std::string& mech_name): arbor_exception(pprintf("invalid ion parameter remapping for mechanism {}", mech_name)) {} diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp index 4e021b2859f2c732da8118ed9c175d955b172134..c350770a8b911b53bc2f2eebfd48b3f4936b2f55 100644 --- a/arbor/fvm_layout.cpp +++ b/arbor/fvm_layout.cpp @@ -283,7 +283,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& struct density_mech_data { std::vector<std::pair<size_type, const mechanism_desc*>> segments; // string_set paramset; - const mechanism_info* info = nullptr; + std::shared_ptr<mechanism_info> info = nullptr; }; std::unordered_map<std::string, density_mech_data> density_mech_table; @@ -300,7 +300,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& }; std::vector<point_data> points; string_set paramset; - const mechanism_info* info = nullptr; + std::shared_ptr<mechanism_info> info = nullptr; }; std::unordered_map<std::string, point_mech_data> point_mech_table; @@ -315,11 +315,11 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& auto update_paramset_and_validate = [&catalogue] - (const mechanism_desc& desc, const mechanism_info*& info, string_set& paramset) + (const mechanism_desc& desc, std::shared_ptr<mechanism_info>& info, string_set& paramset) { auto& name = desc.name(); if (!info) { - info = &catalogue[name]; + info.reset(new mechanism_info(catalogue[name])); } for (const auto& pv: desc.values()) { if (!paramset.count(pv.first)) { @@ -400,11 +400,11 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& } for (auto& entry: density_mech_table) { - verify_ion_usage(entry.first, entry.second.info); + verify_ion_usage(entry.first, entry.second.info.get()); } for (auto& entry: point_mech_table) { - verify_ion_usage(entry.first, entry.second.info); + verify_ion_usage(entry.first, entry.second.info.get()); } // II. Build ion and mechanism configs. @@ -470,7 +470,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& fvm_mechanism_config& config = mechdata.mechanisms[name]; config.kind = mechanismKind::density; - auto nparam = build_param_data(entry.second.paramset, entry.second.info); + auto nparam = build_param_data(entry.second.paramset, entry.second.info.get()); // In order to properly account for partially overriden paramaters in CVs // that are shared between segments, we need to track not only the area-weighted @@ -588,7 +588,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& const std::string& name = entry.first; const auto& points = entry.second.points; - auto nparam = build_param_data(entry.second.paramset, entry.second.info); + auto nparam = build_param_data(entry.second.paramset, entry.second.info.get()); std::vector<std::vector<value_type>> param_value(nparam); // Permute points in this mechanism so that they are in increasing CV order; diff --git a/arbor/include/arbor/arbexcept.hpp b/arbor/include/arbor/arbexcept.hpp index 4f7d0d4d27d83293823b30459042003f4e89938b..6eb4a4bfd8016ae65d3a48db1a7f361f02592e45 100644 --- a/arbor/include/arbor/arbexcept.hpp +++ b/arbor/include/arbor/arbexcept.hpp @@ -89,9 +89,11 @@ struct no_such_parameter: arbor_exception { }; struct invalid_parameter_value: arbor_exception { + invalid_parameter_value(const std::string& mech_name, const std::string& param_name, const std::string& value_str); invalid_parameter_value(const std::string& mech_name, const std::string& param_name, double value); std::string mech_name; std::string param_name; + std::string value_str; double value; }; diff --git a/arbor/include/arbor/mechcat.hpp b/arbor/include/arbor/mechcat.hpp index 76a03df4a5b10f980e608c21b2990a560efdf51c..bbe80e6e7ad6c89a1fe722c15311bda731b3478b 100644 --- a/arbor/include/arbor/mechcat.hpp +++ b/arbor/include/arbor/mechcat.hpp @@ -8,6 +8,7 @@ #include <arbor/mechinfo.hpp> #include <arbor/mechanism.hpp> +#include <arbor/util/optional.hpp> // Mechanism catalogue maintains: // @@ -28,39 +29,41 @@ // There is in addition a global default mechanism catalogue object that is // populated with any builtin mechanisms and mechanisms generated from // module files included with arbor. +// +// When a mechanism name of the form "mech/param=value,..." is requested, if the +// mechanism of that name does not already exist in the catalogue, it will be +// implicitly derived from an existing mechanism "mech", with global parameters +// and ion bindings overridden by the supplied assignments that follow the slash. +// If the mechanism in question has a single ion dependence, then that ion name +// can be omitted in the assignments; "mech/oldion=newion" will make the same +// derived mechanism as simply "mech/newion". namespace arb { +// catalogue_state comprises the private implementation of mechanism_catalogue. +struct catalogue_state; + class mechanism_catalogue { public: using value_type = double; - mechanism_catalogue() = default; - mechanism_catalogue(mechanism_catalogue&& other) = default; - mechanism_catalogue& operator=(mechanism_catalogue&& other) = default; - - // Copying a catalogue requires cloning the prototypes. - mechanism_catalogue(const mechanism_catalogue& other) { - copy_impl(other); - } + mechanism_catalogue(); + mechanism_catalogue(mechanism_catalogue&& other); + mechanism_catalogue& operator=(mechanism_catalogue&& other); - mechanism_catalogue& operator=(const mechanism_catalogue& other) { - copy_impl(other); - return *this; - } + mechanism_catalogue(const mechanism_catalogue& other); + mechanism_catalogue& operator=(const mechanism_catalogue& other); void add(const std::string& name, mechanism_info info); - bool has(const std::string& name) const { - return info_map_.count(name) || is_derived(name); - } + // Has `name` been added, derived, or can it be implicitly derived? + bool has(const std::string& name) const; - bool is_derived(const std::string& name) const { - return derived_map_.count(name); - } + // Is `name` a derived mechanism or can it be implicitly derived? + bool is_derived(const std::string& name) const; // Read-only access to mechanism info. - const mechanism_info& operator[](const std::string& name) const; + mechanism_info operator[](const std::string& name) const; // Read-only access to mechanism fingerprint. const mechanism_fingerprint& fingerprint(const std::string& name) const; @@ -72,7 +75,7 @@ public: const std::vector<std::pair<std::string, double>>& global_params, const std::vector<std::pair<std::string, std::string>>& ion_remap = {}); - // Remove mechanism from catalogue, together with any derived. + // Remove mechanism from catalogue, together with any derivations of it. void remove(const std::string& name); // Clone the implementation associated with name (search derivation hierarchy starting from @@ -101,40 +104,15 @@ public: register_impl(std::type_index(typeid(B)), name, std::move(generic_proto)); } -private: - using mechanism_info_ptr = std::unique_ptr<mechanism_info>; - - template <typename V> - using string_map = std::unordered_map<std::string, V>; - - // Schemata for (un-derived) mechanisms. - string_map<mechanism_info_ptr> info_map_; - - struct derivation { - std::string parent; - string_map<value_type> globals; // global overrides relative to parent - string_map<std::string> ion_remap; // ion name remap overrides relative to parent - mechanism_info_ptr derived_info; - }; - - // Parent and global setting values for derived mechanisms. - string_map<derivation> derived_map_; + ~mechanism_catalogue(); - // Prototype register, keyed on mechanism name, then backend type (index). - string_map<std::unordered_map<std::type_index, mechanism_ptr>> impl_map_; +private: + std::unique_ptr<catalogue_state> state_; - // Concrete-type erased helper methods. std::pair<mechanism_ptr, mechanism_overrides> instance_impl(std::type_index, const std::string&) const; void register_impl(std::type_index, const std::string&, mechanism_ptr); - - // Perform copy and prototype clone from other catalogue (overwrites all entries). - void copy_impl(const mechanism_catalogue&); }; -// Convenience routine for deriving a single-ion dependency mechanism 'name' -// over a new ion 'ion', and adding it to the catalogue as 'name/ion'. - -void parameterize_over_ion(mechanism_catalogue&, const std::string& name, const std::string& ion); // Reference to global default mechanism catalogue. diff --git a/arbor/include/arbor/simulation.hpp b/arbor/include/arbor/simulation.hpp index bff327d1d4c2cbc2cb439eec843cb79bb1eda26b..3dc177a4467455fb8d1c7ee2c7b2cad6afdda4e0 100644 --- a/arbor/include/arbor/simulation.hpp +++ b/arbor/include/arbor/simulation.hpp @@ -18,7 +18,7 @@ namespace arb { using spike_export_function = std::function<void(const std::vector<spike>&)>; -// simulation_state comprises private implentation for simulation class. +// simulation_state comprises private implementation for simulation class. class simulation_state; class simulation { diff --git a/arbor/mechcat.cpp b/arbor/mechcat.cpp index af1eb807e5b3d04bd974255078303f3d04b91f40..05408d157cf1244e0620f3a7fa0b1e883570f333 100644 --- a/arbor/mechcat.cpp +++ b/arbor/mechcat.cpp @@ -1,3 +1,4 @@ +#include <cstdlib> #include <map> #include <memory> #include <string> @@ -6,6 +7,7 @@ #include <arbor/arbexcept.hpp> #include <arbor/mechcat.hpp> +#include "util/either.hpp" #include "util/maputil.hpp" /* Notes on implementation: @@ -16,7 +18,7 @@ * * This contains the mapping between mechanism names and concrete mechanisms * for a specific backend that have been registered with - * register_implementation(). + * register_impl(). * * It is a two-level map, first indexed by name, and then by the back-end * type (using std::type_index). @@ -49,245 +51,510 @@ * global parameter and ion overrides that need to be applied, starting from * the top-most (least-derived) ancestor and working down to the requested derived * mechanism. + * + * The private implementation class catalogue_state does not throw any (catalogue + * related) exceptions, but instead propagates errors via util::either to the + * mechanism_catalogue methods for handling. */ namespace arb { using util::value_by_key; +using util::optional; +using util::nullopt; + using std::make_unique; +using std::make_exception_ptr; -void mechanism_catalogue::add(const std::string& name, mechanism_info info) { - if (has(name)) { - throw duplicate_mechanism(name); - } +using mechanism_info_ptr = std::unique_ptr<mechanism_info>; - info_map_[name] = mechanism_info_ptr(new mechanism_info(std::move(info))); -} +template <typename V> +using string_map = std::unordered_map<std::string, V>; -const mechanism_info& mechanism_catalogue::operator[](const std::string& name) const { - if (const auto& deriv = value_by_key(derived_map_, name)) { - return *(deriv->derived_info.get()); - } - else if (auto p = value_by_key(info_map_, name)) { - return *(p->get()); - } +template <typename T> +struct hopefully_typemap { + using type = util::either<T, std::exception_ptr>; +}; - throw no_such_mechanism(name); -} +template <> +struct hopefully_typemap<void> { + struct placeholder_type {}; + using type = util::either<placeholder_type, std::exception_ptr>; +}; -const mechanism_fingerprint& mechanism_catalogue::fingerprint(const std::string& name) const { - std::string base = name; - while (auto deriv = value_by_key(derived_map_, base)) { - base = deriv->parent; +template <typename T> +using hopefully = typename hopefully_typemap<T>::type; + +// Convert hopefully<T> to T or throw. + +template <typename T> +const T& value(const util::either<T, std::exception_ptr>& x) { + if (!x) { + std::rethrow_exception(x.second()); } + return x.first(); +} - if (const auto& p = value_by_key(info_map_, base)) { - return p.value()->fingerprint; +template <typename T> +T value(util::either<T, std::exception_ptr>&& x) { + if (!x) { + std::rethrow_exception(x.second()); } + return std::move(x.first()); +} - throw no_such_mechanism(name); +void value(const hopefully<void>& x) { + if (!x) { + std::rethrow_exception(x.second()); + } } -void mechanism_catalogue::derive(const std::string& name, const std::string& parent, - const std::vector<std::pair<std::string, double>>& global_params, - const std::vector<std::pair<std::string, std::string>>& ion_remap_vec) -{ - if (has(name)) { - throw duplicate_mechanism(name); +struct derivation { + std::string parent; + string_map<double> globals; // global overrides relative to parent + string_map<std::string> ion_remap; // ion name remap overrides relative to parent + mechanism_info_ptr derived_info; +}; + + +// (Pimpl) catalogue state. + +struct catalogue_state { + catalogue_state() = default; + + catalogue_state(const catalogue_state& other) { + info_map_.clear(); + for (const auto& kv: other.info_map_) { + info_map_[kv.first] = make_unique<mechanism_info>(*kv.second); + } + + derived_map_.clear(); + for (const auto& kv: other.derived_map_) { + const derivation& v = kv.second; + derived_map_[kv.first] = {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); + } } - if (!has(parent)) { - throw no_such_mechanism(parent); + // Check for presence of mechanism or derived mechanism. + bool defined(const std::string& name) const { + return info_map_.count(name) || derived_map_.count(name); } - string_map<std::string> ion_remap_map(ion_remap_vec.begin(), ion_remap_vec.end()); - derivation deriv = {parent, {}, ion_remap_map, nullptr}; - mechanism_info_ptr info = mechanism_info_ptr(new mechanism_info((*this)[deriv.parent])); + // Check if name is derived or implicitly derivable. + bool is_derived(const std::string& name) const { + return derived_map_.count(name) || derive(name); + } - // Update global parameter values in info for derived mechanism. + // Set mechanism info (unchecked). + void bind(const std::string& name, mechanism_info info) { + info_map_[name] = mechanism_info_ptr(new mechanism_info(std::move(info))); + } - for (const auto& kv: global_params) { - const auto& param = kv.first; - const auto& value = kv.second; + // Add derived mechanism (unchecked). + void bind(const std::string& name, derivation deriv) { + derived_map_[name] = std::move(deriv); + } - if (auto p = value_by_key(info->globals, param)) { - if (!p->valid(value)) { - throw invalid_parameter_value(name, param, value); + // Register concrete mechanism for a back-end type. + hopefully<void> register_impl(std::type_index tidx, const std::string& name, std::unique_ptr<mechanism> mech) { + if (auto fptr = fingerprint_ptr(name)) { + if (mech->fingerprint()!=*fptr.first()) { + return make_exception_ptr(fingerprint_mismatch(name)); } + + impl_map_[name][tidx] = std::move(mech); } else { - throw no_such_parameter(name, param); + return fptr.second(); } - deriv.globals[param] = value; - info->globals.at(param).default_value = value; + return {}; } - for (const auto& kv: ion_remap_vec) { - if (!info->ions.count(kv.first)) { - throw invalid_ion_remap(name, kv.first, kv.second); + // Remove mechanism and its derivations and implementations. + void remove(const std::string& name) { + derived_map_.erase(name); + info_map_.erase(name); + impl_map_.erase(name); + + // Erase any dangling derivation map entries. + std::size_t n_delete; + do { + n_delete = 0; + for (auto it = derived_map_.begin(); it!=derived_map_.end(); ) { + const auto& parent = it->second.parent; + if (info_map_.count(parent) || derived_map_.count(parent)) { + ++it; + } + else { + impl_map_.erase(it->first); + derived_map_.erase(it++); + ++n_delete; + } + } + } while (n_delete>0); + } + + // Retrieve mechanism info for mechanism, derived mechanism, or implicitly + // derived mechanism. + hopefully<mechanism_info> info(const std::string& name) const { + if (const auto& deriv = value_by_key(derived_map_, name)) { + return *(deriv->derived_info.get()); + } + else if (auto p = value_by_key(info_map_, name)) { + return *(p->get()); + } + else if (auto deriv = derive(name)) { + return *(deriv.first().derived_info.get()); + } + else { + return deriv.second(); } } - // Update ion dependencies in info to reflect the requested ion remapping. + // Retrieve mechanism fingerprint. The fingerprint of a derived mechanisms + // is that of its parent. + hopefully<const mechanism_fingerprint*> fingerprint_ptr(const std::string& name) const { + hopefully<derivation> implicit_deriv; + const std::string* base = &name; - string_map<ion_dependency> new_ions; - for (const auto& kv: info->ions) { - if (auto new_ion = value_by_key(ion_remap_map, kv.first)) { - if (!new_ions.insert({*new_ion, kv.second}).second) { - throw invalid_ion_remap(name, kv.first, *new_ion); + if (!defined(name)) { + if (implicit_deriv = derive(name)) { + base = &implicit_deriv.first().parent; + } + else { + return implicit_deriv.second(); } } + + while (auto maybe_deriv = value_by_key(derived_map_, *base)) { + base = &maybe_deriv->parent; + } + + if (const auto& p = value_by_key(info_map_, *base)) { + return &p.value()->fingerprint; + } + + throw arbor_internal_error("inconsistent catalogue map state"); + } + + // Construct derived mechanism based on existing parent mechanism and overrides. + hopefully<derivation> derive( + const std::string& name, const std::string& parent, + const std::vector<std::pair<std::string, double>>& global_params, + const std::vector<std::pair<std::string, std::string>>& ion_remap_vec) const + { + if (defined(name)) { + return make_exception_ptr(duplicate_mechanism(name)); + } + else if (!defined(parent)) { + return make_exception_ptr(no_such_mechanism(parent)); + } + + string_map<std::string> ion_remap_map(ion_remap_vec.begin(), ion_remap_vec.end()); + derivation deriv = {parent, {}, ion_remap_map, nullptr}; + + mechanism_info_ptr new_info; + if (auto parent_info = info(parent)) { + new_info.reset(new mechanism_info(parent_info.first())); + } else { - if (!new_ions.insert(kv).second) { - // (find offending remap to report in exception) - for (const auto& entry: ion_remap_map) { - if (entry.second==kv.first) { - throw invalid_ion_remap(name, kv.first, entry.second); + return parent_info.second(); + } + + // Update global parameter values in info for derived mechanism. + + for (const auto& kv: global_params) { + const auto& param = kv.first; + const auto& value = kv.second; + + if (auto p = value_by_key(new_info->globals, param)) { + if (!p->valid(value)) { + return make_exception_ptr(invalid_parameter_value(name, param, value)); + } + } + else { + return make_exception_ptr(no_such_parameter(name, param)); + } + + deriv.globals[param] = value; + new_info->globals.at(param).default_value = value; + } + + for (const auto& kv: ion_remap_vec) { + if (!new_info->ions.count(kv.first)) { + return make_exception_ptr(invalid_ion_remap(name, kv.first, kv.second)); + } + } + + // Update ion dependencies in info to reflect the requested ion remapping. + + string_map<ion_dependency> new_ions; + for (const auto& kv: new_info->ions) { + if (auto new_ion = value_by_key(ion_remap_map, kv.first)) { + if (!new_ions.insert({*new_ion, kv.second}).second) { + return make_exception_ptr(invalid_ion_remap(name, kv.first, *new_ion)); + } + } + else { + if (!new_ions.insert(kv).second) { + // (find offending remap to report in exception) + for (const auto& entry: ion_remap_map) { + if (entry.second==kv.first) { + return make_exception_ptr(invalid_ion_remap(name, kv.first, entry.second)); + } } + throw arbor_internal_error("inconsistent catalogue ion remap state"); } - throw arbor_internal_error("inconsistent catalogue ion remap state"); } } + new_info->ions = std::move(new_ions); + + deriv.derived_info = std::move(new_info); + return deriv; } - info->ions = std::move(new_ions); - deriv.derived_info = std::move(info); - derived_map_[name] = std::move(deriv); -} + // Implicit derivation. + hopefully<derivation> derive(const std::string& name) const { + if (defined(name)) { + return make_exception_ptr(duplicate_mechanism(name)); + } -void mechanism_catalogue::remove(const std::string& name) { - if (!has(name)) { - throw no_such_mechanism(name); - } + auto i = name.find_last_of('/'); + if (i==std::string::npos) { + return make_exception_ptr(no_such_mechanism(name)); + } - if (is_derived(name)) { - derived_map_.erase(name); - } - else { - info_map_.erase(name); - impl_map_.erase(name); - } + std::string base = name.substr(0, i); + if (!defined(base)) { + return make_exception_ptr(no_such_mechanism(base)); + } - // Erase any dangling derivation map entries. - std::size_t n_delete; - do { - n_delete = 0; - for (auto it = derived_map_.begin(); it!=derived_map_.end(); ) { - const auto& parent = it->second.parent; - if (info_map_.count(parent) || derived_map_.count(parent)) { - ++it; + std::string suffix = name.substr(i+1); + + const mechanism_info_ptr& info = derived_map_.count(base)? derived_map_.at(base).derived_info: info_map_.at(base); + bool single_ion = info->ions.size()==1u; + auto is_ion = [&info](const std::string& name) -> bool { return info->ions.count(name); }; + + std::vector<std::pair<std::string, double>> global_params; + std::vector<std::pair<std::string, std::string>> ion_remap; + + while (!suffix.empty()) { + std::string assign; + + auto comma = suffix.find(','); + if (comma==std::string::npos) { + assign = suffix; + suffix.clear(); } else { - derived_map_.erase(it++); - ++n_delete; + assign = suffix.substr(0, comma); + suffix = suffix.substr(comma+1); } - } - } while (n_delete>0); -} - -std::pair<std::unique_ptr<mechanism>, mechanism_overrides> -mechanism_catalogue::instance_impl(std::type_index tidx, const std::string& name) const { - std::pair<std::unique_ptr<mechanism>, mechanism_overrides> mech; - // Find implementation associated with this name or its closest ancestor. + std::string k, v; + auto eq = assign.find('='); + if (eq==std::string::npos) { + if (!single_ion) { + return make_exception_ptr(invalid_ion_remap(assign)); + } - auto impl_name = name; - const mechanism* prototype = nullptr; + k = info->ions.begin()->first; + v = assign; + } + else { + k = assign.substr(0, eq); + v = assign.substr(eq+1); + } - for (;;) { - if (const auto mech_impls = value_by_key(impl_map_, impl_name)) { - if (auto p = value_by_key(mech_impls.value(), tidx)) { - prototype = p->get(); - break; + if (is_ion(k)) { + ion_remap.push_back({k, v}); + } + else { + char* end = 0; + double v_value = std::strtod(v.c_str(), &end); + if (!end || *end) { + return make_exception_ptr(invalid_parameter_value(name, k, v)); + } + global_params.push_back({k, v_value}); } } - // Try parent instead. - if (const auto p = value_by_key(derived_map_, impl_name)) { - impl_name = p->parent; - } - else { - throw no_such_implementation(name); - } + return derive(name, base, global_params, ion_remap); } - mech.first = prototype->clone(); + // Retrieve implementation for this mechanism name or closest ancestor. + hopefully<std::unique_ptr<mechanism>> implementation(std::type_index tidx, const std::string& name) const { + const std::string* impl_name = &name; + hopefully<derivation> implicit_deriv; + + if (!defined(name)) { + implicit_deriv = derive(name); + if (!implicit_deriv) { + return implicit_deriv.second(); + } + impl_name = &implicit_deriv.first().parent; + } + + for (;;) { + if (const auto mech_impls = value_by_key(impl_map_, *impl_name)) { + if (auto p = value_by_key(mech_impls.value(), tidx)) { + return p->get()->clone(); + } + } - // Recurse up the derivation tree to find the most distant ancestor; - // accumulate global parameter settings and ion remappings down to the - // requested mechanism. + // Try parent instead. + if (const auto p = value_by_key(derived_map_, *impl_name)) { + impl_name = &p->parent; + } + else { + return make_exception_ptr(no_such_implementation(name)); + } + } + } - auto apply_globals = [this](auto& self, const std::string& name, mechanism_overrides& over) -> void { - if (auto p = value_by_key(derived_map_, name)) { - self(self, p->parent, over); + // Accumulate override set from derivation chain. + hopefully<mechanism_overrides> overrides(const std::string& name) const { + mechanism_overrides over; - for (auto& kv: p->globals) { + auto apply_deriv = [](mechanism_overrides& over, const derivation& deriv) { + for (auto& kv: deriv.globals) { over.globals[kv.first] = kv.second; } - if (!p->ion_remap.empty()) { - string_map<std::string> new_rebind = p->ion_remap; + if (!deriv.ion_remap.empty()) { + string_map<std::string> new_rebind = deriv.ion_remap; for (auto& kv: over.ion_rebind) { - if (auto opt_v = value_by_key(p->ion_remap, kv.second)) { + if (auto opt_v = value_by_key(deriv.ion_remap, kv.second)) { new_rebind.erase(kv.second); new_rebind[kv.first] = *opt_v; } } for (auto& kv: over.ion_rebind) { - if (!value_by_key(p->ion_remap, kv.second)) { + if (!value_by_key(deriv.ion_remap, kv.second)) { new_rebind[kv.first] = kv.second; } } std::swap(new_rebind, over.ion_rebind); } + }; + + // Recurse up the derivation tree to find the most distant ancestor; + // accumulate global parameter settings and ion remappings down to the + // requested mechanism. + + auto apply_globals = [this, &apply_deriv](auto& self, const std::string& name, mechanism_overrides& over) -> void { + if (auto p = value_by_key(derived_map_, name)) { + self(self, p->parent, over); + apply_deriv(over, *p); + } + }; + + util::optional<derivation> implicit_deriv; + if (!defined(name)) { + if (auto deriv = derive(name)) { + implicit_deriv = std::move(deriv.first()); + } + else { + return deriv.second(); + } } - }; - apply_globals(apply_globals, name, mech.second); - return mech; -} -void mechanism_catalogue::register_impl(std::type_index tidx, const std::string& name, std::unique_ptr<mechanism> mech) { - const mechanism_info& info = (*this)[name]; + apply_globals(apply_globals, implicit_deriv? implicit_deriv->parent: name, over); + if (implicit_deriv) { + apply_deriv(over, implicit_deriv.value()); + } - if (mech->fingerprint()!=info.fingerprint) { - throw fingerprint_mismatch(name); + return over; } - impl_map_[name][tidx] = std::move(mech); + // Schemata for (un-derived) mechanisms. + string_map<mechanism_info_ptr> info_map_; + + // Parent and global setting values for derived mechanisms. + string_map<derivation> derived_map_; + + // Prototype register, keyed on mechanism name, then backend type (index). + string_map<std::unordered_map<std::type_index, mechanism_ptr>> impl_map_; +}; + +// Mechanism catalogue method implementations. + +mechanism_catalogue::mechanism_catalogue(): + state_(new catalogue_state) +{} + +mechanism_catalogue::mechanism_catalogue(mechanism_catalogue&& other) = default; +mechanism_catalogue& mechanism_catalogue::operator=(mechanism_catalogue&& other) = default; + +mechanism_catalogue::mechanism_catalogue(const mechanism_catalogue& other): + state_(new catalogue_state(*other.state_)) +{} + +mechanism_catalogue& mechanism_catalogue::operator=(const mechanism_catalogue& other) { + state_.reset(new catalogue_state(*other.state_)); + return *this; } -void mechanism_catalogue::copy_impl(const mechanism_catalogue& other) { - info_map_.clear(); - for (const auto& kv: other.info_map_) { - info_map_[kv.first] = make_unique<mechanism_info>(*kv.second); +void mechanism_catalogue::add(const std::string& name, mechanism_info info) { + if (state_->defined(name)) { + throw duplicate_mechanism(name); } + state_->bind(name, std::move(info)); +} - derived_map_.clear(); - for (const auto& kv: other.derived_map_) { - const derivation& v = kv.second; - derived_map_[kv.first] = {v.parent, v.globals, v.ion_remap, make_unique<mechanism_info>(*v.derived_info)}; - } +bool mechanism_catalogue::has(const std::string& name) const { + return state_->defined(name) || state_->derive(name); +} - 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(); - } +bool mechanism_catalogue::is_derived(const std::string& name) const { + return state_->is_derived(name); +} - impl_map_[name_impls.first] = std::move(impls); - } +mechanism_info mechanism_catalogue::operator[](const std::string& name) const { + return value(state_->info(name)); +} + +const mechanism_fingerprint& mechanism_catalogue::fingerprint(const std::string& name) const { + return *value(state_->fingerprint_ptr(name)); +} + +void mechanism_catalogue::derive(const std::string& name, const std::string& parent, + const std::vector<std::pair<std::string, double>>& global_params, + const std::vector<std::pair<std::string, std::string>>& ion_remap_vec) +{ + state_->bind(name, value(state_->derive(name, parent, global_params, ion_remap_vec))); } -void parameterize_over_ion(mechanism_catalogue& cat, const std::string& name, const std::string& ion) { - mechanism_info info = cat[name]; - if (info.ions.size()!=1) { - throw invalid_ion_remap(name); +void mechanism_catalogue::remove(const std::string& name) { + if (!has(name)) { + throw no_such_mechanism(name); } + state_->remove(name); +} - std::string from_ion = info.ions.begin()->first; - cat.derive(name+"/"+ion, name, {}, {{from_ion, ion}}); +void mechanism_catalogue::register_impl(std::type_index tidx, const std::string& name, std::unique_ptr<mechanism> mech) { + value(state_->register_impl(tidx, name, std::move(mech))); } +std::pair<mechanism_ptr, mechanism_overrides> mechanism_catalogue::instance_impl(std::type_index tidx, const std::string& name) const { + std::pair<mechanism_ptr, mechanism_overrides> result; + result.first = value(state_->implementation(tidx, name)); + result.second = value(state_->overrides(name)); + + return result; +} + +mechanism_catalogue::~mechanism_catalogue() = default; + } // namespace arb diff --git a/test/unit/test_mechcat.cpp b/test/unit/test_mechcat.cpp index db77676d517f9b234cdca64e8b065e8c3319ebf2..2d7ccac9432538b5f319bd9a4a30e2fe19945ab9 100644 --- a/test/unit/test_mechcat.cpp +++ b/test/unit/test_mechcat.cpp @@ -384,22 +384,51 @@ TEST(mechcat, bad_ion_rename) { EXPECT_THROW(cat.derive("alas", "fleeb", {}, {{"a", "b"}}), invalid_ion_remap); } -TEST(mechcat, parameterize_over_ion) { +TEST(mechcat, implicit_deriv) { auto cat = build_fake_catalogue(); - // Can only use parametrize_over_ion with mechanisms that depend on exactly - // one ion. - - EXPECT_THROW(parameterize_over_ion(cat, "fleeb", "nope"), invalid_ion_remap); - - parameterize_over_ion(cat, "burble", "one"); - parameterize_over_ion(cat, "burble", "two"); - - auto b1 = cat["burble/one"]; - EXPECT_EQ("one", b1.ions.begin()->first); - - auto b2 = cat["burble/two"]; - EXPECT_EQ("two", b2.ions.begin()->first); + mechanism_info burble_derived_info = cat["burble/quux=3,xyzzy=4"]; + EXPECT_EQ(3, burble_derived_info.globals["quux"].default_value); + EXPECT_EQ(4, burble_derived_info.globals["xyzzy"].default_value); + + // If the mechanism is already in the catalogue though, don't make a new derivation. + cat.derive("fleeb/plugh=5", "fleeb", {{"plugh", 7.0}}, {}); + mechanism_info deceptive = cat["fleeb/plugh=5"]; + EXPECT_EQ(7, deceptive.globals["plugh"].default_value); + + // Check ion rebinds, too. + mechanism_info fleeb_derived_info = cat["fleeb/plugh=2,a=foo,b=bar"]; + EXPECT_EQ(2, fleeb_derived_info.globals["plugh"].default_value); + EXPECT_FALSE(fleeb_derived_info.ions.count("a")); + EXPECT_FALSE(fleeb_derived_info.ions.count("b")); + EXPECT_TRUE(fleeb_derived_info.ions.count("foo")); + EXPECT_TRUE(fleeb_derived_info.ions.count("bar")); + + // If only one ion, don't need to give lhs in reassignment. + mechanism_info bleeble_derived_info = cat["bleeble/fish,quux=9"]; + EXPECT_EQ(9, bleeble_derived_info.globals["quux"].default_value); + EXPECT_EQ(-20, bleeble_derived_info.globals["xyzzy"].default_value); + EXPECT_EQ(1u, bleeble_derived_info.ions.size()); + EXPECT_TRUE(bleeble_derived_info.ions.count("fish")); + + // Can't omit lhs if there is more than one ion though. + EXPECT_THROW(cat["fleeb/fish"], invalid_ion_remap); + + // Implicitly derived mechanisms should inherit implementations. + auto fleeb2 = cat.instance<foo_backend>("fleeb2"); + auto fleeb2_derived = cat.instance<foo_backend>("fleeb2/plugh=4.5"); + EXPECT_EQ("special fleeb", fleeb2.mech->internal_name()); + EXPECT_EQ("special fleeb", fleeb2_derived.mech->internal_name()); + EXPECT_EQ(4.5, fleeb2_derived.overrides.globals.at("plugh")); + + // Requesting an implicitly derived instance with improper parameters should throw. + EXPECT_THROW(cat.instance<foo_backend>("fleeb2/fidget=7"), no_such_parameter); + + // Testing for implicit derivation though should not throw. + EXPECT_TRUE(cat.has("fleeb2/plugh=7")); + EXPECT_FALSE(cat.has("fleeb2/fidget=7")); + EXPECT_TRUE(cat.is_derived("fleeb2/plugh=7")); + EXPECT_FALSE(cat.is_derived("fleeb2/fidget=7")); } TEST(mechcat, copy) {