From 1b1e57053a9f49942cd5566ef119d2170c7939f4 Mon Sep 17 00:00:00 2001
From: Ben Cumming <bcumming@cscs.ch>
Date: Tue, 10 Sep 2019 16:32:22 +0200
Subject: [PATCH] Add minset operation to embedded morphology (#864)

* Add `minset` method for location lists that computes the minimal set by the partial order imposed by the morphology tree.
---
 arbor/morph/em_morphology.cpp    | 37 +++++++++++++++++++++++
 arbor/morph/em_morphology.hpp    |  2 ++
 test/unit/test_em_morphology.cpp | 52 ++++++++++++++++++++++++++++++++
 3 files changed, 91 insertions(+)

diff --git a/arbor/morph/em_morphology.cpp b/arbor/morph/em_morphology.cpp
index 6e482c7e..ba6d2751 100644
--- a/arbor/morph/em_morphology.cpp
+++ b/arbor/morph/em_morphology.cpp
@@ -1,4 +1,5 @@
 #include <mutex>
+#include <stack>
 #include <vector>
 
 #include <arbor/morph/error.hpp>
@@ -141,4 +142,40 @@ mlocation em_morphology::canonicalize(mlocation loc) const {
     return loc;
 }
 
+mlocation_list em_morphology::minset(const mlocation_list& in) const {
+    mlocation_list L;
+
+    std::stack<msize_t> stack;
+
+    // All root branches must be searched.
+    for (auto c: morph_.branch_children(mnpos)) {
+        stack.push(c);
+    }
+
+    // Depth-first traversal of the branch tree.
+    while (!stack.empty()) {
+        auto branch = stack.top();
+        stack.pop();
+
+        // Search for a location on branch.
+        auto it = std::lower_bound(in.begin(), in.end(), mlocation{branch, 0});
+
+        // If found, insert to the minset and skip the rest of this sub-tree.
+        if (it!=in.end() && it->branch==branch) {
+            L.push_back(*it);
+            continue;
+        }
+
+        // No location on this branch, so continue searching in this sub-tree.
+        for (auto c: morph_.branch_children(branch)) {
+            stack.push(c);
+        }
+    }
+
+    util::sort(L);
+
+    return L;
+}
+
 } // namespace arb
+
diff --git a/arbor/morph/em_morphology.hpp b/arbor/morph/em_morphology.hpp
index 5b2f3dc4..94aa5b0f 100644
--- a/arbor/morph/em_morphology.hpp
+++ b/arbor/morph/em_morphology.hpp
@@ -34,6 +34,8 @@ public:
     // representation of loc.
     // If include_loc is false, the input location is excluded from the result.
     mlocation_list cover(mlocation, bool include_loc=true) const;
+
+    mlocation_list minset(const mlocation_list&) const;
 };
 
 } // namespace arb
diff --git a/test/unit/test_em_morphology.cpp b/test/unit/test_em_morphology.cpp
index c724f5d1..39528837 100644
--- a/test/unit/test_em_morphology.cpp
+++ b/test/unit/test_em_morphology.cpp
@@ -187,6 +187,57 @@ TEST(em_morphology, cover) {
     }
 }
 
+TEST(em_morphology, minset) {
+    using pvec = std::vector<arb::msize_t>;
+    using svec = std::vector<arb::msample>;
+    using ll = arb::mlocation_list;
+    constexpr auto npos = arb::mnpos;
+
+    // Eight samples
+    //                  no-sphere  sphere
+    //          sample   branch    branch
+    //            0         0         0
+    //           1 3       0 1       1 2
+    //          2   4     0   1     1   2
+    //             5 6       2 3       3 4
+    //                7         3         4
+    pvec parents = {npos, 0, 1, 0, 3, 4, 4, 6};
+    svec samples = {
+        {{  0,  0,  0,  2}, 3},
+        {{ 10,  0,  0,  2}, 3},
+        {{100,  0,  0,  2}, 3},
+        {{  0, 10,  0,  2}, 3},
+        {{  0,100,  0,  2}, 3},
+        {{100,100,  0,  2}, 3},
+        {{  0,200,  0,  2}, 3},
+        {{  0,300,  0,  2}, 3},
+    };
+    arb::sample_tree sm(samples, parents);
+    {
+        arb::em_morphology em(arb::morphology(sm, false));
+
+        EXPECT_EQ((ll{}), em.minset(ll{}));
+        EXPECT_EQ((ll{{2,0.1}}), em.minset(ll{{2,0.1}}));
+        EXPECT_EQ((ll{{0,0.5}, {1,0.5}}), em.minset(ll{{0,0.5}, {1,0.5}}));
+        EXPECT_EQ((ll{{0,0.5}}), em.minset(ll{{0,0.5}}));
+        EXPECT_EQ((ll{{0,0}, {1,0}}), em.minset(ll{{0,0}, {0,0.5}, {1,0}, {1,0.5}}));
+        EXPECT_EQ((ll{{0,0}, {1,0.5}}), em.minset(ll{{0,0}, {0,0.5}, {1,0.5}, {2,0.5}}));
+        EXPECT_EQ((ll{{0,0}, {2,0.5}}), em.minset(ll{{0,0}, {0,0.5}, {2,0.5}}));
+        EXPECT_EQ((ll{{0,0}, {2,0.5}, {3,0}}), em.minset(ll{{0,0}, {0,0.5}, {2,0.5}, {3,0}, {3,1}}));
+        EXPECT_EQ((ll{{0,0}, {1,0}}), em.minset(ll{{0,0}, {0,0.5}, {1,0}, {2,0.5}, {3,0}, {3,1}}));
+    }
+    {
+        arb::em_morphology em(arb::morphology(sm, true));
+
+        EXPECT_EQ((ll{}), em.minset(ll{}));
+        EXPECT_EQ((ll{{2,0.1}}), em.minset(ll{{2,0.1}}));
+        EXPECT_EQ((ll{{0,0.5}}), em.minset(ll{{0,0.5}, {1,0.5}}));
+        EXPECT_EQ((ll{{0,0.5}}), em.minset(ll{{0,0.5}}));
+        EXPECT_EQ((ll{{0,0}}), em.minset(ll{{0,0}, {0,0.5}, {1,0}, {1,0.5}}));
+        EXPECT_EQ((ll{{1,0.5}, {3,0.1}, {4,0.5}}), em.minset(ll{{1,0.5}, {1,1}, {3,0.1}, {4,0.5}, {4,0.7}}));
+    }
+}
+
 // Test expressions that describe location sets (locset).
 TEST(locset, expressions) {
     auto root = arb::ls::root();
@@ -625,3 +676,4 @@ TEST(region, thingify) {
         EXPECT_EQ(thingify(join(lhs, rhs), em), ror);
     }
 }
+
-- 
GitLab