From 292ea4ee1baf8cb06b01c3bbdd9a944e7433022e Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:40:13 +0200 Subject: [PATCH] Allow probing of point state by tag. --- arbor/fvm_lowered_cell_impl.hpp | 47 ++++--- arbor/include/arbor/cable_cell.hpp | 13 +- arbor/include/arbor/util/expected.hpp | 2 +- doc/python/probe_sample.rst | 168 ++++++++++++++++---------- example/probe-demo/probe-demo.cpp | 89 ++++++++------ python/example/single_cell_stdp.py | 2 +- python/probes.cpp | 14 ++- python/strprintf.hpp | 2 +- test/unit/test_probe.cpp | 16 +-- 9 files changed, 215 insertions(+), 138 deletions(-) diff --git a/arbor/fvm_lowered_cell_impl.hpp b/arbor/fvm_lowered_cell_impl.hpp index 730471d109..7064d3ce05 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 @@ -954,7 +966,8 @@ void resolve_probe(const cable_probe_point_state_cell& p, probe_resolution_data< auto mech_index = handle.mech_index; r.raw_handles.push_back(data + mech_index); - metadata.push_back(point_info_of(target - cell_targets_beg, // Convert to cell-local target index. + metadata.push_back(point_info_of("", + target - cell_targets_beg, // Convert to cell-local target index. mech_index, placed_instances, multiplicity)); diff --git a/arbor/include/arbor/cable_cell.hpp b/arbor/include/arbor/cable_cell.hpp index 8295823160..a2294ba53b 100644 --- a/arbor/include/arbor/cable_cell.hpp +++ b/arbor/include/arbor/cable_cell.hpp @@ -52,7 +52,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. }; @@ -108,6 +109,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 { @@ -116,16 +118,19 @@ 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; }; -// 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 { diff --git a/arbor/include/arbor/util/expected.hpp b/arbor/include/arbor/util/expected.hpp index 9d860f6536..1af88eb42f 100644 --- a/arbor/include/arbor/util/expected.hpp +++ b/arbor/include/arbor/util/expected.hpp @@ -486,7 +486,7 @@ struct expected { // Swap ops. void swap(expected& other) { - data_.swap(other.data); + data_.swap(other.data_); } // Accessors. diff --git a/doc/python/probe_sample.rst b/doc/python/probe_sample.rst index a5adf4f193..ce178e95cb 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) 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 + + 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) 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/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..8b0fc50340 100755 --- a/python/example/single_cell_stdp.py +++ b/python/example/single_cell_stdp.py @@ -59,7 +59,7 @@ 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("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/strprintf.hpp b/python/strprintf.hpp index 6380167726..2ccf12c1a3 100644 --- a/python/strprintf.hpp +++ b/python/strprintf.hpp @@ -168,7 +168,7 @@ namespace impl { for (auto& x: s.seq_) { if (!first) o << s.sep_; first = false; - o << s.f(x); + o << s.f_(x); } return o; } diff --git a/test/unit/test_probe.cpp b/test/unit/test_probe.cpp index 43449d0173..78f16e01af 100644 --- a/test/unit/test_probe.cpp +++ b/test/unit/test_probe.cpp @@ -275,8 +275,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); @@ -379,7 +379,7 @@ void run_expsyn_g_cell_probe_test(context ctx) { std::vector cells(2, arb::cable_cell(make_y_morphology(), decor)); // 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. @@ -431,14 +431,16 @@ void run_expsyn_g_cell_probe_test(context ctx) { std::unordered_map cv_expsyn_count; for (unsigned j = 0; j