diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e24708e205be14c1705aee348927c035e0021ed..bbbff66024ff0dac90a0cc42c9116e918e68bd89 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,7 @@ set(BASE_SOURCES common_types_io.cpp cell.cpp + event_binner.cpp morphology.cpp parameter_list.cpp profiling/memory_meter.cpp diff --git a/src/cell_group.hpp b/src/cell_group.hpp index 50c696c3c157c2523a946c058da3649ae919ad8c..229885425818d112b626b61914a3c4e2b514ef7a 100644 --- a/src/cell_group.hpp +++ b/src/cell_group.hpp @@ -8,6 +8,7 @@ #include <algorithms.hpp> #include <cell.hpp> #include <common_types.hpp> +#include <event_binner.hpp> #include <event_queue.hpp> #include <spike.hpp> #include <util/debug.hpp> @@ -19,76 +20,6 @@ namespace nest { namespace mc { -enum class binning_kind { - none, - regular, // => round time down to multiple of binning interval. - following, // => round times down to previous event if within binning interval. -}; - -class event_binner { -public: - using time_type = spike::time_type; - - void reset() { - last_event_times_.clear(); - } - - event_binner(): policy_(binning_kind::none), bin_interval_(0) {} - - event_binner(binning_kind policy, time_type bin_interval): - policy_(policy), bin_interval_(bin_interval) - {} - - // Determine binned time for an event based on policy. - // If `t_min` is specified, the binned time will be no lower than `t_min`. - // Otherwise the returned binned time will be less than or equal to the parameter `t`, - // and within `bin_interval_`. - - time_type bin(cell_gid_type id, time_type t, time_type t_min = std::numeric_limits<time_type>::lowest()) { - time_type t_binned = t; - - switch (policy_) { - case binning_kind::none: - break; - case binning_kind::regular: - if (bin_interval_>0) { - t_binned = std::floor(t/bin_interval_)*bin_interval_; - } - break; - case binning_kind::following: - if (auto last_t = last_event_time(id)) { - if (t-*last_t<bin_interval_) { - t_binned = *last_t; - } - } - update_last_event_time(id, t_binned); - break; - default: - throw std::logic_error("unrecognized binning policy"); - } - - return std::max(t_binned, t_min); - } - -private: - binning_kind policy_; - - // Interval in which event times can be aliased. - time_type bin_interval_; - - // (Consider replacing this with a vector-backed store.) - std::unordered_map<cell_gid_type, time_type> last_event_times_; - - util::optional<time_type> last_event_time(cell_gid_type id) { - auto it = last_event_times_.find(id); - return it==last_event_times_.end()? util::nothing: util::just(it->second); - } - - void update_last_event_time(cell_gid_type id, time_type t) { - last_event_times_[id] = t; - } -}; - template <typename LoweredCell> class cell_group { public: diff --git a/src/event_binner.cpp b/src/event_binner.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6279db9b26e799387b9f3fdb580afc8a09347c3a --- /dev/null +++ b/src/event_binner.cpp @@ -0,0 +1,58 @@ +#include <algorithm> +#include <cmath> +#include <limits> +#include <stdexcept> +#include <unordered_map> + +#include <common_types.hpp> +#include <event_binner.hpp> +#include <spike.hpp> +#include <util/optional.hpp> + +namespace nest { +namespace mc { + +void event_binner::reset() { + last_event_times_.clear(); +} + +event_binner::time_type +event_binner::bin(cell_gid_type id, time_type t, time_type t_min) { + time_type t_binned = t; + + switch (policy_) { + case binning_kind::none: + break; + case binning_kind::regular: + if (bin_interval_>0) { + t_binned = std::floor(t/bin_interval_)*bin_interval_; + } + break; + case binning_kind::following: + if (auto last_t = last_event_time(id)) { + if (t-*last_t<bin_interval_) { + t_binned = *last_t; + } + } + update_last_event_time(id, t_binned); + break; + default: + throw std::logic_error("unrecognized binning policy"); + } + + return std::max(t_binned, t_min); +} + +util::optional<event_binner::time_type> +event_binner::last_event_time(cell_gid_type id) { + auto it = last_event_times_.find(id); + return it==last_event_times_.end()? util::nothing: util::just(it->second); +} + +void event_binner::update_last_event_time(cell_gid_type id, time_type t) { + last_event_times_[id] = t; +} + +} // namespace mc +} // namespace nest + diff --git a/src/event_binner.hpp b/src/event_binner.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a18b9bb0ce3f6d6fa2a14799321794b98d634e01 --- /dev/null +++ b/src/event_binner.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include <limits> +#include <unordered_map> + +#include <common_types.hpp> +#include <spike.hpp> +#include <util/optional.hpp> + +namespace nest { +namespace mc { + +enum class binning_kind { + none, + regular, // => round time down to multiple of binning interval. + following, // => round times down to previous event if within binning interval. +}; + +class event_binner { +public: + using time_type = spike::time_type; + + event_binner(): policy_(binning_kind::none), bin_interval_(0) {} + + event_binner(binning_kind policy, time_type bin_interval): + policy_(policy), bin_interval_(bin_interval) + {} + + void reset(); + + // Determine binned time for an event based on policy. + // If `t_min` is specified, the binned time will be no lower than `t_min`. + // Otherwise the returned binned time will be less than or equal to the parameter `t`, + // and within `bin_interval_`. + + time_type bin(cell_gid_type id, + time_type t, + time_type t_min = std::numeric_limits<time_type>::lowest()); + +private: + binning_kind policy_; + + // Interval in which event times can be aliased. + time_type bin_interval_; + + // (Consider replacing this with a vector-backed store.) + std::unordered_map<cell_gid_type, time_type> last_event_times_; + + util::optional<time_type> last_event_time(cell_gid_type id); + + void update_last_event_time(cell_gid_type id, time_type t); +}; + +} // namespace mc +} // namespace nest + diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 50548d733e16f30f0374478e49dc23f2da9fa5ac..eb2170e5e7601a77b862132124fa9a51e2ba7488 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -38,6 +38,7 @@ set(TEST_SOURCES test_cycle.cpp test_either.cpp test_event_queue.cpp + test_event_binner.cpp test_filter.cpp test_fvm_multi.cpp test_cell_group.cpp diff --git a/tests/unit/test_cell_group.cpp b/tests/unit/test_cell_group.cpp index af5382d740c05c0b48fb73fd74e78b2cf286e1d1..0016b24e2bc74d1edf0989c1f214d3a9c2c9476f 100644 --- a/tests/unit/test_cell_group.cpp +++ b/tests/unit/test_cell_group.cpp @@ -60,101 +60,3 @@ TEST(cell_group, sources) { } } } - -TEST(cell_group, event_binner) { - using testing::seq_almost_eq; - - std::pair<cell_gid_type, float> binning_test_data[] = { - { 11, 0.50 }, - { 12, 0.70 }, - { 14, 0.73 }, - { 11, 1.80 }, - { 12, 1.83 }, - { 11, 1.90 }, - { 11, 2.00 }, - { 14, 2.00 }, - { 11, 2.10 }, - { 14, 2.30 } - }; - - std::unordered_map<cell_gid_type, std::vector<float>> ev_times; - std::vector<float> expected; - - auto run_binner = [&](event_binner&& binner) { - ev_times.clear(); - for (auto p: binning_test_data) { - ev_times[p.first].push_back(binner.bin(p.first, p.second)); - } - }; - - run_binner(event_binner{binning_kind::none, 0}); - - EXPECT_TRUE(seq_almost_eq<float>(ev_times[11], (float []){0.50, 1.80, 1.90, 2.00, 2.10})); - EXPECT_TRUE(seq_almost_eq<float>(ev_times[12], (float []){0.70, 1.83})); - EXPECT_TRUE(ev_times[13].empty()); - EXPECT_TRUE(seq_almost_eq<float>(ev_times[14], (float []){0.73, 2.00, 2.30})); - - run_binner(event_binner{binning_kind::regular, 0.25}); - - EXPECT_TRUE(seq_almost_eq<float>(ev_times[11], (float []){0.50, 1.75, 1.75, 2.00, 2.00})); - EXPECT_TRUE(seq_almost_eq<float>(ev_times[12], (float []){0.50, 1.75})); - EXPECT_TRUE(ev_times[13].empty()); - EXPECT_TRUE(seq_almost_eq<float>(ev_times[14], (float []){0.50, 2.00, 2.25})); - - run_binner(event_binner{binning_kind::following, 0.25}); - - EXPECT_TRUE(seq_almost_eq<float>(ev_times[11], (float []){0.50, 1.80, 1.80, 1.80, 2.10})); - EXPECT_TRUE(seq_almost_eq<float>(ev_times[12], (float []){0.70, 1.83})); - EXPECT_TRUE(ev_times[13].empty()); - EXPECT_TRUE(seq_almost_eq<float>(ev_times[14], (float []){0.73, 2.00, 2.30})); -} - -TEST(cell_group, event_binner_with_min) { - using testing::seq_almost_eq; - - struct test_time { - float time; - float t_min; - }; - test_time test_data[] = { - {0.8f, 1.0f}, - {1.6f, 1.0f}, - {1.9f, 1.8f}, - {2.0f, 1.8f}, - {2.2f, 1.8f} - }; - - std::vector<float> times; - auto run_binner = [&](event_binner&& binner, bool use_min) { - times.clear(); - for (auto p: test_data) { - if (use_min) { - times.push_back(binner.bin(0, p.time, p.t_min)); - } - else { - times.push_back(binner.bin(0, p.time)); - } - } - }; - - // 'none' binning - run_binner(event_binner{binning_kind::none, 0.5}, false); - EXPECT_TRUE(seq_almost_eq<float>(times, (float []){0.8, 1.6, 1.9, 2.0, 2.2})); - - run_binner(event_binner{binning_kind::none, 0.5}, true); - EXPECT_TRUE(seq_almost_eq<float>(times, (float []){1.0, 1.6, 1.9, 2.0, 2.2})); - - // 'regular' binning - run_binner(event_binner{binning_kind::regular, 0.5}, false); - EXPECT_TRUE(seq_almost_eq<float>(times, (float []){0.5, 1.5, 1.5, 2.0, 2.0})); - - run_binner(event_binner{binning_kind::regular, 0.5}, true); - EXPECT_TRUE(seq_almost_eq<float>(times, (float []){1.0, 1.5, 1.8, 2.0, 2.0})); - - // 'following' binning - run_binner(event_binner{binning_kind::following, 0.5}, false); - EXPECT_TRUE(seq_almost_eq<float>(times, (float []){0.8, 1.6, 1.6, 1.6, 2.2})); - - run_binner(event_binner{binning_kind::following, 0.5}, true); - EXPECT_TRUE(seq_almost_eq<float>(times, (float []){1.0, 1.6, 1.8, 1.8, 2.2})); -} diff --git a/tests/unit/test_event_binner.cpp b/tests/unit/test_event_binner.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b99501e082b96c65079def7b702e9f998e39eb20 --- /dev/null +++ b/tests/unit/test_event_binner.cpp @@ -0,0 +1,105 @@ +#include "../gtest.h" + +#include <event_binner.hpp> + +#include "common.hpp" + +using namespace nest::mc; + +TEST(event_binner, basic) { + using testing::seq_almost_eq; + + std::pair<cell_gid_type, float> binning_test_data[] = { + { 11, 0.50 }, + { 12, 0.70 }, + { 14, 0.73 }, + { 11, 1.80 }, + { 12, 1.83 }, + { 11, 1.90 }, + { 11, 2.00 }, + { 14, 2.00 }, + { 11, 2.10 }, + { 14, 2.30 } + }; + + std::unordered_map<cell_gid_type, std::vector<float>> ev_times; + std::vector<float> expected; + + auto run_binner = [&](event_binner&& binner) { + ev_times.clear(); + for (auto p: binning_test_data) { + ev_times[p.first].push_back(binner.bin(p.first, p.second)); + } + }; + + run_binner(event_binner{binning_kind::none, 0}); + + EXPECT_TRUE(seq_almost_eq<float>(ev_times[11], (float []){0.50, 1.80, 1.90, 2.00, 2.10})); + EXPECT_TRUE(seq_almost_eq<float>(ev_times[12], (float []){0.70, 1.83})); + EXPECT_TRUE(ev_times[13].empty()); + EXPECT_TRUE(seq_almost_eq<float>(ev_times[14], (float []){0.73, 2.00, 2.30})); + + run_binner(event_binner{binning_kind::regular, 0.25}); + + EXPECT_TRUE(seq_almost_eq<float>(ev_times[11], (float []){0.50, 1.75, 1.75, 2.00, 2.00})); + EXPECT_TRUE(seq_almost_eq<float>(ev_times[12], (float []){0.50, 1.75})); + EXPECT_TRUE(ev_times[13].empty()); + EXPECT_TRUE(seq_almost_eq<float>(ev_times[14], (float []){0.50, 2.00, 2.25})); + + run_binner(event_binner{binning_kind::following, 0.25}); + + EXPECT_TRUE(seq_almost_eq<float>(ev_times[11], (float []){0.50, 1.80, 1.80, 1.80, 2.10})); + EXPECT_TRUE(seq_almost_eq<float>(ev_times[12], (float []){0.70, 1.83})); + EXPECT_TRUE(ev_times[13].empty()); + EXPECT_TRUE(seq_almost_eq<float>(ev_times[14], (float []){0.73, 2.00, 2.30})); +} + +TEST(event_binner, with_min) { + using testing::seq_almost_eq; + + struct test_time { + float time; + float t_min; + }; + test_time test_data[] = { + {0.8f, 1.0f}, + {1.6f, 1.0f}, + {1.9f, 1.8f}, + {2.0f, 1.8f}, + {2.2f, 1.8f} + }; + + std::vector<float> times; + auto run_binner = [&](event_binner&& binner, bool use_min) { + times.clear(); + for (auto p: test_data) { + if (use_min) { + times.push_back(binner.bin(0, p.time, p.t_min)); + } + else { + times.push_back(binner.bin(0, p.time)); + } + } + }; + + // 'none' binning + run_binner(event_binner{binning_kind::none, 0.5}, false); + EXPECT_TRUE(seq_almost_eq<float>(times, (float []){0.8, 1.6, 1.9, 2.0, 2.2})); + + run_binner(event_binner{binning_kind::none, 0.5}, true); + EXPECT_TRUE(seq_almost_eq<float>(times, (float []){1.0, 1.6, 1.9, 2.0, 2.2})); + + // 'regular' binning + run_binner(event_binner{binning_kind::regular, 0.5}, false); + EXPECT_TRUE(seq_almost_eq<float>(times, (float []){0.5, 1.5, 1.5, 2.0, 2.0})); + + run_binner(event_binner{binning_kind::regular, 0.5}, true); + EXPECT_TRUE(seq_almost_eq<float>(times, (float []){1.0, 1.5, 1.8, 2.0, 2.0})); + + // 'following' binning + run_binner(event_binner{binning_kind::following, 0.5}, false); + EXPECT_TRUE(seq_almost_eq<float>(times, (float []){0.8, 1.6, 1.6, 1.6, 2.2})); + + run_binner(event_binner{binning_kind::following, 0.5}, true); + EXPECT_TRUE(seq_almost_eq<float>(times, (float []){1.0, 1.6, 1.8, 1.8, 2.2})); +}