From 05569a168e333ba32dd5f3d66110346f809b9ec8 Mon Sep 17 00:00:00 2001 From: Sam Yates <halfflat@gmail.com> Date: Thu, 8 Apr 2021 09:30:09 +0200 Subject: [PATCH] Add phase parameter to current clamps. (#1474) * `i_clamp` constructors take new optional phase argument. * Pulse or box envelope clamps are now provided by the static method `i_clamp::box`, taking onset, duration, and amplitude parameters. * Bump pybind11 version so we can have keyword-only arguments in the Python API. * Make frequency and phase keyword-only arguments to Python `iclamp`. * Change frequency units for clamps from Hz to kHz for consistency with other interfaces. Fixes #1454. --- arbor/backends/gpu/shared_state.cpp | 2 ++ arbor/backends/gpu/shared_state.hpp | 3 ++- arbor/backends/gpu/stimulus.cu | 4 ++-- arbor/backends/gpu/stimulus.hpp | 1 + arbor/backends/multicore/shared_state.cpp | 7 +++--- arbor/backends/multicore/shared_state.hpp | 3 ++- arbor/fvm_layout.cpp | 3 +++ arbor/fvm_layout.hpp | 3 ++- arbor/include/arbor/cable_cell_param.hpp | 19 +++++++++++------ arborio/cableio.cpp | 20 ++++++++--------- doc/concepts/decor.rst | 15 +++++++------ doc/fileformat/cable_cell.rst | 26 +++++++++++------------ doc/python/decor.rst | 2 +- example/gap_junctions/gap_junctions.cpp | 2 +- example/probe-demo/probe-demo.cpp | 2 +- python/cells.cpp | 20 +++++++++-------- python/pybind11 | 2 +- test/unit/test_fvm_lowered.cpp | 11 +++++----- test/unit/test_mc_cell_group.cpp | 2 +- test/unit/test_mc_cell_group_gpu.cpp | 2 +- test/unit/test_probe.cpp | 18 ++++++++-------- test/unit/test_s_expr.cpp | 15 +++++++------ test/unit/test_spikes.cpp | 2 +- 23 files changed, 102 insertions(+), 82 deletions(-) diff --git a/arbor/backends/gpu/shared_state.cpp b/arbor/backends/gpu/shared_state.cpp index 9dfa021d..555f9a28 100644 --- a/arbor/backends/gpu/shared_state.cpp +++ b/arbor/backends/gpu/shared_state.cpp @@ -101,6 +101,7 @@ istim_state::istim_state(const fvm_stimulus_config& stim) { std::vector<fvm_index_type> edivs; frequency_ = make_const_view(stim.frequency); + phase_ = make_const_view(stim.phase); arb_assert(n==frequency_.size()); arb_assert(n==stim.envelope_time.size()); @@ -132,6 +133,7 @@ istim_state::istim_state(const fvm_stimulus_config& stim) { ppack_.accu_index = accu_index_.data(); ppack_.accu_to_cv = accu_to_cv_.data(); ppack_.frequency = frequency_.data(); + ppack_.phase = phase_.data(); ppack_.envl_amplitudes = envl_amplitudes_.data(); ppack_.envl_times = envl_times_.data(); ppack_.envl_divs = envl_divs_.data(); diff --git a/arbor/backends/gpu/shared_state.hpp b/arbor/backends/gpu/shared_state.hpp index 6243982e..695821fa 100644 --- a/arbor/backends/gpu/shared_state.hpp +++ b/arbor/backends/gpu/shared_state.hpp @@ -66,7 +66,8 @@ struct istim_state { iarray accu_index_; // Instance to accumulator index (accu_stim_ index) map. iarray accu_to_cv_; // Accumulator index to CV map. - array frequency_; // (Hz) stimulus frequency per instance. + array frequency_; // (kHz) stimulus frequency per instance. + array phase_; // (rad) stimulus waveform phase at t=0. array envl_amplitudes_; // (A/m²) stimulus envelope amplitudes, partitioned by instance. array envl_times_; // (A/m²) stimulus envelope timepoints, partitioned by instance. iarray envl_divs_; // Partition divisions for envl_ arrays, diff --git a/arbor/backends/gpu/stimulus.cu b/arbor/backends/gpu/stimulus.cu index cc3f85ca..a127128c 100644 --- a/arbor/backends/gpu/stimulus.cu +++ b/arbor/backends/gpu/stimulus.cu @@ -17,7 +17,7 @@ namespace kernel { __global__ void istim_add_current_impl(int n, istim_pp pp) { - constexpr double freq_scale = 2*pi*0.001; + constexpr double two_pi = 2*pi; auto i = threadIdx.x + blockDim.x*blockIdx.x; if (i>=n) return; @@ -43,7 +43,7 @@ void istim_add_current_impl(int n, istim_pp pp) { } if (double f = pp.frequency[i]) { - J *= std::sin(freq_scale*f*t); + J *= std::sin(two_pi*f*t + pp.phase[i]); } gpu_atomic_add(&pp.accu_stim[ai], J); diff --git a/arbor/backends/gpu/stimulus.hpp b/arbor/backends/gpu/stimulus.hpp index e03edd43..97447f6e 100644 --- a/arbor/backends/gpu/stimulus.hpp +++ b/arbor/backends/gpu/stimulus.hpp @@ -12,6 +12,7 @@ struct istim_pp { const fvm_index_type* accu_index; const fvm_index_type* accu_to_cv; const fvm_value_type* frequency; + const fvm_value_type* phase; const fvm_value_type* envl_amplitudes; const fvm_value_type* envl_times; const fvm_index_type* envl_divs; diff --git a/arbor/backends/multicore/shared_state.cpp b/arbor/backends/multicore/shared_state.cpp index 0da23bca..442da337 100644 --- a/arbor/backends/multicore/shared_state.cpp +++ b/arbor/backends/multicore/shared_state.cpp @@ -92,7 +92,8 @@ void ion_state::reset() { istim_state::istim_state(const fvm_stimulus_config& stim, unsigned align): alignment(min_alignment(align)), accu_to_cv_(stim.cv_unique.begin(), stim.cv_unique.end(), pad(alignment)), - frequency_(stim.frequency.begin(), stim.frequency.end(), pad(alignment)) + frequency_(stim.frequency.begin(), stim.frequency.end(), pad(alignment)), + phase_(stim.phase.begin(), stim.phase.end(), pad(alignment)) { using util::assign; @@ -138,7 +139,7 @@ void istim_state::reset() { } void istim_state::add_current(const array& time, const iarray& cv_to_intdom, array& current_density) { - constexpr double freq_scale = 2*math::pi<double>*0.001; + constexpr double two_pi = 2*math::pi<double>; // Consider vectorizing... for (auto i: util::count_along(accu_index_)) { @@ -168,7 +169,7 @@ void istim_state::add_current(const array& time, const iarray& cv_to_intdom, arr } if (frequency_[i]) { - J *= std::sin(freq_scale*frequency_[i]*t); + J *= std::sin(two_pi*frequency_[i]*t + phase_[i]); } accu_stim_[ai] += J; diff --git a/arbor/backends/multicore/shared_state.hpp b/arbor/backends/multicore/shared_state.hpp index 2011c668..54c0424b 100644 --- a/arbor/backends/multicore/shared_state.hpp +++ b/arbor/backends/multicore/shared_state.hpp @@ -80,7 +80,8 @@ struct istim_state { iarray accu_index_; // Instance to accumulator index (accu_stim_ index) map. iarray accu_to_cv_; // Accumulator index to CV map. - array frequency_; // (Hz) stimulus frequency per instance. + array frequency_; // (kHz) stimulus frequency per instance. + array phase_; // (rad) stimulus waveform phase at t=0. array envl_amplitudes_; // (A/m²) stimulus envelope amplitudes, partitioned by instance. array envl_times_; // (A/m²) stimulus envelope timepoints, partitioned by instance. iarray envl_divs_; // Partition divisions for envl_ arrays, diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp index 4f19daa7..1f917939 100644 --- a/arbor/fvm_layout.cpp +++ b/arbor/fvm_layout.cpp @@ -753,6 +753,7 @@ fvm_mechanism_data& append(fvm_mechanism_data& left, const fvm_mechanism_data& r append(left.stimuli.cv, right.stimuli.cv); append(left.stimuli.cv_unique, right.stimuli.cv_unique); append(left.stimuli.frequency, right.stimuli.frequency); + append(left.stimuli.phase, right.stimuli.phase); append(left.stimuli.envelope_time, right.stimuli.envelope_time); append(left.stimuli.envelope_amplitude, right.stimuli.envelope_amplitude); @@ -1096,6 +1097,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& config.cv.reserve(n); config.cv_unique.reserve(n); config.frequency.reserve(n); + config.phase.reserve(n); config.envelope_time.reserve(n); config.envelope_amplitude.reserve(n); @@ -1106,6 +1108,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& config.cv.push_back(cv); config.frequency.push_back(stim.frequency); + config.phase.push_back(stim.phase); std::size_t envl_n = stim.envelope.size(); std::vector<double> envl_t, envl_a; diff --git a/arbor/fvm_layout.hpp b/arbor/fvm_layout.hpp index 403d52d4..8d8ba818 100644 --- a/arbor/fvm_layout.hpp +++ b/arbor/fvm_layout.hpp @@ -279,7 +279,8 @@ struct fvm_stimulus_config { // Frequency, amplitude info, per instance. // Note that amplitudes have been scaled by 1/CV area so that they are represent as current densities, not currents. - std::vector<double> frequency; // [Hz] + std::vector<double> frequency; // [kHz] + std::vector<double> phase; // [rad] std::vector<std::vector<double>> envelope_time; // [ms] std::vector<std::vector<double>> envelope_amplitude; // [A/m²] }; diff --git a/arbor/include/arbor/cable_cell_param.hpp b/arbor/include/arbor/cable_cell_param.hpp index c21649a6..0e80bb85 100644 --- a/arbor/include/arbor/cable_cell_param.hpp +++ b/arbor/include/arbor/cable_cell_param.hpp @@ -54,27 +54,32 @@ struct i_clamp { }; std::vector<envelope_point> envelope; - double frequency = 0; // [Hz] 0 => constant + double frequency = 0; // [kHz] 0 => constant + double phase = 0; // [rad] // A default constructed i_clamp, with empty envelope, describes // a trivial stimulus, providing no current at all. i_clamp() = default; // The simple constructor describes a constant amplitude stimulus starting from t=0. - explicit i_clamp(double amplitude, double frequency = 0): + explicit i_clamp(double amplitude, double frequency = 0, double phase = 0): envelope({{0., amplitude}}), - frequency(frequency) + frequency(frequency), + phase(phase) {} // Describe a stimulus by envelope and frequency. - explicit i_clamp(std::vector<envelope_point> envelope, double frequency = 0): + explicit i_clamp(std::vector<envelope_point> envelope, double frequency = 0, double phase = 0): envelope(std::move(envelope)), - frequency(frequency) + frequency(frequency), + phase(phase) {} // A 'box' stimulus with fixed onset time, duration, and constant amplitude. - i_clamp(double onset, double duration, double amplitude, double frequency = 0): - i_clamp({{onset, amplitude}, {onset+duration, amplitude}, {onset+duration, 0.}}, frequency) {} + static i_clamp box(double onset, double duration, double amplitude, double frequency = 0, double phase = 0) { + return i_clamp({{onset, amplitude}, {onset+duration, amplitude}, {onset+duration, 0.}}, frequency, phase); + } + }; // Threshold detector description. diff --git a/arborio/cableio.cpp b/arborio/cableio.cpp index 9a7ff5af..d87b1413 100644 --- a/arborio/cableio.cpp +++ b/arborio/cableio.cpp @@ -61,7 +61,7 @@ s_expr mksexp(const i_clamp& c) { std::transform(c.envelope.begin(), c.envelope.end(), std::back_inserter(evlps), [](const auto& x){return slist(x.t, x.amplitude);}); auto envelope = slist("envelope"_symbol, slist_range(evlps)); - return slist("current-clamp"_symbol, envelope, c.frequency); + return slist("current-clamp"_symbol, envelope, c.frequency, c.phase); } s_expr mksexp(const threshold_detector& d) { return slist("threshold-detector"_symbol, d.threshold); @@ -234,14 +234,14 @@ std::vector<arb::i_clamp::envelope_point> make_envelope(const std::vector<std::v }); return envlp; } -arb::i_clamp make_i_clamp(const std::vector<arb::i_clamp::envelope_point>& envlp, double freq) { - return arb::i_clamp(envlp, freq); +arb::i_clamp make_i_clamp(const std::vector<arb::i_clamp::envelope_point>& envlp, double freq, double phase) { + return arb::i_clamp(envlp, freq, phase); } pulse_tuple make_envelope_pulse(double delay, double duration, double amplitude) { return pulse_tuple{delay, duration, amplitude}; } -arb::i_clamp make_i_clamp_pulse(pulse_tuple p, double freq) { - return arb::i_clamp(std::get<0>(p), std::get<1>(p), std::get<2>(p), freq); +arb::i_clamp make_i_clamp_pulse(pulse_tuple p, double freq, double phase) { + return arb::i_clamp::box(std::get<0>(p), std::get<1>(p), std::get<2>(p), freq, phase); } arb::gap_junction_site make_gap_junction_site() { return arb::gap_junction_site{}; @@ -757,10 +757,10 @@ eval_map named_evals{ "`envelope` with one or more pairs of start time and amplitude (start:real amplitude:real)")}, {"envelope-pulse", make_call<double, double, double>(make_envelope_pulse, "'envelope-pulse' with 3 arguments (delay:real duration:real amplitude:real)")}, - {"current-clamp", make_call<std::vector<arb::i_clamp::envelope_point>, double>(make_i_clamp, - "`current-clamp` with 2 arguments (env:envelope freq:real)")}, - {"current-clamp", make_call<pulse_tuple, double>(make_i_clamp_pulse, - "`current-clamp` with 2 arguments (env:envelope_pulse freq:real)")}, + {"current-clamp", make_call<std::vector<arb::i_clamp::envelope_point>, double, double>(make_i_clamp, + "`current-clamp` with 3 arguments (env:envelope freq:real phase:real)")}, + {"current-clamp", make_call<pulse_tuple, double, double>(make_i_clamp_pulse, + "`current-clamp` with 3 arguments (env:envelope_pulse freq:real phase:real)")}, {"threshold-detector", make_call<double>(make_threshold_detector, "'threshold-detector' with 1 argument (threshold:real)")}, {"gap-junction-site", make_call<>(make_gap_junction_site, @@ -857,4 +857,4 @@ parse_hopefully<cable_cell_component> parse_component(const std::string& s) { parse_hopefully<cable_cell_component> parse_component(std::istream& s) { return parse_component(std::string(std::istreambuf_iterator<char>(s), {})); } -} // namespace arborio \ No newline at end of file +} // namespace arborio diff --git a/doc/concepts/decor.rst b/doc/concepts/decor.rst index 93f6c0b5..9b29d0eb 100644 --- a/doc/concepts/decor.rst +++ b/doc/concepts/decor.rst @@ -290,9 +290,10 @@ See :ref:`modelgapjunctions`. A current stimulus is a DC or sinusoidal current of fixed frequency with a time-varying amplitude governed by a piecewise-linear envelope. -The stimulus is described by two parameters: a frequency in Hertz, where a value of zero denotes DC; -and a sequence of points (*t*\ :sub:`i`\ , *a*\ :sub:`i`\ ) describing the envelope, where the times -*t*\ :sub:`i` are in milliseconds and the amplitudes *a*\ :sub:`i` are in nanoamperes. +The stimulus is described by three parameters: +a sequence of points (*t*\ :sub:`i`\ , *a*\ :sub:`i`\ ) describing the envelope, where the times +*t*\ :sub:`i` are in milliseconds and the amplitudes *a*\ :sub:`i` are in nanoamperes; +a frequency in kilohertz, where a value of zero denotes DC; and the phase in radians at time zero. The stimulus starts at the first timepoint *t*\ :sub:`0` with amplitude *a*\ :sub:`0`, and the amplitude is then interpolated linearly between successive points. The last envelope point @@ -307,11 +308,11 @@ constant stimuli and constant amplitude stimuli restricted to a fixed time inter # Constant stimulus, amplitude 10 nA. decor.place('(root)', arbor.iclamp(10)) - # Constant amplitude 10 nA stimulus at 20 Hz. - decor.place('(root)', arbor.iclamp(10, 20)) + # Constant amplitude 10 nA stimulus at 20 Hz, with initial phase of π/4 radians. + decor.place('(root)', arbor.iclamp(10, frequency=0.020, phasce=math.pi/4)) - # Stimulus at 20 Hz, amplitude 10 nA, for 40 ms starting at t = 30 ms. - decor.place('(root)', arbor.iclamp(30, 40, 10, 20)) + # Stimulus at 1 kHz, amplitude 10 nA, for 40 ms starting at t = 30 ms. + decor.place('(root)', arbor.iclamp(30, 40, 10, frequency=1)) # Piecewise linear stimulus with amplitude ranging from 0 nA to 10 nA, # starting at t = 30 ms and stopping at t = 50 ms. diff --git a/doc/fileformat/cable_cell.rst b/doc/fileformat/cable_cell.rst index 5419912e..8bb9f1ff 100644 --- a/doc/fileformat/cable_cell.rst +++ b/doc/fileformat/cable_cell.rst @@ -148,29 +148,29 @@ The various properties and dynamics of the decor are described as follows: (ion-reversal-potential-method "ca" (mechanism "nernst/ca")) -.. label:: (current-clamp (envelope-pulse delay:real duration:real amplitude:real) freq:real) +.. label:: (current-clamp (envelope-pulse delay:real duration:real amplitude:real) freq:real phase:real) - This creates a *current clamp*. If the frequency ``freq`` (unit Hz) is zero, the current is a square + This creates a *current clamp*. If the frequency ``freq`` (unit kHz) is zero, the current is a square pulse with amplitude ``amplitude`` (unit nA) starting at ``delay`` (unit ms) and lasting for ``duration`` (unit ms). If ``freq`` is non-zero, the current is sinusoidal with amplitude ``amplitude`` and frequency - ``freq`` from time ``delay`` and lasting for ``duration``. + ``freq`` from time ``delay`` and lasting for ``duration``, with phase ``phase`` (unit rad) at time zero. (More information about current clamps can be found :ref:`here <cablecell-stimuli>`). -.. label:: (current-clamp [...(envelope time:real amplitude:real)] freq:real) +.. label:: (current-clamp [...(envelope time:real amplitude:real)] freq:real phase:real) This creates a *current clamp* with an amplitude governed by the given envelopes (``time`` unit ms and - ``amplitude`` unit nA). A frequency ``freq`` (unit Hz) of zero implies that the generated current simply + ``amplitude`` unit nA). A frequency ``freq`` (unit kHz) of zero implies that the generated current simply follows the envelope. A non-zero ``freq`` implies the current is sinusoidal with that frequency and amplitude - that varies according to the envelope. (More information about current clamps can be found - :ref:`here <cablecell-stimuli>`). + that varies according to the envelope. The ``phase`` (unit rad) is the phase of the sinusoidal current + clamp at time zero. (More information about current clamps can be found :ref:`here <cablecell-stimuli>`). For example: .. code:: - (current-clamp (envelope (0 10) (50 10) (50 0)) 40) + (current-clamp (envelope (0 10) (50 10) (50 0)) 0.04 0.15) - This expression describes a sinusoidal current with amplitude 10nA and frequency 40Hz and that lasts - from t = 0ms to t = 50ms, finally leaving the current at 0nA (final amplitude in the envelope). + This expression describes a sinusoidal current with amplitude 10 nA and frequency 40 Hz and that lasts + from t = 0 ms to t = 50 ms, finally leaving the current at 0 nA (final amplitude in the envelope). .. label:: (threshold-detector val:real). @@ -205,7 +205,7 @@ The various properties and dynamics of the decor are described as follows: (place (locset "mylocset") (threshold-detector 10)) - This expression places a 10 mV threshold detector on the locset labeled ``mylocset``. + This expression places a 10 mV threshold detector on the locset labeled ``mylocset``. (The definition of ``mylocset`` should be provided in a label dictionary associated with the decor). @@ -219,7 +219,7 @@ The various properties and dynamics of the decor are described as follows: (default (membrane-potential -65)) - This expression sets the default membrane potential of the cell to -65 mV. + This expression sets the default membrane potential of the cell to -65 mV. Any number of paint, place and default expressions can be used to create a decor as follows: @@ -437,4 +437,4 @@ API --- * :ref:`Python <pycablecellformat>` -* :ref:`C++ <cppcablecellformat>` \ No newline at end of file +* :ref:`C++ <cppcablecellformat>` diff --git a/doc/python/decor.rst b/doc/python/decor.rst index 099bfebd..7a7ca937 100644 --- a/doc/python/decor.rst +++ b/doc/python/decor.rst @@ -167,7 +167,7 @@ Cable cell decoration :param str locations: description of the locset. :param stim: the current stim. - :type stim: :py:class:`i_clamp` + :type stim: :py:class:`iclamp` :rtype: int .. method:: place(locations, d) diff --git a/example/gap_junctions/gap_junctions.cpp b/example/gap_junctions/gap_junctions.cpp index 4c16d2bc..58f37a4d 100644 --- a/example/gap_junctions/gap_junctions.cpp +++ b/example/gap_junctions/gap_junctions.cpp @@ -320,7 +320,7 @@ arb::cable_cell gj_cell(cell_gid_type gid, unsigned ncell, double stim_duration) // Attach a stimulus to the second cell. if (!gid) { - arb::i_clamp stim(0, stim_duration, 0.4); + auto stim = arb::i_clamp::box(0, stim_duration, 0.4); decor.place(arb::mlocation{0, 0.5}, stim); } diff --git a/example/probe-demo/probe-demo.cpp b/example/probe-demo/probe-demo.cpp index 9e9c1600..d2391af4 100644 --- a/example/probe-demo/probe-demo.cpp +++ b/example/probe-demo/probe-demo.cpp @@ -122,7 +122,7 @@ struct cable_recipe: public arb::recipe { arb::decor decor; decor.paint(arb::reg::all(), "hh"); // HH mechanism over whole cell. - decor.place(arb::mlocation{0, 0.}, arb::i_clamp{0., INFINITY, 1.}); // Inject a 1 nA current indefinitely. + decor.place(arb::mlocation{0, 0.}, arb::i_clamp{1.}); // Inject a 1 nA current indefinitely. return arb::cable_cell(tree, {}, decor); } diff --git a/python/cells.cpp b/python/cells.cpp index 76afd9e5..a9f7fabd 100644 --- a/python/cells.cpp +++ b/python/cells.cpp @@ -288,24 +288,25 @@ void register_cells(pybind11::module& m) { "A current clamp for injecting a DC or fixed frequency current governed by a piecewise linear envelope."); i_clamp .def(pybind11::init( - [](double ts, double dur, double cur, double frequency) { - return arb::i_clamp{ts, dur, cur, frequency}; - }), "tstart"_a, "duration"_a, "current"_a, "frequency"_a=0, + [](double ts, double dur, double cur, double frequency, double phase) { + return arb::i_clamp::box(ts, dur, cur, frequency, phase); + }), "tstart"_a, "duration"_a, "current"_a, pybind11::kw_only(), "frequency"_a=0, "phase"_a=0, "Construct finite duration current clamp, constant amplitude") .def(pybind11::init( - [](double cur, double frequency) { - return arb::i_clamp{cur, frequency}; - }), "current"_a, "frequency"_a=0, + [](double cur, double frequency, double phase) { + return arb::i_clamp{cur, frequency, phase}; + }), "current"_a, pybind11::kw_only(), "frequency"_a=0, "phase"_a=0, "Construct constant amplitude current clamp") .def(pybind11::init( - [](std::vector<std::pair<double, double>> envl, double frequency) { + [](std::vector<std::pair<double, double>> envl, double frequency, double phase) { arb::i_clamp clamp; for (const auto& p: envl) { clamp.envelope.push_back({p.first, p.second}); } clamp.frequency = frequency; + clamp.phase = phase; return clamp; - }), "envelope"_a, "frequency"_a=0, + }), "envelope"_a, pybind11::kw_only(), "frequency"_a=0, "phase"_a=0, "Construct current clamp according to (time, amplitude) linear envelope") .def_property_readonly("envelope", [](const arb::i_clamp& obj) { @@ -316,7 +317,8 @@ void register_cells(pybind11::module& m) { return envl; }, "List of (time [ms], amplitude [nA]) points comprising the piecewise linear envelope") - .def_readonly("frequency", &arb::i_clamp::frequency, "Oscillation frequency [Hz], zero implies DC stimulus.") + .def_readonly("frequency", &arb::i_clamp::frequency, "Oscillation frequency (kHz), zero implies DC stimulus.") + .def_readonly("phase", &arb::i_clamp::phase, "Oscillation initial phase (rad)") .def("__repr__", [](const arb::i_clamp& c) { return util::pprintf("<arbor.iclamp: frequency {} Hz>", c.frequency);}) .def("__str__", [](const arb::i_clamp& c) { diff --git a/python/pybind11 b/python/pybind11 index 3b1dbeba..8de7772c 160000 --- a/python/pybind11 +++ b/python/pybind11 @@ -1 +1 @@ -Subproject commit 3b1dbebabc801c9cf6f0953a4c20b904d444f879 +Subproject commit 8de7772cc72daca8e947b79b83fea46214931604 diff --git a/test/unit/test_fvm_lowered.cpp b/test/unit/test_fvm_lowered.cpp index a35e8393..0634c25f 100644 --- a/test/unit/test_fvm_lowered.cpp +++ b/test/unit/test_fvm_lowered.cpp @@ -345,9 +345,9 @@ TEST(fvm_lowered, stimulus) { auto desc = make_cell_ball_and_stick(false); // At end of stick - desc.decorations.place(mlocation{0,1}, i_clamp{5., 80., 0.3}); + desc.decorations.place(mlocation{0,1}, i_clamp::box(5., 80., 0.3)); // On the soma CV, which is over the approximate interval: (cable 0 0 0.1) - desc.decorations.place(mlocation{0,0.05}, i_clamp{1., 2., 0.1}); + desc.decorations.place(mlocation{0,0.05}, i_clamp::box(1., 2., 0.1)); std::vector<cable_cell> cells{desc}; @@ -416,12 +416,13 @@ TEST(fvm_lowered, ac_stimulus) { segment_tree tree; tree.append(mnpos, {0., 0., 0., 1.}, {100., 0., 0., 1.}, 1); - const double freq = 20; // (Hz) + const double freq = 0.02; // (kHz) + const double phase = 0.1; // (radian) const double max_amplitude = 30; // (nA) const double max_time = 8; // (ms) // Envelope is linear ramp from 0 to max_time. - dec.place(mlocation{0, 0}, i_clamp({{0, 0}, {max_time, max_amplitude}, {max_time, 0}}, freq)); + dec.place(mlocation{0, 0}, i_clamp({{0, 0}, {max_time, max_amplitude}, {max_time, 0}}, freq, phase)); std::vector<cable_cell> cells = {cable_cell(tree, {}, dec)}; cable_cell_global_properties gprop; @@ -455,7 +456,7 @@ TEST(fvm_lowered, ac_stimulus) { memory::fill(T, t); state.add_stimulus_current(); - double expected_I = t<=max_time? max_amplitude*t/max_time*std::sin(2*math::pi<double>*t*0.001*freq): 0; + double expected_I = t<=max_time? max_amplitude*t/max_time*std::sin(2*math::pi<double>*t*freq+phase): 0; EXPECT_TRUE(near_relative(-expected_I, J[0]*A[0]*unit_factor, reltol)); } } diff --git a/test/unit/test_mc_cell_group.cpp b/test/unit/test_mc_cell_group.cpp index efc5fbff..564b2179 100644 --- a/test/unit/test_mc_cell_group.cpp +++ b/test/unit/test_mc_cell_group.cpp @@ -28,7 +28,7 @@ namespace { auto d = builder.make_cell(); d.decorations.paint("soma"_lab, "hh"); d.decorations.paint("dend"_lab, "pas"); - d.decorations.place(builder.location({1,1}), i_clamp{5, 80, 0.3}); + d.decorations.place(builder.location({1,1}), i_clamp::box(5, 80, 0.3)); d.decorations.place(builder.location({0, 0}), threshold_detector{0}); return d; } diff --git a/test/unit/test_mc_cell_group_gpu.cpp b/test/unit/test_mc_cell_group_gpu.cpp index 7b2306e6..f7c8fbb7 100644 --- a/test/unit/test_mc_cell_group_gpu.cpp +++ b/test/unit/test_mc_cell_group_gpu.cpp @@ -29,7 +29,7 @@ namespace { auto d = builder.make_cell(); d.decorations.paint("soma"_lab, "hh"); d.decorations.paint("dend"_lab, "pas"); - d.decorations.place(builder.location({1,1}), i_clamp{5, 80, 0.3}); + d.decorations.place(builder.location({1,1}), i_clamp::box(5, 80, 0.3)); d.decorations.place(builder.location({0, 0}), threshold_detector{0}); return d; } diff --git a/test/unit/test_probe.cpp b/test/unit/test_probe.cpp index 7e705019..1ac01253 100644 --- a/test/unit/test_probe.cpp +++ b/test/unit/test_probe.cpp @@ -110,7 +110,7 @@ void run_v_i_probe_test(const context& ctx) { bs.decorations.set_default(cv_policy_fixed_per_branch(1)); - i_clamp stim(0, 100, 0.3); + auto stim = i_clamp::box(0, 100, 0.3); bs.decorations.place(mlocation{1, 1}, stim); cable1d_recipe rec((cable_cell(bs))); @@ -792,7 +792,7 @@ void run_axial_and_ion_current_sampled_probe_test(const context& ctx) { cv_policy policy = cv_policy_fixed_per_branch(n_cv); d.set_default(policy); - d.place(mlocation{0, 0}, i_clamp(0, INFINITY, 0.3)); + d.place(mlocation{0, 0}, i_clamp(0.3)); // The time constant will be membrane capacitance / membrane conductance. // For τ = 0.1 ms, set conductance to 0.01 S/cm² and membrance capacitance @@ -989,8 +989,8 @@ void run_v_sampled_probe_test(const context& ctx) { // samples at the same point on each cell will give the same value at // 0.3 ms, but different at 0.6 ms. - d0.place(mlocation{1, 1}, i_clamp(0, 0.5, 1.)); - d1.place(mlocation{1, 1}, i_clamp(0, 1.0, 1.)); + d0.place(mlocation{1, 1}, i_clamp::box(0, 0.5, 1.)); + d1.place(mlocation{1, 1}, i_clamp::box(0, 1.0, 1.)); mlocation probe_loc{1, 0.2}; std::vector<cable_cell> cells = {{bs.morph, bs.labels, d0}, {bs.morph, bs.labels, d1}}; @@ -1040,7 +1040,7 @@ void run_total_current_probe_test(const context& ctx) { // to 0.01 F/m². const double tau = 0.1; // [ms] - d0.place(mlocation{0, 0}, i_clamp(0, INFINITY, 0.3)); + d0.place(mlocation{0, 0}, i_clamp(0.3)); d0.paint(reg::all(), mechanism_desc("ca_linear").set("g", 0.01)); // [S/cm²] d0.set_default(membrane_capacitance{0.01}); // [F/m²] @@ -1161,13 +1161,13 @@ void run_stimulus_probe_test(const context& ctx) { decor d0, d1; d0.set_default(policy); - d0.place(mlocation{0, 0.5}, i_clamp(0., stim_until, 10.)); - d0.place(mlocation{0, 0.5}, i_clamp(0., stim_until, 20.)); + d0.place(mlocation{0, 0.5}, i_clamp::box(0., stim_until, 10.)); + d0.place(mlocation{0, 0.5}, i_clamp::box(0., stim_until, 20.)); double expected_stim0 = 30; d1.set_default(policy); - d1.place(mlocation{0, 1}, i_clamp(0., stim_until, 30.)); - d1.place(mlocation{0, 1}, i_clamp(0., stim_until, -10.)); + d1.place(mlocation{0, 1}, i_clamp::box(0., stim_until, 30.)); + d1.place(mlocation{0, 1}, i_clamp::box(0., stim_until, -10.)); double expected_stim1 = 20; std::vector<cable_cell> cells = {{m, {}, d0}, {m, {}, d1}}; diff --git a/test/unit/test_s_expr.cpp b/test/unit/test_s_expr.cpp index 543f7779..078d531f 100644 --- a/test/unit/test_s_expr.cpp +++ b/test/unit/test_s_expr.cpp @@ -317,7 +317,7 @@ std::ostream& operator<<(std::ostream& o, const i_clamp& c) { for (const auto& p: c.envelope) { o << " (" << p.t << " " << p.amplitude << ')'; } - return o << ") " << c.frequency << ')'; + return o << ") " << c.frequency << ' ' << c.phase << ')'; } std::ostream& operator<<(std::ostream& o, const threshold_detector& p) { return o << "(threshold-detector " << p.threshold << ')'; @@ -463,7 +463,7 @@ TEST(decor_literals, round_tripping) { auto default_literals = { "(ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\"))"}; auto place_literals = { - "(current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 0)", + "(current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 10 0.25)", "(threshold-detector -10)", "(gap-junction-site)", "(mechanism \"expsyn\")"}; @@ -480,8 +480,8 @@ TEST(decor_literals, round_tripping) { for (auto l: place_literals) { EXPECT_EQ(l, round_trip_variant<placeable>(l)); } - auto clamp_literal = "(current-clamp (envelope-pulse 10 5 0.1) 50)"; - EXPECT_EQ("(current-clamp (envelope (10 0.1) (15 0.1) (15 0)) 50)", round_trip_variant<placeable>(clamp_literal)); + auto clamp_literal = "(current-clamp (envelope-pulse 10 5 0.1) 50 0.5)"; + EXPECT_EQ("(current-clamp (envelope (10 0.1) (15 0.1) (15 0)) 50 0.5)", round_trip_variant<placeable>(clamp_literal)); std::string mech_str = "(mechanism \"kamt\" (\"gbar\" 50) (\"zetam\" 0.1) (\"q10\" 5))"; auto maybe_mech = arborio::parse_expression(mech_str); @@ -524,7 +524,7 @@ TEST(decor_expressions, round_tripping) { "(default (ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\")))" }; auto decorate_place_literals = { - "(place (location 3 0.2) (current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 0))", + "(place (location 3 0.2) (current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 0.5 0.25))", "(place (terminal) (threshold-detector -10))", "(place (root) (gap-junction-site))", "(place (locset \"my!ls\") (mechanism \"expsyn\"))"}; @@ -825,6 +825,7 @@ TEST(cable_cell_literals, errors) { "(mechanism \"pas\" (\"g\" 0.5 0.1) (\"e\" 0.2))", // too many values "(gap-junction-site 0)", // too many arguments "(current-clamp (envelope (10 0.5) (110 0.5) (110 0)))", // too few arguments + "(current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 10)", // too few arguments "(paint (region) (mechanism \"hh\"))", // invalid region "(paint (tag 1) (mechanims hh))", // invalid painting "(paint (terminal) (membrance-capacitance 0.2))", // can't paint a locset @@ -872,7 +873,7 @@ TEST(doc_expressions, parse) { "(locset-def \"my_locset\" (location 3 0.5))", "(mechanism \"hh\" (\"gl\" 0.5) (\"el\" 2))", "(ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\"))", - "(current-clamp (envelope (0 10) (50 10) (50 0)) 40)", + "(current-clamp (envelope (0 10) (50 10) (50 0)) 40 0.25)", "(paint (tag 1) (membrane-capacitance 0.02))", "(place (locset \"mylocset\") (threshold-detector 10))", "(default (membrane-potential -65))", @@ -991,4 +992,4 @@ TEST(doc_expressions, parse) { { EXPECT_TRUE(arborio::parse_expression(expr)); } -} \ No newline at end of file +} diff --git a/test/unit/test_spikes.cpp b/test/unit/test_spikes.cpp index 9ededc62..b2b51586 100644 --- a/test/unit/test_spikes.cpp +++ b/test/unit/test_spikes.cpp @@ -222,7 +222,7 @@ TEST(SPIKES_TEST_CLASS, threshold_watcher_interpolation) { arb::decor decor; decor.set_default(arb::cv_policy_every_segment()); decor.place("\"mid\"", arb::threshold_detector{10}); - decor.place("\"mid\"", arb::i_clamp(0.01+i*dt, duration, 0.5)); + decor.place("\"mid\"", arb::i_clamp::box(0.01+i*dt, duration, 0.5)); decor.place("\"mid\"", arb::mechanism_desc("hh")); arb::cable_cell cell(morpho, dict, decor); -- GitLab