diff --git a/arbor/include/arbor/morph/locset.hpp b/arbor/include/arbor/morph/locset.hpp index 24d9217efb7664d43ae60750fa923fb1cddbed89..a815b67f05c48294eaa7506e765aaa7f31e5f437 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 dc9b7617a00ff65134abc2d1f86933c01933ad54..0e4df9c32ed6dc358831e22656b541bae7f37101 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 a74856bd58d46cd8ec0b70f0d19abe1c1cfc5741..e0ae99ac8b9c7af8b4d69e0345acd6e875218a5e 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 38111045a0ee2cb11a8ff17e5653cd95e3927525..6c261029ced2b92d0715c0db9a5ed1bd37561a15 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 4c2047d539bfb01e0a1c4073eae73d4634ad6895..443a1642de2dd0371f8918d5da234db46c4e8deb 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 a7e40e8aa8298c1f583882fd86bcd41c45d4fea2..e879c17d1a02c7a81f8749e54950fd5d7fb85fcd 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 567ec754168b0c4eb07fc1816cbe35d998cf2aa4..772103e507a15b036b8ca515619d041ebae18d65 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 ebe57bd95bd9dbdb4450c51f87286d624f1e32da..c66e85cf9c5e96d30258b4a07a911244c73594a7 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 35b0db1c25d2cc14b36388d7047a9baca9f5d896..af9ef3c697902f57956d2bfac045c9cbd2caf954 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