diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index d306774691..be1014b850 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -12,8 +12,8 @@ jobs: strategy: fail-fast: false env: - CC: gcc-11 - CXX: g++-11 + CC: gcc-12 + CXX: g++-12 steps: - name: Get build dependencies run: | diff --git a/arbor/cable_cell.cpp b/arbor/cable_cell.cpp index a8098ab14c..41e9c892af 100644 --- a/arbor/cable_cell.cpp +++ b/arbor/cable_cell.cpp @@ -126,7 +126,7 @@ struct cable_cell_impl { cell_lid_type first = lid; for (const auto& loc: locs) { - placed p{loc, lid++, item}; + placed p{loc, lid++, item, label}; mm.push_back(p); } auto range = lid_range(first, lid); diff --git a/arbor/fvm_lowered_cell_impl.hpp b/arbor/fvm_lowered_cell_impl.hpp index 730471d109..d69503566c 100644 --- a/arbor/fvm_lowered_cell_impl.hpp +++ b/arbor/fvm_lowered_cell_impl.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -882,15 +883,15 @@ void resolve_probe(const cable_probe_density_state_cell& p, probe_resolution_dat } inline -auto point_info_of(cell_lid_type target, +auto point_info_of(cell_tag_type target, + cell_lid_type lid, int mech_index, const mlocation_map& instances, const std::vector& multiplicity) { - - auto opt_i = util::binary_search_index(instances, target, [](auto& item) { return item.lid; }); + auto opt_i = util::binary_search_index(instances, lid, [](auto& item) { return item.lid; }); if (!opt_i) throw arbor_internal_error("inconsistent mechanism state"); - - return cable_probe_point_info {target, + return cable_probe_point_info {std::move(target), + lid, multiplicity.empty() ? 1u: multiplicity.at(mech_index), instances[*opt_i].loc}; } @@ -903,6 +904,7 @@ void resolve_probe(const cable_probe_point_state& p, probe_resolution_data& R const auto& mech = p.mechanism; const auto& state = p.state; const auto& target = p.target; + const auto& t_hash = hash_value(target); const auto& data = R.mechanism_state(mech, state); if (!R.mech_instance_by_name.count(mech)) return; const auto mech_id = R.mech_instance_by_name.at(mech)->mechanism_id(); @@ -913,17 +915,27 @@ void resolve_probe(const cable_probe_point_state& p, probe_resolution_data& R // Convert cell-local target number to cellgroup target number. const auto& divs = R.M.target_divs; auto cell = R.cell_idx; - auto cg = target + divs.at(cell); - if (cg >= divs.at(cell + 1)) return; - - const auto& handle = R.handles.at(cg); - if (handle.mech_id != mech_id) return; - auto mech_index = handle.mech_index; - R.result.push_back(fvm_probe_scalar{{data + mech_index}, - point_info_of(target, - mech_index, - synapses.at(mech), - R.M.mechanisms.at(mech).multiplicity)}); + auto cg_lo = divs.at(cell); + auto cg_hi = divs.at(cell + 1); + const auto& [lr_beg, lr_end] = R.cell + .synapse_ranges() + .equal_range(t_hash); + for (auto lr = lr_beg; lr != lr_end; ++lr) { + const auto& [lid_beg, lid_end] = lr->second; + for (auto lid = lid_beg; lid != lid_end; ++lid) { + auto cg = lid + cg_lo; + if (cg >= cg_hi) continue; + const auto& handle = R.handles.at(cg); + if (handle.mech_id != mech_id) return; + auto mech_index = handle.mech_index; + R.result.push_back(fvm_probe_scalar{{data + mech_index}, + point_info_of(target, + lid, + mech_index, + synapses.at(mech), + R.M.mechanisms.at(mech).multiplicity)}); + } + } } template @@ -944,25 +956,35 @@ void resolve_probe(const cable_probe_point_state_cell& p, probe_resolution_data< auto cell_targets_beg = R.M.target_divs.at(R.cell_idx); auto cell_targets_end = R.M.target_divs.at(R.cell_idx + 1); - fvm_probe_multi r; - std::vector metadata; + const auto& decor = R.cell.decorations(); + fvm_probe_multi result; + std::vector metadata; + cell_lid_type lid = 0; for (auto target: util::make_span(cell_targets_beg, cell_targets_end)) { const auto& handle = R.handles.at(target); if (handle.mech_id != mech_id) continue; auto mech_index = handle.mech_index; - r.raw_handles.push_back(data + mech_index); + result.raw_handles.push_back(data + mech_index); + + // Convert to cell-local target index. + const auto& ins = placed_instances.at(lid); + auto lid = target - cell_targets_beg; + auto tag = decor.tag_of(ins.tag); - metadata.push_back(point_info_of(target - cell_targets_beg, // Convert to cell-local target index. + metadata.push_back(point_info_of(tag, + lid, mech_index, placed_instances, multiplicity)); + ++lid; } - r.metadata = std::move(metadata); - r.shrink_to_fit(); - R.result.push_back(std::move(r)); + + result.metadata = std::move(metadata); + result.shrink_to_fit(); + R.result.push_back(std::move(result)); } template diff --git a/arbor/include/arbor/cable_cell.hpp b/arbor/include/arbor/cable_cell.hpp index 3fd84f7b4b..3ef19cbcf3 100644 --- a/arbor/include/arbor/cable_cell.hpp +++ b/arbor/include/arbor/cable_cell.hpp @@ -53,7 +53,8 @@ using cable_sample_range = std::pair; // // Metadata for point process probes. struct ARB_SYMBOL_VISIBLE cable_probe_point_info { - cell_lid_type target; // Target number of point process instance on cell. + cell_tag_type target; // Target tag of point process instance on cell. + cell_lid_type lid; // Target lid of point process instance on cell. unsigned multiplicity; // Number of combined instances at this site. mlocation loc; // Point on cell morphology where instance is placed. }; @@ -109,6 +110,7 @@ struct ARB_SYMBOL_VISIBLE cable_probe_density_state { }; // Value of state variable `state` in density mechanism `mechanism` across components of the cell. +// // Sample value type: `cable_sample_range` // Sample metadata type: `mcable_list` struct ARB_SYMBOL_VISIBLE cable_probe_density_state_cell { @@ -117,16 +119,30 @@ struct ARB_SYMBOL_VISIBLE cable_probe_density_state_cell { }; // Value of state variable `key` in point mechanism `source` at target `target`. +// // Sample value type: `double` // Sample metadata type: `cable_probe_point_info` struct ARB_SYMBOL_VISIBLE cable_probe_point_state { - cell_lid_type target; + cell_tag_type target; std::string mechanism; std::string state; + + // Engage in minimal hygeine. Ideally, we'd disable all nullptr constructors. + cable_probe_point_state(std::nullptr_t, std::string, std::string) = delete; + cable_probe_point_state() = delete; + + constexpr cable_probe_point_state(cell_tag_type t, std::string m, std::string s): + target(std::move(t)), mechanism(std::move(m)), state(std::move(s)) {} + constexpr cable_probe_point_state(const cable_probe_point_state&) = default; + constexpr cable_probe_point_state(cable_probe_point_state&&) = default; + constexpr cable_probe_point_state& operator=(const cable_probe_point_state&) = default; + constexpr cable_probe_point_state& operator=(cable_probe_point_state&&) = default; }; -// Value of state variable `key` in point mechanism `source` at every target with this mechanism. -// Metadata has one entry of type cable_probe_point_info for each matched (possibly coalesced) instance. +// Value of state variable `key` in point mechanism `source` at every target +// with this mechanism. Metadata has one entry of type cable_probe_point_info +// for each matched (possibly coalesced) instance. +// // Sample value type: `cable_sample_range` // Sample metadata type: `std::vector` struct ARB_SYMBOL_VISIBLE cable_probe_point_state_cell { @@ -224,6 +240,7 @@ struct placed { mlocation loc; cell_lid_type lid; T item; + hash_type tag; }; // Note: lid fields of elements of mlocation_map used in cable_cell are strictly increasing. diff --git a/doc/python/probe_sample.rst b/doc/python/probe_sample.rst index a5adf4f193..202857c067 100644 --- a/doc/python/probe_sample.rst +++ b/doc/python/probe_sample.rst @@ -96,39 +96,39 @@ Example return cell def probes(self, gid): - return [A.cable_probe_membrane_voltage('(location 0 0.5)'), - A.cable_probe_membrane_voltage_cell(), - A.cable_probe_membrane_voltage('(join (location 0 0) (location 0 1))'), + return [A.cable_probe_membrane_voltage('(location 0 0.5)', tag="Um-soma"), + A.cable_probe_membrane_voltage_cell(tag="Um-cell"), + A.cable_probe_membrane_voltage('(join (location 0 0) (location 0 1))', tag="Um-ends"), ] - # (4.6) Override the global_properties method + # Override the global_properties method def global_properties(self, kind): return A.neuron_cable_properties() recipe = single_recipe() sim = A.simulation(recipe) - handles = [sim.sample((0, n), A.regular_schedule(0.1*U.ms)) - for n in range(3) ] + handles = {tag: sim.sample((0, n), A.regular_schedule(0.1*U.ms)) + for tag in ["Um-soma", "Um-cell", "Um-ends"]} sim.run(tfinal=1*U.ms) - for hd in handles: - print("Handle", hd) + for tag, hd in handles.items(): + print(f"Handle {hd} Tag '{}'") for d, m in sim.samples(hd): print(" * Meta:", m) print(" * Payload:", d.shape) -This script has a single (scalar) probe, a single vector probe, and a probeset involving two scalar probes. +This script has a scalar probe, a vector probe, and a probeset involving two scalar probes. The script is complete and can be run with Arbor installed, and will output: .. code-block:: - Handle 0 + Handle 0 Tag 'Um-soma' * Meta: (location 0 0.5) * Payload: (10, 2) - Handle 1 + Handle 1 Tag 'Um-cell' * Meta: [(cable 0 0 1), (cable 0 1 1), (cable 1 0 0), (cable 2 0 0), (cable 1 0 1), (cable 2 0 1)] * Payload: (10, 7) - Handle 2 + Handle 2 Tag 'Um-ends' * Meta: (location 0 0) * Payload: (10, 2) * Meta: (location 0 1) @@ -143,177 +143,213 @@ API An opaque object that is the Python representation of :cpp:class:`probe_info`. - See below for ways to create probes. + See below for ways to create probes. In general, all probes are named via + the ``tag`` argument, as seen above. This tag is later used to retrieve the + data collected by the associated probes. Membrane voltage - .. py:function:: cable_probe_membrane_voltage(where) +^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_membrane_voltage(where, tag) Cell membrane potential (mV) at the sites specified by the location expression string ``where``. This value is spatially interpolated. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_membrane_voltage_cell() + .. py:function:: cable_probe_membrane_voltage_cell(tag) Cell membrane potential (mV) associated with each cable in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Axial current - .. py:function:: cable_probe_axial_current(where) +^^^^^^^^^^^^^ + + .. py:function:: cable_probe_axial_current(where, tag) Estimation of intracellular current (nA) in the distal direction at the sites specified by the location expression string ``where``. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. Ionic current - .. py:function:: cable_probe_ion_current_density(where, ion) +^^^^^^^^^^^^^ + + .. py:function:: cable_probe_ion_current_density(where, ion, tag) Transmembrane current density (A/m²) associated with the given ``ion`` at sites specified by the location expression string ``where``. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_ion_current_cell(ion) + .. py:function:: cable_probe_ion_current_cell(ion, tag) Transmembrane current (nA) associated with the given ``ion`` across each cable in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Total ionic current - .. py:function:: cable_probe_total_ion_current_density(where) +^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_total_ion_current_density(where, tag) Transmembrane current density (A/m²) _excluding_ capacitive currents at the sites specified by the location expression string ``where``. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_total_ion_current_cell() + .. py:function:: cable_probe_total_ion_current_cell(tag) Transmembrane current (nA) _excluding_ capacitive currents across each cable in each CV of the cell discretization. Stimulus currents are not included. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Total transmembrane current - .. py:function:: cable_probe_total_current_cell() +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_total_current_cell(tag) Transmembrane current (nA) *including* capacitive currents across each cable in each CV of the cell discretization. Stimulus currents are not included. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Total stimulus current - .. py:function:: cable_probe_stimulus_current_cell() +^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_stimulus_current_cell(tag) Total stimulus current (nA) across each cable in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Density mechanism state variable - .. py:function:: cable_probe_density_state(where, mechanism, state) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_density_state(where, mechanism, state, tag) The value of the state variable ``state`` in the density mechanism ``mechanism`` at the sites specified by the location expression ``where``. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_density_state_cell(mechanism, state) + .. py:function:: cable_probe_density_state_cell(mechanism, state, tag) The value of the state variable ``state`` in the density mechanism ``mechanism`` on each cable in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Point process state variable - .. py:function:: cable_probe_point_state(target, mechanism, state) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_point_state(target, mechanism, state, tag) The value of the state variable ``state`` in the point process ``mechanism`` associated with the target index ``target`` on the cell. If the given mechanism is not associated with the target index, no probe will be generated. - Metadata: an object of type :class:`cable_point_probe_info`, comprising three fields: + **Metadata**: + + .. py:class:: cable_point_probe_info - * ``target``: target index on the cell; + .. py:attribute:: target - * ``multiplicity``: number of targets sharing the same state in the discretization; + tag of target mechanism on the cell - * ``location``: :class:`location` object corresponding to the target site. + .. py:attribute:: lid - .. py:function:: cable_probe_point_state_cell(mechanism, state) + local id of target; + + .. py:attribute:: multiplicity + + number of targets sharing the same state in the discretization; + + .. py:attribute:: location + + :class:`location` object corresponding to the target site. + + .. py:function:: cable_probe_point_state_cell(mechanism, state, tag) The value of the state variable ``state`` in the point process ``mechanism`` at each of the targets where that mechanism is defined. - Metadata: a list of :class:`cable_point_probe_info` values, one for each matching + **Metadata**: a list of :class:`cable_point_probe_info` values, one for each matching target. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Ionic internal concentration - .. py:function:: cable_probe_ion_int_concentration(where, ion) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_ion_int_concentration(where, ion, tag) Ionic internal concentration (mmol/L) of the given ``ion`` at the sites specified by the location expression string ``where``. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_ion_int_concentration_cell(ion) + .. py:function:: cable_probe_ion_int_concentration_cell(ion, tag) Ionic internal concentration (mmol/L) of the given ``ion`` in each cable in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Ionic external concentration - .. py:function:: cable_probe_ion_ext_concentration(where, ion) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Ionic external concentration (mmol/L) of the given ``ion`` at the - sites specified by the location expression string ``where``. + .. py:function:: cable_probe_ion_ext_concentration(where, ion, tag) - Metadata: the explicit :class:`location` of the sample site. + Ionic external concentration (mM) of the given ``ion`` at the sites specified + by the location expression string ``where``. - .. py:function:: cable_probe_ion_ext_concentration_cell(ion) + **Metadata**: the explicit :class:`location` of the sample site. + + .. py:function:: cable_probe_ion_ext_concentration_cell(ion, tag) Ionic external concentration (mmol/L) of the given ``ion`` in each able in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Ionic diffusion concrentration - .. py:function:: cable_probe_ion_diff_concentration_cell(ion) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_ion_diff_concentration_cell(ion, tag) Diffusive ionic concentration of the given ``ion`` for each cable in each CV. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. - .. py:function:: cable_probe_ion_diff_concentration(where, ion) + .. py:function:: cable_probe_ion_diff_concentration(where, ion, tag) Diffusive ionic concentration of the given ``ion`` at the sites specified by the location expression string ``where``. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. .. _pycablecell-probesample-lif: @@ -321,8 +357,8 @@ LIF Cell probing ================ Membrane voltage - .. py:function:: lif_probe_voltage() + .. py:function:: lif_probe_voltage(tag) Current cell membrane potential (mV). - Metadata: none + **Metadata**: none diff --git a/example/lfp/lfp.cpp b/example/lfp/lfp.cpp index f87cfd9601..e32a1a3b15 100644 --- a/example/lfp/lfp.cpp +++ b/example/lfp/lfp.cpp @@ -46,7 +46,7 @@ struct lfp_demo_recipe: public arb::recipe { return {{arb::cable_probe_total_current_cell{}, "Itotal"}, {arb::cable_probe_membrane_voltage{synapse_location_}, "Um"}, {arb::cable_probe_total_ion_current_density{synapse_location_}, "Iion"}, - {arb::cable_probe_point_state{0, "expsyn", "g"}, "expsyn-g"}}; + {arb::cable_probe_point_state{"syn", "expsyn", "g"}, "expsyn-g"}}; } arb::cell_kind get_cell_kind(cell_gid_type) const override { diff --git a/example/probe-demo/probe-demo.cpp b/example/probe-demo/probe-demo.cpp index ed58297ae7..2a108ab28d 100644 --- a/example/probe-demo/probe-demo.cpp +++ b/example/probe-demo/probe-demo.cpp @@ -215,53 +215,72 @@ bool parse_options(options& opt, int& argc, char** argv) { using L = arb::mlocation; // Map probe argument to output variable name, scalarity, and a lambda that makes specific probe address from a location. - std::pair>> probe_tbl[] { + + using probe_spec_t = std::tuple>; + + std::pair probe_tbl[] { // located probes - {"v", {"v", true, [](double x) { return arb::cable_probe_membrane_voltage{L{0, x}}; }}}, - {"i_axial", {"i_axial", true, [](double x) { return arb::cable_probe_axial_current{L{0, x}}; }}}, - {"j_ion", {"j_ion", true, [](double x) { return arb::cable_probe_total_ion_current_density{L{0, x}}; }}}, - {"j_na", {"j_na", true, [](double x) { return arb::cable_probe_ion_current_density{L{0, x}, "na"}; }}}, - {"j_k", {"j_k", true, [](double x) { return arb::cable_probe_ion_current_density{L{0, x}, "k"}; }}}, - {"c_na", {"c_na", true, [](double x) { return arb::cable_probe_ion_int_concentration{L{0, x}, "na"}; }}}, - {"c_k", {"c_k", true, [](double x) { return arb::cable_probe_ion_int_concentration{L{0, x}, "k"}; }}}, - {"hh_m", {"hh_m", true, [](double x) { return arb::cable_probe_density_state{L{0, x}, "hh", "m"}; }}}, - {"hh_h", {"hh_h", true, [](double x) { return arb::cable_probe_density_state{L{0, x}, "hh", "h"}; }}}, - {"hh_n", {"hh_n", true, [](double x) { return arb::cable_probe_density_state{L{0, x}, "hh", "n"}; }}}, - {"expsyn_g", {"expsyn_ g", true, [](arb::cell_lid_type i) { return arb::cable_probe_point_state{i, "expsyn", "g"}; }}}, + {"v", {"v", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_membrane_voltage{L{0, x}}; }}}, + {"i_axial", {"i_axial", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_axial_current{L{0, x}}; }}}, + {"j_ion", {"j_ion", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_total_ion_current_density{L{0, x}}; }}}, + {"j_na", {"j_na", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_current_density{L{0, x}, "na"}; }}}, + {"j_k", {"j_k", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_current_density{L{0, x}, "k"}; }}}, + {"c_na", {"c_na", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_int_concentration{L{0, x}, "na"}; }}}, + {"c_k", {"c_k", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_int_concentration{L{0, x}, "k"}; }}}, + {"hh_m", {"hh_m", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_density_state{L{0, x}, "hh", "m"}; }}}, + {"hh_h", {"hh_h", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_density_state{L{0, x}, "hh", "h"}; }}}, + {"hh_n", {"hh_n", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_density_state{L{0, x}, "hh", "n"}; }}}, + {"expsyn_g", {"expsyn_ g", true, [](std::any a) { auto t = std::any_cast(a); return arb::cable_probe_point_state{t, "expsyn", "g"}; }}}, // all-of-cell probes - {"all_v", {"v", false, [](double) { return arb::cable_probe_membrane_voltage_cell{}; }}}, - {"all_i_ion", {"i_ion", false, [](double) { return arb::cable_probe_total_ion_current_cell{}; }}}, - {"all_i_na", {"i_na", false, [](double) { return arb::cable_probe_ion_current_cell{"na"}; }}}, - {"all_i_k", {"i_k", false, [](double) { return arb::cable_probe_ion_current_cell{"k"}; }}}, - {"all_i", {"i", false, [](double) { return arb::cable_probe_total_current_cell{}; }}}, - {"all_c_na", {"c_na", false, [](double) { return arb::cable_probe_ion_int_concentration_cell{"na"}; }}}, - {"all_c_k", {"c_k", false, [](double) { return arb::cable_probe_ion_int_concentration_cell{"k"}; }}}, - {"all_hh_m", {"hh_m", false, [](double) { return arb::cable_probe_density_state_cell{"hh", "m"}; }}}, - {"all_hh_h", {"hh_h", false, [](double) { return arb::cable_probe_density_state_cell{"hh", "h"}; }}}, - {"all_hh_n", {"hh_n", false, [](double) { return arb::cable_probe_density_state_cell{"hh", "n"}; }}}, - {"all_expsyn_g", {"expsyn_ g", false, [](arb::cell_lid_type) { return arb::cable_probe_point_state_cell{"expsyn", "g"}; }}}, + {"all_v", {"v", false, [](std::any) { return arb::cable_probe_membrane_voltage_cell{}; }}}, + {"all_i_ion", {"i_ion", false, [](std::any) { return arb::cable_probe_total_ion_current_cell{}; }}}, + {"all_i_na", {"i_na", false, [](std::any) { return arb::cable_probe_ion_current_cell{"na"}; }}}, + {"all_i_k", {"i_k", false, [](std::any) { return arb::cable_probe_ion_current_cell{"k"}; }}}, + {"all_i", {"i", false, [](std::any) { return arb::cable_probe_total_current_cell{}; }}}, + {"all_c_na", {"c_na", false, [](std::any) { return arb::cable_probe_ion_int_concentration_cell{"na"}; }}}, + {"all_c_k", {"c_k", false, [](std::any) { return arb::cable_probe_ion_int_concentration_cell{"k"}; }}}, + {"all_hh_m", {"hh_m", false, [](std::any) { return arb::cable_probe_density_state_cell{"hh", "m"}; }}}, + {"all_hh_h", {"hh_h", false, [](std::any) { return arb::cable_probe_density_state_cell{"hh", "h"}; }}}, + {"all_hh_n", {"hh_n", false, [](std::any) { return arb::cable_probe_density_state_cell{"hh", "n"}; }}}, + {"all_expsyn_g", {"expsyn_ g", false, [](std::any) { return arb::cable_probe_point_state_cell{"expsyn", "g"}; }}}, }; - std::tuple> probe_spec; - double probe_pos = 0.5; + probe_spec_t probe_spec; + std::any p_pos; + + auto double_or_string = [](const char* arg) -> to::maybe { + try { + return {{std::stod(arg)}}; + } + catch (const std::exception& e) { + return {{std::string(arg)}}; + } + }; to::option cli_opts[] = { { to::action(do_help), to::flag, to::exit, "-h", "--help" }, + { opt.sim_dt, "--dt" }, + { opt.sim_end, "--until" }, + { opt.sample_dt, "-t", "--sample" }, + { to::sink(p_pos, double_or_string), "-x", "--at" }, + { opt.n_cv, "-n", "--n-cv" }, { {probe_spec, to::keywords(probe_tbl)}, to::single }, - { opt.sim_dt, "--dt" }, - { opt.sim_end, "--until" }, - { opt.sample_dt, "-t", "--sample" }, - { probe_pos, "-x", "--at" }, - { opt.n_cv, "-n", "--n-cv" } }; + const auto& [p_name, p_scalar, p_addr] = probe_spec; + if (!p_pos.has_value() && (p_name == "exp_syn_g")) { + p_pos = "synapse0"; + } + else { + p_pos = 0.5; + } + if (!to::run(cli_opts, argc, argv+1)) return false; - if (!get<2>(probe_spec)) throw to::user_option_error("missing PROBE"); + if (!p_addr) throw to::user_option_error("missing PROBE"); if (argv[1]) throw to::user_option_error("unrecognized option"); - - opt.value_name = get<0>(probe_spec); - opt.scalar_probe = get<1>(probe_spec); - opt.probe_addr = get<2>(probe_spec)(probe_pos); + opt.value_name = p_name; + opt.scalar_probe = p_scalar; + opt.probe_addr = p_addr(p_pos); return true; } diff --git a/python/example/single_cell_stdp.py b/python/example/single_cell_stdp.py index d460fbd7d6..2df25e81b2 100755 --- a/python/example/single_cell_stdp.py +++ b/python/example/single_cell_stdp.py @@ -59,7 +59,9 @@ def event_generators(self, gid): def probes(self, gid): def mk(s, t): - return A.cable_probe_point_state(1, "expsyn_stdp", state=s, tag=t) + return A.cable_probe_point_state( + "stpd_synapse", "expsyn_stdp", state=s, tag=t + ) return [ A.cable_probe_membrane_voltage('"center"', "Um"), diff --git a/python/probes.cpp b/python/probes.cpp index ad734fff89..bb264688d4 100644 --- a/python/probes.cpp +++ b/python/probes.cpp @@ -194,7 +194,7 @@ arb::probe_info cable_probe_density_state_cell(const char* mechanism, const char return {arb::cable_probe_density_state_cell{mechanism, state}, tag}; }; -arb::probe_info cable_probe_point_state(arb::cell_lid_type target, const char* mechanism, const char* state, const std::string& tag) { +arb::probe_info cable_probe_point_state(const arb::cell_tag_type& target, const char* mechanism, const char* state, const std::string& tag) { return {arb::cable_probe_point_state{target, mechanism, state}, tag}; } @@ -257,15 +257,17 @@ void register_cable_probes(pybind11::module& m, pyarb_global_ptr global_ptr) { cable_probe_point_info .def_readwrite("target", &arb::cable_probe_point_info::target, - "The target index of the point process instance on the cell.") + "The tag of the point process instance on the cell.") + .def_readwrite("lid", &arb::cable_probe_point_info::lid, + "The local index of the point process instance on the cell.") .def_readwrite("multiplicity", &arb::cable_probe_point_info::multiplicity, "Number of coalesced point processes (linear synapses) associated with this instance.") .def_readwrite("location", &arb::cable_probe_point_info::loc, "Location of point process instance on cell.") .def("__str__", [](arb::cable_probe_point_info m) { - return pprintf("", m.target, m.multiplicity, m.loc);}) + return pprintf("", m.target, m.lid, m.multiplicity, m.loc);}) .def("__repr__",[](arb::cable_probe_point_info m) { - return pprintf("", m.target, m.multiplicity, m.loc);}); + return pprintf("", m.target, m.lid, m.multiplicity, m.loc);}); // Probe address constructors: @@ -306,8 +308,8 @@ void register_cable_probes(pybind11::module& m, pyarb_global_ptr global_ptr) { "mechanism"_a, "state"_a, "tag"_a); m.def("cable_probe_point_state", &cable_probe_point_state, - "Probe specification for a cable cell point mechanism state variable value at a given target index.", - "target"_a, "mechanism"_a, "state"_a, "tag"_a); + "Probe specification for a cable cell point mechanism state variable value at a given target index.", + "target"_a, "mechanism"_a, "state"_a, "tag"_a); m.def("cable_probe_point_state_cell", &cable_probe_point_state_cell, "Probe specification for a cable cell point mechanism state variable value at every corresponding target.", diff --git a/python/test/unit/test_probes.py b/python/test/unit/test_probes.py index f72bfb4e3e..c35a531546 100644 --- a/python/test/unit/test_probes.py +++ b/python/test/unit/test_probes.py @@ -58,7 +58,7 @@ def probes(self, _): ), A.cable_probe_density_state_cell(mechanism="hh", state="n", tag="hh-n-all"), A.cable_probe_point_state( - target=0, mechanism="expsyn", state="g", tag="expsyn-g" + target="syn0", mechanism="expsyn", state="g", tag="expsyn-g" ), A.cable_probe_point_state_cell( mechanism="exp2syn", state="B", tag="expsyn-B-all" @@ -127,14 +127,14 @@ def test_probe_addr_metadata(self): self.assertEqual(1, len(m)) self.assertEqual(A.location(0, 0.08), m[0].location) self.assertEqual(1, m[0].multiplicity) - self.assertEqual(0, m[0].target) + self.assertEqual("syn0", m[0].target) m = sim.probe_metadata((0, "expsyn-B-all")) self.assertEqual(1, len(m)) self.assertEqual(1, len(m[0])) self.assertEqual(A.location(0, 0.09), m[0][0].location) self.assertEqual(1, m[0][0].multiplicity) - self.assertEqual(1, m[0][0].target) + self.assertEqual("syn1", m[0][0].target) m = sim.probe_metadata((0, "ina")) self.assertEqual(1, len(m)) diff --git a/test/unit/test_probe.cpp b/test/unit/test_probe.cpp index 99a6b249a1..f9dc34ccb5 100644 --- a/test/unit/test_probe.cpp +++ b/test/unit/test_probe.cpp @@ -274,8 +274,8 @@ void run_expsyn_g_probe_test(context ctx) { auto run_test = [&](bool coalesce_synapses) { cable1d_recipe rec(cable_cell(bs), coalesce_synapses); - rec.add_probe(0, "expsyn-g-1", cable_probe_point_state{0u, "expsyn", "g"}); - rec.add_probe(0, "expsyn-g-2", cable_probe_point_state{1u, "expsyn", "g"}); + rec.add_probe(0, "expsyn-g-1", cable_probe_point_state{"syn0", "expsyn", "g"}); + rec.add_probe(0, "expsyn-g-2", cable_probe_point_state{"syn1", "expsyn", "g"}); fvm_cell lcell(*ctx); auto fvm_info = lcell.initialize({0}, rec); @@ -365,11 +365,11 @@ void run_expsyn_g_cell_probe_test(context ctx) { unsigned n_expsyn = 0; for (unsigned bid = 0; bid<3u; ++bid) { for (unsigned j = 0; j<10; ++j) { - auto idx = (bid*10+j)*2; + auto idx = 2*(bid*10 + j); mlocation expsyn_loc{bid, 0.1*j}; decor.place(expsyn_loc, synapse("expsyn"), "syn"+std::to_string(idx)); expsyn_target_loc_map[2*n_expsyn] = expsyn_loc; - decor.place(mlocation{bid, 0.1*j+0.05}, synapse("exp2syn"), "syn"+std::to_string(idx+1)); + decor.place(mlocation{bid, 0.1*j + 0.05}, synapse("exp2syn"), "syn"+std::to_string(idx+1)); ++n_expsyn; } } @@ -377,7 +377,7 @@ void run_expsyn_g_cell_probe_test(context ctx) { std::vector cells(2, arb::cable_cell(make_y_morphology(), decor, {}, policy)); // Weight for target (gid, lid) - auto weight = [](auto gid, auto tgt) -> float { return tgt + 100*gid; }; + auto weight = [](cell_gid_type gid, cell_lid_type lid) -> float { return lid + 100*gid; }; // Manually send an event to each expsyn synapse and integrate for a tiny time step. // Set up one stream per cell. @@ -429,14 +429,16 @@ void run_expsyn_g_cell_probe_test(context ctx) { std::unordered_map cv_expsyn_count; for (unsigned j = 0; j