diff --git a/src/event_queue.hpp b/src/event_queue.hpp index 31f6a5a10cf6b0c9ff9c0ff049909b801cd25b34..2a4c7ec7a21ce58460bb8d121c4b0c50df52789b 100644 --- a/src/event_queue.hpp +++ b/src/event_queue.hpp @@ -3,18 +3,20 @@ #include <cstdint> #include <ostream> #include <queue> +#include <type_traits> #include "common_types.hpp" +#include "util/meta.hpp" #include "util/optional.hpp" namespace nest { namespace mc { -/* An event class Event must comply with the following conventions: - * Typedefs: - * time_type floating point type used to represent event times - * Member functions: - * time_type when() const return time value associated with event +/* Event classes `Event` used with `event_queue` must be move and copy constructible, + * and either have a public field `time` that returns the time value, or provide an + * overload of `event_time(const Event&)` which returns this value. + * + * Time values must be well ordered with respect to `operator>`. */ template <typename Time> @@ -24,8 +26,6 @@ struct postsynaptic_spike_event { cell_member_type target; time_type time; float weight; - - time_type when() const { return time; } }; template <typename Time> @@ -34,53 +34,58 @@ struct sample_event { std::uint32_t sampler_index; time_type time; - - time_type when() const { return time; } }; -/* Event objects must have a method event_time() which returns a value - * from a type with a total ordering with respect to <, >, etc. - */ +// Configuration point: define `event_time(ev)` for event objects `ev` +// that do not have the corresponding `time` member field. + +template <typename Event> +auto event_time(const Event& ev) -> decltype(ev.time) { + return ev.time; +} + +namespace impl { + using ::nest::mc::event_time; + + // wrap in `impl::` namespace to obtain correct ADL for return type. + template <typename Event> + using event_time_type = decltype(event_time(std::declval<Event>())); +} template <typename Event> class event_queue { public : using value_type = Event; - using time_type = typename Event::time_type; + using time_type = impl::event_time_type<Event>; - // create event_queue() {} - // push stuff - template <typename Iter> - void push(Iter b, Iter e) { - for (; b!=e; ++b) { - queue_.push(*b); - } - } - - // push thing void push(const value_type& e) { queue_.push(e); } + bool empty() const { + return size()==0; + } + std::size_t size() const { return queue_.size(); } - // pop until - util::optional<value_type> pop_if_before(time_type t_until) { - if (!queue_.empty() && queue_.top().when() < t_until) { - auto ev = queue_.top(); - queue_.pop(); - return ev; - } - else { - return util::nothing; - } + // Pop and return top event `ev` of queue if `t_until` > `event_time(ev)`. + util::optional<value_type> pop_if_before(const time_type& t_until) { + using ::nest::mc::event_time; + if (!queue_.empty() && t_until > event_time(queue_.top())) { + auto ev = queue_.top(); + queue_.pop(); + return ev; + } + else { + return util::nothing; + } } - // clear everything + // Clear queue and free storage. void clear() { queue_ = decltype(queue_){}; } @@ -88,7 +93,8 @@ public : private: struct event_greater { bool operator()(const Event& a, const Event& b) { - return a.when() > b.when(); + using ::nest::mc::event_time; + return event_time(a) > event_time(b); } }; diff --git a/tests/unit/test_event_queue.cpp b/tests/unit/test_event_queue.cpp index 81b24752401d804a5534d8b55384c3784491e485..6a6752b651bbd1538682a7f48f1535d9a06e1757 100644 --- a/tests/unit/test_event_queue.cpp +++ b/tests/unit/test_event_queue.cpp @@ -1,5 +1,6 @@ #include "../gtest.h" +#include <cmath> #include <vector> #include <event_queue.hpp> @@ -16,38 +17,12 @@ TEST(event_queue, push) q.push({{8u, 2u}, 20.f, 2.f}); q.push({{2u, 3u}, 8.f, 2.f}); - std::vector<float> times; - while(q.size()) { - times.push_back( - q.pop_if_before(std::numeric_limits<float>::max())->time - ); - } - - //std::copy(times.begin(), times.end(), std::ostream_iterator<float>(std::cout, ",")); - //std::cout << "\n"; - EXPECT_TRUE(std::is_sorted(times.begin(), times.end())); -} - -TEST(event_queue, push_range) -{ - using namespace nest::mc; - using ps_event_queue = event_queue<postsynaptic_spike_event<float>>; - - postsynaptic_spike_event<float> events[] = { - {{1u, 0u}, 2.f, 2.f}, - {{4u, 1u}, 1.f, 2.f}, - {{8u, 2u}, 20.f, 2.f}, - {{2u, 3u}, 8.f, 2.f} - }; - - ps_event_queue q; - q.push(std::begin(events), std::end(events)); + EXPECT_EQ(4u, q.size()); std::vector<float> times; - while(q.size()) { - times.push_back( - q.pop_if_before(std::numeric_limits<float>::max())->time - ); + float maxtime(INFINITY); + while (!q.empty()) { + times.push_back(q.pop_if_before(maxtime)->time); } EXPECT_TRUE(std::is_sorted(times.begin(), times.end())); @@ -73,37 +48,92 @@ TEST(event_queue, pop_if_before) }; ps_event_queue q; - q.push(std::begin(events), std::end(events)); + for (const auto& ev: events) { + q.push(ev); + } - EXPECT_EQ(q.size(), 4u); + EXPECT_EQ(4u, q.size()); auto e1 = q.pop_if_before(0.); EXPECT_FALSE(e1); - EXPECT_EQ(q.size(), 4u); + EXPECT_EQ(4u, q.size()); auto e2 = q.pop_if_before(5.); EXPECT_TRUE(e2); EXPECT_EQ(e2->target, target[0]); - EXPECT_EQ(q.size(), 3u); + EXPECT_EQ(3u, q.size()); auto e3 = q.pop_if_before(5.); EXPECT_TRUE(e3); EXPECT_EQ(e3->target, target[1]); - EXPECT_EQ(q.size(), 2u); + EXPECT_EQ(2u, q.size()); auto e4 = q.pop_if_before(2.5); EXPECT_FALSE(e4); - EXPECT_EQ(q.size(), 2u); + EXPECT_EQ(2u, q.size()); auto e5 = q.pop_if_before(5.); EXPECT_TRUE(e5); EXPECT_EQ(e5->target, target[2]); - EXPECT_EQ(q.size(), 1u); + EXPECT_EQ(1u, q.size()); q.pop_if_before(5.); - EXPECT_EQ(q.size(), 0u); + EXPECT_EQ(0u, q.size()); + EXPECT_TRUE(q.empty()); // empty queue should always return "false" auto e6 = q.pop_if_before(100.); EXPECT_FALSE(e6); } + +// Event queues can be defined for arbitrary copy-constructible events +// for which `event_time(ev)` returns the corresponding time. Time values just +// need to be well-ordered on '>'. + +struct wrapped_float { + wrapped_float() {} + wrapped_float(float f): f(f) {} + + float f; + bool operator>(wrapped_float x) const { return f>x.f; } +}; + +struct minimal_event { + wrapped_float value; + explicit minimal_event(float x): value(x) {} +}; + +const wrapped_float& event_time(const minimal_event& ev) { return ev.value; } + +TEST(event_queue, minimal_event_impl) +{ + using nest::mc::event_queue; + + minimal_event events[] = { + minimal_event(3.f), + minimal_event(2.f), + minimal_event(2.f), + minimal_event(10.f) + }; + + std::vector<float> expected; + for (const auto& ev: events) { + expected.push_back(ev.value.f); + } + std::sort(expected.begin(), expected.end()); + + event_queue<minimal_event> q; + for (auto& ev: events) { + q.push(ev); + } + + wrapped_float maxtime(INFINITY); + + std::vector<float> times; + while (q.size()) { + times.push_back(q.pop_if_before(maxtime)->value.f); + } + + EXPECT_EQ(expected, times); +} +