diff --git a/arbor/cable_cell.cpp b/arbor/cable_cell.cpp index 6dea2d3991bb90191d8470dd16b64cc46dbe3d02..65088e389a70454a0fcfcb3e7ae806eda8527ca6 100644 --- a/arbor/cable_cell.cpp +++ b/arbor/cable_cell.cpp @@ -7,6 +7,7 @@ #include <arbor/morph/morphology.hpp> #include <arbor/morph/mprovider.hpp> #include <arbor/segment.hpp> +#include <arbor/util/pp_util.hpp> #include "util/piecewise.hpp" #include "util/rangeutil.hpp" @@ -22,16 +23,15 @@ using value_type = cable_cell::value_type; using index_type = cable_cell::index_type; using size_type = cable_cell::size_type; +template <typename T> struct constant_type { + template <typename> using type = T; +}; + struct cable_cell_impl { using value_type = cable_cell::value_type; using index_type = cable_cell::index_type; using size_type = cable_cell::size_type; - using stimulus_instance = cable_cell::stimulus_instance; - using synapse_instance = cable_cell::synapse_instance; - using gap_junction_instance = cable_cell::gap_junction_instance; - using detector_instance = cable_cell::detector_instance; - cable_cell_impl(const arb::morphology& m, const label_dict& dictionary, bool compartments_from_discretization): @@ -105,11 +105,9 @@ struct cable_cell_impl { cable_cell_impl(const cable_cell_impl& other): parents(other.parents), - stimuli(other.stimuli), - synapses(other.synapses), - gap_junction_sites(other.gap_junction_sites), - spike_detectors(other.spike_detectors), - provider(other.provider) + provider(other.provider), + region_map(other.region_map), + location_map(other.location_map) { // unique_ptr's cannot be copy constructed, do a manual assignment segments.reserve(other.segments.size()); @@ -126,75 +124,101 @@ struct cable_cell_impl { // the segments std::vector<segment_ptr> segments; - // the stimuli - std::vector<stimulus_instance> stimuli; + // Embedded morphology and labelled region/locset lookup. + mprovider provider; - // the synapses - std::vector<synapse_instance> synapses; + // Regional assignments. + cable_cell_region_map region_map; - // the gap_junctions - std::vector<gap_junction_instance> gap_junction_sites; + // Point assignments. + cable_cell_location_map location_map; - // the sensors - std::vector<detector_instance> spike_detectors; + // Track number of point assignments by type for lid/target numbers. + dynamic_typed_map<constant_type<cell_lid_type>::type> placed_count; - // Embedded morphology and labelled region/locset lookup. - mprovider provider; + template <typename T> + mlocation_map<T>& get_location_map(const T&) { + return location_map.get<T>(); + } - template <typename Desc, typename T> - lid_range place(const mlocation_list& locs, const Desc& desc, std::vector<T>& list) { - const auto first = list.size(); + mlocation_map<mechanism_desc>& get_location_map(const mechanism_desc& desc) { + return location_map.get<mechanism_desc>()[desc.name()]; + } + + template <typename Item> + lid_range place(const locset& ls, const Item& item) { + auto& mm = get_location_map(item); + cell_lid_type& lid = placed_count.get<Item>(); + cell_lid_type first = lid; - for (auto loc: locs) { - list.push_back({loc, desc}); + for (auto l: thingify(ls, provider)) { + placed<Item> p{l, lid++, item}; + mm.push_back(p); } + return lid_range(first, lid); + } - return lid_range(first, list.size()); + void assert_valid_segment(index_type i) const { + if (i>=segments.size()) { + throw cable_cell_error("no such segment"); + } } - template <typename Desc, typename T> - lid_range place(const locset& locs, const Desc& desc, std::vector<T>& list) { - return place(thingify(locs, provider), desc, list); + void paint_segment(segment_ptr& s, const mechanism_desc& p) { + s->add_mechanism(p); } - lid_range place_gj(const mlocation_list& locs) { - const auto first = gap_junction_sites.size(); + void paint_segment(segment_ptr& s, init_membrane_potential p) { + s->parameters.init_membrane_potential = p.value; + } - gap_junction_sites.insert(gap_junction_sites.end(), locs.begin(), locs.end()); + void paint_segment(segment_ptr& s, axial_resistivity p) { + s->parameters.axial_resistivity = p.value; + } - return lid_range(first, gap_junction_sites.size()); + void paint_segment(segment_ptr& s, temperature_K p) { + s->parameters.temperature_K = p.value; } - lid_range place_gj(const locset& locs) { - return place_gj(thingify(locs, provider)); + void paint_segment(segment_ptr& s, membrane_capacitance p) { + s->parameters.membrane_capacitance = p.value; } - void assert_valid_segment(index_type i) const { - if (i>=segments.size()) { - throw cable_cell_error("no such segment"); - } + void paint_segment(segment_ptr& s, initial_ion_data p) { + s->parameters.ion_data[p.ion] = p.initial; + } + + template <typename T> + mcable_map<T>& get_region_map(const T&) { + return region_map.get<T>(); } - bool valid_location(const mlocation& loc) const { - return test_invariants(loc) && loc.branch<segments.size(); + mcable_map<mechanism_desc>& get_region_map(const mechanism_desc& desc) { + return region_map.get<mechanism_desc>()[desc.name()]; } - template <typename F> - void paint(const mcable_list& cables, F&& f) { + mcable_map<initial_ion_data>& get_region_map(const initial_ion_data& init) { + return region_map.get<initial_ion_data>()[init.ion]; + } + + template <typename Property> + void paint(const region& reg, const Property& prop) { + mcable_list cables = thingify(reg, provider); + auto& mm = get_region_map(prop); + for (auto c: cables) { + if (!mm.insert(c, prop)) { + throw cable_cell_error(util::pprintf("cable {} overpaints", c)); + } + if (c.prox_pos!=0 || c.dist_pos!=1) { throw cable_cell_error(util::pprintf( "cable_cell does not support regions with partial branches: {}", c)); } assert_valid_segment(c.branch); - f(segments[c.branch]); + paint_segment(segments[c.branch], prop); } } - - template <typename F> - void paint(const region& reg, F&& f) { - paint(thingify(reg, provider), std::forward<F>(f)); - } }; using impl_ptr = std::unique_ptr<cable_cell_impl, void (*)(cable_cell_impl*)>; @@ -249,22 +273,6 @@ bool cable_cell::has_soma() const { return !segment(0)->is_placeholder(); } -const std::vector<cable_cell::gap_junction_instance>& cable_cell::gap_junction_sites() const { - return impl_->gap_junction_sites; -} - -const std::vector<cable_cell::synapse_instance>& cable_cell::synapses() const { - return impl_->synapses; -} - -const std::vector<cable_cell::detector_instance>& cable_cell::detectors() const { - return impl_->spike_detectors; -} - -const std::vector<cable_cell::stimulus_instance>& cable_cell::stimuli() const { - return impl_->stimuli; -} - const concrete_embedding& cable_cell::embedding() const { return impl_->provider.embedding(); } @@ -277,61 +285,32 @@ const mprovider& cable_cell::provider() const { return impl_->provider; } - -// -// Painters. -// -// Implementation of user API for painting density channel and electrical properties on cells. -// - -void cable_cell::paint(const region& target, mechanism_desc desc) { - impl_->paint(target, - [&desc](segment_ptr& s){return s->add_mechanism(desc);}); -} - -void cable_cell::paint(const region& target, cable_cell_local_parameter_set params) { - impl_->paint(target, - [¶ms](segment_ptr& s){return s->parameters = params;}); -} - -// -// Placers. -// -// Implementation of user API for placing discrete items on cell morphology, -// such as synapses, spike detectors and stimuli. -// - -// -// Synapses. -// - -lid_range cable_cell::place(const locset& ls, const mechanism_desc& desc) { - return impl_->place(ls, desc, impl_->synapses); +const cable_cell_location_map& cable_cell::location_assignments() const { + return impl_->location_map; } -// -// Stimuli. -// - -lid_range cable_cell::place(const locset& ls, const i_clamp& desc) { - return impl_->place(ls, desc, impl_->stimuli); +const cable_cell_region_map& cable_cell::region_assignments() const { + return impl_->region_map; } -// -// Gap junctions. -// +// Forward paint methods to implementation class. -lid_range cable_cell::place(const locset& ls, gap_junction_site) { - return impl_->place_gj(ls); +#define FWD_PAINT(proptype)\ +void cable_cell::paint(const region& target, proptype prop) {\ + impl_->paint(target, prop);\ } +ARB_PP_FOREACH(FWD_PAINT,\ + mechanism_desc, init_membrane_potential, axial_resistivity,\ + temperature_K, membrane_capacitance, initial_ion_data) -// -// Spike detectors. -// +// Forward place methods to implementation class. -lid_range cable_cell::place(const locset& ls, const threshold_detector& desc) { - return impl_->place(ls, desc.threshold, impl_->spike_detectors); +#define FWD_PLACE(proptype)\ +lid_range cable_cell::place(const locset& target, proptype prop) {\ + return impl_->place(target, prop);\ } +ARB_PP_FOREACH(FWD_PLACE,\ + mechanism_desc, i_clamp, gap_junction_site, threshold_detector) // // TODO: deprectate the following as soon as discretization code catches up with em_morphology diff --git a/arbor/cable_cell_param.cpp b/arbor/cable_cell_param.cpp index f6843b80b462111474cfbc4cdb6f78934c1112b8..bc7e26152b127f40b26f15514b5669fc36c6ea4c 100644 --- a/arbor/cable_cell_param.cpp +++ b/arbor/cable_cell_param.cpp @@ -50,14 +50,12 @@ void check_global_properties(const cable_cell_global_properties& G) { } } } + util::optional<double> init_membrane_potential; // [mV] + util::optional<double> temperature_K; // [K] + util::optional<double> axial_resistivity; // [Ω·cm] + util::optional<double> membrane_capacitance; // [F/m²] -cable_cell_local_parameter_set neuron_parameter_defaults = { - // ion defaults: internal concentration [mM], external concentration [mM], reversal potential [mV] - { - {"na", {10.0, 140.0, 115 - 65.}}, - {"k", {54.4, 2.5, -12 - 65.}}, - {"ca", {5e-5, 2.0, 12.5*std::log(2.0/5e-5)}} - }, +cable_cell_parameter_set neuron_parameter_defaults = { // initial membrane potential [mV] -65.0, // temperatue [K] @@ -65,7 +63,14 @@ cable_cell_local_parameter_set neuron_parameter_defaults = { // axial resistivity [Ω·cm] 35.4, // membrane capacitance [F/m²] - 0.01 + 0.01, + // ion defaults: + // internal concentration [mM], external concentration [mM], reversal potential [mV] + { + {"na", {10.0, 140.0, 115 - 65.}}, + {"k", {54.4, 2.5, -12 - 65.}}, + {"ca", {5e-5, 2.0, 12.5*std::log(2.0/5e-5)}} + }, }; // Discretization policy implementations: diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp index c0c5a1d8a46af8db9372f4a4096a1d7a01a403ef..deb1d7806c7bfa9f4edcf355cb0df59009b90e54 100644 --- a/arbor/fvm_layout.cpp +++ b/arbor/fvm_layout.cpp @@ -5,6 +5,7 @@ #include <arbor/arbexcept.hpp> #include <arbor/cable_cell.hpp> +#include <arbor/morph/mcable_map.hpp> #include <arbor/util/optional.hpp> #include "algorithms.hpp" @@ -14,6 +15,7 @@ #include "util/maputil.hpp" #include "util/meta.hpp" #include "util/partition.hpp" +#include "util/piecewise.hpp" #include "util/rangeutil.hpp" #include "util/transform.hpp" @@ -549,15 +551,16 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& }; auto cell_segment_part = D.cell_segment_part(); - size_type target_id = 0; + size_type target_count = 0; for (auto cell_idx: make_span(0, D.ncell)) { auto& cell = cells[cell_idx]; auto seg_range = cell_segment_part[cell_idx]; + size_type target_offset = target_count; auto add_ion_segment = [&gparam, &cell, &ion_segments] - (const std::string& ion_name, size_type segment_idx, const cable_cell_local_parameter_set& seg_param, const ion_dependency* iondep = nullptr) + (const std::string& ion_name, size_type segment_idx, const cable_cell_parameter_set& seg_param, const ion_dependency* iondep = nullptr) { const auto& global_ion_data = gparam.ion_data; const auto& cell_ion_data = cell.default_parameters.ion_data; @@ -614,33 +617,40 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& } } - for (const auto& cellsyn: cell.synapses()) { - const mechanism_desc& desc = cellsyn.mechanism; - size_type cv = D.branch_location_cv(cell_idx, cellsyn.location); - const auto& name = desc.name(); - + for (const auto& cellsyn_by_name: cell.synapses()) { + const auto& name = cellsyn_by_name.first; point_mech_data& entry = point_mech_table[name]; - update_paramset_and_validate(desc, entry.info, entry.paramset); - entry.points.push_back({cv, target_id++, &desc}); - const segment_ptr& seg = cell.segments()[cellsyn.location.branch]; - size_type segment_idx = D.cell_segment_bounds[cell_idx]+cellsyn.location.branch; + for (const auto& placed_synapse: cellsyn_by_name.second) { + mlocation loc = placed_synapse.loc; + cell_lid_type target = target_offset + placed_synapse.lid; + ++target_count; + const mechanism_desc& desc = placed_synapse.item; + + update_paramset_and_validate(desc, entry.info, entry.paramset); + + size_type cv = D.branch_location_cv(cell_idx, loc); + entry.points.push_back({cv, target, &desc}); + + const segment_ptr& seg = cell.segments()[loc.branch]; + size_type segment_idx = D.cell_segment_bounds[cell_idx]+loc.branch; - for (const auto& ion_entry: entry.info->ions) { - const std::string& ion_name = ion_entry.first; - const ion_dependency& iondep = ion_entry.second; + for (const auto& ion_entry: entry.info->ions) { + const std::string& ion_name = ion_entry.first; + const ion_dependency& iondep = ion_entry.second; - add_ion_segment(ion_name, segment_idx, seg->parameters, &iondep); + add_ion_segment(ion_name, segment_idx, seg->parameters, &iondep); - if (ion_entry.second.read_reversal_potential) { - ion_revpot_segments[ion_name][cell_idx].insert(segment_idx); + if (ion_entry.second.read_reversal_potential) { + ion_revpot_segments[ion_name][cell_idx].insert(segment_idx); + } } } } - for (const auto& stimulus: cell.stimuli()) { - size_type cv = D.branch_location_cv(cell_idx, stimulus.location); - stimuli.push_back({cv, stimulus.clamp}); + for (const auto& loc_clamp: cell.stimuli()) { + size_type cv = D.branch_location_cv(cell_idx, loc_clamp.loc); + stimuli.push_back({cv, loc_clamp.item}); } // Add segments to ion_segments map which intersect with existing segments, so diff --git a/arbor/fvm_lowered_cell_impl.hpp b/arbor/fvm_lowered_cell_impl.hpp index b8dc2f5a14fa688ebd12f8c67633aa1bd34628a6..32642764db8f1696963ce028a0d89a6c6f136246 100644 --- a/arbor/fvm_lowered_cell_impl.hpp +++ b/arbor/fvm_lowered_cell_impl.hpp @@ -516,9 +516,9 @@ void fvm_lowered_cell_impl<B>::initialize( for (auto cell_idx: make_span(ncell)) { cell_gid_type gid = gids[cell_idx]; - for (auto detector: cells[cell_idx].detectors()) { - detector_cv.push_back(D.branch_location_cv(cell_idx, detector.location)); - detector_threshold.push_back(detector.threshold); + for (auto entry: cells[cell_idx].detectors()) { + detector_cv.push_back(D.branch_location_cv(cell_idx, entry.loc)); + detector_threshold.push_back(entry.item.threshold); } for (cell_lid_type j: make_span(rec.num_probes(gid))) { @@ -563,9 +563,9 @@ std::vector<fvm_gap_junction> fvm_lowered_cell_impl<B>::fvm_gap_junctions( if (rec.num_gap_junction_sites(gids[cell_idx])) { gid_to_cvs[gids[cell_idx]].reserve(rec.num_gap_junction_sites(gids[cell_idx])); - auto cell_gj = cells[cell_idx].gap_junction_sites(); + const auto& cell_gj = cells[cell_idx].gap_junction_sites(); for (auto gj : cell_gj) { - auto cv = D.branch_location_cv(cell_idx, gj); + auto cv = D.branch_location_cv(cell_idx, gj.loc); gid_to_cvs[gids[cell_idx]].push_back(cv); } } diff --git a/arbor/include/arbor/cable_cell.hpp b/arbor/include/arbor/cable_cell.hpp index 9745d922d2f4f4b5c4c2504571ef639a6a79d156..b87d5e6efa9c2211b542a61f0dafd13a68bd456d 100644 --- a/arbor/include/arbor/cable_cell.hpp +++ b/arbor/include/arbor/cable_cell.hpp @@ -1,6 +1,8 @@ #pragma once #include <string> +#include <unordered_map> +#include <utility> #include <vector> #include <arbor/arbexcept.hpp> @@ -9,10 +11,12 @@ #include <arbor/constants.hpp> #include <arbor/mechcat.hpp> #include <arbor/morph/label_dict.hpp> +#include <arbor/morph/mcable_map.hpp> #include <arbor/morph/mprovider.hpp> #include <arbor/morph/morphology.hpp> #include <arbor/morph/primitives.hpp> #include <arbor/segment.hpp> +#include <arbor/util/typed_map.hpp> namespace arb { @@ -39,6 +43,42 @@ struct cell_probe_address { // Forward declare the implementation, for PIMPL. struct cable_cell_impl; +// Typed maps for access to painted and placed assignments: +// +// Mechanisms and initial ion data are further keyed by +// mechanism name and ion name respectively. + +template <typename T> +using region_assignment = + std::conditional_t< + std::is_same<T, mechanism_desc>::value || std::is_same<T, initial_ion_data>::value, + std::unordered_map<std::string, mcable_map<T>>, + mcable_map<T>>; + +template <typename T> +struct placed { + mlocation loc; + cell_lid_type lid; + T item; +}; + +template <typename T> +using mlocation_map = std::vector<placed<T>>; + +template <typename T> +using location_assignment = + std::conditional_t< + std::is_same<T, mechanism_desc>::value, + std::unordered_map<std::string, mlocation_map<T>>, + mlocation_map<T>>; + +using cable_cell_region_map = static_typed_map<region_assignment, + mechanism_desc, init_membrane_potential, axial_resistivity, + temperature_K, membrane_capacitance, initial_ion_data>; + +using cable_cell_location_map = static_typed_map<location_assignment, + mechanism_desc, i_clamp, gap_junction_site, threshold_detector>; + // High-level abstract representation of a cell and its segments class cable_cell { public: @@ -49,21 +89,6 @@ public: using gap_junction_instance = mlocation; - struct synapse_instance { - mlocation location; - mechanism_desc mechanism; - }; - - struct stimulus_instance { - mlocation location; - i_clamp clamp; - }; - - struct detector_instance { - mlocation location; - double threshold; - }; - cable_cell_parameter_set default_parameters; /// Default constructor @@ -88,6 +113,32 @@ public: // the number of branches in the cell size_type num_branches() const; + // Set cell-wide default physical and ion parameters. + + void set_default(init_membrane_potential prop) { + default_parameters.init_membrane_potential = prop.value; + } + + void set_default(axial_resistivity prop) { + default_parameters.axial_resistivity = prop.value; + } + + void set_default(temperature_K prop) { + default_parameters.temperature_K = prop.value; + } + + void set_default(membrane_capacitance prop) { + default_parameters.membrane_capacitance = prop.value; + } + + void set_default(initial_ion_data prop) { + default_parameters.ion_data[prop.ion] = prop.initial; + } + + void set_default(ion_reversal_potential_method prop) { + default_parameters.reversal_potential_method[prop.ion] = prop.method; + } + // All of the members marked with LEGACY below will be removed once // the discretization code has moved from consuming segments to em_morphology. @@ -130,26 +181,45 @@ public: void paint(const region&, mechanism_desc); // Properties. - void paint(const region&, cable_cell_local_parameter_set); + void paint(const region&, init_membrane_potential); + void paint(const region&, axial_resistivity); + void paint(const region&, temperature_K); + void paint(const region&, membrane_capacitance); + void paint(const region&, initial_ion_data); // Synapses. - lid_range place(const locset&, const mechanism_desc&); + lid_range place(const locset&, mechanism_desc); // Stimuli. - lid_range place(const locset&, const i_clamp&); + lid_range place(const locset&, i_clamp); // Gap junctions. lid_range place(const locset&, gap_junction_site); // Spike detectors. - lid_range place(const locset&, const threshold_detector&); + lid_range place(const locset&, threshold_detector); + + // Convenience access to placed items. + + const std::unordered_map<std::string, mlocation_map<mechanism_desc>>& synapses() const { + return location_assignments().get<mechanism_desc>(); + } + + const mlocation_map<gap_junction_site>& gap_junction_sites() const { + return location_assignments().get<gap_junction_site>(); + } + + const mlocation_map<threshold_detector>& detectors() const { + return location_assignments().get<threshold_detector>(); + } - // Access to placed items. + const mlocation_map<i_clamp>& stimuli() const { + return location_assignments().get<i_clamp>(); + } - const std::vector<synapse_instance>& synapses() const; - const std::vector<gap_junction_instance>& gap_junction_sites() const; - const std::vector<detector_instance>& detectors() const; - const std::vector<stimulus_instance>& stimuli() const; + // Generic access to painted and placed items. + const cable_cell_region_map& region_assignments() const; + const cable_cell_location_map& location_assignments() const; // Checks that two cells have the same // - number and type of segments diff --git a/arbor/include/arbor/cable_cell_param.hpp b/arbor/include/arbor/cable_cell_param.hpp index 92b1822cf2cd5b82ce1495f43373c913f31887ed..3cc08754792c3372cd70767500b1bc5d6bf789d4 100644 --- a/arbor/include/arbor/cable_cell_param.hpp +++ b/arbor/include/arbor/cable_cell_param.hpp @@ -18,6 +18,17 @@ struct cable_cell_error: arbor_exception { arbor_exception("cable_cell: "+what) {} }; +// Ion inital concentration and reversal potential +// parameters, as used in cable_cell_parameter_set, +// and set locally via painting initial_ion_data +// (see below). + +struct cable_cell_ion_data { + double init_int_concentration = NAN; + double init_ext_concentration = NAN; + double init_reversal_potential = NAN; +}; + // Current clamp description for stimulus specification. struct i_clamp { using value_type = double; @@ -41,6 +52,25 @@ struct threshold_detector { // Tag type for dispatching cable_cell::place() calls that add gap junction sites. struct gap_junction_site {}; +// Setter types for painting physical and ion parameters or setting +// cell-wide default: + +struct init_membrane_potential { + double value = NAN; // [mV] +}; + +struct temperature_K { + double value = NAN; // [K] +}; + +struct axial_resistivity { + double value = NAN; // [[Ω·cm] +}; + +struct membrane_capacitance { + double value = NAN; // [F/m²] +}; + // Mechanism description, viz. mechanism name and // (non-global) parameter settings. Used to assign // density and point mechanisms to segments and @@ -104,6 +134,16 @@ private: std::unordered_map<std::string, double> param_; }; +struct initial_ion_data { + std::string ion; + cable_cell_ion_data initial; +}; + +struct ion_reversal_potential_method { + std::string ion; + mechanism_desc method; +}; + // FVM discretization policies/hints. // // CV polices, given a cable cell, provide a locset comprising @@ -236,45 +276,28 @@ inline cv_policy default_cv_policy() { } // Cable cell ion and electrical defaults. -// -// Parameters can be overridden with `cable_cell_local_parameter_set` -// on unbranched segments within a cell; per-cell and global defaults -// use `cable_cell_parameter_set`, which extends the parameter set -// to supply per-cell or global ion reversal potential calculation -// mechanisms. -struct cable_cell_ion_data { - double init_int_concentration = NAN; - double init_ext_concentration = NAN; - double init_reversal_potential = NAN; -}; +// Parameters can be given as per-cell and global defaults via +// cable_cell::default_parameters and cable_cell_global_properties::default_parameters +// respectively. +// +// With the exception of `reversal_potential_method`, these properties can +// be set locally witihin a cell using the `cable_cell::paint()`, and the +// cell defaults can be individually set with `cable_cell:set_default()`. -struct cable_cell_local_parameter_set { - std::unordered_map<std::string, cable_cell_ion_data> ion_data; +struct cable_cell_parameter_set { util::optional<double> init_membrane_potential; // [mV] util::optional<double> temperature_K; // [K] util::optional<double> axial_resistivity; // [Ω·cm] util::optional<double> membrane_capacitance; // [F/m²] -}; -struct cable_cell_parameter_set: public cable_cell_local_parameter_set { + std::unordered_map<std::string, cable_cell_ion_data> ion_data; std::unordered_map<std::string, mechanism_desc> reversal_potential_method; - cv_policy discretization = default_cv_policy(); - // We'll need something like this until C++17, for sane initialization syntax. - cable_cell_parameter_set() = default; - cable_cell_parameter_set( - cable_cell_local_parameter_set p, - std::unordered_map<std::string, mechanism_desc> m = {}, - cv_policy d = default_cv_policy() - ): - cable_cell_local_parameter_set(std::move(p)), - reversal_potential_method(std::move(m)), - discretization(std::move(d)) - {} + cv_policy discretization = default_cv_policy(); }; -extern cable_cell_local_parameter_set neuron_parameter_defaults; +extern cable_cell_parameter_set neuron_parameter_defaults; // Global cable cell data. diff --git a/arbor/include/arbor/morph/mcable_map.hpp b/arbor/include/arbor/morph/mcable_map.hpp new file mode 100644 index 0000000000000000000000000000000000000000..94b638b1e9e73db56b3440a057acca364170efa7 --- /dev/null +++ b/arbor/include/arbor/morph/mcable_map.hpp @@ -0,0 +1,122 @@ +#pragma once + +// Represent an mcable_list where each cable has an associated value. +// +// The only mutating operations are insert, emplace, and clear. + +#include <algorithm> +#include <vector> + +#include <arbor/assert.hpp> +#include <arbor/morph/primitives.hpp> +#include <arbor/util/optional.hpp> + +namespace arb { + +template <typename T> +struct mcable_map { + // Forward subset of vector interface: + + using value_type = std::pair<mcable, T>; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + using const_iterator = typename std::vector<value_type>::const_iterator; + using iterator = const_iterator; + + using const_reverse_iterator = typename std::vector<value_type>::const_reverse_iterator; + using reverse_iterator = const_reverse_iterator; + + const_iterator begin() const noexcept { return elements_.cbegin(); } + const_iterator cbegin() const noexcept { return elements_.cbegin(); } + + const_reverse_iterator rbegin() const noexcept { return elements_.crbegin(); } + const_reverse_iterator crbegin() const noexcept { return elements_.crbegin(); } + + const_iterator end() const noexcept { return elements_.cend(); } + const_iterator cend() const noexcept { return elements_.cend(); } + + const_reverse_iterator rend() const noexcept { return elements_.crend(); } + const_reverse_iterator crend() const noexcept { return elements_.crend(); } + + const value_type& operator[](size_type i) const { return elements_[i]; } + + decltype(auto) front() const { return *begin(); } + decltype(auto) back() const { return *rbegin(); } + + std::size_t size() const noexcept { return elements_.size(); } + bool empty() const noexcept { return !size(); } + + void clear() { elements_.clear(); } + + // mcable_map-specific operations: + + // Insertion is successful iff c intersects with cables in at most two discrete points. + // insert() and emplace() return true on success. + + bool insert(const mcable& c, T value) { + auto opt_it = insertion_point(c); + if (!opt_it) return false; + + elements_.insert(*opt_it, value_type{c, std::move(value)}); + assert_invariants(); + return true; + } + + template <typename... Args> + bool emplace(const mcable& c, Args&&... args) { + auto opt_it = insertion_point(c); + if (!opt_it) return false; + + elements_.emplace(*opt_it, + std::piecewise_construct, + std::forward_as_tuple(c), + std::forward_as_tuple(std::forward<Args>(args)...)); + + assert_invariants(); + return true; + } + + mcable_list support() const { + mcable_list s; + s.reserve(elements_.size()); + std::transform(elements_.begin(), elements_.end(), std::back_inserter(s), + [](const auto& x) { return x.first; }); + + return s; + } + +private: + std::vector<value_type> elements_; + + util::optional<typename std::vector<value_type>::iterator> insertion_point(const mcable& c) { + struct as_mcable { + mcable value; + as_mcable(const value_type& x): value(x.first) {} + as_mcable(const mcable& x): value(x) {} + }; + + auto it = std::lower_bound(elements_.begin(), elements_.end(), c, + [](as_mcable a, as_mcable b) { return a.value<b.value; }); + + if (it!=elements_.begin()) { + mcable prior = std::prev(it)->first; + if (prior.branch==c.branch && prior.dist_pos>c.prox_pos) { + return util::nullopt; + } + } + if (it!=elements_.end()) { + mcable next = it->first; + if (c.branch==next.branch && c.dist_pos>next.prox_pos) { + return util::nullopt; + } + } + return it; + } + + void assert_invariants() { + arb_assert(test_invariants(support())); + } +}; + +} // namespace arb diff --git a/arbor/include/arbor/segment.hpp b/arbor/include/arbor/segment.hpp index 10264eb525079b5ba51f78f3f19feee77bbb669d..b87bcf36e8a54bb65d0c7fb41895e53c4f7f6031 100644 --- a/arbor/include/arbor/segment.hpp +++ b/arbor/include/arbor/segment.hpp @@ -113,7 +113,7 @@ public: return mechanisms_; } - cable_cell_local_parameter_set parameters; // override cell and global defaults + cable_cell_parameter_set parameters; // override cell and global defaults protected: segment(section_kind kind): kind_(kind) {} diff --git a/arbor/include/arbor/util/typed_map.hpp b/arbor/include/arbor/util/typed_map.hpp new file mode 100644 index 0000000000000000000000000000000000000000..785602e1e9e5352c190a85d312aee0260ca0c2ea --- /dev/null +++ b/arbor/include/arbor/util/typed_map.hpp @@ -0,0 +1,83 @@ +#pragma once + +// Maps keyed by type, with a fixed set of keys (static_typed_map) or +// an arbitrary set (dynamic_typed_map). +// +// The first template parameter maps the key type to its corresponding +// value type. +// +// Example: associating a vector of ints with 'int' and a vector of +// doubles with 'double': +// +// static_typed_map<std::vector, int, double> m; +// m.get<int>() = {1, 2, 3}; +// m.get<double>() = {1.2, 2.3}; + +#include <tuple> +#include <typeindex> +#include <unordered_map> + +#include <arbor/util/any.hpp> + +namespace arb { + +template <template <class> typename E> +struct dynamic_typed_map { + // Retrieve value by reference associated with type T; create entry with + // default value if no entry in map for T. + template <typename T> + E<T>& get() { + arb::util::any& store_entry = tmap_[std::type_index(typeid(T))]; + if (!store_entry.has_value()) { + store_entry = arb::util::any(E<T>{}); + } + + return arb::util::any_cast<E<T>&>(store_entry); + } + + // Retrieve value by const reference associated with type T; + // throw if no entry in map for T. + template <typename T> + const E<T>& get() const { + return arb::util::any_cast<const E<T>&>(tmap_.at(std::type_index(typeid(T)))); + } + + // True if map has an entry for type T. + template <typename T> + bool has() const { return tmap_.count(std::type_index(typeid(T))); } + +private: + std::unordered_map<std::type_index, arb::util::any> tmap_; +}; + +template <template <class> typename E, typename... Keys> +struct static_typed_map { + // Retrieve value by reference associated with type T. + template <typename T> + E<T>& get() { + return std::get<index<T, Keys...>()>(tmap_); + } + + // Retrieve value by const reference associated with type T. + template <typename T> + const E<T>& get() const { + return std::get<index<T, Keys...>()>(tmap_); + } + + // True if map has an entry for type T. + template <typename T> + constexpr bool has() const { return index<T, Keys...>()<sizeof...(Keys); } + +private: + std::tuple<E<Keys>...> tmap_; + + template <typename T> + static constexpr int index() { return 1; } + + template <typename T, typename H, typename... A> + static constexpr int index() { + return std::is_same<H, T>::value? 0: 1+index<T, A...>(); + } +}; + +} // namespace arb diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 8deea24dcce1ab01983d8a176b770427ad338faa..07d3d52fcca410e6dd3fc76ce7b9336f2fd9f5b1 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -105,6 +105,7 @@ set(unit_sources test_mask_stream.cpp test_math.cpp test_matrix.cpp + test_mcable_map.cpp test_mc_cell_group.cpp test_mechanisms.cpp test_mech_temp_diam.cpp diff --git a/test/unit/test_fvm_layout.cpp b/test/unit/test_fvm_layout.cpp index e1a0a5f82ea84b4bba3156d15955b8765d53bc62..b27a7b6df5948d7991da8de8bf0df2ec419b3045 100644 --- a/test/unit/test_fvm_layout.cpp +++ b/test/unit/test_fvm_layout.cpp @@ -101,17 +101,10 @@ namespace { cell.paint("soma", "hh"); cell.paint("dend", "pas"); - cable_cell_local_parameter_set p1; - p1.membrane_capacitance = 0.017; - cable_cell_local_parameter_set p2; - p2.membrane_capacitance = 0.013; - cable_cell_local_parameter_set p3; - p3.membrane_capacitance = 0.018; - using ::arb::reg::branch; - cell.paint(branch(b1), p1); - cell.paint(branch(b2), p2); - cell.paint(branch(b3), p3); + cell.paint(branch(b1), membrane_capacitance{0.017}); + cell.paint(branch(b2), membrane_capacitance{0.013}); + cell.paint(branch(b3), membrane_capacitance{0.018}); cell.place(mlocation{2,1}, i_clamp{5., 80., 0.45}); cell.place(mlocation{3,1}, i_clamp{40., 10.,-0.2}); diff --git a/test/unit/test_mcable_map.cpp b/test/unit/test_mcable_map.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f5eb215c3b312316f58457c4f2ff9eecaefd07e4 --- /dev/null +++ b/test/unit/test_mcable_map.cpp @@ -0,0 +1,109 @@ +#include "../test/gtest.h" + +#include <arbor/morph/mcable_map.hpp> +#include <arbor/morph/primitives.hpp> + +#include "util/span.hpp" + +using namespace arb; + +TEST(mcable_map, insertion) { + mcable_list cl{{0, 0.2, 0.8}, {1, 0, 1}, {2, 0.2, 0.4}, {2, 0.4, 0.7}, {2, 0.8, 1.}, {4, 0.2, 0.4}}; + mcable_map<int> mm; + bool ok = false; + + int j = 10; + for (const mcable& c: cl) { + ok = mm.insert(c, j++); + EXPECT_TRUE(ok); + } + auto expected_size = cl.size(); + EXPECT_EQ(expected_size, mm.size()); + + // insertions at front or in middle: + ok = mm.insert(mcable{0, 0.1, 0.2}, 1); + EXPECT_TRUE(ok); + ++expected_size; + EXPECT_EQ(expected_size, mm.size()); + + ok = mm.insert(mcable{0, 0.05, 0.2}, 1); + EXPECT_FALSE(ok); // overlap + EXPECT_EQ(expected_size, mm.size()); + + ok = mm.insert(mcable{1, 1., 1.}, 1); + EXPECT_TRUE(ok); // overlapping one point is fine + ++expected_size; + EXPECT_EQ(expected_size, mm.size()); + + ok = mm.insert(mcable{3, 0., 1.}, 1); + EXPECT_TRUE(ok); + ++expected_size; + EXPECT_EQ(expected_size, mm.size()); + + ok = mm.insert(mcable{2, 0.7, 0.8}, 1); + EXPECT_TRUE(ok); + ++expected_size; + EXPECT_EQ(expected_size, mm.size()); + + ok = mm.insert(mcable{2, 0., 0.3}, 1); + EXPECT_FALSE(ok); // overlap + EXPECT_EQ(expected_size, mm.size()); +} + +TEST(mcable_map, emplace) { + struct foo { + foo(char a, int b): sa(a+1), sb(b+2) {} + foo(const foo&) = delete; + foo(foo&&) = default; + + foo& operator=(const foo&) = delete; + foo& operator=(foo&&) = default; + + char sa; + int sb; + }; + + mcable_map<foo> mm; + mm.emplace(mcable{1, 0.2, 0.4}, 'a', 3); + mm.emplace(mcable{2, 0.2, 0.4}, 'x', 5); + ASSERT_EQ(2u, mm.size()); + + EXPECT_EQ('b', mm[0].second.sa); + EXPECT_EQ(5, mm[0].second.sb); + + EXPECT_EQ('y', mm[1].second.sa); + EXPECT_EQ(7, mm[1].second.sb); +} + +TEST(mcable_map, access) { + mcable_list cl{{0, 0.2, 0.8}, {1, 0, 1}, {2, 0.2, 0.4}, {2, 0.4, 0.7}, {2, 0.8, 1.}, {4, 0.2, 0.4}}; + mcable_map<int> mm; + bool ok = false; + + int k = 10; + for (const mcable& c: cl) { + ok = mm.insert(c, k++); + ASSERT_TRUE(ok); + } + auto size = cl.size(); + ASSERT_EQ(size, mm.size()); + + for (std::size_t i = 0; i<size; ++i) { + EXPECT_EQ(cl[i], mm[i].first); + EXPECT_EQ(int(10+i), mm[i].second); + } + + std::size_t j = 0; + for (const auto& el: mm) { + EXPECT_EQ(cl[j], el.first); + EXPECT_EQ(int(10+j), el.second); + ++j; + } + + j = size; + for (auto r = mm.rbegin(); r!=mm.rend(); ++r) { + --j; + EXPECT_EQ(cl[j], r->first); + EXPECT_EQ(int(10+j), r->second); + } +} diff --git a/test/unit/test_synapses.cpp b/test/unit/test_synapses.cpp index 0c10474c08d58fe8d2bd7b3af51c3f6f800fc52f..de1a796b88763ffe33e31b3dc96c84469d43516e 100644 --- a/test/unit/test_synapses.cpp +++ b/test/unit/test_synapses.cpp @@ -39,20 +39,19 @@ TEST(synapses, add_to_cell) { cell.place(mlocation{0, 0.2}, "exp2syn"); cell.place(mlocation{0, 0.3}, "expsyn"); - EXPECT_EQ(3u, cell.synapses().size()); - const auto& syns = cell.synapses(); + auto syns = cell.synapses(); - EXPECT_EQ(syns[0].location.branch, 0u); - EXPECT_EQ(syns[0].location.pos, 0.1); - EXPECT_EQ(syns[0].mechanism.name(), "expsyn"); + ASSERT_EQ(2u, syns["expsyn"].size()); + ASSERT_EQ(1u, syns["exp2syn"].size()); - EXPECT_EQ(syns[1].location.branch, 0u); - EXPECT_EQ(syns[1].location.pos, 0.2); - EXPECT_EQ(syns[1].mechanism.name(), "exp2syn"); + EXPECT_EQ((mlocation{0, 0.1}), syns["expsyn"][0].loc); + EXPECT_EQ("expsyn", syns["expsyn"][0].item.name()); - EXPECT_EQ(syns[2].location.branch, 0u); - EXPECT_EQ(syns[2].location.pos, 0.3); - EXPECT_EQ(syns[2].mechanism.name(), "expsyn"); + EXPECT_EQ((mlocation{0, 0.3}), syns["expsyn"][1].loc); + EXPECT_EQ("expsyn", syns["expsyn"][1].item.name()); + + EXPECT_EQ((mlocation{0, 0.2}), syns["exp2syn"][0].loc); + EXPECT_EQ("exp2syn", syns["exp2syn"][0].item.name()); // adding a synapse to an invalid branch location should throw. EXPECT_THROW(cell.place(mlocation{1, 0.3}, "expsyn"), std::runtime_error);