From a62d3e81d347cc35c7fd5f740911d804877a352b Mon Sep 17 00:00:00 2001
From: Sam Yates <yates@cscs.ch>
Date: Tue, 5 Feb 2019 16:08:32 +0100
Subject: [PATCH] Add test for correct delivery of events to individual cells
 in mc_cell_group. (#684)

* Add unit test for event delivery pathing.
* Temporarily disable GJ-specific test pending GJ PR.
---
 test/unit/CMakeLists.txt          |   1 +
 test/unit/test_event_delivery.cpp | 130 ++++++++++++++++++++++++++++++
 2 files changed, 131 insertions(+)
 create mode 100644 test/unit/test_event_delivery.cpp

diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index f64b834b..977372b9 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -63,6 +63,7 @@ set(unit_sources
     test_domain_decomposition.cpp
     test_either.cpp
     test_event_binner.cpp
+    test_event_delivery.cpp
     test_event_generators.cpp
     test_event_queue.cpp
     test_filter.cpp
diff --git a/test/unit/test_event_delivery.cpp b/test/unit/test_event_delivery.cpp
new file mode 100644
index 00000000..d521f8bf
--- /dev/null
+++ b/test/unit/test_event_delivery.cpp
@@ -0,0 +1,130 @@
+// Check that events are routed correctly to underlying cell implementations.
+//
+// Model:
+// * N identical cells, one synapse and gap junction site each.
+// * Set up dynamics so that one incoming spike generates one outgoing spike.
+// * Inject events one per cell in a given order, and confirm generated spikes
+//   are in the same order.
+
+// Note: this test anticipates the gap junction PR; the #if guards below
+// will be removed when that PR is merged.
+
+#define NO_GJ_YET
+
+#include <arbor/common_types.hpp>
+#include <arbor/domain_decomposition.hpp>
+#include <arbor/simulation.hpp>
+#include <arbor/mc_cell.hpp>
+#include <arbor/spike.hpp>
+#include <arbor/spike_event.hpp>
+
+#include "util/rangeutil.hpp"
+#include "util/transform.hpp"
+
+#include "../simple_recipes.hpp"
+#include "../gtest.h"
+
+using namespace arb;
+
+using n_mc_cell_recipe = homogeneous_recipe<cell_kind::cable1d_neuron, mc_cell>;
+
+struct test_recipe: public n_mc_cell_recipe {
+    explicit test_recipe(int n): n_mc_cell_recipe(n, test_cell()) {}
+
+    static mc_cell test_cell() {
+        mc_cell c;
+        c.add_soma(10.)->add_mechanism("pas");
+        c.add_synapse({0, 0.5}, "expsyn");
+        c.add_detector({0, 0.5}, -64);
+#ifndef NO_GJ_YET
+        c.add_gap_junction({0, 0.5});
+#endif
+        return c;
+    }
+
+    cell_size_type num_sources(cell_gid_type) const override { return 1; }
+    cell_size_type num_targets(cell_gid_type) const override { return 1; }
+};
+
+using gid_vector = std::vector<cell_gid_type>;
+using group_gids_type = std::vector<gid_vector>;
+
+std::vector<cell_gid_type> run_test_sim(const recipe& R, const group_gids_type& group_gids) {
+    arb::context ctx = make_context(proc_allocation{});
+    unsigned n = R.num_cells();
+
+    domain_decomposition D;
+    D.gid_domain = [](cell_gid_type) { return 0; };
+    D.num_domains = 1;
+    D.num_local_cells = n;
+    D.num_global_cells = n;
+
+    for (const auto& gidvec: group_gids) {
+        group_description group{cell_kind::cable1d_neuron, gidvec, backend_kind::multicore};
+        D.groups.push_back(group);
+    }
+
+    std::vector<spike> spikes;
+
+    simulation sim(R, D, ctx);
+    sim.set_global_spike_callback(
+            [&spikes](const std::vector<spike>& ss) {
+                spikes.insert(spikes.end(), ss.begin(), ss.end());
+            });
+
+    constexpr time_type ev_delta_t = 0.2;
+
+    pse_vector cell_events;
+    for (unsigned i = 0; i<n; ++i) {
+        cell_events.push_back({{i, 0u}, i*ev_delta_t, 1.f});
+    }
+
+    sim.inject_events(cell_events);
+    sim.run((n+1)*ev_delta_t, 0.01);
+
+    std::vector<cell_gid_type> spike_gids;
+    util::sort_by(spikes, [](auto s) { return s.time; });
+    util::assign(spike_gids, util::transform_view(spikes, [](auto s) { return s.source.gid; }));
+
+    return spike_gids;
+}
+
+
+TEST(mc_event_delivery, one_cell_per_group) {
+    gid_vector spike_gids = run_test_sim(test_recipe(10), {{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}});
+    EXPECT_EQ((gid_vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), spike_gids);
+}
+
+TEST(mc_event_delivery, two_interleaved_groups) {
+    gid_vector spike_gids = run_test_sim(test_recipe(10), {{0, 2, 4, 6, 8}, {1, 3, 5, 7, 9}});
+    EXPECT_EQ((gid_vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), spike_gids);
+}
+
+#ifndef NO_GJ_YET
+
+typedef std::vector<std::pair<unsigned, unsigned>> cell_gj_pairs;
+
+struct test_recipe_gj: public test_recipe {
+    explicit test_recipe_gj(int n, cell_gj_pairs gj_pairs):
+        test_recipe(n), gj_pairs_(std::move(gj_pairs)) {}
+
+    cell_size_type num_gap_junctions(cell_gid_type) const override { return 1; }
+
+    std::vector<gap_junction_connection> gap_junctions_on(cell_gid_type i) const override {
+        std::vector<gap_junction_connection> gjs;
+        for (auto p: gj_pairs_) {
+            if (p.first == i || p.second == i) gjs.push_back({{p.first, 0u}, {p.second, 0u}, 0.});
+        }
+        return gjs;
+    }
+
+    cell_gj_pairs gj_pairs_;
+};
+
+TEST(mc_event_delivery, gj_reordered) {
+    test_recipe_gj R(5, {{1u, 3u}, {2u, 4u}});
+    gid_vector spike_gids = run_test_sim(R, {{0, 1, 2, 3, 4}});
+    EXPECT_EQ((gid_vector{0, 1, 2, 3, 4}), spike_gids);
+}
+
+#endif // ndef NO_GJ_YET
-- 
GitLab