diff --git a/arbor/include/arbor/morph/place_pwlin.hpp b/arbor/include/arbor/morph/place_pwlin.hpp index 5be0c0a11d3163c9e96f5e685ecc07aed88f84ca..da278fab7804ee74546da648dd34ff572d312fbc 100644 --- a/arbor/include/arbor/morph/place_pwlin.hpp +++ b/arbor/include/arbor/morph/place_pwlin.hpp @@ -71,8 +71,19 @@ struct place_pwlin_data; struct place_pwlin { explicit place_pwlin(const morphology& m, const isometry& iso = isometry{}); + + // Any point corresponding to the location loc. mpoint at(mlocation loc) const; + // All points corresponding to the location loc. + std::vector<mpoint> all_at(mlocation loc) const; + + // A minimal set of segments or part segments whose union is coterminous with extent. + std::vector<msegment> segments(const mextent& extent) const; + + // Maximal set of segments or part segments whose union is coterminous with extent. + std::vector<msegment> all_segments(const mextent& extent) const; + private: std::shared_ptr<place_pwlin_data> data_; }; diff --git a/arbor/morph/embed_pwlin.cpp b/arbor/morph/embed_pwlin.cpp index ef5ab6500abd236f0df373bd01b30d8201cdcf0b..b46afbe2a0991d2b773e0e5bb5df7c1361a90440 100644 --- a/arbor/morph/embed_pwlin.cpp +++ b/arbor/morph/embed_pwlin.cpp @@ -6,7 +6,6 @@ #include <arbor/morph/morphology.hpp> #include <arbor/morph/primitives.hpp> -#include "morph/pwlin_common.hpp" #include "util/piecewise.hpp" #include "util/range.hpp" #include "util/rangeutil.hpp" @@ -17,6 +16,34 @@ namespace arb { using util::rat_element; +template <unsigned p, unsigned q> +using pw_ratpoly = util::pw_elements<rat_element<p, q>>; + +template <unsigned p, unsigned q> +using branch_pw_ratpoly = std::vector<pw_ratpoly<p, q>>; + +// Special case handling required for degenerate branches of length zero: +template <typename Elem> +static bool is_degenerate(const util::pw_elements<Elem>& pw) { + return pw.bounds().second==0; +} + +template <unsigned p, unsigned q> +double interpolate(const branch_pw_ratpoly<p, q>& f, unsigned bid, double pos) { + const auto& pw = f.at(bid); + if (is_degenerate(pw)) pos = 0; + + auto piece = pw(pos); + auto& bounds = piece.first; // TODO: C++17 structured binding. + auto& element = piece.second; + + if (bounds.first==bounds.second) return element[0]; + else { + double x = (pos-bounds.first)/(bounds.second-bounds.first); + return element(x); + } +} + // Length, area, and ixa are polynomial or rational polynomial functions of branch position, // continuos and monotonically increasing with respect to distance from root. // @@ -221,11 +248,11 @@ embed_pwlin::embed_pwlin(const arb::morphology& m) { } double branch_length = seg_pos.back(); - double length_scale = branch_length>0? 1./branch_length: 0; - for (auto& d: seg_pos) { - d *= length_scale; + if (branch_length!=0) { + for (auto& d: seg_pos) { + d /= branch_length; + } } - seg_pos.back() = 1; // Circumvent any rounding infelicities. for (auto d: seg_pos) { all_segment_ends_.push_back({bid, d}); @@ -243,55 +270,44 @@ embed_pwlin::embed_pwlin(const arb::morphology& m) { segment_cables_[seg.id] = mcable{bid, pos0, pos1}; } - double length_0 = parent==mnpos? 0: data_->length[parent].back().second[1]; data_->length[bid].push_back(0., 1, rat_element<1, 0>(length_0, length_0+branch_length)); double area_0 = parent==mnpos? 0: data_->area[parent].back().second[2]; double ixa_0 = parent==mnpos? 0: data_->ixa[parent].back().second[2]; - if (length_scale==0) { - // Zero-length branch? Weird, but make best show of it. - auto& s = segments.front().prox; - double r = s.radius; - double z = s.z; - data_->radius[bid].push_back(0., 1., rat_element<1, 0>(r, r)); - data_->directed_projection[bid].push_back(0., 1., rat_element<1, 0>(z-proj_shift, z-proj_shift)); - data_->area[bid].push_back(0., 1., rat_element<2, 0>(area_0, area_0, area_0)); - data_->ixa[bid].push_back(0., 1., rat_element<1, 1>(ixa_0, ixa_0, ixa_0)); + for (auto i: util::count_along(segments)) { + auto prox = segments[i].prox; + auto dist = segments[i].dist; + + double p0 = seg_pos[i]; + double p1 = seg_pos[i+1]; + + double z0 = prox.z - proj_shift; + double z1 = dist.z - proj_shift; + data_->directed_projection[bid].push_back(p0, p1, rat_element<1, 0>(z0, z1)); + + double r0 = prox.radius; + double r1 = dist.radius; + data_->radius[bid].push_back(p0, p1, rat_element<1, 0>(r0, r1)); + + double dx = (p1-p0)*branch_length; + double dr = r1-r0; + double c = pi*std::sqrt(dr*dr+dx*dx); + double area_half = area_0 + (0.75*r0+0.25*r1)*c; + double area_1 = area_0 + (r0+r1)*c; + data_->area[bid].push_back(p0, p1, rat_element<2, 0>(area_0, area_half, area_1)); + area_0 = area_1; + + // (Test for positive dx explicitly in case r0 is zero.) + double ixa_half = ixa_0 + (dx>0? dx/(pi*r0*(r0+r1)): 0); + double ixa_1 = ixa_0 + (dx>0? dx/(pi*r0*r1): 0); + data_->ixa[bid].push_back(p0, p1, rat_element<1, 1>(ixa_0, ixa_half, ixa_1)); + ixa_0 = ixa_1; } - else { - for (auto i: util::count_along(segments)) { - auto prox = segments[i].prox; - auto dist = segments[i].dist; - - double p0 = seg_pos[i]; - double p1 = seg_pos[i+1]; - if (p0==p1) continue; - - double z0 = prox.z - proj_shift; - double z1 = dist.z - proj_shift; - data_->directed_projection[bid].push_back(p0, p1, rat_element<1, 0>(z0, z1)); - - double r0 = prox.radius; - double r1 = dist.radius; - data_->radius[bid].push_back(p0, p1, rat_element<1, 0>(r0, r1)); - - double dx = (p1-p0)*branch_length; - double dr = r1-r0; - double c = pi*std::sqrt(dr*dr+dx*dx); - double area_half = area_0 + (0.75*r0+0.25*r1)*c; - double area_1 = area_0 + (r0+r1)*c; - data_->area[bid].push_back(p0, p1, rat_element<2, 0>(area_0, area_half, area_1)); - area_0 = area_1; - - double ixa_half = ixa_0 + dx/(pi*r0*(r0+r1)); - double ixa_1 = ixa_0 + dx/(pi*r0*r1); - data_->ixa[bid].push_back(p0, p1, rat_element<1, 1>(ixa_0, ixa_half, ixa_1)); - ixa_0 = ixa_1; - } - arb_assert((data_->radius[bid].size()>0)); + arb_assert((data_->radius[bid].size()>0)); + if (branch_length!=0) { arb_assert((data_->radius[bid].bounds()==std::pair<double, double>(0., 1.))); arb_assert((data_->area[bid].bounds()==std::pair<double, double>(0., 1.))); arb_assert((data_->ixa[bid].bounds()==std::pair<double, double>(0., 1.))); diff --git a/arbor/morph/place_pwlin.cpp b/arbor/morph/place_pwlin.cpp index 9fe9afdb1643e88886fc1e8820fe27cec00dcb4e..136fcf90dcec081c79b4877ec15c58f9f4830f06 100644 --- a/arbor/morph/place_pwlin.cpp +++ b/arbor/morph/place_pwlin.cpp @@ -6,7 +6,6 @@ #include <arbor/morph/place_pwlin.hpp> #include <arbor/morph/primitives.hpp> -#include "morph/pwlin_common.hpp" #include "util/piecewise.hpp" #include "util/rangeutil.hpp" #include "util/ratelem.hpp" @@ -17,18 +16,111 @@ namespace arb { using util::rat_element; struct place_pwlin_data { - branch_pw_ratpoly<1, 0> x, y, z, r; // [µm] + // Piecewise-constant indices into segment data, by branch. + std::vector<util::pw_elements<std::size_t>> segment_index; + + // Segments from segment tree, after isometry is applied. + std::vector<msegment> segments; explicit place_pwlin_data(msize_t n_branch): - x(n_branch), y(n_branch), z(n_branch), r(n_branch) + segment_index(n_branch) {} }; +static mpoint interpolate_segment(const std::pair<double, double>& bounds, const msegment& seg, double pos) { + if (bounds.first==bounds.second) { + return seg.prox; + } + else { + double u = (pos-bounds.first)/(bounds.second-bounds.first); + + util::rat_element<1, 0> x{seg.prox.x, seg.dist.x}; + util::rat_element<1, 0> y{seg.prox.y, seg.dist.y}; + util::rat_element<1, 0> z{seg.prox.z, seg.dist.z}; + util::rat_element<1, 0> r{seg.prox.radius, seg.dist.radius}; + + return {x(u), y(u), z(u), r(u)}; + } +} + +template <typename Elem> +static bool is_degenerate(const util::pw_elements<Elem>& pw) { + return pw.bounds().second==0; +} + mpoint place_pwlin::at(mlocation loc) const { - return { interpolate(data_->x, loc.branch, loc.pos), - interpolate(data_->y, loc.branch, loc.pos), - interpolate(data_->z, loc.branch, loc.pos), - interpolate(data_->r, loc.branch, loc.pos) }; + const auto& index = data_->segment_index.at(loc.branch); + double pos = is_degenerate(index)? 0: loc.pos; + + auto piece = index(pos); // Here and below, TODO: C++17 structured bindings for piece.first and second. + return interpolate_segment(piece.first, data_->segments.at(piece.second), pos); +} + +std::vector<mpoint> place_pwlin::all_at(mlocation loc) const { + std::vector<mpoint> result; + const auto& index = data_->segment_index.at(loc.branch); + double pos = is_degenerate(index)? 0: loc.pos; + + for (auto piece: util::make_range(index.equal_range(pos))) { + result.push_back(interpolate_segment(piece.first, data_->segments.at(piece.second), pos)); + } + return result; +} + +template <bool exclude_trivial> +static std::vector<msegment> extent_segments_impl(const place_pwlin_data& data, const mextent& extent) { + std::vector<msegment> result; + + for (mcable c: extent) { + const auto& index = data.segment_index.at(c.branch); + if (is_degenerate(index)) { + c.prox_pos = c.dist_pos = 0; + } + + auto b = index.equal_range(c.prox_pos).first; + auto e = index.equal_range(c.dist_pos).second; + + for (auto piece: util::make_range(b, e)) { + const auto& bounds = piece.first; + const msegment& seg = data.segments.at(piece.second); + + auto partial_bounds = bounds; + msegment partial = seg; + + if (c.prox_pos>bounds.first) { + arb_assert(c.prox_pos<=bounds.second); + partial.prox = interpolate_segment(bounds, seg, c.prox_pos); + partial_bounds.first = c.prox_pos; + } + if (c.dist_pos<bounds.second) { + arb_assert(c.dist_pos>=bounds.first); + partial.dist = interpolate_segment(bounds, seg, c.dist_pos); + partial_bounds.second = c.dist_pos; + } + + // With exclude_trivial set, skip zero-length segments if cable is non-trivial. + if (exclude_trivial && partial_bounds.first==partial_bounds.second && c.prox_pos!=c.dist_pos) { + continue; + } + + result.push_back(partial); + + // With exclude_trivial set, keep only one zero-length (partial) segment if cable is trivial. + if (exclude_trivial && c.prox_pos==c.dist_pos) { + break; + } + } + } + + return result; +} + +std::vector<msegment> place_pwlin::all_segments(const mextent& extent) const { + return extent_segments_impl<false>(*data_, extent); +} + +std::vector<msegment> place_pwlin::segments(const mextent& extent) const { + return extent_segments_impl<true>(*data_, extent); } place_pwlin::place_pwlin(const arb::morphology& m, const isometry& iso) { @@ -37,8 +129,6 @@ place_pwlin::place_pwlin(const arb::morphology& m, const isometry& iso) { if (!n_branch) return; - std::vector<double> sample_pos_on_branch; - std::vector<double> seg_pos; for (msize_t bid = 0; bid<n_branch; ++bid) { auto& segments = m.branch_segments(bid); @@ -51,35 +141,22 @@ place_pwlin::place_pwlin(const arb::morphology& m, const isometry& iso) { } double branch_length = seg_pos.back(); - double length_scale = branch_length>0? 1./branch_length: 0; - for (auto& x: seg_pos) { - x *= length_scale; + if (branch_length!=0) { + for (auto& x: seg_pos) { + x /= branch_length; + } } - if (length_scale==0) { - // Zero-length branch case? - mpoint p = iso.apply(segments[0].prox); + for (auto i: util::count_along(segments)) { + msegment seg = segments[i]; + double p0 = seg_pos[i]; + double p1 = seg_pos[i+1]; - data_->x[bid].push_back(0., 1., rat_element<1, 0>(p.x, p.x)); - data_->y[bid].push_back(0., 1., rat_element<1, 0>(p.y, p.y)); - data_->z[bid].push_back(0., 1., rat_element<1, 0>(p.z, p.z)); - data_->r[bid].push_back(0., 1., rat_element<1, 0>(p.radius, p.radius)); - } - else { - for (auto i: util::count_along(segments)) { - auto& seg = segments[i]; - double p0 = seg_pos[i]; - double p1 = seg_pos[i+1]; - if (p0==p1) continue; - - mpoint u0 = iso.apply(seg.prox); - mpoint u1 = iso.apply(seg.dist); - - data_->x[bid].push_back(p0, p1, rat_element<1, 0>(u0.x, u1.x)); - data_->y[bid].push_back(p0, p1, rat_element<1, 0>(u0.y, u1.y)); - data_->z[bid].push_back(p0, p1, rat_element<1, 0>(u0.z, u1.z)); - data_->r[bid].push_back(p0, p1, rat_element<1, 0>(u0.radius, u1.radius)); - } + seg.prox = iso.apply(seg.prox); + seg.dist = iso.apply(seg.dist); + + data_->segment_index[bid].push_back(p0, p1, data_->segments.size()); + data_->segments.push_back(seg); } } }; diff --git a/arbor/morph/pwlin_common.hpp b/arbor/morph/pwlin_common.hpp deleted file mode 100644 index 54d1d983f25b6e8c3b549d79ca37cea342dec056..0000000000000000000000000000000000000000 --- a/arbor/morph/pwlin_common.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -// Per-branch piecewise rational polynomial definitions and interpolation. - -#include <utility> -#include <vector> - -#include "util/piecewise.hpp" -#include "util/ratelem.hpp" - -namespace arb { - -template <unsigned p, unsigned q> -using pw_ratpoly = util::pw_elements<util::rat_element<p, q>>; - -template <unsigned p, unsigned q> -using branch_pw_ratpoly = std::vector<pw_ratpoly<p, q>>; - -template <unsigned p, unsigned q> -double interpolate(const pw_ratpoly<p, q>& f, double pos) { - unsigned index = f.index_of(pos); - - const auto& element = f.element(index); - std::pair<double, double> bounds = f.interval(index); - - if (bounds.first==bounds.second) return element[0]; - else { - double x = (pos-bounds.first)/(bounds.second-bounds.first); - return element(x); - } -} - -template <unsigned p, unsigned q> -double interpolate(const branch_pw_ratpoly<p, q>& f, unsigned bid, double pos) { - return interpolate(f.at(bid), pos); -} - -} // namespace arb diff --git a/arbor/util/piecewise.hpp b/arbor/util/piecewise.hpp index 4e4d42d1e7cd777a7abf27c8283524e67d7cdefa..ef067d62cdaa0e8fc64ef2b364bbdccf618f48d2 100644 --- a/arbor/util/piecewise.hpp +++ b/arbor/util/piecewise.hpp @@ -138,6 +138,7 @@ struct pw_elements { value_type front() const { return (*this)[0]; } value_type back() const { return (*this)[size()-1]; } + // Return index of right-most element whose corresponding closed interval contains x. size_type index_of(double x) const { if (empty()) return npos; @@ -146,6 +147,17 @@ struct pw_elements { else return partn.index(x); } + // Return iterator pair spanning elements whose corresponding closed intervals contain x. + std::pair<iterator, iterator> equal_range(double x) const { + auto eq = std::equal_range(vertex_.begin(), vertex_.end(), x); + + if (eq.first==vertex_.end()) return {end(), end()}; + if (eq.first>vertex_.begin()) --eq.first; + if (eq.second==vertex_.end()) --eq.second; + + return {begin()+(eq.first-vertex_.begin()), begin()+(eq.second-vertex_.begin())}; + } + value_type operator()(double x) const { size_type i = index_of(x); if (i==npos) { diff --git a/test/unit/test_morph_place.cpp b/test/unit/test_morph_place.cpp index dc7bebb5eb3a743fc05519a9e1e75cbc68fe4490..6d02a26e73cf87ed60ce10d6c3b1218f3194cda2 100644 --- a/test/unit/test_morph_place.cpp +++ b/test/unit/test_morph_place.cpp @@ -7,9 +7,9 @@ #include <arbor/morph/primitives.hpp> #include "util/piecewise.hpp" +#include "util/rangeutil.hpp" #include "../test/gtest.h" -#include "common.hpp" #include "common_cells.hpp" using namespace arb; @@ -291,3 +291,253 @@ TEST(place_pwlin, branched) { EXPECT_TRUE(mpoint_almost_eq(x_1, p_1)); EXPECT_TRUE(mpoint_almost_eq(x_2, p_2)); } + +TEST(place_pwlin, all_at) { + // One branch, two discontinguous segments. + { + mpoint p0p{0, 0, 0, 1}; + mpoint p0d{2, 3, 4, 5}; + mpoint p1p{3, 4, 5, 7}; + mpoint p1d{6, 6, 7, 8}; + + segment_tree tree; + msize_t s0 = tree.append(mnpos, p0p, p0d, 0); + msize_t s1 = tree.append(s0, p1p, p1d, 0); + + morphology m(tree); + mprovider p(m, label_dict{}); + place_pwlin place(m); + + mlocation_list end_s0 = thingify(ls::most_distal(reg::segment(s0)), p); + ASSERT_EQ(1u, end_s0.size()); + mlocation_list end_s1 = thingify(ls::most_distal(reg::segment(s1)), p); + ASSERT_EQ(1u, end_s1.size()); + + auto points_end_s1 = place.all_at(end_s1[0]); + ASSERT_EQ(1u, points_end_s1.size()); + EXPECT_TRUE(mpoint_almost_eq(p1d, points_end_s1[0])); + + auto points_end_s0 = place.all_at(end_s0[0]); + ASSERT_EQ(2u, points_end_s0.size()); + EXPECT_TRUE(mpoint_almost_eq(p0d, points_end_s0[0])); + EXPECT_TRUE(mpoint_almost_eq(p1p, points_end_s0[1])); + } + + // One branch, multiple zero-length segments at end. + { + mpoint p0p{0, 0, 0, 1}; + mpoint p0d{2, 3, 4, 5}; + mpoint p1p{3, 4, 5, 7}; + mpoint p1d = p1p; + mpoint p2p{6, 6, 7, 8}; + mpoint p2d = p2p; + + segment_tree tree; + msize_t s0 = tree.append(mnpos, p0p, p0d, 0); + msize_t s1 = tree.append(s0, p1p, p1d, 0); + (void)tree.append(s1, p2p, p2d, 0); + + morphology m(tree); + mprovider p(m, label_dict{}); + place_pwlin place(m); + + auto points_end_b0 = place.all_at(mlocation{0, 1}); + ASSERT_EQ(3u, points_end_b0.size()); + EXPECT_TRUE(mpoint_almost_eq(p0d, points_end_b0[0])); + EXPECT_TRUE(mpoint_almost_eq(p1d, points_end_b0[1])); + EXPECT_TRUE(mpoint_almost_eq(p2d, points_end_b0[2])); + } + + // Zero length branch comprising multiple zero-length segments. + // (Please don't do this.) + { + mpoint p0p{2, 3, 4, 5}; + mpoint p0d = p0p; + mpoint p1p{3, 4, 5, 7}; + mpoint p1d = p1p; + mpoint p2p{6, 6, 7, 8}; + mpoint p2d = p2p; + + segment_tree tree; + msize_t s0 = tree.append(mnpos, p0p, p0d, 0); + msize_t s1 = tree.append(s0, p1p, p1d, 0); + (void)tree.append(s1, p2p, p2d, 0); + + morphology m(tree); + mprovider p(m, label_dict{}); + place_pwlin place(m); + + auto points_begin_b0 = place.all_at(mlocation{0, 0}); + ASSERT_EQ(3u, points_begin_b0.size()); + EXPECT_TRUE(mpoint_almost_eq(p0d, points_begin_b0[0])); + EXPECT_TRUE(mpoint_almost_eq(p1d, points_begin_b0[1])); + EXPECT_TRUE(mpoint_almost_eq(p2d, points_begin_b0[2])); + + auto points_mid_b0 = place.all_at(mlocation{0, 0.5}); + ASSERT_EQ(3u, points_mid_b0.size()); + EXPECT_TRUE(mpoint_almost_eq(p0d, points_mid_b0[0])); + EXPECT_TRUE(mpoint_almost_eq(p1d, points_mid_b0[1])); + EXPECT_TRUE(mpoint_almost_eq(p2d, points_mid_b0[2])); + + auto points_end_b0 = place.all_at(mlocation{0, 1}); + ASSERT_EQ(3u, points_end_b0.size()); + EXPECT_TRUE(mpoint_almost_eq(p0d, points_end_b0[0])); + EXPECT_TRUE(mpoint_almost_eq(p1d, points_end_b0[1])); + EXPECT_TRUE(mpoint_almost_eq(p2d, points_end_b0[2])); + } +} + +TEST(place_pwlin, segments) { + // Y-shaped morphology with some discontinuous + // and zero-length segments. + + segment_tree tree; + + // root branch + + mpoint p0p{0, 0, 0, 1}; + mpoint p0d{1, 0, 0, 1}; + mpoint p1p = p0d; + mpoint p1d{2, 0, 0, 1}; + + msize_t s0 = tree.append(mnpos, p0p, p0d, 0); + msize_t s1 = tree.append(s0, p1p, p1d, 0); + + // branch A (total length 2) + + mpoint p2p{2, 0, 0, 1}; + mpoint p2d{2, 1, 0, 1}; + mpoint p3p{2, 1, 0, 0.5}; + mpoint p3d{2, 2, 0, 0.5}; + mpoint p4p{8, 9, 7, 1.5}; // some random zero-length segments on the end... + mpoint p4d = p4p; + mpoint p5p{3, 1, 3, 0.5}; + mpoint p5d = p5p; + + msize_t s2 = tree.append(s1, p2p, p2d, 0); + msize_t s3 = tree.append(s2, p3p, p3d, 0); + msize_t s4 = tree.append(s3, p4p, p4d, 0); + msize_t s5 = tree.append(s4, p5p, p5d, 0); + (void)s5; + + // branch B (total length 4) + + mpoint p6p{2, 0, 0, 1}; + mpoint p6d{2, 0, 2, 1}; + mpoint p7p{2, 0, 2, 0.5}; // a zero-length segment in the middle... + mpoint p7d = p7p; + mpoint p8p{2, 0, 3, 1}; + mpoint p8d{2, 0, 5, 1}; + + msize_t s6 = tree.append(s1, p6p, p6d, 0); + msize_t s7 = tree.append(s6, p7p, p7d, 0); + msize_t s8 = tree.append(s7, p8p, p8d, 0); + (void)s8; + + morphology m(tree); + mprovider p(m, label_dict{}); + place_pwlin place(m); + + // Thingify a segment expression to work out which branch id is A and + // which is B. + + mextent s2_extent = thingify(reg::segment(2), p); + msize_t branch_A = s2_extent.front().branch; + + mextent s6_extent = thingify(reg::segment(6), p); + msize_t branch_B = s6_extent.front().branch; + + ASSERT_TRUE((branch_A==1 && branch_B==2) || (branch_A==2 && branch_B==1)); + + // Region 1: all of branch A, middle section of branch B. + + region r1 = join(reg::branch(branch_A), reg::cable(branch_B, 0.25, 0.75)); + mextent x1 = thingify(r1, p); + + std::vector<msegment> x1min = place.segments(x1); + std::vector<msegment> x1all = place.all_segments(x1); + + auto seg_id = [](const msegment& s) { return s.id; }; + + util::sort_by(x1min, seg_id); + std::vector<msize_t> x1min_seg_ids = util::assign_from(util::transform_view(x1min, seg_id)); + + util::sort_by(x1all, seg_id); + std::vector<msize_t> x1all_seg_ids = util::assign_from(util::transform_view(x1all, seg_id)); + + ASSERT_EQ((std::vector<msize_t>{2, 3, 6, 8}), x1min_seg_ids); + ASSERT_EQ((std::vector<msize_t>{2, 3, 4, 5, 6, 7, 8}), x1all_seg_ids); + + EXPECT_TRUE(mpoint_almost_eq(p2p, x1all[0].prox)); + EXPECT_TRUE(mpoint_almost_eq(p2d, x1all[0].dist)); + + EXPECT_TRUE(mpoint_almost_eq(p3p, x1all[1].prox)); + EXPECT_TRUE(mpoint_almost_eq(p3d, x1all[1].dist)); + + EXPECT_TRUE(mpoint_almost_eq(p4p, x1all[2].prox)); + EXPECT_TRUE(mpoint_almost_eq(p4d, x1all[2].dist)); + + EXPECT_TRUE(mpoint_almost_eq(p5p, x1all[3].prox)); + EXPECT_TRUE(mpoint_almost_eq(p5d, x1all[3].dist)); + + EXPECT_FALSE(mpoint_almost_eq(p6p, x1all[4].prox)); + EXPECT_TRUE(mpoint_almost_eq(p6d, x1all[4].dist)); + EXPECT_TRUE(mpoint_almost_eq(mpoint{2, 0, 1, 1}, x1all[4].prox)); + + EXPECT_TRUE(mpoint_almost_eq(p7p, x1all[5].prox)); + EXPECT_TRUE(mpoint_almost_eq(p7d, x1all[5].dist)); + + EXPECT_TRUE(mpoint_almost_eq(p8p, x1all[6].prox)); + EXPECT_FALSE(mpoint_almost_eq(p8d, x1all[6].dist)); + EXPECT_TRUE(mpoint_almost_eq(mpoint{2, 0, 4, 1}, x1all[6].dist)); + + // Region 2: first half of branch A. Test exclusion of zero-length partial + // segments. + + region r2 = reg::cable(branch_A, 0., 0.5); + mextent x2 = thingify(r2, p); + + std::vector<msegment> x2min = place.segments(x2); + std::vector<msegment> x2all = place.all_segments(x2); + + util::sort_by(x2min, seg_id); + std::vector<msize_t> x2min_seg_ids = util::assign_from(util::transform_view(x2min, seg_id)); + + util::sort_by(x2all, seg_id); + std::vector<msize_t> x2all_seg_ids = util::assign_from(util::transform_view(x2all, seg_id)); + + ASSERT_EQ((std::vector<msize_t>{2}), x2min_seg_ids); + ASSERT_EQ((std::vector<msize_t>{2, 3}), x2all_seg_ids); + + EXPECT_TRUE(mpoint_almost_eq(p3p, x2all[1].prox)); + EXPECT_TRUE(mpoint_almost_eq(p3p, x2all[1].dist)); + + // Region 3: trivial region, midpont of branch B. + + region r3 = reg::cable(branch_B, 0.5, 0.5); + mextent x3 = thingify(r3, p); + + std::vector<msegment> x3min = place.segments(x3); + std::vector<msegment> x3all = place.all_segments(x3); + + util::sort_by(x3min, seg_id); + std::vector<msize_t> x3min_seg_ids = util::assign_from(util::transform_view(x3min, seg_id)); + + util::sort_by(x3all, seg_id); + std::vector<msize_t> x3all_seg_ids = util::assign_from(util::transform_view(x3all, seg_id)); + + ASSERT_EQ(1u, x3min_seg_ids.size()); // Could be end of s6, all of s7, or beginning of s8 + ASSERT_EQ((std::vector<msize_t>{6, 7, 8}), x3all_seg_ids); + + EXPECT_TRUE(mpoint_almost_eq(x3min[0].prox, x3min[0].dist)); + EXPECT_TRUE(mpoint_almost_eq(p6d, x3min[0].prox) || + mpoint_almost_eq(p7d, x3min[0].prox) || + mpoint_almost_eq(p8p, x3min[0].prox)); + + EXPECT_TRUE(mpoint_almost_eq(p6d, x3all[0].prox)); + EXPECT_TRUE(mpoint_almost_eq(p6d, x3all[0].dist)); + EXPECT_TRUE(mpoint_almost_eq(p7d, x3all[1].prox)); + EXPECT_TRUE(mpoint_almost_eq(p7d, x3all[1].dist)); + EXPECT_TRUE(mpoint_almost_eq(p8p, x3all[2].prox)); + EXPECT_TRUE(mpoint_almost_eq(p8p, x3all[2].dist)); +} diff --git a/test/unit/test_piecewise.cpp b/test/unit/test_piecewise.cpp index 03047be6c591964c5bdc28c9d0ac9c799de24db9..65b74361eb74186dffab9ce942ba6310a04560ff 100644 --- a/test/unit/test_piecewise.cpp +++ b/test/unit/test_piecewise.cpp @@ -231,6 +231,67 @@ TEST(piecewise, index_of) { EXPECT_EQ(pw_npos, v0.index_of(0.)); } +TEST(piecewise, equal_range) { + { + pw_elements<int> p{{1, 2, 3, 4}, {10, 9, 8}}; + + auto er0 = p.equal_range(0.0); + ASSERT_EQ(er0.first, er0.second); + + auto er1 = p.equal_range(1.0); + ASSERT_EQ(1u, er1.second-er1.first); + EXPECT_EQ(10, er1.first->second); + + auto er2 = p.equal_range(2.0); + ASSERT_EQ(2u, er2.second-er2.first); + auto iter = er2.first; + EXPECT_EQ(10, iter++->second); + EXPECT_EQ(9, iter->second); + + auto er3_5 = p.equal_range(3.5); + ASSERT_EQ(1u, er3_5.second-er3_5.first); + EXPECT_EQ(8, er3_5.first->second); + + auto er4 = p.equal_range(4.0); + ASSERT_EQ(1u, er4.second-er4.first); + EXPECT_EQ(8, er4.first->second); + + auto er5 = p.equal_range(5.0); + ASSERT_EQ(er5.first, er5.second); + } + + { + pw_elements<int> p{{1, 1, 2, 2, 2, 3, 3}, {10, 11, 12, 13, 14, 15}}; + + auto er0 = p.equal_range(0.0); + ASSERT_EQ(er0.first, er0.second); + + auto er1 = p.equal_range(1.0); + ASSERT_EQ(2u, er1.second-er1.first); + auto iter = er1.first; + EXPECT_EQ(10, iter++->second); + EXPECT_EQ(11, iter++->second); + + auto er2 = p.equal_range(2.0); + ASSERT_EQ(4u, er2.second-er2.first); + iter = er2.first; + EXPECT_EQ(11, iter++->second); + EXPECT_EQ(12, iter++->second); + EXPECT_EQ(13, iter++->second); + EXPECT_EQ(14, iter++->second); + + auto er3 = p.equal_range(3.0); + ASSERT_EQ(2u, er3.second-er3.first); + iter = er3.first; + EXPECT_EQ(14, iter++->second); + EXPECT_EQ(15, iter++->second); + + auto er5 = p.equal_range(5.0); + ASSERT_EQ(er5.first, er5.second); + } + +} + TEST(piecewise, push) { pw_elements<int> q; using dp = std::pair<double, double>;