diff --git a/example/brunel/brunel_miniapp.cpp b/example/brunel/brunel_miniapp.cpp index ea6ca86d0821b016a77aaacc01aeeadf148c6143..37a1da79c0131f035973ad399c40358781779525 100644 --- a/example/brunel/brunel_miniapp.cpp +++ b/example/brunel/brunel_miniapp.cpp @@ -120,8 +120,8 @@ public: return cell; } - std::vector<event_generator_ptr> event_generators(cell_gid_type gid) const override { - std::vector<arb::event_generator_ptr> gens; + std::vector<event_generator> event_generators(cell_gid_type gid) const override { + std::vector<arb::event_generator> gens; std::mt19937_64 G; G.seed(gid + seed_); @@ -131,12 +131,7 @@ public: time_type t0 = 0; cell_member_type target{gid, 0}; - gens.push_back(arb::make_event_generator<pgen>( - target, - weight_ext_, - G, - t0, - lambda_)); + gens.emplace_back(pgen(target, weight_ext_, G, t0, lambda_)); return gens; } diff --git a/example/generators/event_gen.cpp b/example/generators/event_gen.cpp index df69cd2e8599d735fe2d94b32fcd05d5dac27a9f..41293bea9f14951e3fa3cf728f52fb83a313268c 100644 --- a/example/generators/event_gen.cpp +++ b/example/generators/event_gen.cpp @@ -72,7 +72,7 @@ public: } // Return two generators attached to the one cell. - std::vector<arb::event_generator_ptr> event_generators(cell_gid_type gid) const override { + std::vector<arb::event_generator> event_generators(cell_gid_type gid) const override { EXPECTS(gid==0); // There is only one cell in the model using RNG = std::mt19937_64; @@ -88,21 +88,19 @@ public: double w_i = -0.005; // Make two event generators. - std::vector<arb::event_generator_ptr> gens; + std::vector<arb::event_generator> gens; // Add excitatory generator gens.push_back( - arb::make_event_generator<pgen>( - cell_member_type{0,0}, // Target synapse (gid, local_id). - w_e, // Weight of events to deliver - RNG(29562872), // Random number generator to use - t0, // Events start being delivered from this time - lambda_e)); // Expected frequency (events per ms) + pgen(cell_member_type{0,0}, // Target synapse (gid, local_id). + w_e, // Weight of events to deliver + RNG(29562872), // Random number generator to use + t0, // Events start being delivered from this time + lambda_e)); // Expected frequency (events per ms) // Add inhibitory generator - gens.push_back( - arb::make_event_generator<pgen>( - cell_member_type{0,0}, w_i, RNG(86543891), t0, lambda_i)); + gens.emplace_back( + pgen(cell_member_type{0,0}, w_i, RNG(86543891), t0, lambda_i)); return gens; } diff --git a/example/generators/readme.md b/example/generators/readme.md index d910aadcea678e192046a49acd6a0f61a2f1c299..a18a853ab09637ea739696be01e062ef97c680e2 100644 --- a/example/generators/readme.md +++ b/example/generators/readme.md @@ -87,7 +87,7 @@ To add the event generators to the synapse, we implement the `recipe::event_gene The implementation of this with hard-coded frequencies and weights is: ```C++ - std::vector<arb::event_generator_ptr> event_generators(cell_gid_type gid) const override { + std::vector<arb::event_generator> event_generators(cell_gid_type gid) const override { // The type of random number generator to use. using RNG = std::mt19937_64; @@ -104,47 +104,33 @@ The implementation of this with hard-coded frequencies and weights is: double w_i = -0.005; // Make two event generators. - std::vector<arb::event_generator_ptr> gens; + std::vector<arb::event_generator> gens; // Add excitatory generator - gens.push_back( - arb::make_event_generator<pgen>( - cell_member_type{0,0}, // Target synapse (gid, local_id). - w_e, // Weight of events to deliver - RNG(29562872), // Random number generator to use - t0, // Events start being delivered from this time - lambda_e)); // Expected frequency (events per ms) + gens.emplace_back( + pgen(cell_member_type{0,0}, // Target synapse (gid, local_id). + w_e, // Weight of events to deliver + RNG(29562872), // Random number generator to use + t0, // Events start being delivered from this time + lambda_e)); // Expected frequency (events per ms) // Add inhibitory generator - gens.push_back( - arb::make_event_generator<pgen>( - cell_member_type{0,0}, w_i, RNG(86543891), t0, lambda_i)); + gens.emplace_back( + pgen(cell_member_type{0,0}, w_i, RNG(86543891), t0, lambda_i)); return gens; } ``` -The `recipe::event_generators(gid)` method returns a vector of `event_generator_ptr` that are attached to the cell with `gid`. - -The `event_generator_ptr` is an alias for a `unique_ptr` wrapped around an `event_generator`. - -```C++ -using event_generator_ptr = std::unique_ptr<event_generator>; -``` +The `recipe::event_generators(gid)` method returns a vector of `event_generator`s that are attached to the cell with `gid`. In the implementation, an empty vector is created, and the generators are created and `push_back`ed into the vector one after the other. -The helper function `make_event_generator` is used to simplify the process of creating an event generator and wrapping it in a `unique_ptr`. -It has the same semantics as [http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique](`std::make_unique`): - -* It takes as a template parameter the specialized type of `event_generator`, in this case `pgen`. -* Then it takes as arguments the arguments to pass to the constructor of `pgen`. - -Of the arguments passed to the Poisson event generators, the random number generator state require further explanation. +Of the arguments used to construct the Poisson event generator `pgen`, the random number generator state require further explanation. Each Poisson generator has its own private random number generator state. The initial random number state is provided on construction. -For a real world model, the state should have a seed that is some reproducable hash of `gid` and the generator id, to ensure reproducable random streams. -For this simple example, we use hard coded seeds to initialize the random number state. +For a real world model, the state should have a seed that is a hash of `gid` and the generator id, to ensure reproducable random streams. +For this simple example, we use hard-coded seeds to initialize the random number state. ### Sampling Voltages diff --git a/src/event_generator.hpp b/src/event_generator.hpp index 2a461198c9f599e297ac4d5449659250de62fbbb..60819ef951544a76bd7dd3d5834e6212dc65d785 100644 --- a/src/event_generator.hpp +++ b/src/event_generator.hpp @@ -11,38 +11,122 @@ namespace arb { +inline +postsynaptic_spike_event terminal_pse() { + return postsynaptic_spike_event{cell_member_type{0,0}, max_time, 0}; +} + // An event_generator generates a sequence of events to be delivered to a cell. // The sequence of events is always in ascending order, i.e. each event will be // greater than the event that proceded it, where events are ordered by: // - delivery time; // - then target id for events with the same delivery time; // - then weight for events with the same delivery time and target. -struct event_generator { - // Return the next event in the stream. - // Returns the same event if called multiple times without calling pop(). - virtual postsynaptic_spike_event next() = 0; +class event_generator { +public: + // + // copy, move and constructor interface + // + + event_generator(): event_generator(dummy_generator()) {} + + template <typename Impl> + event_generator(Impl&& impl): + impl_(new wrap<Impl>(std::forward<Impl>(impl))) + {} + + event_generator(event_generator&& other) = default; + event_generator& operator=(event_generator&& other) = default; + + event_generator(const event_generator& other): + impl_(other.impl_->clone()) + {} + + event_generator& operator=(const event_generator& other) { + impl_ = other.impl_->clone(); + return *this; + } + + // + // event generator interface + // + + // Get the current event in the stream. + // Does not modify the state of the stream, i.e. multiple calls to + // next() will return the same event in the absence of calls to pop(), + // advance() or reset(). + postsynaptic_spike_event next() { + return impl_->next(); + } // Move the generator to the next event in the stream. - virtual void pop() = 0; + void pop() { + impl_->pop(); + } // Reset the generator to the same state that it had on construction. - virtual void reset() = 0; + void reset() { + impl_->reset(); + } // Update state of the generator such that the event returned by next() is // the first event with delivery time >= t. - virtual void advance(time_type t) = 0; + void advance(time_type t) { + return impl_->advance(t); + } - virtual ~event_generator() {}; -}; +private: + struct interface { + virtual postsynaptic_spike_event next() = 0; + virtual void pop() = 0; + virtual void advance(time_type t) = 0; + virtual void reset() = 0; + virtual std::unique_ptr<interface> clone() = 0; + virtual ~interface() {} + }; + + std::unique_ptr<interface> impl_; + + template <typename Impl> + struct wrap: interface { + explicit wrap(const Impl& impl): wrapped(impl) {} + explicit wrap(Impl&& impl): wrapped(std::move(impl)) {} + + postsynaptic_spike_event next() override { + return wrapped.next(); + } -inline -postsynaptic_spike_event terminal_pse() { - return postsynaptic_spike_event{cell_member_type{0,0}, max_time, 0}; -} + void pop() override { + return wrapped.pop(); + } + + void advance(time_type t) override { + return wrapped.advance(t); + } + + void reset() override { + wrapped.reset(); + } + + std::unique_ptr<interface> clone() override { + return std::unique_ptr<interface>(new wrap<Impl>(wrapped)); + } + + Impl wrapped; + }; + + struct dummy_generator { + postsynaptic_spike_event next() { return terminal_pse(); } + void pop() {} + void reset() {} + void advance(time_type t) {}; + }; + +}; // Generator that feeds events that are specified with a vector. // Makes a copy of the input sequence of events. -struct vector_backed_generator: public event_generator { +struct vector_backed_generator { using pse = postsynaptic_spike_event; vector_backed_generator(pse_vector events): events_(std::move(events)), @@ -53,21 +137,21 @@ struct vector_backed_generator: public event_generator { } } - postsynaptic_spike_event next() override { + postsynaptic_spike_event next() { return it_==events_.end()? terminal_pse(): *it_; } - void pop() override { + void pop() { if (it_!=events_.end()) { ++it_; } } - void reset() override { + void reset() { it_ = events_.begin(); } - void advance(time_type t) override { + void advance(time_type t) { it_ = std::lower_bound(events_.begin(), events_.end(), t, event_time_less()); } @@ -81,7 +165,7 @@ private: // Care must be taken to avoid lifetime issues, to ensure that the generator // does not outlive the sequence. template <typename Seq> -struct seq_generator: public event_generator { +struct seq_generator { using pse = postsynaptic_spike_event; seq_generator(Seq& events): events_(events), @@ -90,21 +174,21 @@ struct seq_generator: public event_generator { EXPECTS(std::is_sorted(events_.begin(), events_.end())); } - postsynaptic_spike_event next() override { + postsynaptic_spike_event next() { return it_==events_.end()? terminal_pse(): *it_; } - void pop() override { + void pop() { if (it_!=events_.end()) { ++it_; } } - void reset() override { + void reset() { it_ = events_.begin(); } - void advance(time_type t) override { + void advance(time_type t) { it_ = std::lower_bound(events_.begin(), events_.end(), t, event_time_less()); } @@ -117,7 +201,7 @@ private: // Generates a set of regularly spaced events: // * with delivery times t=t_start+n*dt, ∀ t ∈ [t_start, t_stop) // * with a set target and weight -struct regular_generator: public event_generator { +struct regular_generator { using pse = postsynaptic_spike_event; regular_generator(cell_member_type target, @@ -133,18 +217,18 @@ struct regular_generator: public event_generator { t_stop_(tstop) {} - postsynaptic_spike_event next() override { + postsynaptic_spike_event next() { const auto t = time(); return t<t_stop_? postsynaptic_spike_event{target_, t, weight_}: terminal_pse(); } - void pop() override { + void pop() { ++step_; } - void advance(time_type t0) override { + void advance(time_type t0) { t0 = std::max(t0, t_start_); step_ = (t0-t_start_)/dt_; @@ -159,7 +243,7 @@ struct regular_generator: public event_generator { } } - void reset() override { + void reset() { step_ = 0; } @@ -179,7 +263,7 @@ private: // Generates a stream of events at times described by a Poisson point process // with rate_per_ms spikes per ms. template <typename RandomNumberEngine> -struct poisson_generator: public event_generator { +struct poisson_generator { using pse = postsynaptic_spike_event; poisson_generator(cell_member_type target, @@ -198,23 +282,23 @@ struct poisson_generator: public event_generator { reset(); } - postsynaptic_spike_event next() override { + postsynaptic_spike_event next() { return next_<t_stop_? postsynaptic_spike_event{target_, next_, weight_}: terminal_pse(); } - void pop() override { + void pop() { next_ += exp_(rng_); } - void advance(time_type t0) override { + void advance(time_type t0) { while (next_<t0) { pop(); } } - void reset() override { + void reset() { rng_ = reset_state_; next_ = t_start_; pop(); @@ -229,15 +313,7 @@ private: const time_type t_start_; const time_type t_stop_; time_type next_; - }; -using event_generator_ptr = std::unique_ptr<event_generator>; - -template <typename T, typename... Args> -event_generator_ptr make_event_generator(Args&&... args) { - return event_generator_ptr(new T(std::forward<Args>(args)...)); -} - } // namespace arb diff --git a/src/merge_events.cpp b/src/merge_events.cpp index e5cda151b3e4be3bbfc64e1cc7c1891a626fdfb7..c839dd071a8c6c6d3c2ad74855517db190a722ce 100644 --- a/src/merge_events.cpp +++ b/src/merge_events.cpp @@ -30,7 +30,7 @@ namespace impl { // unsigned is used for storing the index, because if drawing events from more // event generators than can be counted using an unsigned a complete redesign // will be needed. -tourney_tree::tourney_tree(std::vector<event_generator_ptr>& input): +tourney_tree::tourney_tree(std::vector<event_generator>& input): input_(input), n_lanes_(input_.size()) { @@ -47,7 +47,7 @@ tourney_tree::tourney_tree(std::vector<event_generator_ptr>& input): // Set the leaf nodes for (auto i=0u; i<leaves_; ++i) { heap_[leaf(i)] = i<n_lanes_? - key_val(i, input[i]->next()): + key_val(i, input[i].next()): key_val(i, terminal_pse()); // null leaf node } // Walk the tree to initialize the non-leaf nodes @@ -80,9 +80,9 @@ void tourney_tree::pop() { unsigned lane = id(0); unsigned i = leaf(lane); // draw the next event from the input lane - input_[lane]->pop(); + input_[lane].pop(); // place event the leaf node for this lane - event(i) = input_[lane]->next(); + event(i) = input_[lane].next(); // re-heapify the tree with a single walk from leaf to root while ((i=parent(i))) { @@ -144,7 +144,7 @@ unsigned tourney_tree::next_power_2(unsigned x) const { void merge_events(time_type t0, time_type t1, const pse_vector& lc, pse_vector& events, - std::vector<event_generator_ptr>& generators, + std::vector<event_generator>& generators, pse_vector& lf) { using std::distance; @@ -167,12 +167,12 @@ void merge_events(time_type t0, time_type t1, EXPECTS(generators.size()>2u); // Make an event generator with all the events in events. - generators[0] = make_event_generator<seq_generator<pse_vector>>(events); + generators[0] = seq_generator<pse_vector>(events); // Make an event generator with all the events in lc with time >= t0 auto lc_it = lower_bound(lc.begin(), lc.end(), t0, event_time_less()); auto lc_range = util::make_range(lc_it, lc.end()); - generators[1] = make_event_generator<seq_generator<decltype(lc_range)>>(lc_range); + generators[1] = seq_generator<decltype(lc_range)>(lc_range); // Perform k-way merge of all events in events, lc and the generators // that are due to be delivered in the interval [tâ‚€, tâ‚) @@ -190,6 +190,9 @@ void merge_events(time_type t0, time_type t1, const auto n = m + distance(lc_it, lc.end()) + distance(ev_it, events.end()); lf.resize(n); std::merge(ev_it, events.end(), lc_it, lc.end(), lf.begin()+m); + + // clear the generators associated with temporary event sequences + generators[0] = generators[1] = event_generator(); } else { // Handle the case where the cell has no event generators: only events diff --git a/src/merge_events.hpp b/src/merge_events.hpp index 9976d6cc655e2306610dcd47fc78049df7a96424..df1596ccd9be123688d3760dd50b2ef782042f5c 100644 --- a/src/merge_events.hpp +++ b/src/merge_events.hpp @@ -40,7 +40,7 @@ void merge_events(time_type t0, time_type t1, const pse_vector& lc, pse_vector& pending_events, - std::vector<event_generator_ptr>& generators, + std::vector<event_generator>& generators, pse_vector& lf); namespace impl { @@ -51,7 +51,7 @@ namespace impl { using key_val = std::pair<unsigned, postsynaptic_spike_event>; public: - tourney_tree(std::vector<event_generator_ptr>& input); + tourney_tree(std::vector<event_generator>& input); bool empty() const; bool empty(time_type t) const; postsynaptic_spike_event head() const; @@ -73,7 +73,7 @@ namespace impl { unsigned next_power_2(unsigned x) const; std::vector<key_val> heap_; - const std::vector<event_generator_ptr>& input_; + std::vector<event_generator>& input_; unsigned leaves_; unsigned nodes_; unsigned n_lanes_; diff --git a/src/model.cpp b/src/model.cpp index 36e8c8e1c033ec9bb6f2f69905427a797e6b2ba2..3e0dd8700f085f8ec311bf199f9b21cee6c8e26a 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -85,10 +85,8 @@ void model::reset() { // Reset all event generators, and advance to t_. for (auto& lane: event_generators_) { for (auto& gen: lane) { - if (gen) { - gen->reset(); - gen->advance(t_); - } + gen.reset(); + gen.advance(t_); } } diff --git a/src/model.hpp b/src/model.hpp index acac870322b451c2fa3bb1995f8c699d66330873..1ace8489034f2bab325a0b77a8d522efb50aaacd 100644 --- a/src/model.hpp +++ b/src/model.hpp @@ -76,7 +76,7 @@ private: std::vector<cell_group_ptr> cell_groups_; // one set of event_generators for each local cell - std::vector<std::vector<event_generator_ptr>> event_generators_; + std::vector<std::vector<event_generator>> event_generators_; using local_spike_store_type = thread_private_spike_store; util::double_buffer<local_spike_store_type> local_spikes_; diff --git a/src/recipe.hpp b/src/recipe.hpp index a8981dbc6191e0ff17f1385b4a4bd17922cbbd5f..0a193548ea637e72e4001e2543b965099b9d7aa9 100644 --- a/src/recipe.hpp +++ b/src/recipe.hpp @@ -64,7 +64,7 @@ public: virtual cell_size_type num_targets(cell_gid_type) const { return 0; } virtual cell_size_type num_probes(cell_gid_type) const { return 0; } - virtual std::vector<event_generator_ptr> event_generators(cell_gid_type) const { + virtual std::vector<event_generator> event_generators(cell_gid_type) const { return {}; } virtual std::vector<cell_connection> connections_on(cell_gid_type) const { diff --git a/tests/global_communication/test_domain_decomposition.cpp b/tests/global_communication/test_domain_decomposition.cpp index d6cef200ee086c16be70b392dd80e2804e5a5e01..5fc97e5aca52a5060b590fffa0567921454bc417 100644 --- a/tests/global_communication/test_domain_decomposition.cpp +++ b/tests/global_communication/test_domain_decomposition.cpp @@ -54,7 +54,7 @@ namespace { return {}; } - std::vector<event_generator_ptr> event_generators(cell_gid_type) const override { + std::vector<event_generator> event_generators(cell_gid_type) const override { return {}; } diff --git a/tests/unit/test_event_generators.cpp b/tests/unit/test_event_generators.cpp index 76b6c6c30976e6ee0dd4bac2747bf5b0c123b546..34afb0588216a0bbce0d7965fbdfa10fbdc8c52f 100644 --- a/tests/unit/test_event_generators.cpp +++ b/tests/unit/test_event_generators.cpp @@ -108,7 +108,7 @@ TEST(event_generator, regular_rounding) { time_type int_len = 5*dt; time_type t1 = t0 + int_len; time_type t2 = t1 + int_len; - auto gen = regular_generator(target, weight, t0, dt); + event_generator gen = regular_generator(target, weight, t0, dt); // Take the interval I_a: t ∈ [t0, t2) // And the two sub-interavls @@ -147,7 +147,7 @@ TEST(event_generators, seq) { return pse_vector(in.begin()+b, in.begin()+e); }; - seq_generator<pse_vector> gen(in); + event_generator gen = seq_generator<pse_vector>(in); // Test pop, next and reset. for (auto e: in) { diff --git a/tests/unit/test_lif_cell_group.cpp b/tests/unit/test_lif_cell_group.cpp index 8cc7f921a22892defacbbf555b34c29eaadf7401..9793533031243974fe4db6a811474fa9b215d31f 100644 --- a/tests/unit/test_lif_cell_group.cpp +++ b/tests/unit/test_lif_cell_group.cpp @@ -81,7 +81,7 @@ public: probe_info get_probe(cell_member_type probe_id) const override { return {}; } - std::vector<event_generator_ptr> event_generators(cell_gid_type) const override { + std::vector<event_generator> event_generators(cell_gid_type) const override { return {}; } @@ -134,7 +134,7 @@ public: probe_info get_probe(cell_member_type probe_id) const override { return {}; } - std::vector<event_generator_ptr> event_generators(cell_gid_type) const override { + std::vector<event_generator> event_generators(cell_gid_type) const override { return {}; } diff --git a/tests/unit/test_merge_events.cpp b/tests/unit/test_merge_events.cpp index 481f8cf9379813192b812415b3e1e0297937f9a6..d7b8d0a24b60a302c5b1d39f64ece28e106dacaf 100644 --- a/tests/unit/test_merge_events.cpp +++ b/tests/unit/test_merge_events.cpp @@ -6,7 +6,7 @@ using namespace arb; -std::vector<event_generator_ptr> empty_gens; +std::vector<event_generator> empty_gens; // Test the trivial case of merging empty sets TEST(merge_events, empty) @@ -127,10 +127,9 @@ TEST(merge_events, X) {{3, 0}, 26, 4}, }; - std::vector<event_generator_ptr> generators(2); - generators.push_back( - make_event_generator<regular_generator> - (cell_member_type{4,2}, 42.f, t0, 5)); + std::vector<event_generator> generators(2); + generators.emplace_back( + regular_generator(cell_member_type{4,2}, 42.f, t0, 5)); merge_events(t0, t1, lc, events, generators, lf); @@ -170,9 +169,9 @@ TEST(merge_events, tourney_seq) {{0, 0}, 5.5, 5}, }; - std::vector<event_generator_ptr> generators; - generators.push_back(make_event_generator<seq_generator<pse_vector>>(g1)); - generators.push_back(make_event_generator<seq_generator<pse_vector>>(g2)); + std::vector<event_generator> generators; + generators.emplace_back(seq_generator<pse_vector>(g1)); + generators.emplace_back(seq_generator<pse_vector>(g2)); impl::tourney_tree tree(generators); pse_vector lf; @@ -201,30 +200,28 @@ TEST(merge_events, tourney_poisson) time_type t0 = 0; time_type lambda = 10; // expected: tfinal*lambda=1000 events per generator - std::vector<event_generator_ptr> generators; + std::vector<event_generator> generators; for (auto i=0u; i<ngen; ++i) { cell_member_type tgt{0, i}; float weight = i; // the first and last generators have the same seed to test that sorting // of events with the same time but different weights works properly. rndgen G(i%(ngen-1)); - generators.push_back( - make_event_generator< - poisson_generator<std::mt19937_64>> - (tgt, weight, G, t0, lambda)); + generators.emplace_back( + poisson_generator<std::mt19937_64>(tgt, weight, G, t0, lambda)); } // manually generate the expected output pse_vector expected; for (auto& gen: generators) { // Push all events before tfinal in gen to the expected values. - while (gen->next().time<tfinal) { - expected.push_back(gen->next()); - gen->pop(); + while (gen.next().time<tfinal) { + expected.push_back(gen.next()); + gen.pop(); } // Reset the generator so that it is ready to generate the same // events again for the tournament tree test. - gen->reset(); + gen.reset(); } // Manually sort the expected events. util::sort(expected);