diff --git a/arbor/include/arbor/lif_cell.hpp b/arbor/include/arbor/lif_cell.hpp index cff3eeb55104b4b6da453d661e6a8b467a61c297..64557616f2e03d0fdae119a5c17fe0acda9b2a2e 100644 --- a/arbor/include/arbor/lif_cell.hpp +++ b/arbor/include/arbor/lif_cell.hpp @@ -16,11 +16,17 @@ struct ARB_SYMBOL_VISIBLE lif_cell { double C_m = 20; // Membrane capacitance [pF]. double E_L = 0; // Resting potential [mV]. double V_m = E_L; // Initial value of the Membrane potential [mV]. - double V_reset = E_L; // Reset potential [mV]. double t_ref = 2; // Refractory period [ms]. lif_cell() = delete; lif_cell(cell_tag_type source, cell_tag_type target): source(std::move(source)), target(std::move(target)) {} }; +// LIF probe metadata, to be passed to sampler callbacks. Intentionally left blank. +struct ARB_SYMBOL_VISIBLE lif_probe_metadata {}; + +// Voltage estimate [mV]. +// Sample value type: `double` +struct ARB_SYMBOL_VISIBLE lif_probe_voltage {}; + } // namespace arb diff --git a/arbor/lif_cell_group.cpp b/arbor/lif_cell_group.cpp index 0fa71789ec73ee47148e657e05ccc89dc4eda65a..1dffa20d3c89b66b800edd0ff3ae2cc2932b8208 100644 --- a/arbor/lif_cell_group.cpp +++ b/arbor/lif_cell_group.cpp @@ -5,6 +5,8 @@ #include "profile/profiler_macro.hpp" #include "util/rangeutil.hpp" #include "util/span.hpp" +#include "util/filter.hpp" +#include "util/maputil.hpp" using namespace arb; @@ -13,8 +15,16 @@ lif_cell_group::lif_cell_group(const std::vector<cell_gid_type>& gids, const rec gids_(gids) { for (auto gid: gids_) { - if (!rec.get_probes(gid).empty()) { - throw bad_cell_probe(cell_kind::lif, gid); + auto probes = rec.get_probes(gid); + for (const auto lid: util::count_along(probes)) { + const auto& probe = probes[lid]; + if (probe.address.type() == typeid(lif_probe_voltage)) { + cell_member_type id{gid, static_cast<cell_lid_type>(lid)}; + probes_[id] = {probe.tag, lif_probe_kind::voltage, {}}; + } + else { + throw bad_cell_probe{cell_kind::lif, gid}; + } } } // Default to no binning of events @@ -22,6 +32,7 @@ lif_cell_group::lif_cell_group(const std::vector<cell_gid_type>& gids, const rec cells_.reserve(gids_.size()); last_time_updated_.resize(gids_.size()); + next_time_updatable_.resize(gids_.size()); for (auto lid: util::make_span(gids_.size())) { cells_.push_back(util::any_cast<lif_cell>(rec.get_cell_description(gids_[lid]))); @@ -41,11 +52,9 @@ cell_kind lif_cell_group::get_cell_kind() const { void lif_cell_group::advance(epoch ep, time_type dt, const event_lane_subrange& event_lanes) { PE(advance:lif); - if (event_lanes.size() > 0) { - for (auto lid: util::make_span(gids_.size())) { - // Advance each cell independently. - advance_cell(ep.t1, dt, lid, event_lanes[lid]); - } + for (auto lid: util::make_span(gids_.size())) { + // Advance each cell independently. + advance_cell(ep.t1, dt, lid, event_lanes); } PL(); } @@ -59,10 +68,30 @@ void lif_cell_group::clear_spikes() { } // TODO: implement sampler -void lif_cell_group::add_sampler(sampler_association_handle h, cell_member_predicate probeset_ids, - schedule sched, sampler_function fn, sampling_policy policy) {} -void lif_cell_group::remove_sampler(sampler_association_handle h) {} -void lif_cell_group::remove_all_samplers() {} +void lif_cell_group::add_sampler(sampler_association_handle h, + cell_member_predicate probeset_ids, + schedule sched, + sampler_function fn, + sampling_policy policy) { + std::lock_guard<std::mutex> guard(sampler_mex_); + std::vector<cell_member_type> probeset = + util::assign_from(util::filter(util::keys(probes_), probeset_ids)); + auto assoc = arb::sampler_association{std::move(sched), + std::move(fn), + std::move(probeset), + policy}; + auto result = samplers_.insert({h, std::move(assoc)}); + arb_assert(result.second); +} + +void lif_cell_group::remove_sampler(sampler_association_handle h) { + std::lock_guard<std::mutex> guard(sampler_mex_); + samplers_.erase(h); +} +void lif_cell_group::remove_all_samplers() { + std::lock_guard<std::mutex> guard(sampler_mex_); + samplers_.clear(); +} // TODO: implement binner_ void lif_cell_group::set_binning_policy(binning_kind policy, time_type bin_interval) { @@ -71,52 +100,143 @@ void lif_cell_group::set_binning_policy(binning_kind policy, time_type bin_inter void lif_cell_group::reset() { spikes_.clear(); util::fill(last_time_updated_, 0.); + util::fill(next_time_updatable_, 0.); } // Advances a single cell (lid) with the exact solution (jumps can be arbitrary). // Parameter dt is ignored, since we make jumps between two consecutive spikes. -void lif_cell_group::advance_cell(time_type tfinal, time_type dt, cell_gid_type lid, pse_vector& event_lane) { - // Current time of last update. - auto t = last_time_updated_[lid]; +void lif_cell_group::advance_cell(time_type tfinal, time_type dt, cell_gid_type lid, const event_lane_subrange& event_lanes) { + const auto gid = gids_[lid]; auto& cell = cells_[lid]; - const auto n_events = event_lane.size(); - - // Integrate until tfinal using the exact solution of membrane voltage differential equation. - for (unsigned i=0; i<n_events; ++i ) { - auto& ev = event_lane[i]; - const auto time = ev.time; - auto weight = ev.weight; - - if (time < t) continue; // skip event if a neuron is in refactory period - if (time >= tfinal) break; // end of integration interval - - // if there are events that happened at the same time as this event, process them as well - while (i + 1 < n_events && event_lane[i+1].time <= time) { - weight += event_lane[i+1].weight; - i++; + // time of last update. + auto t = last_time_updated_[lid]; + // spikes to process + const auto n_events = static_cast<int>(event_lanes.size() ? event_lanes[lid].size() : 0); + int event_idx = 0; + // collected sampling data + std::unordered_map<sampler_association_handle, + std::unordered_map<cell_member_type, + std::vector<sample_record>>> sampled; + // samples to process + std::size_t n_values = 0; + std::vector<std::pair<time_type, sampler_association_handle>> samples; + { + std::lock_guard<std::mutex> guard(sampler_mex_); + for (auto& [hdl, assoc]: samplers_) { + // Construct sampling times + const auto& times = util::make_range(assoc.sched.events(t, tfinal)); + const auto n_times = times.size(); + // Count up the samplers touching _our_ gid + int delta = 0; + for (const auto& pid: assoc.probeset_ids) { + if (pid.gid != gid) continue; + arb_assert (0 == sampled[hdl].count(pid)); + sampled[hdl][pid].reserve(n_times); + delta += n_times; + } + if (delta == 0) continue; + n_values += delta; + // only exact sampling: ignore lax and never look at policy + for (auto t: times) samples.emplace_back(t, hdl); + } + } + std::sort(samples.begin(), samples.end()); + int n_samples = samples.size(); + int sample_idx = 0; + // Now allocate some scratch space for the probed values, if we don't, + // re-alloc might move our data + std::vector<value_type> sampled_voltages; + sampled_voltages.reserve(n_values); + // integrate until tfinal using the exact solution of membrane voltage differential equation. + for (;;) { + const auto event_time = event_idx < n_events ? event_lanes[lid][event_idx].time : tfinal; + const auto sample_time = sample_idx < n_samples ? samples[sample_idx].first : tfinal; + const auto time = std::min(event_time, sample_time); + // bail at end of integration interval + if (time >= tfinal) break; + // Check what to do, we might need to process events **and/or** perform + // sampling. + // NB. we put events before samples, if they collide we'll see + // the update in sampling. + + bool do_event = time == event_time; + bool do_sample = time == sample_time; + + if (do_event) { + const auto& event_lane = event_lanes[lid]; + // process all events at time t + auto weight = 0.0; + for (; event_idx < n_events && event_lane[event_idx].time <= time; ++event_idx) { + weight += event_lane[event_idx].weight; + } + // skip event if neuron is in refactory period + if (time >= t) { + // Let the membrane potential decay. + cell.V_m *= exp((t - time) / cell.tau_m); + // Add jump due to spike(s). + cell.V_m += weight / cell.C_m; + // Update current time + t = time; + // If crossing threshold occurred + if (cell.V_m >= cell.V_th) { + // save spike + spikes_.push_back({{gid, 0}, time}); + // Advance to account for the refractory period. + // This means decay will also start at t + t_ref + t += cell.t_ref; + // Reset the voltage to resting potential. + cell.V_m = cell.E_L; + } + } } - // Let the membrane potential decay. - auto decay = exp(-(time - t) / cell.tau_m); - cell.V_m *= decay; - auto update = weight / cell.C_m; - // Add jump due to spike. - cell.V_m += update; - t = time; - // If crossing threshold occurred - if (cell.V_m >= cell.V_th) { - cell_member_type spike_neuron_gid = {gids_[lid], 0}; - spike s = {spike_neuron_gid, t}; - spikes_.push_back(s); - - // Advance the last_time_updated to account for the refractory period. - t += cell.t_ref; - - // Reset the voltage to resting potential. - cell.V_m = cell.E_L; + if (do_sample) { + // Consume all sample events at this time + for (; sample_idx < n_samples && samples[sample_idx].first <= time; ++sample_idx) { + const auto& [s_time, hdl] = samples[sample_idx]; + for (const auto& key: samplers_[hdl].probeset_ids) { + const auto& kind = probes_.at(key).kind; + // This is the only thing we know how to do: Probing U(t) + switch (kind) { + case lif_probe_kind::voltage: { + // Compute, but do not _set_ V_m + auto U = cell.V_m; + if (time >= t) U *= exp((t - time) / cell.tau_m); + // Store U for later use. + sampled_voltages.push_back(U); + // Set up reference to sampled value + sampled[hdl][key].push_back(sample_record{time, {&sampled_voltages.back()}}); + break; + } + default: + throw arbor_internal_error{"Invalid LIF probe kind"}; + } + } + } + } + if (!(do_sample || do_event)) { + throw arbor_internal_error{"LIF cell group: Must select either sample or spike event; got neither."}; } + last_time_updated_[lid] = t; } + arb_assert (sampled_voltages.size() == n_values); + // Now we need to call all sampler callbacks with the data we have collected + { + std::lock_guard<std::mutex> guard(sampler_mex_); + for (const auto& [k, vs]: sampled) { + const auto& fun = samplers_[k].sampler; + for (const auto& [id, us]: vs) { + auto meta = get_probe_metadata(id)[0]; + fun(meta, us.size(), us.data()); + } + } + } +} - // This is the last time a cell was updated. - last_time_updated_[lid] = t; +std::vector<probe_metadata> lif_cell_group::get_probe_metadata(cell_member_type key) const { + if (probes_.count(key)) { + return {probe_metadata{key, {}, 0, {&probes_.at(key).metadata}}}; + } else { + return {}; + } } diff --git a/arbor/lif_cell_group.hpp b/arbor/lif_cell_group.hpp index b89e442f863206f13f5b5b1c25e2b293f8044be8..dbebbfa7776bc4a9701fdf77c3ab19a7445a92ee 100644 --- a/arbor/lif_cell_group.hpp +++ b/arbor/lif_cell_group.hpp @@ -1,6 +1,7 @@ #pragma once #include <vector> +#include <mutex> #include <arbor/export.hpp> #include <arbor/common_types.hpp> @@ -9,6 +10,7 @@ #include <arbor/sampling.hpp> #include <arbor/spike.hpp> +#include "sampler_map.hpp" #include "cell_group.hpp" #include "label_resolution.hpp" @@ -37,10 +39,21 @@ public: virtual void remove_sampler(sampler_association_handle) override; virtual void remove_all_samplers() override; + virtual std::vector<probe_metadata> get_probe_metadata(cell_member_type) const override; + private: + enum class lif_probe_kind { voltage }; + + struct lif_probe_info { + probe_tag tag; + lif_probe_kind kind; + lif_probe_metadata metadata; + }; + + // Advances a single cell (lid) with the exact solution (jumps can be arbitrary). // Parameter dt is ignored, since we make jumps between two consecutive spikes. - void advance_cell(time_type tfinal, time_type dt, cell_gid_type lid, pse_vector& event_lane); + void advance_cell(time_type tfinal, time_type dt, cell_gid_type lid, const event_lane_subrange& event_lane); // List of the gids of the cells in the group. std::vector<cell_gid_type> gids_; @@ -53,6 +66,16 @@ private: // Time when the cell was last updated. std::vector<time_type> last_time_updated_; + // Time when the cell can _next_ be updated; + std::vector<time_type> next_time_updatable_; + + // SAFETY: We need to access samplers_ through a mutex since + // simulation::add_sampler might be called concurrently. + std::mutex sampler_mex_; + sampler_association_map samplers_; + + // LIF probe metadata, precalculated to pass to callbacks + std::unordered_map<cell_member_type, lif_probe_info> probes_; }; } // namespace arb diff --git a/doc/concepts/cable_cell.rst b/doc/concepts/cable_cell.rst index a619051264b61e50e8ecf49eb786f9f6f81b6d64..3364f20fb435993aac7a4c8eeed1b5d3643fb94f 100644 --- a/doc/concepts/cable_cell.rst +++ b/doc/concepts/cable_cell.rst @@ -46,7 +46,6 @@ Once constructed, the cable cell can be queried for specific information about t labels mechanisms decor - probe_sample API --- diff --git a/doc/concepts/index.rst b/doc/concepts/index.rst index f1cf92b0715ba07eece665febaa1d5534e22e52b..cd0f20c36b2b773de6c1200277c2175c672161eb 100644 --- a/doc/concepts/index.rst +++ b/doc/concepts/index.rst @@ -42,3 +42,5 @@ of the model over the locally available computational resources. In order to visualize the result of detected spikes a spike recorder can be used, and to analyse Arbor's performance a meter manager is available. + +:ref:`probesample` shows how to extract data from simulations. diff --git a/doc/concepts/lif_cell.rst b/doc/concepts/lif_cell.rst index c965c967b03998fefb433f3339d429e2a1969100..18fdcd44d21ba757e38a966e5750d98b615848a1 100644 --- a/doc/concepts/lif_cell.rst +++ b/doc/concepts/lif_cell.rst @@ -5,19 +5,35 @@ LIF cells The description of a LIF cell is used to control the leaky integrate-and-fire dynamics: -* Resting potential. -* Reset potential. -* Initial value of membrane potential. -* Membrane potential decaying constant. -* Membrane capacitance. -* Firing threshold. -* Refractory period. - -The morphology of a LIF cell is automatically modelled as a single :term:`compartment <control volume>`; -each cell has one built-in **source** and one built-in **target** which need to be given labels when the -cell is created. The labels are used to form connections to and from the cell. -LIF cells do not support adding additional **sources** or **targets** to the description. They do not support -**gap junctions**. They do not support adding density or point mechanisms. +* Resting potential :math:`E_\mathrm{L}` +* Membrane potential decaying constant :math:`\tau_\mathrm{m}` +* Membrane capacitance :math:`C_\mathrm{m}` +* Firing threshold :math:`U_\mathrm{threshold}` +* Refractory period :math:`t_\mathrm{ref}` + +The morphology of a LIF cell is automatically modelled as a single +:term:`compartment <control volume>`; each cell has one built-in **source** and +one built-in **target** which need to be given labels when the cell is created. +The labels are used to form connections to and from the cell. LIF cells do not +support adding additional **sources** or **targets** to the description. They do +not support **gap junctions**. They do not support adding density or point +mechanisms. + +The LIF cell's time dynamics are this: + +0. :math:`U_\mathrm{m}(0) = E_\mathrm{L}` +1. If the cell is in its refractory state :math:`U_\mathrm{m}(t) = E_\mathrm{L}` +2. Otherwise :math:`U'_\mathrm{m}(t) = \sum\limits_\mathrm{spike} w_\mathrm{spike} \cdot\delta(t - t_\mathrm{spike}) -\frac{1}{\tau_\mathrm{m}}U_\mathrm{m}(t)` +3. If :math:`U_\mathrm{m}(t_0) \geq U_\mathrm{threshold}`: emit spike and enter refractory period until :math:`t = t_0 + t_\mathrm{ref}` + +LIF cells can be probed to obtain their current membrane potential, see :ref:`probesample`. + +.. figure:: ../images/lif.svg + :width: 400 + :align: center + + Plot of the potential over time for a LIF cell. + API --- diff --git a/doc/concepts/probe_sample.rst b/doc/concepts/probe_sample.rst index 0e3b0fcf94eb9c5f923a876459638b842a5ae298..9b553eb6833ea294de65a4d81eeb111b5d1ece3f 100644 --- a/doc/concepts/probe_sample.rst +++ b/doc/concepts/probe_sample.rst @@ -1,7 +1,12 @@ .. _probesample: -Cable cell probing and sampling -=============================== +Probing and Sampling +==================== + +Both cable cells and LIF cells can be probed, see here for more details on cells +:ref:`modelcells`. The LIF cell, however, has a much smaller set of observable +quantities and only offers scalar probes. Thus, the following discussion is +tailored to the cable cell. Definitions *********** @@ -64,7 +69,6 @@ Spiking Threshold detectors have a dual use: they can be used to record spike times, but are also used in propagating signals between cells. See also :term:`threshold detector` and :ref:`cablecell-threshold-detectors`. - API --- diff --git a/doc/cpp/probe_sample.rst b/doc/cpp/probe_sample.rst index cb7b08cfbbc5a2dbf666c2e340e850416b987c89..c9eb0c231eb8aadc3cce12ed60e9f11b80cd94df 100644 --- a/doc/cpp/probe_sample.rst +++ b/doc/cpp/probe_sample.rst @@ -660,3 +660,19 @@ call the *sampler* callback once for probe in *probe set*, with *n* sample value In addition to the ``lax`` sampling policy, ``mc_cell_group`` supports the ``exact`` policy. Integration steps will be shortened such that any sample times associated with an ``exact`` policy can be satisfied precisely. + +LIF cell probing and sampling +=============================== + +Membrane voltage +---------------- + +.. code:: + + struct lif_probe_voltage {}; + +Queries cell membrane potential. + +* Sample value: ``double``. Membrane potential (mV). + +* Metadata: none diff --git a/doc/images/lif.svg b/doc/images/lif.svg new file mode 100644 index 0000000000000000000000000000000000000000..b4c31ddb8ffbe5ba1c45bc325adfe110a6d90ea9 --- /dev/null +++ b/doc/images/lif.svg @@ -0,0 +1,1297 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Created with matplotlib (https://matplotlib.org/) --> +<svg height="288pt" version="1.1" viewBox="0 0 432 288" width="432pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <metadata> + <rdf:RDF xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <cc:Work> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <dc:date>2022-10-27T16:15:02.520502</dc:date> + <dc:format>image/svg+xml</dc:format> + <dc:creator> + <cc:Agent> + <dc:title>Matplotlib v3.3.4, https://matplotlib.org/</dc:title> + </cc:Agent> + </dc:creator> + </cc:Work> + </rdf:RDF> + </metadata> + <defs> + <style type="text/css">*{stroke-linecap:butt;stroke-linejoin:round;}</style> + </defs> + <g id="figure_1"> + <g id="patch_1"> + <path d="M 0 288 +L 432 288 +L 432 0 +L 0 0 +z +" style="fill:none;"/> + </g> + <g id="axes_1"> + <g id="patch_2"> + <path d="M 54 252 +L 388.8 252 +L 388.8 34.56 +L 54 34.56 +z +" style="fill:#ffffff;"/> + </g> + <g id="PathCollection_1"> + <defs> + <path d="M 0 3 +L 0 -3 +" id="m13e9bbe748" style="stroke:#000000;stroke-width:1.5;"/> + </defs> + <g clip-path="url(#pe30e7958f1)"> + <use style="stroke:#000000;stroke-width:1.5;" x="109.8" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="120.96" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="132.12" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="143.28" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="154.44" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="165.6" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="176.76" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="187.92" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="199.08" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="210.24" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="221.4" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="232.56" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="243.72" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="254.88" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="266.04" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="277.2" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="288.36" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="299.52" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="310.68" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="321.84" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="333" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="344.16" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="355.32" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="366.48" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="377.64" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="388.8" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="399.96" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="411.12" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="422.28" xlink:href="#m13e9bbe748" y="88.92"/> + <use style="stroke:#000000;stroke-width:1.5;" x="433.44" xlink:href="#m13e9bbe748" y="88.92"/> + </g> + </g> + <g id="PathCollection_2"> + <defs> + <path d="M 0 3 +L 0 -3 +" id="m028051d5a0" style="stroke:#ff0000;stroke-width:1.5;"/> + </defs> + <g clip-path="url(#pe30e7958f1)"> + <use style="fill:#ff0000;stroke:#ff0000;stroke-width:1.5;" x="176.76" xlink:href="#m028051d5a0" y="70.8"/> + <use style="fill:#ff0000;stroke:#ff0000;stroke-width:1.5;" x="333" xlink:href="#m028051d5a0" y="70.8"/> + </g> + </g> + <g id="matplotlib.axis_1"> + <g id="xtick_1"> + <g id="line2d_1"> + <defs> + <path d="M 0 0 +L 0 3.5 +" id="m9275e96962" style="stroke:#000000;stroke-width:0.8;"/> + </defs> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m9275e96962" y="252"/> + </g> + </g> + <g id="text_1"> + <!-- 0.0 --> + <g transform="translate(46.048437 266.598437)scale(0.1 -0.1)"> + <defs> + <path d="M 31.78125 66.40625 +Q 24.171875 66.40625 20.328125 58.90625 +Q 16.5 51.421875 16.5 36.375 +Q 16.5 21.390625 20.328125 13.890625 +Q 24.171875 6.390625 31.78125 6.390625 +Q 39.453125 6.390625 43.28125 13.890625 +Q 47.125 21.390625 47.125 36.375 +Q 47.125 51.421875 43.28125 58.90625 +Q 39.453125 66.40625 31.78125 66.40625 +z +M 31.78125 74.21875 +Q 44.046875 74.21875 50.515625 64.515625 +Q 56.984375 54.828125 56.984375 36.375 +Q 56.984375 17.96875 50.515625 8.265625 +Q 44.046875 -1.421875 31.78125 -1.421875 +Q 19.53125 -1.421875 13.0625 8.265625 +Q 6.59375 17.96875 6.59375 36.375 +Q 6.59375 54.828125 13.0625 64.515625 +Q 19.53125 74.21875 31.78125 74.21875 +z +" id="DejaVuSans-48"/> + <path d="M 10.6875 12.40625 +L 21 12.40625 +L 21 0 +L 10.6875 0 +z +" id="DejaVuSans-46"/> + </defs> + <use xlink:href="#DejaVuSans-48"/> + <use x="63.623047" xlink:href="#DejaVuSans-46"/> + <use x="95.410156" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="xtick_2"> + <g id="line2d_2"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="98.64" xlink:href="#m9275e96962" y="252"/> + </g> + </g> + <g id="text_2"> + <!-- 0.2 --> + <g transform="translate(90.688438 266.598437)scale(0.1 -0.1)"> + <defs> + <path d="M 19.1875 8.296875 +L 53.609375 8.296875 +L 53.609375 0 +L 7.328125 0 +L 7.328125 8.296875 +Q 12.9375 14.109375 22.625 23.890625 +Q 32.328125 33.6875 34.8125 36.53125 +Q 39.546875 41.84375 41.421875 45.53125 +Q 43.3125 49.21875 43.3125 52.78125 +Q 43.3125 58.59375 39.234375 62.25 +Q 35.15625 65.921875 28.609375 65.921875 +Q 23.96875 65.921875 18.8125 64.3125 +Q 13.671875 62.703125 7.8125 59.421875 +L 7.8125 69.390625 +Q 13.765625 71.78125 18.9375 73 +Q 24.125 74.21875 28.421875 74.21875 +Q 39.75 74.21875 46.484375 68.546875 +Q 53.21875 62.890625 53.21875 53.421875 +Q 53.21875 48.921875 51.53125 44.890625 +Q 49.859375 40.875 45.40625 35.40625 +Q 44.1875 33.984375 37.640625 27.21875 +Q 31.109375 20.453125 19.1875 8.296875 +z +" id="DejaVuSans-50"/> + </defs> + <use xlink:href="#DejaVuSans-48"/> + <use x="63.623047" xlink:href="#DejaVuSans-46"/> + <use x="95.410156" xlink:href="#DejaVuSans-50"/> + </g> + </g> + </g> + <g id="xtick_3"> + <g id="line2d_3"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="143.28" xlink:href="#m9275e96962" y="252"/> + </g> + </g> + <g id="text_3"> + <!-- 0.4 --> + <g transform="translate(135.328438 266.598437)scale(0.1 -0.1)"> + <defs> + <path d="M 37.796875 64.3125 +L 12.890625 25.390625 +L 37.796875 25.390625 +z +M 35.203125 72.90625 +L 47.609375 72.90625 +L 47.609375 25.390625 +L 58.015625 25.390625 +L 58.015625 17.1875 +L 47.609375 17.1875 +L 47.609375 0 +L 37.796875 0 +L 37.796875 17.1875 +L 4.890625 17.1875 +L 4.890625 26.703125 +z +" id="DejaVuSans-52"/> + </defs> + <use xlink:href="#DejaVuSans-48"/> + <use x="63.623047" xlink:href="#DejaVuSans-46"/> + <use x="95.410156" xlink:href="#DejaVuSans-52"/> + </g> + </g> + </g> + <g id="xtick_4"> + <g id="line2d_4"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="187.92" xlink:href="#m9275e96962" y="252"/> + </g> + </g> + <g id="text_4"> + <!-- 0.6 --> + <g transform="translate(179.968438 266.598437)scale(0.1 -0.1)"> + <defs> + <path d="M 33.015625 40.375 +Q 26.375 40.375 22.484375 35.828125 +Q 18.609375 31.296875 18.609375 23.390625 +Q 18.609375 15.53125 22.484375 10.953125 +Q 26.375 6.390625 33.015625 6.390625 +Q 39.65625 6.390625 43.53125 10.953125 +Q 47.40625 15.53125 47.40625 23.390625 +Q 47.40625 31.296875 43.53125 35.828125 +Q 39.65625 40.375 33.015625 40.375 +z +M 52.59375 71.296875 +L 52.59375 62.3125 +Q 48.875 64.0625 45.09375 64.984375 +Q 41.3125 65.921875 37.59375 65.921875 +Q 27.828125 65.921875 22.671875 59.328125 +Q 17.53125 52.734375 16.796875 39.40625 +Q 19.671875 43.65625 24.015625 45.921875 +Q 28.375 48.1875 33.59375 48.1875 +Q 44.578125 48.1875 50.953125 41.515625 +Q 57.328125 34.859375 57.328125 23.390625 +Q 57.328125 12.15625 50.6875 5.359375 +Q 44.046875 -1.421875 33.015625 -1.421875 +Q 20.359375 -1.421875 13.671875 8.265625 +Q 6.984375 17.96875 6.984375 36.375 +Q 6.984375 53.65625 15.1875 63.9375 +Q 23.390625 74.21875 37.203125 74.21875 +Q 40.921875 74.21875 44.703125 73.484375 +Q 48.484375 72.75 52.59375 71.296875 +z +" id="DejaVuSans-54"/> + </defs> + <use xlink:href="#DejaVuSans-48"/> + <use x="63.623047" xlink:href="#DejaVuSans-46"/> + <use x="95.410156" xlink:href="#DejaVuSans-54"/> + </g> + </g> + </g> + <g id="xtick_5"> + <g id="line2d_5"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="232.56" xlink:href="#m9275e96962" y="252"/> + </g> + </g> + <g id="text_5"> + <!-- 0.8 --> + <g transform="translate(224.608438 266.598437)scale(0.1 -0.1)"> + <defs> + <path d="M 31.78125 34.625 +Q 24.75 34.625 20.71875 30.859375 +Q 16.703125 27.09375 16.703125 20.515625 +Q 16.703125 13.921875 20.71875 10.15625 +Q 24.75 6.390625 31.78125 6.390625 +Q 38.8125 6.390625 42.859375 10.171875 +Q 46.921875 13.96875 46.921875 20.515625 +Q 46.921875 27.09375 42.890625 30.859375 +Q 38.875 34.625 31.78125 34.625 +z +M 21.921875 38.8125 +Q 15.578125 40.375 12.03125 44.71875 +Q 8.5 49.078125 8.5 55.328125 +Q 8.5 64.0625 14.71875 69.140625 +Q 20.953125 74.21875 31.78125 74.21875 +Q 42.671875 74.21875 48.875 69.140625 +Q 55.078125 64.0625 55.078125 55.328125 +Q 55.078125 49.078125 51.53125 44.71875 +Q 48 40.375 41.703125 38.8125 +Q 48.828125 37.15625 52.796875 32.3125 +Q 56.78125 27.484375 56.78125 20.515625 +Q 56.78125 9.90625 50.3125 4.234375 +Q 43.84375 -1.421875 31.78125 -1.421875 +Q 19.734375 -1.421875 13.25 4.234375 +Q 6.78125 9.90625 6.78125 20.515625 +Q 6.78125 27.484375 10.78125 32.3125 +Q 14.796875 37.15625 21.921875 38.8125 +z +M 18.3125 54.390625 +Q 18.3125 48.734375 21.84375 45.5625 +Q 25.390625 42.390625 31.78125 42.390625 +Q 38.140625 42.390625 41.71875 45.5625 +Q 45.3125 48.734375 45.3125 54.390625 +Q 45.3125 60.0625 41.71875 63.234375 +Q 38.140625 66.40625 31.78125 66.40625 +Q 25.390625 66.40625 21.84375 63.234375 +Q 18.3125 60.0625 18.3125 54.390625 +z +" id="DejaVuSans-56"/> + </defs> + <use xlink:href="#DejaVuSans-48"/> + <use x="63.623047" xlink:href="#DejaVuSans-46"/> + <use x="95.410156" xlink:href="#DejaVuSans-56"/> + </g> + </g> + </g> + <g id="xtick_6"> + <g id="line2d_6"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="277.2" xlink:href="#m9275e96962" y="252"/> + </g> + </g> + <g id="text_6"> + <!-- 1.0 --> + <g transform="translate(269.248437 266.598437)scale(0.1 -0.1)"> + <defs> + <path d="M 12.40625 8.296875 +L 28.515625 8.296875 +L 28.515625 63.921875 +L 10.984375 60.40625 +L 10.984375 69.390625 +L 28.421875 72.90625 +L 38.28125 72.90625 +L 38.28125 8.296875 +L 54.390625 8.296875 +L 54.390625 0 +L 12.40625 0 +z +" id="DejaVuSans-49"/> + </defs> + <use xlink:href="#DejaVuSans-49"/> + <use x="63.623047" xlink:href="#DejaVuSans-46"/> + <use x="95.410156" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="xtick_7"> + <g id="line2d_7"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="321.84" xlink:href="#m9275e96962" y="252"/> + </g> + </g> + <g id="text_7"> + <!-- 1.2 --> + <g transform="translate(313.888438 266.598437)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-49"/> + <use x="63.623047" xlink:href="#DejaVuSans-46"/> + <use x="95.410156" xlink:href="#DejaVuSans-50"/> + </g> + </g> + </g> + <g id="xtick_8"> + <g id="line2d_8"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="366.48" xlink:href="#m9275e96962" y="252"/> + </g> + </g> + <g id="text_8"> + <!-- 1.4 --> + <g transform="translate(358.528437 266.598437)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-49"/> + <use x="63.623047" xlink:href="#DejaVuSans-46"/> + <use x="95.410156" xlink:href="#DejaVuSans-52"/> + </g> + </g> + </g> + <g id="text_9"> + <!-- Time $(t/ms)$ --> + <g transform="translate(192.35 280.276563)scale(0.1 -0.1)"> + <defs> + <path d="M -0.296875 72.90625 +L 61.375 72.90625 +L 61.375 64.59375 +L 35.5 64.59375 +L 35.5 0 +L 25.59375 0 +L 25.59375 64.59375 +L -0.296875 64.59375 +z +" id="DejaVuSans-84"/> + <path d="M 9.421875 54.6875 +L 18.40625 54.6875 +L 18.40625 0 +L 9.421875 0 +z +M 9.421875 75.984375 +L 18.40625 75.984375 +L 18.40625 64.59375 +L 9.421875 64.59375 +z +" id="DejaVuSans-105"/> + <path d="M 52 44.1875 +Q 55.375 50.25 60.0625 53.125 +Q 64.75 56 71.09375 56 +Q 79.640625 56 84.28125 50.015625 +Q 88.921875 44.046875 88.921875 33.015625 +L 88.921875 0 +L 79.890625 0 +L 79.890625 32.71875 +Q 79.890625 40.578125 77.09375 44.375 +Q 74.3125 48.1875 68.609375 48.1875 +Q 61.625 48.1875 57.5625 43.546875 +Q 53.515625 38.921875 53.515625 30.90625 +L 53.515625 0 +L 44.484375 0 +L 44.484375 32.71875 +Q 44.484375 40.625 41.703125 44.40625 +Q 38.921875 48.1875 33.109375 48.1875 +Q 26.21875 48.1875 22.15625 43.53125 +Q 18.109375 38.875 18.109375 30.90625 +L 18.109375 0 +L 9.078125 0 +L 9.078125 54.6875 +L 18.109375 54.6875 +L 18.109375 46.1875 +Q 21.1875 51.21875 25.484375 53.609375 +Q 29.78125 56 35.6875 56 +Q 41.65625 56 45.828125 52.96875 +Q 50 49.953125 52 44.1875 +z +" id="DejaVuSans-109"/> + <path d="M 56.203125 29.59375 +L 56.203125 25.203125 +L 14.890625 25.203125 +Q 15.484375 15.921875 20.484375 11.0625 +Q 25.484375 6.203125 34.421875 6.203125 +Q 39.59375 6.203125 44.453125 7.46875 +Q 49.3125 8.734375 54.109375 11.28125 +L 54.109375 2.78125 +Q 49.265625 0.734375 44.1875 -0.34375 +Q 39.109375 -1.421875 33.890625 -1.421875 +Q 20.796875 -1.421875 13.15625 6.1875 +Q 5.515625 13.8125 5.515625 26.8125 +Q 5.515625 40.234375 12.765625 48.109375 +Q 20.015625 56 32.328125 56 +Q 43.359375 56 49.78125 48.890625 +Q 56.203125 41.796875 56.203125 29.59375 +z +M 47.21875 32.234375 +Q 47.125 39.59375 43.09375 43.984375 +Q 39.0625 48.390625 32.421875 48.390625 +Q 24.90625 48.390625 20.390625 44.140625 +Q 15.875 39.890625 15.1875 32.171875 +z +" id="DejaVuSans-101"/> + <path id="DejaVuSans-32"/> + <path d="M 31 75.875 +Q 24.46875 64.65625 21.28125 53.65625 +Q 18.109375 42.671875 18.109375 31.390625 +Q 18.109375 20.125 21.3125 9.0625 +Q 24.515625 -2 31 -13.1875 +L 23.1875 -13.1875 +Q 15.875 -1.703125 12.234375 9.375 +Q 8.59375 20.453125 8.59375 31.390625 +Q 8.59375 42.28125 12.203125 53.3125 +Q 15.828125 64.359375 23.1875 75.875 +z +" id="DejaVuSans-40"/> + <path d="M 42.28125 54.6875 +L 40.921875 47.703125 +L 23 47.703125 +L 17.1875 18.015625 +Q 16.890625 16.359375 16.75 15.234375 +Q 16.609375 14.109375 16.609375 13.484375 +Q 16.609375 10.359375 18.484375 8.9375 +Q 20.359375 7.515625 24.515625 7.515625 +L 33.59375 7.515625 +L 32.078125 0 +L 23.484375 0 +Q 15.484375 0 11.546875 3.125 +Q 7.625 6.25 7.625 12.59375 +Q 7.625 13.71875 7.765625 15.0625 +Q 7.90625 16.40625 8.203125 18.015625 +L 14.015625 47.703125 +L 6.390625 47.703125 +L 7.8125 54.6875 +L 15.28125 54.6875 +L 18.3125 70.21875 +L 27.296875 70.21875 +L 24.3125 54.6875 +z +" id="DejaVuSans-Oblique-116"/> + <path d="M 25.390625 72.90625 +L 33.6875 72.90625 +L 8.296875 -9.28125 +L 0 -9.28125 +z +" id="DejaVuSans-47"/> + <path d="M 89.796875 33.015625 +L 83.40625 0 +L 74.421875 0 +L 80.71875 32.71875 +Q 81.109375 34.8125 81.296875 36.328125 +Q 81.5 37.84375 81.5 38.921875 +Q 81.5 43.3125 79.046875 45.75 +Q 76.609375 48.1875 72.21875 48.1875 +Q 65.671875 48.1875 60.546875 43.28125 +Q 55.421875 38.375 53.90625 30.515625 +L 47.90625 0 +L 38.921875 0 +L 45.3125 32.71875 +Q 45.703125 34.515625 45.890625 36.046875 +Q 46.09375 37.59375 46.09375 38.8125 +Q 46.09375 43.265625 43.65625 45.71875 +Q 41.21875 48.1875 36.921875 48.1875 +Q 30.28125 48.1875 25.140625 43.28125 +Q 20.015625 38.375 18.5 30.515625 +L 12.5 0 +L 3.515625 0 +L 14.203125 54.6875 +L 23.1875 54.6875 +L 21.484375 46.1875 +Q 25.140625 50.984375 30.046875 53.484375 +Q 34.96875 56 40.578125 56 +Q 46.53125 56 50.359375 52.875 +Q 54.203125 49.75 54.984375 44.1875 +Q 59.078125 49.953125 64.46875 52.96875 +Q 69.875 56 75.875 56 +Q 82.90625 56 86.734375 51.953125 +Q 90.578125 47.90625 90.578125 40.484375 +Q 90.578125 38.875 90.375 36.9375 +Q 90.1875 35.015625 89.796875 33.015625 +z +" id="DejaVuSans-Oblique-109"/> + <path d="M 50 53.078125 +L 48.296875 44.578125 +Q 44.734375 46.53125 40.765625 47.5 +Q 36.8125 48.484375 32.625 48.484375 +Q 25.53125 48.484375 21.453125 46.0625 +Q 17.390625 43.65625 17.390625 39.5 +Q 17.390625 34.671875 26.859375 32.078125 +Q 27.59375 31.890625 27.9375 31.78125 +L 30.8125 30.90625 +Q 39.796875 28.421875 42.796875 25.6875 +Q 45.796875 22.953125 45.796875 18.21875 +Q 45.796875 9.515625 38.890625 4.046875 +Q 31.984375 -1.421875 20.796875 -1.421875 +Q 16.453125 -1.421875 11.671875 -0.578125 +Q 6.890625 0.25 1.125 2 +L 2.875 11.28125 +Q 7.8125 8.734375 12.59375 7.421875 +Q 17.390625 6.109375 21.78125 6.109375 +Q 28.375 6.109375 32.5 8.9375 +Q 36.625 11.765625 36.625 16.109375 +Q 36.625 20.796875 25.78125 23.6875 +L 24.859375 23.921875 +L 21.78125 24.703125 +Q 14.9375 26.515625 11.765625 29.46875 +Q 8.59375 32.421875 8.59375 37.015625 +Q 8.59375 45.75 15.15625 50.875 +Q 21.734375 56 33.015625 56 +Q 37.453125 56 41.671875 55.265625 +Q 45.90625 54.546875 50 53.078125 +z +" id="DejaVuSans-Oblique-115"/> + <path d="M 8.015625 75.875 +L 15.828125 75.875 +Q 23.140625 64.359375 26.78125 53.3125 +Q 30.421875 42.28125 30.421875 31.390625 +Q 30.421875 20.453125 26.78125 9.375 +Q 23.140625 -1.703125 15.828125 -13.1875 +L 8.015625 -13.1875 +Q 14.5 -2 17.703125 9.0625 +Q 20.90625 20.125 20.90625 31.390625 +Q 20.90625 42.671875 17.703125 53.65625 +Q 14.5 64.65625 8.015625 75.875 +z +" id="DejaVuSans-41"/> + </defs> + <use transform="translate(0 0.015625)" xlink:href="#DejaVuSans-84"/> + <use transform="translate(61.083984 0.015625)" xlink:href="#DejaVuSans-105"/> + <use transform="translate(88.867188 0.015625)" xlink:href="#DejaVuSans-109"/> + <use transform="translate(186.279297 0.015625)" xlink:href="#DejaVuSans-101"/> + <use transform="translate(247.802734 0.015625)" xlink:href="#DejaVuSans-32"/> + <use transform="translate(279.589844 0.015625)" xlink:href="#DejaVuSans-40"/> + <use transform="translate(318.603516 0.015625)" xlink:href="#DejaVuSans-Oblique-116"/> + <use transform="translate(357.8125 0.015625)" xlink:href="#DejaVuSans-47"/> + <use transform="translate(391.503906 0.015625)" xlink:href="#DejaVuSans-Oblique-109"/> + <use transform="translate(488.916016 0.015625)" xlink:href="#DejaVuSans-Oblique-115"/> + <use transform="translate(541.015625 0.015625)" xlink:href="#DejaVuSans-41"/> + </g> + </g> + </g> + <g id="matplotlib.axis_2"> + <g id="ytick_1"> + <g id="line2d_9"> + <defs> + <path d="M 0 0 +L -3.5 0 +" id="m7d9d9dfb13" style="stroke:#000000;stroke-width:0.8;"/> + </defs> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m7d9d9dfb13" y="233.88"/> + </g> + </g> + <g id="text_10"> + <!-- −40 --> + <g transform="translate(25.895312 237.679219)scale(0.1 -0.1)"> + <defs> + <path d="M 10.59375 35.5 +L 73.1875 35.5 +L 73.1875 27.203125 +L 10.59375 27.203125 +z +" id="DejaVuSans-8722"/> + </defs> + <use xlink:href="#DejaVuSans-8722"/> + <use x="83.789062" xlink:href="#DejaVuSans-52"/> + <use x="147.412109" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_2"> + <g id="line2d_10"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m7d9d9dfb13" y="197.64"/> + </g> + </g> + <g id="text_11"> + <!-- −30 --> + <g transform="translate(25.895312 201.439219)scale(0.1 -0.1)"> + <defs> + <path d="M 40.578125 39.3125 +Q 47.65625 37.796875 51.625 33 +Q 55.609375 28.21875 55.609375 21.1875 +Q 55.609375 10.40625 48.1875 4.484375 +Q 40.765625 -1.421875 27.09375 -1.421875 +Q 22.515625 -1.421875 17.65625 -0.515625 +Q 12.796875 0.390625 7.625 2.203125 +L 7.625 11.71875 +Q 11.71875 9.328125 16.59375 8.109375 +Q 21.484375 6.890625 26.8125 6.890625 +Q 36.078125 6.890625 40.9375 10.546875 +Q 45.796875 14.203125 45.796875 21.1875 +Q 45.796875 27.640625 41.28125 31.265625 +Q 36.765625 34.90625 28.71875 34.90625 +L 20.21875 34.90625 +L 20.21875 43.015625 +L 29.109375 43.015625 +Q 36.375 43.015625 40.234375 45.921875 +Q 44.09375 48.828125 44.09375 54.296875 +Q 44.09375 59.90625 40.109375 62.90625 +Q 36.140625 65.921875 28.71875 65.921875 +Q 24.65625 65.921875 20.015625 65.03125 +Q 15.375 64.15625 9.8125 62.3125 +L 9.8125 71.09375 +Q 15.4375 72.65625 20.34375 73.4375 +Q 25.25 74.21875 29.59375 74.21875 +Q 40.828125 74.21875 47.359375 69.109375 +Q 53.90625 64.015625 53.90625 55.328125 +Q 53.90625 49.265625 50.4375 45.09375 +Q 46.96875 40.921875 40.578125 39.3125 +z +" id="DejaVuSans-51"/> + </defs> + <use xlink:href="#DejaVuSans-8722"/> + <use x="83.789062" xlink:href="#DejaVuSans-51"/> + <use x="147.412109" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_3"> + <g id="line2d_11"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m7d9d9dfb13" y="161.4"/> + </g> + </g> + <g id="text_12"> + <!-- −20 --> + <g transform="translate(25.895312 165.199219)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-8722"/> + <use x="83.789062" xlink:href="#DejaVuSans-50"/> + <use x="147.412109" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_4"> + <g id="line2d_12"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m7d9d9dfb13" y="125.16"/> + </g> + </g> + <g id="text_13"> + <!-- −10 --> + <g transform="translate(25.895312 128.959219)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-8722"/> + <use x="83.789062" xlink:href="#DejaVuSans-49"/> + <use x="147.412109" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_5"> + <g id="line2d_13"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m7d9d9dfb13" y="88.92"/> + </g> + </g> + <g id="text_14"> + <!-- 0 --> + <g transform="translate(40.6375 92.719219)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_6"> + <g id="line2d_14"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m7d9d9dfb13" y="52.68"/> + </g> + </g> + <g id="text_15"> + <!-- 10 --> + <g transform="translate(34.275 56.479219)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-49"/> + <use x="63.623047" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="text_16"> + <!-- Potential $(U_\mathrm{m}/mV)$ --> + <g transform="translate(19.815625 188.08)rotate(-90)scale(0.1 -0.1)"> + <defs> + <path d="M 19.671875 64.796875 +L 19.671875 37.40625 +L 32.078125 37.40625 +Q 38.96875 37.40625 42.71875 40.96875 +Q 46.484375 44.53125 46.484375 51.125 +Q 46.484375 57.671875 42.71875 61.234375 +Q 38.96875 64.796875 32.078125 64.796875 +z +M 9.8125 72.90625 +L 32.078125 72.90625 +Q 44.34375 72.90625 50.609375 67.359375 +Q 56.890625 61.8125 56.890625 51.125 +Q 56.890625 40.328125 50.609375 34.8125 +Q 44.34375 29.296875 32.078125 29.296875 +L 19.671875 29.296875 +L 19.671875 0 +L 9.8125 0 +z +" id="DejaVuSans-80"/> + <path d="M 30.609375 48.390625 +Q 23.390625 48.390625 19.1875 42.75 +Q 14.984375 37.109375 14.984375 27.296875 +Q 14.984375 17.484375 19.15625 11.84375 +Q 23.34375 6.203125 30.609375 6.203125 +Q 37.796875 6.203125 41.984375 11.859375 +Q 46.1875 17.53125 46.1875 27.296875 +Q 46.1875 37.015625 41.984375 42.703125 +Q 37.796875 48.390625 30.609375 48.390625 +z +M 30.609375 56 +Q 42.328125 56 49.015625 48.375 +Q 55.71875 40.765625 55.71875 27.296875 +Q 55.71875 13.875 49.015625 6.21875 +Q 42.328125 -1.421875 30.609375 -1.421875 +Q 18.84375 -1.421875 12.171875 6.21875 +Q 5.515625 13.875 5.515625 27.296875 +Q 5.515625 40.765625 12.171875 48.375 +Q 18.84375 56 30.609375 56 +z +" id="DejaVuSans-111"/> + <path d="M 18.3125 70.21875 +L 18.3125 54.6875 +L 36.8125 54.6875 +L 36.8125 47.703125 +L 18.3125 47.703125 +L 18.3125 18.015625 +Q 18.3125 11.328125 20.140625 9.421875 +Q 21.96875 7.515625 27.59375 7.515625 +L 36.8125 7.515625 +L 36.8125 0 +L 27.59375 0 +Q 17.1875 0 13.234375 3.875 +Q 9.28125 7.765625 9.28125 18.015625 +L 9.28125 47.703125 +L 2.6875 47.703125 +L 2.6875 54.6875 +L 9.28125 54.6875 +L 9.28125 70.21875 +z +" id="DejaVuSans-116"/> + <path d="M 54.890625 33.015625 +L 54.890625 0 +L 45.90625 0 +L 45.90625 32.71875 +Q 45.90625 40.484375 42.875 44.328125 +Q 39.84375 48.1875 33.796875 48.1875 +Q 26.515625 48.1875 22.3125 43.546875 +Q 18.109375 38.921875 18.109375 30.90625 +L 18.109375 0 +L 9.078125 0 +L 9.078125 54.6875 +L 18.109375 54.6875 +L 18.109375 46.1875 +Q 21.34375 51.125 25.703125 53.5625 +Q 30.078125 56 35.796875 56 +Q 45.21875 56 50.046875 50.171875 +Q 54.890625 44.34375 54.890625 33.015625 +z +" id="DejaVuSans-110"/> + <path d="M 34.28125 27.484375 +Q 23.390625 27.484375 19.1875 25 +Q 14.984375 22.515625 14.984375 16.5 +Q 14.984375 11.71875 18.140625 8.90625 +Q 21.296875 6.109375 26.703125 6.109375 +Q 34.1875 6.109375 38.703125 11.40625 +Q 43.21875 16.703125 43.21875 25.484375 +L 43.21875 27.484375 +z +M 52.203125 31.203125 +L 52.203125 0 +L 43.21875 0 +L 43.21875 8.296875 +Q 40.140625 3.328125 35.546875 0.953125 +Q 30.953125 -1.421875 24.3125 -1.421875 +Q 15.921875 -1.421875 10.953125 3.296875 +Q 6 8.015625 6 15.921875 +Q 6 25.140625 12.171875 29.828125 +Q 18.359375 34.515625 30.609375 34.515625 +L 43.21875 34.515625 +L 43.21875 35.40625 +Q 43.21875 41.609375 39.140625 45 +Q 35.0625 48.390625 27.6875 48.390625 +Q 23 48.390625 18.546875 47.265625 +Q 14.109375 46.140625 10.015625 43.890625 +L 10.015625 52.203125 +Q 14.9375 54.109375 19.578125 55.046875 +Q 24.21875 56 28.609375 56 +Q 40.484375 56 46.34375 49.84375 +Q 52.203125 43.703125 52.203125 31.203125 +z +" id="DejaVuSans-97"/> + <path d="M 9.421875 75.984375 +L 18.40625 75.984375 +L 18.40625 0 +L 9.421875 0 +z +" id="DejaVuSans-108"/> + <path d="M 15.484375 72.90625 +L 25.390625 72.90625 +L 16.796875 28.609375 +Q 16.265625 25.640625 16.046875 23.703125 +Q 15.828125 21.78125 15.828125 20.3125 +Q 15.828125 13.578125 19.578125 10.078125 +Q 23.34375 6.59375 30.609375 6.59375 +Q 40.046875 6.59375 45.28125 11.765625 +Q 50.53125 16.9375 52.78125 28.609375 +L 61.375 72.90625 +L 71.296875 72.90625 +L 62.5 27.390625 +Q 59.625 12.640625 51.5625 5.609375 +Q 43.5 -1.421875 29.5 -1.421875 +Q 18.5625 -1.421875 12.1875 4.078125 +Q 5.8125 9.578125 5.8125 19 +Q 5.8125 20.703125 6.046875 22.828125 +Q 6.296875 24.953125 6.78125 27.390625 +z +" id="DejaVuSans-Oblique-85"/> + <path d="M 20.609375 0 +L 7.8125 72.90625 +L 17.484375 72.90625 +L 28.078125 10.203125 +L 63.484375 72.90625 +L 74.21875 72.90625 +L 32.078125 0 +z +" id="DejaVuSans-Oblique-86"/> + </defs> + <use transform="translate(0 0.015625)" xlink:href="#DejaVuSans-80"/> + <use transform="translate(60.302734 0.015625)" xlink:href="#DejaVuSans-111"/> + <use transform="translate(121.484375 0.015625)" xlink:href="#DejaVuSans-116"/> + <use transform="translate(160.693359 0.015625)" xlink:href="#DejaVuSans-101"/> + <use transform="translate(222.216797 0.015625)" xlink:href="#DejaVuSans-110"/> + <use transform="translate(285.595703 0.015625)" xlink:href="#DejaVuSans-116"/> + <use transform="translate(324.804688 0.015625)" xlink:href="#DejaVuSans-105"/> + <use transform="translate(352.587891 0.015625)" xlink:href="#DejaVuSans-97"/> + <use transform="translate(413.867188 0.015625)" xlink:href="#DejaVuSans-108"/> + <use transform="translate(441.650391 0.015625)" xlink:href="#DejaVuSans-32"/> + <use transform="translate(473.4375 0.015625)" xlink:href="#DejaVuSans-40"/> + <use transform="translate(512.451172 0.015625)" xlink:href="#DejaVuSans-Oblique-85"/> + <use transform="translate(585.644531 -16.390625)scale(0.7)" xlink:href="#DejaVuSans-109"/> + <use transform="translate(656.567383 0.015625)" xlink:href="#DejaVuSans-47"/> + <use transform="translate(690.258789 0.015625)" xlink:href="#DejaVuSans-Oblique-109"/> + <use transform="translate(787.670898 0.015625)" xlink:href="#DejaVuSans-Oblique-86"/> + <use transform="translate(856.079102 0.015625)" xlink:href="#DejaVuSans-41"/> + </g> + </g> + </g> + <g id="line2d_15"> + <path clip-path="url(#pe30e7958f1)" d="M 54 52.68 +L 388.8 52.68 +" style="fill:none;stroke:#808080;stroke-linecap:square;stroke-width:1.5;"/> + </g> + <g id="line2d_16"> + <path clip-path="url(#pe30e7958f1)" d="M 54 241.128 +L 388.8 241.128 +" style="fill:none;stroke:#808080;stroke-linecap:square;stroke-width:1.5;"/> + </g> + <g id="line2d_17"> + <path clip-path="url(#pe30e7958f1)" d="M 54 172.272 +L 59.58 172.06388 +L 65.16 171.85628 +L 70.74 171.649199 +L 76.32 171.442634 +L 81.9 171.236585 +L 87.48 171.03105 +L 93.06 170.826029 +L 98.64 170.62152 +L 104.22 170.417521 +L 109.8 152.094032 +L 115.38 151.936294 +L 120.96 133.65895 +L 126.54 133.547242 +L 132.12 115.315814 +L 137.7 115.249906 +L 143.28 97.064164 +L 148.86 97.043829 +L 154.44 78.903545 +L 160.02 78.928555 +L 165.6 60.833502 +L 171.18 60.903631 +L 176.76 241.128 +L 182.34 241.128 +L 187.92 241.128 +L 193.5 241.128 +L 199.08 241.128 +L 204.66 241.128 +L 210.24 241.128 +L 215.82 241.128 +L 221.4 223.008 +L 226.98 222.673199 +L 232.56 204.219233 +L 238.14 203.931345 +L 243.72 185.524176 +L 249.3 185.282967 +L 254.88 166.922361 +L 260.46 166.727598 +L 266.04 148.413322 +L 271.62 148.264775 +L 277.2 129.996598 +L 282.78 129.894035 +L 288.36 111.671728 +L 293.94 111.614919 +L 299.52 93.438253 +L 305.1 93.426971 +L 310.68 75.295718 +L 316.26 75.329736 +L 321.84 57.243669 +L 327.42 57.322761 +L 333 241.128 +L 338.58 241.128 +L 344.16 241.128 +L 349.74 241.128 +L 355.32 241.128 +L 360.9 241.128 +L 366.48 241.128 +L 372.06 241.128 +L 377.64 223.008 +L 383.22 222.673199 +" style="fill:none;stroke:#1f77b4;stroke-linecap:square;stroke-width:1.5;"/> + </g> + <g id="patch_3"> + <path d="M 54 252 +L 54 34.56 +" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/> + </g> + <g id="patch_4"> + <path d="M 388.8 252 +L 388.8 34.56 +" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/> + </g> + <g id="patch_5"> + <path d="M 54 252 +L 388.8 252 +" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/> + </g> + <g id="patch_6"> + <path d="M 54 34.56 +L 388.8 34.56 +" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/> + </g> + <g id="text_17"> + <g id="patch_7"> + <path d="M 56.696 58.759688 +L 105.496 58.759688 +L 105.496 41.081563 +L 56.696 41.081563 +z +" style="fill:#ffffff;stroke:#ffffff;stroke-linejoin:miter;"/> + </g> + <!-- $U_\mathrm{threshold}$ --> + <g transform="translate(60.696 52.68)scale(0.1 -0.1)"> + <defs> + <path d="M 54.890625 33.015625 +L 54.890625 0 +L 45.90625 0 +L 45.90625 32.71875 +Q 45.90625 40.484375 42.875 44.328125 +Q 39.84375 48.1875 33.796875 48.1875 +Q 26.515625 48.1875 22.3125 43.546875 +Q 18.109375 38.921875 18.109375 30.90625 +L 18.109375 0 +L 9.078125 0 +L 9.078125 75.984375 +L 18.109375 75.984375 +L 18.109375 46.1875 +Q 21.34375 51.125 25.703125 53.5625 +Q 30.078125 56 35.796875 56 +Q 45.21875 56 50.046875 50.171875 +Q 54.890625 44.34375 54.890625 33.015625 +z +" id="DejaVuSans-104"/> + <path d="M 41.109375 46.296875 +Q 39.59375 47.171875 37.8125 47.578125 +Q 36.03125 48 33.890625 48 +Q 26.265625 48 22.1875 43.046875 +Q 18.109375 38.09375 18.109375 28.8125 +L 18.109375 0 +L 9.078125 0 +L 9.078125 54.6875 +L 18.109375 54.6875 +L 18.109375 46.1875 +Q 20.953125 51.171875 25.484375 53.578125 +Q 30.03125 56 36.53125 56 +Q 37.453125 56 38.578125 55.875 +Q 39.703125 55.765625 41.0625 55.515625 +z +" id="DejaVuSans-114"/> + <path d="M 44.28125 53.078125 +L 44.28125 44.578125 +Q 40.484375 46.53125 36.375 47.5 +Q 32.28125 48.484375 27.875 48.484375 +Q 21.1875 48.484375 17.84375 46.4375 +Q 14.5 44.390625 14.5 40.28125 +Q 14.5 37.15625 16.890625 35.375 +Q 19.28125 33.59375 26.515625 31.984375 +L 29.59375 31.296875 +Q 39.15625 29.25 43.1875 25.515625 +Q 47.21875 21.78125 47.21875 15.09375 +Q 47.21875 7.46875 41.1875 3.015625 +Q 35.15625 -1.421875 24.609375 -1.421875 +Q 20.21875 -1.421875 15.453125 -0.5625 +Q 10.6875 0.296875 5.421875 2 +L 5.421875 11.28125 +Q 10.40625 8.6875 15.234375 7.390625 +Q 20.0625 6.109375 24.8125 6.109375 +Q 31.15625 6.109375 34.5625 8.28125 +Q 37.984375 10.453125 37.984375 14.40625 +Q 37.984375 18.0625 35.515625 20.015625 +Q 33.0625 21.96875 24.703125 23.78125 +L 21.578125 24.515625 +Q 13.234375 26.265625 9.515625 29.90625 +Q 5.8125 33.546875 5.8125 39.890625 +Q 5.8125 47.609375 11.28125 51.796875 +Q 16.75 56 26.8125 56 +Q 31.78125 56 36.171875 55.265625 +Q 40.578125 54.546875 44.28125 53.078125 +z +" id="DejaVuSans-115"/> + <path d="M 45.40625 46.390625 +L 45.40625 75.984375 +L 54.390625 75.984375 +L 54.390625 0 +L 45.40625 0 +L 45.40625 8.203125 +Q 42.578125 3.328125 38.25 0.953125 +Q 33.9375 -1.421875 27.875 -1.421875 +Q 17.96875 -1.421875 11.734375 6.484375 +Q 5.515625 14.40625 5.515625 27.296875 +Q 5.515625 40.1875 11.734375 48.09375 +Q 17.96875 56 27.875 56 +Q 33.9375 56 38.25 53.625 +Q 42.578125 51.265625 45.40625 46.390625 +z +M 14.796875 27.296875 +Q 14.796875 17.390625 18.875 11.75 +Q 22.953125 6.109375 30.078125 6.109375 +Q 37.203125 6.109375 41.296875 11.75 +Q 45.40625 17.390625 45.40625 27.296875 +Q 45.40625 37.203125 41.296875 42.84375 +Q 37.203125 48.484375 30.078125 48.484375 +Q 22.953125 48.484375 18.875 42.84375 +Q 14.796875 37.203125 14.796875 27.296875 +z +" id="DejaVuSans-100"/> + </defs> + <use transform="translate(0 0.09375)" xlink:href="#DejaVuSans-Oblique-85"/> + <use transform="translate(73.193359 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-116"/> + <use transform="translate(100.639648 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-104"/> + <use transform="translate(145.004883 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-114"/> + <use transform="translate(173.78418 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-101"/> + <use transform="translate(216.850586 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-115"/> + <use transform="translate(253.320312 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-104"/> + <use transform="translate(297.685547 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-111"/> + <use transform="translate(340.512695 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-108"/> + <use transform="translate(359.960938 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-100"/> + </g> + </g> + <g id="text_18"> + <g id="patch_8"> + <path d="M 56.696 247.207688 +L 75.196 247.207688 +L 75.196 229.529563 +L 56.696 229.529563 +z +" style="fill:#ffffff;stroke:#ffffff;stroke-linejoin:miter;"/> + </g> + <!-- $E_\mathrm{L}$ --> + <g transform="translate(60.696 241.128)scale(0.1 -0.1)"> + <defs> + <path d="M 16.890625 72.90625 +L 62.984375 72.90625 +L 61.375 64.59375 +L 25.09375 64.59375 +L 20.90625 43.015625 +L 55.71875 43.015625 +L 54.109375 34.71875 +L 19.28125 34.71875 +L 14.203125 8.296875 +L 51.3125 8.296875 +L 49.703125 0 +L 2.6875 0 +z +" id="DejaVuSans-Oblique-69"/> + <path d="M 9.8125 72.90625 +L 19.671875 72.90625 +L 19.671875 8.296875 +L 55.171875 8.296875 +L 55.171875 0 +L 9.8125 0 +z +" id="DejaVuSans-76"/> + </defs> + <use transform="translate(0 0.09375)" xlink:href="#DejaVuSans-Oblique-69"/> + <use transform="translate(63.183594 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-76"/> + </g> + </g> + <g id="text_19"> + <g id="patch_9"> + <path d="M 56.696 167.479688 +L 79.196 167.479688 +L 79.196 149.801563 +L 56.696 149.801563 +z +" style="fill:#ffffff;stroke:#ffffff;stroke-linejoin:miter;"/> + </g> + <!-- $U_\mathrm{m}$ --> + <g transform="translate(60.696 161.4)scale(0.1 -0.1)"> + <use transform="translate(0 0.09375)" xlink:href="#DejaVuSans-Oblique-85"/> + <use transform="translate(73.193359 -16.3125)scale(0.7)" xlink:href="#DejaVuSans-109"/> + </g> + </g> + <g id="text_20"> + <!-- Spikes in --> + <g transform="translate(60.696 92.544)scale(0.1 -0.1)"> + <defs> + <path d="M 53.515625 70.515625 +L 53.515625 60.890625 +Q 47.90625 63.578125 42.921875 64.890625 +Q 37.9375 66.21875 33.296875 66.21875 +Q 25.25 66.21875 20.875 63.09375 +Q 16.5 59.96875 16.5 54.203125 +Q 16.5 49.359375 19.40625 46.890625 +Q 22.3125 44.4375 30.421875 42.921875 +L 36.375 41.703125 +Q 47.40625 39.59375 52.65625 34.296875 +Q 57.90625 29 57.90625 20.125 +Q 57.90625 9.515625 50.796875 4.046875 +Q 43.703125 -1.421875 29.984375 -1.421875 +Q 24.8125 -1.421875 18.96875 -0.25 +Q 13.140625 0.921875 6.890625 3.21875 +L 6.890625 13.375 +Q 12.890625 10.015625 18.65625 8.296875 +Q 24.421875 6.59375 29.984375 6.59375 +Q 38.421875 6.59375 43.015625 9.90625 +Q 47.609375 13.234375 47.609375 19.390625 +Q 47.609375 24.75 44.3125 27.78125 +Q 41.015625 30.8125 33.5 32.328125 +L 27.484375 33.5 +Q 16.453125 35.6875 11.515625 40.375 +Q 6.59375 45.0625 6.59375 53.421875 +Q 6.59375 63.09375 13.40625 68.65625 +Q 20.21875 74.21875 32.171875 74.21875 +Q 37.3125 74.21875 42.625 73.28125 +Q 47.953125 72.359375 53.515625 70.515625 +z +" id="DejaVuSans-83"/> + <path d="M 18.109375 8.203125 +L 18.109375 -20.796875 +L 9.078125 -20.796875 +L 9.078125 54.6875 +L 18.109375 54.6875 +L 18.109375 46.390625 +Q 20.953125 51.265625 25.265625 53.625 +Q 29.59375 56 35.59375 56 +Q 45.5625 56 51.78125 48.09375 +Q 58.015625 40.1875 58.015625 27.296875 +Q 58.015625 14.40625 51.78125 6.484375 +Q 45.5625 -1.421875 35.59375 -1.421875 +Q 29.59375 -1.421875 25.265625 0.953125 +Q 20.953125 3.328125 18.109375 8.203125 +z +M 48.6875 27.296875 +Q 48.6875 37.203125 44.609375 42.84375 +Q 40.53125 48.484375 33.40625 48.484375 +Q 26.265625 48.484375 22.1875 42.84375 +Q 18.109375 37.203125 18.109375 27.296875 +Q 18.109375 17.390625 22.1875 11.75 +Q 26.265625 6.109375 33.40625 6.109375 +Q 40.53125 6.109375 44.609375 11.75 +Q 48.6875 17.390625 48.6875 27.296875 +z +" id="DejaVuSans-112"/> + <path d="M 9.078125 75.984375 +L 18.109375 75.984375 +L 18.109375 31.109375 +L 44.921875 54.6875 +L 56.390625 54.6875 +L 27.390625 29.109375 +L 57.625 0 +L 45.90625 0 +L 18.109375 26.703125 +L 18.109375 0 +L 9.078125 0 +z +" id="DejaVuSans-107"/> + </defs> + <use xlink:href="#DejaVuSans-83"/> + <use x="63.476562" xlink:href="#DejaVuSans-112"/> + <use x="126.953125" xlink:href="#DejaVuSans-105"/> + <use x="154.736328" xlink:href="#DejaVuSans-107"/> + <use x="209.021484" xlink:href="#DejaVuSans-101"/> + <use x="270.544922" xlink:href="#DejaVuSans-115"/> + <use x="322.644531" xlink:href="#DejaVuSans-32"/> + <use x="354.431641" xlink:href="#DejaVuSans-105"/> + <use x="382.214844" xlink:href="#DejaVuSans-110"/> + </g> + </g> + <g id="text_21"> + <!-- Spikes out --> + <g transform="translate(60.696 70.8)scale(0.1 -0.1)"> + <defs> + <path d="M 8.5 21.578125 +L 8.5 54.6875 +L 17.484375 54.6875 +L 17.484375 21.921875 +Q 17.484375 14.15625 20.5 10.265625 +Q 23.53125 6.390625 29.59375 6.390625 +Q 36.859375 6.390625 41.078125 11.03125 +Q 45.3125 15.671875 45.3125 23.6875 +L 45.3125 54.6875 +L 54.296875 54.6875 +L 54.296875 0 +L 45.3125 0 +L 45.3125 8.40625 +Q 42.046875 3.421875 37.71875 1 +Q 33.40625 -1.421875 27.6875 -1.421875 +Q 18.265625 -1.421875 13.375 4.4375 +Q 8.5 10.296875 8.5 21.578125 +z +M 31.109375 56 +z +" id="DejaVuSans-117"/> + </defs> + <use xlink:href="#DejaVuSans-83"/> + <use x="63.476562" xlink:href="#DejaVuSans-112"/> + <use x="126.953125" xlink:href="#DejaVuSans-105"/> + <use x="154.736328" xlink:href="#DejaVuSans-107"/> + <use x="209.021484" xlink:href="#DejaVuSans-101"/> + <use x="270.544922" xlink:href="#DejaVuSans-115"/> + <use x="322.644531" xlink:href="#DejaVuSans-32"/> + <use x="354.431641" xlink:href="#DejaVuSans-111"/> + <use x="415.613281" xlink:href="#DejaVuSans-117"/> + <use x="478.992188" xlink:href="#DejaVuSans-116"/> + </g> + </g> + </g> + </g> + <defs> + <clipPath id="pe30e7958f1"> + <rect height="217.44" width="334.8" x="54" y="34.56"/> + </clipPath> + </defs> +</svg> diff --git a/doc/index.rst b/doc/index.rst index af35bfc62fc4babf421df1090235a2ac5b1579c1..a13eafa3e1e2608d4c27ca73ac51f1258f92ff6c 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -129,6 +129,7 @@ A full list of our software attributions can be found `here <https://github.com/ concepts/lif_cell concepts/spike_source_cell concepts/benchmark_cell + concepts/probe_sample .. toctree:: :caption: Modelling: diff --git a/doc/python/lif_cell.rst b/doc/python/lif_cell.rst index d8623077df2f5084877d5a44a9825318f5ee4c5d..e79f1b1cfeb381b2e18901bbb5b7eab7c3bc1365 100644 --- a/doc/python/lif_cell.rst +++ b/doc/python/lif_cell.rst @@ -48,7 +48,3 @@ LIF cells .. attribute:: t_ref Refractory period [ms]. - - .. attribute:: V_reset - - Reset potential [mV]. \ No newline at end of file diff --git a/doc/python/probe_sample.rst b/doc/python/probe_sample.rst index da12e17a5245b9738c3624c2e4971a39145c5ddd..634237fbb96230db480c7d52ea850ca870d64403 100644 --- a/doc/python/probe_sample.rst +++ b/doc/python/probe_sample.rst @@ -298,3 +298,12 @@ Ionic external concentration Kind: :term:`vector probe`. +LIF Cell probing +================ + +Membrane voltage + .. py:function:: lif_probe_voltage() + + Current cell membrane potential (mV). + + Metadata: none diff --git a/example/brunel/brunel.cpp b/example/brunel/brunel.cpp index e1e19b3b8399cd6a50297ce4e05d9b2085fd8f80..1807b11d38af6956e404f88040bcba81600617db 100644 --- a/example/brunel/brunel.cpp +++ b/example/brunel/brunel.cpp @@ -136,7 +136,6 @@ public: cell.C_m = 20; cell.E_L = 0; cell.V_m = 0; - cell.V_reset = 0; cell.t_ref = 2; return cell; } diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 678bfe0693f48b2d2b07a404a94093cc4fced5f3..69e0f8bd1c6895cbee200eb930fd6adb1b93c883 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -23,7 +23,7 @@ endif() set(pyarb_source cable_cell_io.cpp - cable_probes.cpp + probes.cpp cells.cpp config.cpp context.cpp diff --git a/python/cells.cpp b/python/cells.cpp index 7d9159e52345dc6043a37983f8070cf60a50ee65..01ca7384ac2846e6822b67854d291af2c323a72f 100644 --- a/python/cells.cpp +++ b/python/cells.cpp @@ -130,8 +130,8 @@ std::optional<arb::mechanism_desc> maybe_method(pybind11::object method) { std::string lif_str(const arb::lif_cell& c){ return util::pprintf( - "<arbor.lif_cell: tau_m {}, V_th {}, C_m {}, E_L {}, V_m {}, t_ref {}, V_reset {}>", - c.tau_m, c.V_th, c.C_m, c.E_L, c.V_m, c.t_ref, c.V_reset); + "<arbor.lif_cell: tau_m {}, V_th {}, C_m {}, E_L {}, V_m {}, t_ref {}>", + c.tau_m, c.V_th, c.C_m, c.E_L, c.V_m, c.t_ref); } @@ -230,8 +230,6 @@ void register_cells(pybind11::module& m) { "Initial value of the Membrane potential [mV].") .def_readwrite("t_ref", &arb::lif_cell::t_ref, "Refractory period [ms].") - .def_readwrite("V_reset", &arb::lif_cell::V_reset, - "Reset potential [mV].") .def_readwrite("source", &arb::lif_cell::source, "Label of the single build-in source on the cell.") .def_readwrite("target", &arb::lif_cell::target, diff --git a/python/example/brunel.py b/python/example/brunel.py index a147e7378402a30567abe8891541652318cb9309..ba8258e2c807f989dd7960ff8b651a3c3ea7c152 100755 --- a/python/example/brunel.py +++ b/python/example/brunel.py @@ -105,7 +105,6 @@ class brunel_recipe(arbor.recipe): cell.C_m = 20 cell.E_L = 0 cell.V_m = 0 - cell.V_reset = 0 cell.t_ref = 2 return cell diff --git a/python/cable_probes.cpp b/python/probes.cpp similarity index 88% rename from python/cable_probes.cpp rename to python/probes.cpp index a309578ad772965a3fbc65b82a91ec380a24089a..a589166bdcc3c10dd2188017d5ede7317111655c 100644 --- a/python/cable_probes.cpp +++ b/python/probes.cpp @@ -8,6 +8,7 @@ #include <arborio/label_parse.hpp> #include <arbor/cable_cell.hpp> +#include <arbor/lif_cell.hpp> #include <arbor/morph/locset.hpp> #include <arbor/recipe.hpp> #include <arbor/sampling.hpp> @@ -26,7 +27,7 @@ namespace pyarb { // to cable_cell scalar- and vector-valued probes. template <typename Meta> -struct recorder_cable_base: sample_recorder { +struct recorder_base: sample_recorder { // Return stride-column array: first column is time, remainder correspond to sample. py::object samples() const override { @@ -49,14 +50,14 @@ protected: std::vector<double> sample_raw_; std::ptrdiff_t stride_; - recorder_cable_base(const Meta* meta_ptr, std::ptrdiff_t width): + recorder_base(const Meta* meta_ptr, std::ptrdiff_t width): meta_(*meta_ptr), stride_(1+width) {} }; template <typename Meta> -struct recorder_cable_scalar: recorder_cable_base<Meta> { - using recorder_cable_base<Meta>::sample_raw_; +struct recorder_cable_scalar: recorder_base<Meta> { + using recorder_base<Meta>::sample_raw_; void record(any_ptr, std::size_t n_sample, const arb::sample_record* records) override { for (std::size_t i = 0; i<n_sample; ++i) { @@ -71,12 +72,32 @@ struct recorder_cable_scalar: recorder_cable_base<Meta> { } protected: - recorder_cable_scalar(const Meta* meta_ptr): recorder_cable_base<Meta>(meta_ptr, 1) {} + recorder_cable_scalar(const Meta* meta_ptr): recorder_base<Meta>(meta_ptr, 1) {} }; +struct recorder_lif: recorder_base<arb::lif_probe_metadata> { + using recorder_base<arb::lif_probe_metadata>::sample_raw_; + + void record(any_ptr, std::size_t n_sample, const arb::sample_record* records) override { + for (std::size_t i = 0; i<n_sample; ++i) { + if (auto* v_ptr = any_cast<double*>(records[i].data)) { + sample_raw_.push_back(records[i].time); + sample_raw_.push_back(*v_ptr); + } + else { + std::string ty = records[i].data.type().name(); + throw arb::arbor_internal_error("LIF recorder: unexpected sample type " + ty); + } + } + } + + recorder_lif(const arb::lif_probe_metadata* meta_ptr): recorder_base<arb::lif_probe_metadata>(meta_ptr, 1) {} +}; + + template <typename Meta> -struct recorder_cable_vector: recorder_cable_base<Meta> { - using recorder_cable_base<Meta>::sample_raw_; +struct recorder_cable_vector: recorder_base<Meta> { + using recorder_base<Meta>::sample_raw_; void record(any_ptr, std::size_t n_sample, const arb::sample_record* records) override { for (std::size_t i = 0; i<n_sample; ++i) { @@ -92,7 +113,7 @@ struct recorder_cable_vector: recorder_cable_base<Meta> { protected: recorder_cable_vector(const Meta* meta_ptr, std::ptrdiff_t width): - recorder_cable_base<Meta>(meta_ptr, width) {} + recorder_base<Meta>(meta_ptr, width) {} }; // Specific recorder classes: @@ -132,6 +153,8 @@ void register_probe_meta_maps(pyarb_global_ptr g) { }); } + + // Wrapper functions around cable_cell probe types that return arb::probe_info values: // (Probe tag value is implicitly left at zero.) @@ -211,6 +234,12 @@ arb::probe_info cable_probe_ion_ext_concentration_cell(const char* ion) { return arb::cable_probe_ion_ext_concentration_cell{ion}; } +// LIF cell probes +arb::probe_info lif_probe_voltage() { + return arb::lif_probe_voltage{}; +} + + // Add wrappers to module, recorder factories to global data. void register_cable_probes(pybind11::module& m, pyarb_global_ptr global_ptr) { @@ -219,6 +248,9 @@ void register_cable_probes(pybind11::module& m, pyarb_global_ptr global_ptr) { // Probe metadata wrappers: + py::class_<arb::lif_probe_metadata> lif_probe_metadata(m, "lif_probe_metadata", + "Probe metadata associated with a LIF cell probe."); + py::class_<arb::cable_probe_point_info> cable_probe_point_info(m, "cable_probe_point_info", "Probe metadata associated with a cable cell probe for point process state."); @@ -236,6 +268,9 @@ void register_cable_probes(pybind11::module& m, pyarb_global_ptr global_ptr) { // Probe address constructors: + m.def("lif_probe_voltage", &lif_probe_voltage, + "Probe specification for LIF cell membrane voltage."); + m.def("cable_probe_membrane_voltage", &cable_probe_membrane_voltage, "Probe specification for cable cell membrane voltage interpolated at points in a location set.", "where"_a); @@ -314,6 +349,7 @@ void register_cable_probes(pybind11::module& m, pyarb_global_ptr global_ptr) { register_probe_meta_maps<arb::cable_probe_point_info, recorder_cable_scalar_point_info>(global_ptr); register_probe_meta_maps<arb::mcable_list, recorder_cable_vector_mcable>(global_ptr); register_probe_meta_maps<std::vector<arb::cable_probe_point_info>, recorder_cable_vector_point_info>(global_ptr); + register_probe_meta_maps<arb::lif_probe_metadata, recorder_lif>(global_ptr); } } // namespace pyarb diff --git a/python/pyarb.hpp b/python/pyarb.hpp index 954195a4396ed3ccc7af40ea9843dfdec2fb3f53..1b4241c113e10fcbf1904c611d532fa95ca696b0 100644 --- a/python/pyarb.hpp +++ b/python/pyarb.hpp @@ -57,7 +57,8 @@ struct recorder_factory_map { return map_.at(meta.type())(meta); } catch (std::out_of_range&) { - throw arb::arbor_internal_error("unrecognized probe metadata type"); + std::string ty = meta.type().name(); + throw arb::arbor_internal_error("unrecognized probe metadata type " + ty); } } }; diff --git a/python/test/unit/test_cable_probes.py b/python/test/unit/test_probes.py similarity index 78% rename from python/test/unit/test_cable_probes.py rename to python/test/unit/test_probes.py index daa0ffd29c474eea45a67c0262abf0befd91b514..ff045a64cb5b4f00a88d87400359955ced3f3aef 100644 --- a/python/test/unit/test_cable_probes.py +++ b/python/test/unit/test_probes.py @@ -2,6 +2,7 @@ import unittest import arbor as A +import numpy as np """ tests for cable probe wrappers @@ -166,3 +167,63 @@ class TestCableProbes(unittest.TestCase): m = sim.probe_metadata((0, 16)) self.assertEqual(1, len(m)) self.assertEqual(all_cv_cables, m[0]) + + +class lif_recipe(A.recipe): + def __init__(self): + A.recipe.__init__(self) + + def num_cells(self): + return 1 + + def cell_kind(self, gid): + return A.cell_kind.lif + + def global_properties(self, kind): + return None + + def probes(self, gid): + return [ + # probe id (0, 0) + A.lif_probe_voltage(), + ] + + def cell_description(self, gid): + cell = A.lif_cell("src", "tgt") + cell.E_L = -42 + cell.V_m = -23 + cell.t_ref = 0.2 + return cell + + +class TestLifProbes(unittest.TestCase): + def test_probe_addr_metadata(self): + rec = lif_recipe() + sim = A.simulation(rec) + + m = sim.probe_metadata((0, 0)) + self.assertEqual(1, len(m)) + self.assertTrue(all(isinstance(i, A.lif_probe_metadata) for i in m)) + + def test_probe_result(self): + rec = lif_recipe() + sim = A.simulation(rec) + hdl = sim.sample((0, 0), A.regular_schedule(0.1)) + sim.run(1.0, 0.05) + smp = sim.samples(hdl) + exp = np.array( + [ + [0.0, -23.0], + [0.1, -22.77114618], + [0.2, -22.54456949], + [0.3, -22.32024727], + [0.4, -22.0981571], + [0.5, -21.87827676], + [0.6, -21.66058427], + [0.7, -21.44505786], + [0.8, -21.23167597], + [0.9, -21.02041726], + ] + ) + for d, _ in smp: + np.testing.assert_allclose(d, exp) diff --git a/test/unit/test_lif_cell_group.cpp b/test/unit/test_lif_cell_group.cpp index 6916f01a999ad3d385393b72cc4ddc84ca7aeda0..60d8d8c9eee2caff969e33e6e28a0aa2c1570dbe 100644 --- a/test/unit/test_lif_cell_group.cpp +++ b/test/unit/test_lif_cell_group.cpp @@ -1,5 +1,7 @@ #include <gtest/gtest.h> +#include "common.hpp" + #include <arbor/arbexcept.hpp> #include <arbor/cable_cell.hpp> #include <arbor/domain_decomposition.hpp> @@ -114,7 +116,7 @@ public: probe_recipe() {} cell_size_type num_cells() const override { - return 1; + return 2; } cell_kind get_cell_kind(cell_gid_type gid) const override { return cell_kind::lif; @@ -123,17 +125,29 @@ public: return {}; } util::unique_any get_cell_description(cell_gid_type gid) const override { - return lif_cell("src", "tgt"); + auto cell = lif_cell("src", "tgt"); + if (gid == 0) { + cell.E_L = -42; + cell.V_m = -23; + cell.t_ref = 0.2; + } + return cell; } - std::vector<probe_info> get_probes(cell_gid_type gid) const override{ - return {arb::cable_probe_membrane_voltage{mlocation{0, 0}}}; + std::vector<probe_info> get_probes(cell_gid_type gid) const override { + if (gid == 0) { + return {arb::lif_probe_voltage{}, arb::lif_probe_voltage{}}; + } else { + return {arb::lif_probe_voltage{}}; + } } + std::vector<event_generator> event_generators(cell_gid_type) const override { return {regular_generator({"tgt"}, 100.0, 0.25, 0.05)}; } }; + TEST(lif_cell_group, throw) { probe_recipe rec; auto context = make_context(); auto decomp = partition_load_balance(rec, context); - EXPECT_THROW(simulation(rec, context, decomp), bad_cell_probe); + EXPECT_NO_THROW(simulation(rec, context, decomp)); } TEST(lif_cell_group, recipe) @@ -188,12 +202,9 @@ TEST(lif_cell_group, ring) // Total simulation time. time_type simulation_time = 100; - auto context = make_context(); auto recipe = ring_recipe(num_lif_cells, weight, delay); - auto decomp = partition_load_balance(recipe, context); - // Creates a simulation with a ring recipe of lif neurons - simulation sim(recipe, context, decomp); + simulation sim(recipe); std::vector<spike> spike_buffer; @@ -221,3 +232,115 @@ TEST(lif_cell_group, ring) } } +struct Um_type { + constexpr static double delta = 1e-6; + + double t; + double u; + + friend std::ostream& operator<<(std::ostream& os, const Um_type& um) { + os << "{ " << um.t << ", " << um.u << " }"; + return os; + } + + friend bool operator==(const Um_type& lhs, const Um_type& rhs) { + return (std::abs(lhs.t - rhs.t) <= delta) + && (std::abs(lhs.u - rhs.u) <= delta); + } +}; + +TEST(lif_cell_group, probe) { + auto ums = std::unordered_map<cell_member_type, std::vector<Um_type>>{}; + auto fun = [&ums](probe_metadata pm, + std::size_t n, + const sample_record* samples) { + for (int ix = 0; ix < n; ++ix) { + const auto& [t, v] = samples[ix]; + double u = *util::any_cast<double*>(v); + ums[pm.id].push_back({t, u}); + } + }; + auto rec = probe_recipe{}; + auto sim = simulation(rec); + + sim.add_sampler(all_probes, regular_schedule(0.025), fun); + + std::vector<double> spikes; + + sim.set_global_spike_callback( + [&spikes](const std::vector<spike>& spk) { for (const auto& s: spk) spikes.push_back(s.time); } + ); + + sim.run(1.5, 0.005); + std::vector<Um_type> exp = {{0, -23}, + {0.025, -22.9425718}, + {0.05, -22.885287}, + {0.075, -22.8281453}, + {0.1, -22.7711462}, + {0.125, -22.7142894}, + {0.15, -22.6575746}, + {0.175, -22.6010014}, + {0.2, -22.5445695}, + {0.225, -22.4882785}, + {0.25, -17.432128}, + {0.275, -17.3886021}, + {0.3, -12.3451849}, + {0.325, -12.3143605}, + {0.35, -7.28361301}, + {0.375, -7.26542672}, + {0.4, -2.24728584}, + {0.425, -2.24167464}, + {0.45, 2.76392255}, + {0.475, 2.75702137}, + {0.5, 7.75013743}, + {0.525, 7.73078628}, + {0.55, -42}, + {0.575, -42}, + {0.6, -42}, + {0.625, -42}, + {0.65, -42}, + {0.675, -42}, + {0.7, -42}, + {0.725, -42}, + {0.75, -37}, + {0.775, -36.9076155}, + {0.8, -31.8154617}, + {0.825, -31.7360224}, + {0.85, -26.6567815}, + {0.875, -26.5902227}, + {0.9, -21.5238302}, + {0.925, -21.4700878}, + {0.95, -16.4164796}, + {0.975, -16.3754897}, + {1, -11.3346021}, + {1.025, -11.306301}, + {1.05, -6.27807055}, + {1.075, -6.26239498}, + {1.1, -1.24675854}, + {1.125, -1.24364554}, + {1.15, 3.75945969}, + {1.175, 3.75007278}, + {1.2, 8.74070931}, + {1.225, 8.71888483}, + {1.25, -42}, + {1.275, -42}, + {1.3, -42}, + {1.325, -42}, + {1.35, -42}, + {1.375, -42}, + {1.4, -42}, + {1.425, -42}, + {1.45, -37}, + {1.475, -36.9076155},}; + + ASSERT_TRUE(testing::seq_eq(ums[{0, 0}], exp)); + ASSERT_TRUE(testing::seq_eq(ums[{0, 1}], exp)); + // gid == 1 is different, but of same size + EXPECT_EQ((ums[{1, 0}].size()), exp.size()); + ASSERT_FALSE(testing::seq_eq(ums[{1, 0}], exp)); + // now check the spikes + std::sort(spikes.begin(), spikes.end()); + EXPECT_EQ(spikes.size(), 3); + std::vector<double> sexp{0.35, 0.55, 1.25}; + ASSERT_TRUE(testing::seq_almost_eq<double>(spikes, sexp)); +}