From 13131745410a16982d21a641cc9e13dff18ed96b Mon Sep 17 00:00:00 2001
From: thorstenhater <24411438+thorstenhater@users.noreply.github.com>
Date: Mon, 2 Nov 2020 13:03:01 +0100
Subject: [PATCH] Interface/merge cat names (#1208)

* Add interface for querying names.

* Add derived mechanisms to list, clean-up.

* Add unit tests.

* Add python bindings and docs.
---
 arbor/include/arbor/mechcat.hpp |  3 +++
 arbor/mechcat.cpp               | 13 ++++++++++
 doc/python/mechanisms.rst       |  6 +++++
 python/mechanism.cpp            |  4 +++-
 test/unit/test_mechcat.cpp      | 42 +++++++++++++++++++++++++++++++++
 5 files changed, 67 insertions(+), 1 deletion(-)

diff --git a/arbor/include/arbor/mechcat.hpp b/arbor/include/arbor/mechcat.hpp
index 0fc0a23f..436edbc2 100644
--- a/arbor/include/arbor/mechcat.hpp
+++ b/arbor/include/arbor/mechcat.hpp
@@ -110,6 +110,9 @@ public:
 
    ~mechanism_catalogue();
 
+   // Grab a collection of all mechanism names in the catalogue.
+   std::vector<std::string> mechanism_names() const;
+
 private:
     std::unique_ptr<catalogue_state> state_;
 
diff --git a/arbor/mechcat.cpp b/arbor/mechcat.cpp
index 3fac9e80..001ab95e 100644
--- a/arbor/mechcat.cpp
+++ b/arbor/mechcat.cpp
@@ -8,6 +8,7 @@
 #include <arbor/mechcat.hpp>
 #include <arbor/util/expected.hpp>
 
+#include "util/rangeutil.hpp"
 #include "util/maputil.hpp"
 
 /* Notes on implementation:
@@ -479,6 +480,14 @@ struct catalogue_state {
         return over;
     }
 
+    // Collect all mechanism names present in this catalogue
+    std::vector<std::string> mechanism_names() const {
+        std::vector<std::string> result;
+        util::assign(result, util::keys(info_map_));
+        util::append(result, util::keys(derived_map_));
+        return result;
+    }
+
     // Schemata for (un-derived) mechanisms.
     string_map<mechanism_info_ptr> info_map_;
 
@@ -495,6 +504,10 @@ mechanism_catalogue::mechanism_catalogue():
     state_(new catalogue_state)
 {}
 
+std::vector<std::string> mechanism_catalogue::mechanism_names() const {
+    return state_->mechanism_names();
+}
+
 mechanism_catalogue::mechanism_catalogue(mechanism_catalogue&& other) = default;
 mechanism_catalogue& mechanism_catalogue::operator=(mechanism_catalogue&& other) = default;
 
diff --git a/doc/python/mechanisms.rst b/doc/python/mechanisms.rst
index e31f137a..b55aa460 100644
--- a/doc/python/mechanisms.rst
+++ b/doc/python/mechanisms.rst
@@ -280,6 +280,12 @@ Mechanism catalogues
         :return: mechanism metadata
         :rtype: :class:`mechanism_info`
 
+    .. py:method:: names()
+
+        Return a list names of all the mechanisms in the catalogue.
+
+        :return: list
+
     .. py:method:: derive(name, parent, globals={}, ions={})
 
         Derive a new mechanism with *name* from the mechanism *parent*.
diff --git a/python/mechanism.cpp b/python/mechanism.cpp
index 9c65cc2d..e0fc9846 100644
--- a/python/mechanism.cpp
+++ b/python/mechanism.cpp
@@ -107,7 +107,9 @@ void register_mechanisms(pybind11::module& m) {
     cat
         .def(pybind11::init<const arb::mechanism_catalogue&>())
         .def("has", &arb::mechanism_catalogue::has,
-                "name"_a, "Is 'name' in the catalogue?")
+             "name"_a, "Is 'name' in the catalogue?")
+        .def("keys", &arb::mechanism_catalogue::mechanism_names,
+             "Return a list of all mechanisms in this catalogues.")
         .def("is_derived", &arb::mechanism_catalogue::is_derived,
                 "name"_a, "Is 'name' a derived mechanism or can it be implicitly derived?")
         .def("__getitem__",
diff --git a/test/unit/test_mechcat.cpp b/test/unit/test_mechcat.cpp
index 31526aa2..ffd6b265 100644
--- a/test/unit/test_mechcat.cpp
+++ b/test/unit/test_mechcat.cpp
@@ -244,6 +244,48 @@ TEST(mechcat, fingerprint) {
         arb::fingerprint_mismatch);
 }
 
+TEST(mechcat, names) {
+    // All names are caught; covers `add' and `derive'
+    {
+        auto cat = build_fake_catalogue();
+        auto names  = cat.mechanism_names();
+        auto expect = std::vector<std::string>{"bleeble", "burble", "fleeb", "fleeb1", "fleeb2", "fleeb3", "special_fleeb"};
+        std::sort(names.begin(), names.end());
+        EXPECT_EQ(names, expect);
+    }
+
+    // Deriving names does not add to catalogue
+    {
+        auto cat = build_fake_catalogue();
+        auto info   = cat["burble/quux=3,xyzzy=4"];
+        auto names  = cat.mechanism_names();
+        auto expect = std::vector<std::string>{"bleeble", "burble", "fleeb", "fleeb1", "fleeb2", "fleeb3", "special_fleeb"};
+        std::sort(names.begin(), names.end());
+        EXPECT_EQ(names, expect);
+    }
+
+    // Deleting a mechanism removes it and all derived from it.
+    {
+        auto cat = build_fake_catalogue();
+        cat.remove("fleeb");
+        auto names  = cat.mechanism_names();
+        auto expect = std::vector<std::string>{"bleeble", "burble"};
+        std::sort(names.begin(), names.end());
+        EXPECT_EQ(names, expect);
+    }
+
+    // Empty means empty.
+    {
+        auto cat = build_fake_catalogue();
+        cat.remove("fleeb");
+        cat.remove("burble");
+        auto names  = cat.mechanism_names();
+        auto expect = std::vector<std::string>{};
+        std::sort(names.begin(), names.end());
+        EXPECT_EQ(names, expect);
+    }
+}
+
 TEST(mechcat, derived_info) {
     auto cat = build_fake_catalogue();
 
-- 
GitLab