diff --git a/arbor/include/arbor/morph/segment_tree.hpp b/arbor/include/arbor/morph/segment_tree.hpp index 0d9c6b564a..814d7d2cb0 100644 --- a/arbor/include/arbor/morph/segment_tree.hpp +++ b/arbor/include/arbor/morph/segment_tree.hpp @@ -88,5 +88,4 @@ apply(const segment_tree&, const isometry&); // Roots of regions of specific tag in segment tree ARB_ARBOR_API std::vector tag_roots(const segment_tree& in, int tag); - -} // namespace arb \ No newline at end of file +} // namespace arb diff --git a/arbor/morph/segment_tree.cpp b/arbor/morph/segment_tree.cpp index 92eb13d6fb..551e213ed1 100644 --- a/arbor/morph/segment_tree.cpp +++ b/arbor/morph/segment_tree.cpp @@ -1,4 +1,3 @@ -#include #include #include @@ -246,6 +245,5 @@ ARB_ARBOR_API std::vector tag_roots(const segment_tree& t, int tag) { return tag_roots; } - } // namespace arb diff --git a/arborio/CMakeLists.txt b/arborio/CMakeLists.txt index 5a4bae4254..6016a3417d 100644 --- a/arborio/CMakeLists.txt +++ b/arborio/CMakeLists.txt @@ -7,7 +7,8 @@ set(arborio-sources label_parse.cpp neuroml.cpp networkio.cpp - nml_parse_morphology.cpp) + nml_parse_morphology.cpp + debug.cpp) add_library(arborio ${arborio-sources}) diff --git a/arborio/debug.cpp b/arborio/debug.cpp new file mode 100644 index 0000000000..17becc18ca --- /dev/null +++ b/arborio/debug.cpp @@ -0,0 +1,99 @@ +#include + +#include + +#include +#include + +namespace arborio { + +template +std::vector render(const T& tree, + arb::msize_t root, + const std::multimap& children, + P print) { + // ASCII art elements + // TODO these could be customizable, but need conformant lengths + const std::string vline = " | "; + const std::string hline = "---"; + const std::string blank = " "; + const std::string split = "-+-"; + const std::string start = " +-"; + + auto n_child = children.count(root); + auto seg = print(root, tree); + if (0 == n_child) return {seg}; + + auto sep = std::string(seg.size(), ' '); + const auto& [beg, end] = children.equal_range(root); + + std::vector res = {seg}; + arb::msize_t cdx = 0; + for (auto it = beg; it != end; ++it) { + const auto& [parent, child] = *it; + auto rows = render(tree, child, children, print); + auto rdx = 0; + for (const auto& row: rows) { + // Append the first row directly onto our segments, this [- -] -- [- -] + if (rdx == 0) { + // The first child of a node may span a sub-tree + if (cdx == 0) { + res.back() += split + row; + } else { + // Other children get connected to the vertical line + res.push_back(sep + start + row); + } + cdx++; + } else { + // If there are more children, extend the subtree by showing a + // vertical line + res.push_back(sep + (cdx < n_child ? vline : blank) + row); + } + ++rdx; + } + } + // res.push_back(sep); + return res; +} + +ARB_ARBORIO_API std::string default_segment_printer(const arb::msize_t id, const arb::segment_tree&) { + auto lbl = (id == arb::mnpos) ? "(root)" : std::to_string(id); + return "[-- id=" + lbl + " --]" ; +} + +std::string ARB_ARBORIO_API default_branch_printer(const arb::msize_t id, const arb::morphology& mrf) { + auto lbl = (id == arb::mnpos) ? std::string("(root)") : std::to_string(id); + return "<-- id=" + std::to_string(id) + " len=" + std::to_string(mrf.branch_segments(id).size()) + " -->" ; +} + +ARB_ARBORIO_API std::string show(const arb::segment_tree& tree) { + if (tree.empty()) return ""; + + std::multimap children; + const auto& ps = tree.parents(); + for (arb::msize_t idx = 0; idx < tree.size(); ++idx) { + auto parent = ps[idx]; + children.emplace(parent, idx); + } + + auto res = render(tree, 0, children, default_segment_printer); + return std::accumulate(res.begin(), res.end(), + std::string{}, + [](auto lhs, auto rhs) { return lhs + rhs + "\n"; }); +} + +ARB_ARBORIO_API std::string show(const arb::morphology& mrf) { + if (mrf.empty()) return ""; + + std::multimap children; + for (arb::msize_t idx = 0; idx < mrf.num_branches(); ++idx) { + auto parent = mrf.branch_parent(idx); + children.emplace(parent, idx); + } + + auto res = render(mrf, 0, children, default_branch_printer); + return std::accumulate(res.begin(), res.end(), + std::string{}, + [](auto lhs, auto rhs) { return lhs + rhs + "\n"; }); +} +} diff --git a/arborio/include/arborio/debug.hpp b/arborio/include/arborio/debug.hpp new file mode 100644 index 0000000000..84faf5299d --- /dev/null +++ b/arborio/include/arborio/debug.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include +#include + +namespace arborio { +ARB_ARBORIO_API std::string show(const arb::segment_tree&); +ARB_ARBORIO_API std::string show(const arb::morphology&); +} diff --git a/doc/cpp/morphology.rst b/doc/cpp/morphology.rst index 3bf52f7f74..0de4df537a 100644 --- a/doc/cpp/morphology.rst +++ b/doc/cpp/morphology.rst @@ -31,7 +31,6 @@ consistent parent-child indexing, and with ``n`` segments numbered from ``0`` to .. cpp:class:: segment_tree - .. cpp:function:: segment_tree() Construct an empty segment tree. @@ -66,6 +65,10 @@ consistent parent-child indexing, and with ``n`` segments numbered from ``0`` to A list of the segments. +.. cpp:function:: std::string show(const arb::segment_tree&) + + Return a string representation of the tree. + .. cpp:function:: std::pair split_at(const segment_tree& t, msize_t id) Split a segment_tree into a pair of subtrees at the given id, @@ -100,9 +103,47 @@ consistent parent-child indexing, and with ``n`` segments numbered from ``0`` to Morphology API -------------- -.. todo:: +.. cpp:class:: morphology + + .. cpp:function:: morphology() + + Construct an empty morphology. + + .. cpp:function:: morphology(const segment_tree&) + + Construct a morphology from a segment tree. + + .. cpp:function:: segment_tree to_segment_tree() const + + Reconcstruct the underlying segment tree. + + .. cpp:function:: bool empty() const + + Is this the trivial morphology? + + .. cpp:function:: msize_t num_branches() const + + The number of branches in the morphology. + + .. cpp:function:: msize_t branch_parent(msize_t b) const + + The parent branch of branch ``b``. Return ``mnpos`` if branch has no parent. - Describe morphology methods. + .. cpp:function:: const std::vector& branch_children(msize_t b) const + + The child branches of branch ``b``. If b is ``mnpos``, return root branches. + + .. cpp:function:: const std::vector& terminal_branches() const + + Branches with no children. + + .. cpp:function:: const std::vector& branch_segments(msize_t b) const + + Range of segments in a branch. + +.. cpp:function:: std::string show(const arb::morphology&) + + Return a string representation of the tree underlying the morphology. .. _cppcablecell-morphology-construction: @@ -200,6 +241,28 @@ by two stitches: cable_cell cell(stitched.morphology(), dec, stitched.labels()); +Debug Ouput +----------- + +Tree representations of :cpp:type:`segment_tree` and :cpp:type:`morphology` can +be obtained by including ``arborio/debug.hpp`` which contains a series of +:cpp:func:`show` functions that return ASCII renderings of the given object. + +Example for an arbitrary segment tree + +.. code:: + + [-- id=0 --]-+-[-- id=1 --] + +-[-- id=2 --]-+-[-- id=3 --] + +-[-- id=4 --] + +and for the equivalent morphology + +.. code:: + + <-- id=0 len=1 -->-+-<-- id=1 len=1 --> + +-<-- id=2 len=1 -->-+-<-- id=3 len=1 --> + +-<-- id=4 len=1 --> .. _locsets-and-regions: diff --git a/doc/python/morphology.rst b/doc/python/morphology.rst index b649a3c580..691c4042b2 100644 --- a/doc/python/morphology.rst +++ b/doc/python/morphology.rst @@ -299,6 +299,12 @@ Cable cell morphology A list of the segments. + .. method:: show + + Return a string containing an ASCII rendering of the tree. + + :return: string + .. py:class:: morphology A *morphology* describes the geometry of a cell as unbranched cables @@ -352,6 +358,12 @@ Cable cell morphology :param int i: branch index :rtype: list[msegment] + .. method:: show + + Return a string containing an ASCII rendering of the morphology. + + :return: string + .. py:class:: place_pwlin A :class:`place_pwlin` object allows the querying of the 3-d location of locations and cables diff --git a/python/morphology.cpp b/python/morphology.cpp index 1a3c305e48..4ab870ab88 100644 --- a/python/morphology.cpp +++ b/python/morphology.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "util.hpp" #include "error.hpp" @@ -292,6 +293,9 @@ void register_morphology(py::module& m) { .def("tag_roots", [](const arb::segment_tree& t, int tag) { return arb::tag_roots(t, tag); }, "Get roots of tag region of this segment tree.") + .def("show", + [] (const arb::segment_tree& t) { return arborio::show(t); }, + "Return an ASCII representation of this segment tree.") .def("__str__", [](const arb::segment_tree& s) { return util::pprintf("", s);}); @@ -321,6 +325,9 @@ void register_morphology(py::module& m) { "i"_a, "A list of the segments in branch i, ordered from proximal to distal ends of the branch.") .def("to_segment_tree", &arb::morphology::to_segment_tree, "Convert this morphology to a segment_tree.") + .def("show", + [] (const arb::morphology& t) { return arborio::show(t); }, + "Return an ASCII representation.") .def("__str__", [](const arb::morphology& m) { return util::pprintf("", m); diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index b75a4555bc..96f7e6d328 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -149,6 +149,7 @@ set(unit_sources test_vector.cpp test_version.cpp test_v_clamp.cpp + test_debug.cpp # unit test driver test.cpp diff --git a/test/unit/test_asc.cpp b/test/unit/test_asc.cpp index 0c06fbdc40..5e0443b742 100644 --- a/test/unit/test_asc.cpp +++ b/test/unit/test_asc.cpp @@ -1,6 +1,3 @@ -#include -#include - #include #include #include diff --git a/test/unit/test_debug.cpp b/test/unit/test_debug.cpp new file mode 100644 index 0000000000..eb944e99f5 --- /dev/null +++ b/test/unit/test_debug.cpp @@ -0,0 +1,49 @@ +#include +#include + +#include + +#include + +TEST(debug_io, single) { + arb::segment_tree tree; + arb::msize_t par = arb::mnpos; + tree.append(par, {0, 0, 0, 5}, {0, 0, 10, 5}, 42); + + EXPECT_EQ("[-- id=0 --]\n", arborio::show(tree)); + EXPECT_EQ("<-- id=0 len=1 -->\n", arborio::show(arb::morphology{tree})); +} + +TEST(debug_io, fork) { + arb::segment_tree tree; + arb::msize_t par = arb::mnpos; + par = tree.append(par, {0, 0, 0, 5}, {0, 0, 10, 5}, 42); + tree.append(par, {0, 0, 10, 5}, {0, 1, 10, 5}, 23); + tree.append(par, {0, 0, 10, 5}, {0, -1, 10, 5}, 23); + + EXPECT_EQ("[-- id=0 --]-+-[-- id=1 --]\n" + " +-[-- id=2 --]\n", + arborio::show(tree)); + EXPECT_EQ("<-- id=0 len=1 -->-+-<-- id=1 len=1 -->\n" + " +-<-- id=2 len=1 -->\n", + arborio::show(arb::morphology{tree})); +} + +TEST(debug_io, complex) { + arb::segment_tree tree; + arb::msize_t lvl0 = arb::mnpos; + lvl0 = tree.append(lvl0, {0, 0, 0, 5}, {0, 0, 10, 5}, 42); + tree.append(lvl0, {0, 0, 10, 5}, {0, 1, 10, 5}, 23); + auto lvl1 = tree.append(lvl0, {0, 0, 10, 5}, {0, -1, 10, 5}, 23); + tree.append(lvl1, {0, -1, 10, 5}, { 1, -1, 10, 5}, 23); + tree.append(lvl1, {0, -1, 10, 5}, {-1, -1, 10, 5}, 23); + + EXPECT_EQ("[-- id=0 --]-+-[-- id=1 --]\n" + " +-[-- id=2 --]-+-[-- id=3 --]\n" + " +-[-- id=4 --]\n", + arborio::show(tree)); + EXPECT_EQ("<-- id=0 len=1 -->-+-<-- id=1 len=1 -->\n" + " +-<-- id=2 len=1 -->-+-<-- id=3 len=1 -->\n" + " +-<-- id=4 len=1 -->\n", + arborio::show(arb::morphology{tree})); +}