#include <optional> #include <stdexcept> #include <pybind11/pybind11.h> #include "pybind11/pytypes.h" #include <pybind11/stl.h> #include <arbor/cable_cell_param.hpp> #include <arbor/mechanism.hpp> #include <arbor/mechcat.hpp> #include "arbor/mechinfo.hpp" #include "conversion.hpp" #include "strprintf.hpp" namespace pyarb { void apply_derive(arb::mechanism_catalogue& m, const std::string& name, const std::string& parent, const std::unordered_map<std::string, double>& globals, const std::unordered_map<std::string, std::string>& ions) { if (globals.empty() && ions.empty()) { m.derive(name, parent); return; } std::vector<std::pair<std::string, double>> G; for (auto& g: globals) { G.push_back({g.first, g.second}); } std::vector<std::pair<std::string, std::string>> I; for (auto& i: ions) { I.push_back({i.first, i.second}); } m.derive(name, parent, G, I); } void register_mechanisms(pybind11::module& m) { using std::optional; using namespace pybind11::literals; pybind11::class_<arb::mechanism_field_spec> field_spec(m, "mechanism_field", "Basic information about a mechanism field."); field_spec .def(pybind11::init<const arb::mechanism_field_spec&>()) .def_readonly("units", &arb::mechanism_field_spec::units) .def_readonly("default", &arb::mechanism_field_spec::default_value) .def_readonly("min", &arb::mechanism_field_spec::lower_bound) .def_readonly("max", &arb::mechanism_field_spec::upper_bound) .def("__repr__", [](const arb::mechanism_field_spec& spec) { return util::pprintf("{units: '{}', default: {}, min: {}, max: {}}", (spec.units.size()? spec.units.c_str(): "1"), spec.default_value, spec.lower_bound, spec.upper_bound); }) .def("__str__", [](const arb::mechanism_field_spec& spec) { return util::pprintf("{units: '{}', default: {}, min: {}, max: {}}", (spec.units.size()? spec.units.c_str(): "1"), spec.default_value, spec.lower_bound, spec.upper_bound); }); pybind11::class_<arb::ion_dependency> ion_dep(m, "ion_dependency", "Information about a mechanism's dependence on an ion species."); ion_dep .def(pybind11::init<const arb::ion_dependency&>()) .def_readonly("write_int_con", &arb::ion_dependency::write_concentration_int) .def_readonly("write_ext_con", &arb::ion_dependency::write_concentration_ext) .def_readonly("write_rev_pot", &arb::ion_dependency::write_reversal_potential) .def_readonly("read_rev_pot", &arb::ion_dependency::read_reversal_potential) .def("__repr__", [](const arb::ion_dependency& dep) { auto tf = [](bool x) {return x? "True": "False";}; return util::pprintf("{write_int_con: {}, write_ext_con: {}, write_rev_pot: {}, read_rev_pot: {}}", tf(dep.write_concentration_int), tf(dep.write_concentration_ext), tf(dep.write_reversal_potential), tf(dep.read_reversal_potential)); }) .def("__str__", [](const arb::ion_dependency& dep) { auto tf = [](bool x) {return x? "True": "False";}; return util::pprintf("{write_int_con: {}, write_ext_con: {}, write_rev_pot: {}, read_rev_pot: {}}", tf(dep.write_concentration_int), tf(dep.write_concentration_ext), tf(dep.write_reversal_potential), tf(dep.read_reversal_potential)); }) ; pybind11::class_<arb::mechanism_info> mech_inf(m, "mechanism_info", "Meta data about a mechanism's fields and ion dependendencies."); mech_inf .def(pybind11::init<const arb::mechanism_info&>()) .def_readonly("globals", &arb::mechanism_info::globals, "Global fields have one value common to an instance of a mechanism, are constant in time and set at instantiation.") .def_readonly("parameters", &arb::mechanism_info::parameters, "Parameter fields may vary across the extent of a mechanism, but are constant in time and set at instantiation.") .def_readonly("state", &arb::mechanism_info::state, "State fields vary in time and across the extent of a mechanism, and potentially can be sampled at run-time.") .def_readonly("ions", &arb::mechanism_info::ions, "Ion dependencies.") .def_readonly("linear", &arb::mechanism_info::linear, "True if a synapse mechanism has linear current contributions so that multiple instances on the same compartment can be coalesced.") .def_readonly("post_events", &arb::mechanism_info::post_events, "True if a synapse mechanism has a `POST_EVENT` procedure defined.") .def_property_readonly("kind", [](const arb::mechanism_info& info) { return arb_mechsnism_kind_str(info.kind); }, "String representation of the kind of the mechanism.") .def("__repr__", [](const arb::mechanism_info& inf) { return util::pprintf("(arbor.mechanism_info)"); }) .def("__str__", [](const arb::mechanism_info& inf) { return util::pprintf("(arbor.mechanism_info)"); }); pybind11::class_<arb::mechanism_catalogue> cat(m, "catalogue"); struct mech_cat_iter_state { mech_cat_iter_state(const arb::mechanism_catalogue &cat_, pybind11::object ref_): names(cat_.mechanism_names()), ref(ref_), cat(cat_) { } std::vector<std::string> names; // cache the names else these will be allocated multiple times pybind11::object ref; // keep a reference to cat lest it dies while we iterate const arb::mechanism_catalogue& cat; // to query the C++ object size_t idx = 0; // where we are in the sequence std::string next() { if (idx == names.size()) throw pybind11::stop_iteration(); return names[idx++]; } }; struct py_mech_cat_key_iterator { py_mech_cat_key_iterator(const arb::mechanism_catalogue &cat_, pybind11::object ref_): state{cat_, ref_} { } mech_cat_iter_state state; std::string next() { return state.next(); } }; struct py_mech_cat_item_iterator { py_mech_cat_item_iterator(const arb::mechanism_catalogue &cat_, pybind11::object ref_): state{cat_, ref_} { } mech_cat_iter_state state; std::tuple<std::string, arb::mechanism_info> next() { auto name = state.next(); return {name, state.cat[name]}; } }; struct py_mech_cat_value_iterator { py_mech_cat_value_iterator(const arb::mechanism_catalogue &cat_, pybind11::object ref_): state{cat_, ref_} { } mech_cat_iter_state state; arb::mechanism_info next() { return state.cat[state.next()]; } }; pybind11::class_<py_mech_cat_key_iterator>(m, "MechCatKeyIterator") .def("__iter__", [](py_mech_cat_key_iterator &it) -> py_mech_cat_key_iterator& { return it; }) .def("__next__", &py_mech_cat_key_iterator::next); pybind11::class_<py_mech_cat_value_iterator>(m, "MechCatValueIterator") .def("__iter__", [](py_mech_cat_value_iterator &it) -> py_mech_cat_value_iterator& { return it; }) .def("__next__", &py_mech_cat_value_iterator::next); pybind11::class_<py_mech_cat_item_iterator>(m, "MechCatItemIterator") .def("__iter__", [](py_mech_cat_item_iterator &it) -> py_mech_cat_item_iterator& { return it; }) .def("__next__", &py_mech_cat_item_iterator::next); cat .def(pybind11::init()) .def(pybind11::init<const arb::mechanism_catalogue&>()) .def("__contains__", &arb::mechanism_catalogue::has, "name"_a, "Is 'name' in the catalogue?") .def("__iter__", [](pybind11::object cat) { return py_mech_cat_key_iterator(cat.cast<const arb::mechanism_catalogue &>(), cat); }, "Return an iterator over all mechanism names in this catalogues.") .def("keys", [](pybind11::object cat) { return py_mech_cat_key_iterator(cat.cast<const arb::mechanism_catalogue &>(), cat); }, "Return an iterator over all mechanism names in this catalogues.") .def("values", [](pybind11::object cat) { return py_mech_cat_value_iterator(cat.cast<const arb::mechanism_catalogue &>(), cat); }, "Return an iterator over all mechanism info values in this catalogues.") .def("items", [](pybind11::object cat) { return py_mech_cat_item_iterator(cat.cast<const arb::mechanism_catalogue &>(), cat); }, "Return an iterator over all (name, mechanism) tuples in this catalogues.") .def("is_derived", &arb::mechanism_catalogue::is_derived, "name"_a, "Is 'name' a derived mechanism or can it be implicitly derived?") .def("__getitem__", [](arb::mechanism_catalogue& c, const char* name) { try { return c[name]; } catch (...) { throw pybind11::key_error(name); } }) .def("extend", &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>{}, "ions"_a=std::unordered_map<std::string, std::string>{}) .def("__repr__", [](const arb::mechanism_catalogue& cat) { return util::pprintf("<arbor.mechanism_catalogue>"); }) .def("__str__", [](const arb::mechanism_catalogue& cat) { return util::pprintf("<arbor.mechanism_catalogue>"); }); m.def("default_catalogue", [](){return arb::global_default_catalogue();}); m.def("allen_catalogue", [](){return arb::global_allen_catalogue();}); m.def("bbp_catalogue", [](){return arb::global_bbp_catalogue();}); m.def("load_catalogue", [](const std::string& fn){return arb::load_catalogue(fn);}); // arb::mechanism_desc // For specifying a mechanism in the cable_cell interface. pybind11::class_<arb::mechanism_desc> mechanism_desc(m, "mechanism"); mechanism_desc .def(pybind11::init([](const char* name) {return arb::mechanism_desc{name};}), "name"_a, "The name of the mechanism" ) // allow construction of a description with parameters provided in a dictionary: // mech = arbor.mechanism('mech_name', {'param1': 1.2, 'param2': 3.14}) .def(pybind11::init( [](const char* name, std::unordered_map<std::string, double> params) { arb::mechanism_desc md(name); for (const auto& p: params) md.set(p.first, p.second); return md; }), "name"_a, "The name of the mechanism", "params"_a, "A dictionary of parameter values, with parameter name as key.", "Example usage setting parameters:\n" " m = arbor.mechanism('expsyn', {'tau': 1.4})\n" "will create parameters for the 'expsyn' mechanism, with the provided value\n" "for 'tau' overrides the default. If a parameter is not set, the default\n" "(as defined in NMODL) is used.\n\n" "Example overriding a global parameter:\n" " m = arbor.mechanism('nernst/R=8.3145,F=96485')") .def("set", [](arb::mechanism_desc& md, std::string name, double value) { md.set(name, value); }, "name"_a, "value"_a, "Set parameter value.") .def_property_readonly("name", [](const arb::mechanism_desc& md) { return md.name(); }, "The name of the mechanism.") .def_property_readonly("values", [](const arb::mechanism_desc& md) { return md.values(); }, "A dictionary of parameter values with parameter name as key.") .def("__repr__", [](const arb::mechanism_desc& md) { return util::pprintf("<arbor.mechanism: name '{}', parameters {}", md.name(), util::dictionary_csv(md.values())); }) .def("__str__", [](const arb::mechanism_desc& md) { return util::pprintf("('{}' {})", md.name(), util::dictionary_csv(md.values())); }); } } // namespace pyarb