From 7e9d9bd08b228662ded7fef432a23e80a1eedaf0 Mon Sep 17 00:00:00 2001 From: Ben Cumming <bcumming@cscs.ch> Date: Tue, 12 May 2020 15:07:42 +0200 Subject: [PATCH] restrict locset (#1033) Add a `restrict` locset expression type that returns all locations in a locset that are in a specified region. Also allow region and locset names with hyphens. Fixes #1031 Fixes #1032 --- arbor/include/arbor/morph/locset.hpp | 3 +++ arbor/morph/locset.cpp | 34 +++++++++++++++++++++++++ python/cells.cpp | 2 +- python/morph_parse.cpp | 2 ++ python/morphology.cpp | 6 ++--- python/s_expr.cpp | 6 +++-- python/test/cpp/s_expr.cpp | 8 +++++- test/unit/morph_pred.hpp | 8 +++++- test/unit/test_morph_expr.cpp | 37 ++++++++++++++++++++++++++++ 9 files changed, 98 insertions(+), 8 deletions(-) diff --git a/arbor/include/arbor/morph/locset.hpp b/arbor/include/arbor/morph/locset.hpp index 24d9217e..a815b67f 100644 --- a/arbor/include/arbor/morph/locset.hpp +++ b/arbor/include/arbor/morph/locset.hpp @@ -142,6 +142,9 @@ locset most_distal(region reg); // Most proximal point of a region. locset most_proximal(region reg); +// Returns all locations in a locset that are also in the region. +locset restrict(locset ls, region reg); + // A range `left` to `right` of randomly selected locations with a // uniform distribution from region `reg` generated using `seed` locset uniform(region reg, unsigned left, unsigned right, uint64_t seed); diff --git a/arbor/morph/locset.cpp b/arbor/morph/locset.cpp index dc9b7617..0e4df9c3 100644 --- a/arbor/morph/locset.cpp +++ b/arbor/morph/locset.cpp @@ -348,6 +348,40 @@ std::ostream& operator<<(std::ostream& o, const lsum& x) { return o << "(sum " << x.lhs << " " << x.rhs << ")"; } +// Restrict a locset on to a region: returns all locations in the locset that +// are also in the region. + +struct lrestrict_ { + locset ls; + region reg; +}; + +mlocation_list thingify_(const lrestrict_& P, const mprovider& p) { + mlocation_list L; + + auto cables = thingify(P.reg, p).cables(); + auto ends = util::transform_view(cables, [](const auto& c){return mlocation{c.branch, c.dist_pos};}); + + for (auto l: thingify(P.ls, p)) { + auto it = std::lower_bound(ends.begin(), ends.end(), l); + if (it==ends.end()) continue; + const auto& c = cables[std::distance(ends.begin(), it)]; + if (c.branch==l.branch && c.prox_pos<=l.pos) { + L.push_back(l); + } + } + + return L; +} + +locset restrict(locset ls, region reg) { + return locset{lrestrict_{std::move(ls), std::move(reg)}}; +} + +std::ostream& operator<<(std::ostream& o, const lrestrict_& x) { + return o << "(restrict " << x.ls << " " << x.reg << ")"; +} + } // namespace ls // The intersect and join operations in the arb:: namespace with locset so that diff --git a/python/cells.cpp b/python/cells.cpp index a74856bd..e0ae99ac 100644 --- a/python/cells.cpp +++ b/python/cells.cpp @@ -529,7 +529,7 @@ void register_cells(pybind11::module& m) { .def("locations", [](arb::cable_cell& c, const char* label) {return c.concrete_locset(label);}, "label"_a, "The locations of the cell morphology for a locset label.") - .def("region", + .def("cables", [](arb::cable_cell& c, const char* label) {return c.concrete_region(label).cables();}, "label"_a, "The cable segments of the cell morphology for a region label.") // Discretization control. diff --git a/python/morph_parse.cpp b/python/morph_parse.cpp index 38111045..6c261029 100644 --- a/python/morph_parse.cpp +++ b/python/morph_parse.cpp @@ -239,6 +239,8 @@ std::unordered_multimap<std::string, evaluator> eval_map { "'on_branches' with 1 argument: (pos:double)")}, {"locset", make_call<std::string>(arb::ls::named, "'locset' with 1 argument: (name:string)")}, + {"restrict", make_call<arb::locset, arb::region>(arb::ls::restrict, + "'restrict' with 2 arguments: (ls:locset, reg:region)")}, {"join", make_fold<arb::locset>(static_cast<arb::locset(*)(arb::locset, arb::locset)>(arb::join), "'join' with at least 2 arguments: (locset locset [...locset])")}, {"sum", make_fold<arb::locset>(static_cast<arb::locset(*)(arb::locset, arb::locset)>(arb::sum), diff --git a/python/morphology.cpp b/python/morphology.cpp index 4c2047d5..443a1642 100644 --- a/python/morphology.cpp +++ b/python/morphology.cpp @@ -38,7 +38,7 @@ void register_morphology(pybind11::module& m) { " position: The relative position (from 0., proximal, to 1., distal) on the branch.\n") .def_readonly("branch", &arb::mlocation::branch, "The id of the branch.") - .def_readonly("position", &arb::mlocation::pos, + .def_readonly("pos", &arb::mlocation::pos, "The relative position on the branch (∈ [0.,1.], where 0. means proximal and 1. distal).") .def("__str__", [](arb::mlocation l) { return util::pprintf("(location {} {})", l.branch, l.pos); }) @@ -101,9 +101,9 @@ void register_morphology(pybind11::module& m) { return c; }), "branch_id"_a, "prox"_a, "dist"_a) - .def_readonly("prox_pos", &arb::mcable::prox_pos, + .def_readonly("prox", &arb::mcable::prox_pos, "The relative position of the proximal end of the cable on its branch ∈ [0,1].") - .def_readonly("dist_pos", &arb::mcable::dist_pos, + .def_readonly("dist", &arb::mcable::dist_pos, "The relative position of the distal end of the cable on its branch ∈ [0,1].") .def_readonly("branch", &arb::mcable::branch, "The id of the branch on which the cable lies.") diff --git a/python/s_expr.cpp b/python/s_expr.cpp index a7e40e8a..e879c17d 100644 --- a/python/s_expr.cpp +++ b/python/s_expr.cpp @@ -155,15 +155,17 @@ private: } // Parse alphanumeric sequence that starts with an alphabet character, - // and my contain alphabet, numeric or underscor '_' characters. + // and my contain alphabet, numeric or underscore '_' characters. // // Valid names: // sub_dendrite + // sub-dendrite // temp_ // branch3 // A // Invalid names: // _cat ; can't start with underscore + // -cat ; can't start with hyphen // 2ndvar ; can't start with numeric character // // Returns the appropriate token kind if name is a keyword. @@ -182,7 +184,7 @@ private: while(1) { c = *current_; - if(is_alphanumeric(c) || c=='_') { + if(is_alphanumeric(c) || c=='_' || c=='-') { name += character(); } else { diff --git a/python/test/cpp/s_expr.cpp b/python/test/cpp/s_expr.cpp index 567ec754..772103e5 100644 --- a/python/test/cpp/s_expr.cpp +++ b/python/test/cpp/s_expr.cpp @@ -16,15 +16,21 @@ TEST(s_expr, identifier) { EXPECT_TRUE(test_identifier("f_1__")); EXPECT_TRUE(test_identifier("A_1__")); + EXPECT_TRUE(test_identifier("A-1")); + EXPECT_TRUE(test_identifier("hello-world")); + EXPECT_TRUE(test_identifier("hello--world")); + EXPECT_TRUE(test_identifier("hello--world_")); + EXPECT_FALSE(test_identifier("_foobar")); + EXPECT_FALSE(test_identifier("-foobar")); EXPECT_FALSE(test_identifier("2dogs")); EXPECT_FALSE(test_identifier("1")); EXPECT_FALSE(test_identifier("_")); + EXPECT_FALSE(test_identifier("-")); EXPECT_FALSE(test_identifier("")); EXPECT_FALSE(test_identifier(" foo")); EXPECT_FALSE(test_identifier("foo ")); EXPECT_FALSE(test_identifier("foo bar")); - EXPECT_FALSE(test_identifier("foo-bar")); EXPECT_FALSE(test_identifier("")); } diff --git a/test/unit/morph_pred.hpp b/test/unit/morph_pred.hpp index ebe57bd9..c66e85cf 100644 --- a/test/unit/morph_pred.hpp +++ b/test/unit/morph_pred.hpp @@ -4,6 +4,7 @@ #include "../gtest.h" +#include <arbor/morph/locset.hpp> #include <arbor/morph/morphology.hpp> #include <arbor/morph/primitives.hpp> #include <arbor/morph/region.hpp> @@ -69,7 +70,7 @@ inline ::testing::AssertionResult region_eq(const arb::mprovider& p, arb::region inline ::testing::AssertionResult mlocationlist_eq(const arb::mlocation_list& as, const arb::mlocation_list& bs) { if (as.size()!=bs.size()) { return ::testing::AssertionFailure() - << "cablelists " << as << " and " << bs << " differ"; + << "mlocation lists " << as << " and " << bs << " differ"; } for (auto i: arb::util::count_along(as)) { @@ -80,5 +81,10 @@ inline ::testing::AssertionResult mlocationlist_eq(const arb::mlocation_list& as return ::testing::AssertionSuccess(); } +inline ::testing::AssertionResult locset_eq(const arb::mprovider& p, arb::locset a, arb::locset b) { + return mlocationlist_eq(thingify(a, p), thingify(b, p)); +} + + } // namespace testing diff --git a/test/unit/test_morph_expr.cpp b/test/unit/test_morph_expr.cpp index 35b0db1c..af9ef3c6 100644 --- a/test/unit/test_morph_expr.cpp +++ b/test/unit/test_morph_expr.cpp @@ -20,6 +20,7 @@ using namespace arb; using embedding = embed_pwlin; using testing::region_eq; +using testing::locset_eq; using testing::cablelist_eq; using testing::mlocationlist_eq; @@ -574,6 +575,42 @@ TEST(region, thingify_moderate_morphologies) { EXPECT_TRUE(region_eq(mp, radius_gt(reg_c_, 2), cl{{0,0.55,0.7},{2,0,0.5},{3,0.1,0.375},{3,0.9,1}})); EXPECT_TRUE(region_eq(mp, radius_gt(reg_d_, 2), cl{{0,0.55,0.7},{2,0,0.5},{3,0.1,0.375},{3,0.75,0.9}})); + // Test restriction + + { + using ll = mlocation_list; + // two empty inputs -> empty output + EXPECT_TRUE(locset_eq(mp, ls::restrict(ll{}, {}), ll{})); + // empty locset + non-empty region -> empty output + EXPECT_TRUE(locset_eq(mp, ls::restrict(ll{}, reg::all()), ll{})); + // non-empty locset + empty region -> empty output + EXPECT_TRUE(locset_eq(mp, ls::restrict(ll{{0,0.4}, {3, 0.1}}, {}), ll{})); + + ll locs{{0,0.4}, {3,0.1}}; + // none of locs in region -> empty output + EXPECT_TRUE(locset_eq(mp, ls::restrict(locs, branch(1)), ll{})); + // some but not all locs in region -> correct subset + EXPECT_TRUE(locset_eq(mp, ls::restrict(locs, branch(0)), ll{{0,0.4}})); + // all locs in region -> locs in output + EXPECT_TRUE(locset_eq(mp, ls::restrict(locs, join(branch(0), branch(3))), locs)); + // all locs in region -> locs in output + EXPECT_TRUE(locset_eq(mp, ls::restrict(locs, join(branch(0), branch(1), branch(3))), locs)); + // should also work with non-ordered input locset + std::reverse(locs.begin(), locs.end()); + EXPECT_TRUE(locset_eq(mp, ls::restrict(locs, join(branch(0), branch(3))), locs)); + + mlocation loc{1,0.5}; + // location at end of cable + EXPECT_TRUE(locset_eq(mp, ls::restrict(loc, cable(1, 0.2, 0.5)), loc)); + // location at start of cable + EXPECT_TRUE(locset_eq(mp, ls::restrict(loc, cable(1, 0.5, 0.7)), loc)); + // location in zero-length cable + EXPECT_TRUE(locset_eq(mp, ls::restrict(loc, cable(1, 0.5, 0.5)), loc)); + // location between cable end points + EXPECT_TRUE(locset_eq(mp, ls::restrict(loc, cable(1, 0.2, 0.7)), loc)); + } + + // Test some more interesting intersections and unions. // 123456789 123456789 -- GitLab