diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp index d99e43e5bdeba33439f044cd2c3aa2efe657c16c..4a619cf9a087dc7fa151ee9613adc6d5ff6357c4 100644 --- a/miniapp/miniapp.cpp +++ b/miniapp/miniapp.cpp @@ -1,4 +1,6 @@ #include <iostream> +#include <fstream> +#include <sstream> #include <cell.hpp> #include <cell_group.hpp> @@ -10,6 +12,7 @@ #include "profiling/profiler.hpp" #include "communication/communicator.hpp" #include "communication/global_policy.hpp" +#include "util/optional.hpp" using namespace nest; @@ -23,6 +26,8 @@ using global_policy = nest::mc::communication::global_policy; using communicator_type = mc::communication::communicator<global_policy>; +using nest::mc::util::optional; + struct model { communicator_type communicator; std::vector<cell_group> cell_groups; @@ -92,6 +97,63 @@ struct model { // TODO : only stored here because init_communicator() and update_gids() are split std::vector<id_type> source_map; std::vector<id_type> target_map; + + // traces from probes + struct trace_data { + using sample_type = std::pair<float,double>; + std::string name; + index_type id; + std::vector<sample_type> samples; + }; + + // different traces may be written to by different threads; + // during simulation, each trace_sampler will be responsible for its + // corresponding element in the traces vector. + + std::vector<trace_data> traces; + + // make a sampler that records to traces + struct simple_sampler_functor { + std::vector<trace_data> &traces_; + size_t trace_index_ = 0; + float requested_sample_time_ = 0; + float dt_ = 0; + + simple_sampler_functor(std::vector<trace_data> &traces, size_t index, float dt) : + traces_(traces), trace_index_(index), dt_(dt) {} + + optional<float> operator()(float t, double v) { + traces_[trace_index_].samples.push_back({t,v}); + return requested_sample_time_ += dt_; + } + }; + + mc::sampler make_simple_sampler(index_type probe_gid, const std::string name, + index_type id, float dt) + { + traces.push_back(trace_data{name, id}); + return {probe_gid, simple_sampler_functor(traces, traces.size()-1, dt)}; + } + + void reset_traces() { + // do not call during simulation: thread-unsafe access to traces. + traces.clear(); + } + + void dump_traces() { + // do not call during simulation: thread-unsafe access to traces. + for (const auto& trace: traces) { + std::stringstream path; + path << "trace_" << trace.id << "_" << trace.name << ".dat"; + + std::ofstream file(path.str()); + file << "time\t" << trace.name << "\n"; + for (const auto& sample: trace.samples) { + file << sample.first << "\t" << sample.second << "\n"; + } + file.close(); + } + } }; // define some global model parameters @@ -166,6 +228,7 @@ int main(int argc, char** argv) { if (!id) { std::cout << "there were " << m.communicator.num_spikes() << " spikes\n"; } + m.dump_traces(); #ifdef SPLAT if (!global_policy::id()) { @@ -254,7 +317,23 @@ void all_to_all_model(nest::mc::io::options& opt, model& m) { // m.init_communicator(); - mc::util::profiler_enter("setup", "connections"); + // monitor soma and dendrite on a few cells + float sample_dt = 0.1; + index_type monitor_group_gids[] = { 0, 1, 2 }; + for (auto gid : monitor_group_gids) { + if (!m.communicator.is_local_group(gid)) { + continue; + } + + auto lid = m.communicator.group_lid(gid); + auto probe_soma = m.cell_groups[lid].probe_gid_range().first; + auto probe_dend = probe_soma+1; + + m.cell_groups[lid].add_sampler(m.make_simple_sampler(probe_soma, "vsoma", gid, sample_dt)); + m.cell_groups[lid].add_sampler(m.make_simple_sampler(probe_dend, "vdend", gid, sample_dt)); + } + + mc::util::profiler_enter("setup", "connections"); // lid is local cell/group id for (auto lid=0u; lid<ncell_local; ++lid) { auto target = m.communicator.target_gid_from_group_lid(lid); @@ -271,7 +350,7 @@ void all_to_all_model(nest::mc::io::options& opt, model& m) { } m.communicator.construct(); - mc::util::profiler_leave(2); + mc::util::profiler_leave(2); m.update_gids(); } @@ -323,6 +402,14 @@ mc::cell make_cell(int compartments_per_segment, int num_synapses) { cell.add_synapse({1, 0.5}); } + // add probes: + auto probe_soma = cell.add_probe({0, 0}, mc::cell::membrane_voltage); + auto probe_dendrite = cell.add_probe({1, 0.5}, mc::cell::membrane_voltage); + + EXPECTS(probe_soma==0); + EXPECTS(probe_dendrite==1); + (void)probe_soma, (void)probe_dendrite; + return cell; } diff --git a/src/cell.hpp b/src/cell.hpp index 0e13e588d42f7d213a7aa6f4928f1d043f95e07d..f53fb0592c94d76c3dfcd7552814345fde28f874 100644 --- a/src/cell.hpp +++ b/src/cell.hpp @@ -40,13 +40,15 @@ int find_compartment_index( /// high-level abstract representation of a cell and its segments class cell { - public: +public: // types using index_type = int; using value_type = double; using point_type = point<value_type>; + enum probe_sort { membrane_voltage, membrane_current }; + // constructor cell(); @@ -139,7 +141,18 @@ class cell { return spike_detectors_; } - private: + ////////////////// + // probes + ////////////////// + index_type add_probe(segment_location loc, enum probe_sort sort) { + probes_.push_back({loc, sort}); + return probes_.size()-1; + } + + const std::vector<std::pair<segment_location, enum probe_sort>>& + probes() const { return probes_; } + +private: // storage for connections std::vector<index_type> parents_; @@ -155,6 +168,9 @@ class cell { // the sensors std::vector<std::pair<segment_location, double>> spike_detectors_; + + // the probes + std::vector<std::pair<segment_location, enum probe_sort>> probes_; }; // Checks that two cells have the same diff --git a/src/cell_group.hpp b/src/cell_group.hpp index 4964ad10a2328c96593f9e6b07b136baa7ca7fcd..922b7b197091af6cf4ba7ecc345030bcff3e380e 100644 --- a/src/cell_group.hpp +++ b/src/cell_group.hpp @@ -1,5 +1,6 @@ #pragma once +#include <cstdint> #include <vector> #include <cell.hpp> @@ -12,10 +13,21 @@ namespace nest { namespace mc { +// samplers take a time and sample value, and return an optional time +// for the next desired sample. + +struct sampler { + using index_type = int; + using time_type = float; + using value_type = double; + + index_type probe_gid; // samplers are attached to probes + std::function<util::optional<time_type>(time_type, value_type)> sample; +}; + template <typename Cell> class cell_group { - public : - +public: using index_type = uint32_t; using cell_type = Cell; using value_type = typename cell_type::value_type; @@ -52,6 +64,18 @@ class cell_group { first_target_gid_ = lid; } + index_type num_probes() const { + return cell_.num_probes(); + } + + void set_probe_gids(index_type gid) { + first_probe_gid_ = gid; + } + + std::pair<index_type, index_type> probe_gid_range() const { + return { first_probe_gid_, first_probe_gid_+cell_.num_probes() }; + } + #ifdef SPLAT void splat(std::string fname) { char buffer[128]; @@ -65,6 +89,20 @@ class cell_group { void advance(double tfinal, double dt) { while (cell_.time()<tfinal) { + // take any pending samples + float cell_time = cell_.time(); + while (auto m = sample_events_.pop_if_before(cell_time)) { + auto &sampler = samplers_[m->sampler_index]; + EXPECTS((bool)sampler.sample); + + index_type probe_index = sampler.probe_gid-first_probe_gid_; + auto next = sampler.sample(cell_.time(), cell_.probe(probe_index)); + if (next) { + m->time = std::max(*next, cell_time); + sample_events_.push(*m); + } + } + #ifdef SPLAT tt.push_back(cell_.time()); vs.push_back(cell_.voltage({0,0.0})); @@ -126,7 +164,13 @@ class cell_group { spikes_.clear(); } - private : + void add_sampler(const sampler &s, float start_time = 0) { + unsigned sampler_index = samplers_.size(); + samplers_.push_back(s); + sample_events_.push({sampler_index, start_time}); + } + +private: #ifdef SPLAT // REMOVE as soon as we have a better way to probe cell state @@ -145,10 +189,19 @@ class cell_group { std::vector<communication::spike<index_type>> spikes_; /// pending events to be delivered - event_queue events_; + event_queue<postsynaptic_spike_event> events_; + + /// pending samples to be taken + event_queue<sample_event> sample_events_; /// the global id of the first target (e.g. a synapse) in this group index_type first_target_gid_; + + /// the global id of the first probe in this group + index_type first_probe_gid_; + + /// collection of samplers to be run against probes in this group + std::vector<sampler> samplers_; }; } // namespace mc diff --git a/src/communication/communicator.hpp b/src/communication/communicator.hpp index 517a2f6c3884c66df22914776849fcfd2da7e136..34185ac0fbd795294211ced7190d8b84a3e70874 100644 --- a/src/communication/communicator.hpp +++ b/src/communication/communicator.hpp @@ -90,11 +90,17 @@ public: } id_type target_lid(id_type gid) { - EXPECTS(is_local_group(gid)); + EXPECTS(is_local_group(gid)); return gid - target_gid_map_[domain_id()]; } + id_type group_lid(id_type gid) { + EXPECTS(is_local_group(gid)); + + return gid - group_gid_map_[domain_id()]; + } + // builds the optimized data structure void construct() { if (!std::is_sorted(connections_.begin(), connections_.end())) { @@ -188,7 +194,7 @@ public: return communication_policy_.size(); } - const std::vector<local_event>& queue(int i) const { + const std::vector<postsynaptic_spike_event>& queue(int i) const { return events_[i]; } @@ -234,7 +240,7 @@ private: local_spike_store_type thread_spikes_; std::vector<connection> connections_; - std::vector<std::vector<nest::mc::local_event>> events_; + std::vector<std::vector<postsynaptic_spike_event>> events_; // local target group i has targets in the half open range // [target_map_[i], target_map_[i+1]) diff --git a/src/communication/connection.hpp b/src/communication/connection.hpp index 528c57b59fddbfc4b7f3c96772c5c0c04685758b..ee1acfd72dc21cffe01f28239bca29526366df31 100644 --- a/src/communication/connection.hpp +++ b/src/communication/connection.hpp @@ -25,7 +25,7 @@ public: id_type source() const { return source_; } id_type destination() const { return destination_; } - local_event make_event(spike<id_type> s) { + postsynaptic_spike_event make_event(spike<id_type> s) { return {destination_, s.time + delay_, weight_}; } diff --git a/src/event_queue.hpp b/src/event_queue.hpp index 989e34873a1f3ff851d0a3a9f85276cea9d9bf7a..7f3034f3f08bbb2235bf457c95e8eedfe8d0895d 100644 --- a/src/event_queue.hpp +++ b/src/event_queue.hpp @@ -9,22 +9,31 @@ namespace nest { namespace mc { -struct local_event { +struct postsynaptic_spike_event { uint32_t target; float time; float weight; }; -inline bool operator < (local_event const& l, local_event const& r) { - return l.time < r.time; -} +inline float event_time(const postsynaptic_spike_event &ev) { return ev.time; } -inline bool operator > (local_event const& l, local_event const& r) { - return l.time > r.time; -} +struct sample_event { + uint32_t sampler_index; + float time; +}; +inline float event_time(const sample_event &ev) { return ev.time; } + +/* Event objects must have a method event_time() which returns a value + * from a type with a total ordering with respect to <, >, etc. + */ + +template <typename Event> class event_queue { public : + using value_type = Event; + using time_type = decltype(event_time(std::declval<Event>())); + // create event_queue() {} @@ -37,7 +46,7 @@ public : } // push thing - void push(local_event e) { + void push(const value_type &e) { queue_.push(e); } @@ -46,8 +55,8 @@ public : } // pop until - util::optional<local_event> pop_if_before(float t_until) { - if (!queue_.empty() && queue_.top().time < t_until) { + util::optional<value_type> pop_if_before(time_type t_until) { + if (!queue_.empty() && event_time(queue_.top()) < t_until) { auto ev = queue_.top(); queue_.pop(); return ev; @@ -58,10 +67,16 @@ public : } private: + struct event_greater { + bool operator()(const Event &a, const Event &b) { + return event_time(a) > event_time(b); + } + }; + std::priority_queue< - local_event, - std::vector<local_event>, - std::greater<local_event> + Event, + std::vector<Event>, + event_greater > queue_; }; @@ -69,7 +84,7 @@ private: } // namespace mc inline -std::ostream& operator<< (std::ostream& o, nest::mc::local_event e) +std::ostream& operator<< (std::ostream& o, const nest::mc::postsynaptic_spike_event& e) { return o << "event[" << e.target << "," << e.time << "," << e.weight << "]"; } diff --git a/src/fvm_cell.hpp b/src/fvm_cell.hpp index c89dcdf0c3e920b4aa370f71a48767f4e20d1040..947b7a7ba233340274f5113c12d075d7851a1ea1 100644 --- a/src/fvm_cell.hpp +++ b/src/fvm_cell.hpp @@ -29,7 +29,7 @@ namespace fvm { template <typename T, typename I> class fvm_cell { - public : +public: fvm_cell() = default; @@ -116,7 +116,7 @@ class fvm_cell { void advance(value_type dt); /// pass an event to the appropriate synapse and call net_receive - void apply_event(local_event e) { + void apply_event(postsynaptic_spike_event e) { mechanisms_[synapse_index_]->net_receive(e.target, e.weight); } @@ -135,7 +135,14 @@ class fvm_cell { value_type time() const { return t_; } - private: + value_type probe(uint32_t i) const { + auto p = probes_[i]; + return (this->*p.first)[p.second]; + } + + std::size_t num_probes() const { return probes_.size(); } + +private: /// current time value_type t_ = value_type{0}; @@ -179,8 +186,7 @@ class fvm_cell { std::vector<std::pair<uint32_t, i_clamp>> stimulii_; - /// event queue - event_queue events_; + std::vector<std::pair<const vector_type fvm_cell::*, uint32_t>> probes_; }; //////////////////////////////////////////////////////////////////////////////// @@ -397,6 +403,21 @@ fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) synapse_index_ = mechanisms_.size()-1; // don't forget to give point processes access to cv_areas_ mechanisms_[synapse_index_]->set_areas(cv_areas_); + + // record probe locations by index into corresponding state vector + for (auto probe : cell.probes()) { + uint32_t comp = find_compartment_index(probe.first, graph); + switch (probe.second) { + case nest::mc::cell::membrane_voltage: + probes_.push_back({&fvm_cell::voltage_, comp}); + break; + case nest::mc::cell::membrane_current: + probes_.push_back({&fvm_cell::current_, comp}); + break; + default: + throw std::logic_error("unrecognized probe sort"); + } + } } template <typename T, typename I> diff --git a/tests/test_event_queue.cpp b/tests/test_event_queue.cpp index e67ade0bea22a417ca417a78c550b67bb0c3e45b..5ac4c75747d761638b0bbd4b90a9b6fed6fa3c18 100644 --- a/tests/test_event_queue.cpp +++ b/tests/test_event_queue.cpp @@ -7,8 +7,9 @@ TEST(event_queue, push) { using namespace nest::mc; + using ps_event_queue = event_queue<postsynaptic_spike_event>; - event_queue q; + ps_event_queue q; q.push({1u, 2.f, 2.f}); q.push({4u, 1.f, 2.f}); @@ -30,15 +31,16 @@ TEST(event_queue, push) TEST(event_queue, push_range) { using namespace nest::mc; + using ps_event_queue = event_queue<postsynaptic_spike_event>; - local_event events[] = { + postsynaptic_spike_event events[] = { {1u, 2.f, 2.f}, {4u, 1.f, 2.f}, {8u, 20.f, 2.f}, {2u, 8.f, 2.f} }; - event_queue q; + ps_event_queue q; q.push(std::begin(events), std::end(events)); std::vector<float> times; @@ -54,15 +56,16 @@ TEST(event_queue, push_range) TEST(event_queue, pop_if_before) { using namespace nest::mc; + using ps_event_queue = event_queue<postsynaptic_spike_event>; - local_event events[] = { + postsynaptic_spike_event events[] = { {1u, 1.f, 2.f}, {2u, 2.f, 2.f}, {3u, 3.f, 2.f}, {4u, 4.f, 2.f} }; - event_queue q; + ps_event_queue q; q.push(std::begin(events), std::end(events)); EXPECT_EQ(q.size(), 4u); diff --git a/tests/test_optional.cpp b/tests/test_optional.cpp index 6755525af3cfb70d610f29dc5fc0c525f68856be..9c6f35cfa658ab1c45cee53250eaa747d89dea25 100644 --- a/tests/test_optional.cpp +++ b/tests/test_optional.cpp @@ -282,7 +282,7 @@ TEST(optionalm,conversion) { TEST(optionalm,or_operator) { optional<const char *> default_msg="default"; - auto x=nullptr | default_msg; + auto x=(char *)0 | default_msg; EXPECT_TRUE((bool)x); EXPECT_STREQ("default",x.get());