diff --git a/src/algorithms.hpp b/src/algorithms.hpp
index 7be1406ac7581df6924c146e6b86aaa8201755bb..94e198cb4da61d9e86e1b9424645b1ad739b113b 100644
--- a/src/algorithms.hpp
+++ b/src/algorithms.hpp
@@ -6,8 +6,9 @@
 #include <type_traits>
 #include <vector>
 
-#include "util.hpp"
-#include "util/debug.hpp"
+#include <util.hpp>
+#include <util/debug.hpp>
+#include <util/meta.hpp>
 
 /*
  * Some simple wrappers around stl algorithms to improve readability of code
@@ -23,18 +24,18 @@ namespace mc {
 namespace algorithms {
 
 template <typename C>
-typename C::value_type
+typename util::sequence_traits<C>::value_type
 sum(C const& c)
 {
-    using value_type = typename C::value_type;
-    return std::accumulate(c.begin(), c.end(), value_type{0});
+    using value_type = typename util::sequence_traits<C>::value_type;
+    return std::accumulate(std::begin(c), std::end(c), value_type{0});
 }
 
 template <typename C>
-typename C::value_type
+typename util::sequence_traits<C>::value_type
 mean(C const& c)
 {
-    return sum(c)/c.size();
+    return sum(c)/util::size(c);
 }
 
 template <typename C>
diff --git a/src/compartment.hpp b/src/compartment.hpp
index 083155ffb7c5a3ea0d9d089740b6d7a3345ae234..db548fa39b7f4958b6575449a3734004d10ae687 100644
--- a/src/compartment.hpp
+++ b/src/compartment.hpp
@@ -4,8 +4,12 @@
 #include <utility>
 
 #include <common_types.hpp>
+#include <math.hpp>
 #include <util/counter.hpp>
+#include <util/iterutil.hpp>
+#include <util/partition.hpp>
 #include <util/span.hpp>
+#include "util/rangeutil.hpp"
 #include <util/transform.hpp>
 
 namespace nest {
@@ -79,6 +83,206 @@ compartment_range<size_type, real_type> make_compartment_range(
         compartment_maker<size_type, real_type>(num_compartments, length, radius_L, radius_R));
 }
 
+/// Divided compartments for use with (e.g.) fvm control volume setup
+
+struct semi_compartment {
+    using value_type = double;
+    using value_pair = std::pair<value_type, value_type>;
+
+    value_type length;
+    value_type area;
+    value_type volume;
+    value_pair radii;
+
+    semi_compartment& operator+=(const semi_compartment& x) {
+        length += x.length;
+        area += x.area;
+        volume += x.volume;
+        radii.second = x.radii.second;
+        return *this;
+    }
+
+    static semi_compartment frustrum(value_type l, value_type r1, value_type r2) {
+        using namespace math;
+        return semi_compartment{
+            l,
+            area_frustrum(l, r1, r2),
+            volume_frustrum(l, r1, r2),
+            {r1, r2}
+        };
+    }
+};
+
+struct div_compartment {
+    using value_type = typename semi_compartment::value_type;
+    using value_pair = typename semi_compartment::value_pair;
+    using size_type = cell_local_size_type;
+
+    size_type index = 0;
+    semi_compartment left;
+    semi_compartment right;
+
+    div_compartment() = default;
+    div_compartment(size_type i, semi_compartment l, semi_compartment r):
+        index(i), left(std::move(l)), right(std::move(r))
+    {}
+
+    value_type length() const { return left.length+right.length; }
+    value_type area() const { return left.area+right.area; }
+    value_type volume() const { return left.volume+right.volume; }
+    value_pair radii() const { return {left.radii.first, right.radii.second}; }
+};
+
+/// Divided compartments can be made from cables with sub-segments by 
+/// sampling or integrating or by approximating cable as a single frustrum.
+
+class div_compartment_by_ends {
+public:
+    using value_type = div_compartment::value_type;
+    using size_type = div_compartment::size_type;
+
+    // `lengths` must be a sequence of length at least one.
+    // `radii` must be a sequence of length `size(lengths)+1`.
+    template <typename Radii, typename Lengths>
+    div_compartment_by_ends(size_type n, const Radii& radii, const Lengths& lengths):
+        oon_(1/value_type(n)),
+        length_(algorithms::sum(lengths)),
+        ra_(util::front(radii)),
+        rb_(util::back(radii))
+    {}
+
+    div_compartment operator()(size_type i) const {
+        value_type r1 = math::lerp(ra_, rb_, i*oon_);
+        value_type rc = math::lerp(ra_, rb_, (i+0.5)*oon_);
+        value_type r2 = math::lerp(ra_, rb_, (i+1)*oon_);
+        value_type semilength = length_*oon_*0.5;
+
+        return div_compartment(
+            i,
+            semi_compartment::frustrum(semilength, r1, rc),
+            semi_compartment::frustrum(semilength, rc, r2)
+        );
+    }
+
+private:
+    value_type oon_;
+    value_type length_;
+    value_type ra_;
+    value_type rb_;
+};
+
+class div_compartment_sampler {
+public:
+    using value_type = div_compartment::value_type;
+    using size_type = div_compartment::size_type;
+
+    // `lengths` must be a sequence of length at least one.
+    // `radii` must be a sequence of length `size(lengths)+1`.
+    template <typename Radii, typename Lengths>
+    div_compartment_sampler(size_type n, const Radii& radii, const Lengths& lengths) {
+        // set up offset-to-subsegment lookup and interpolation
+        using namespace util;
+
+        segs_ = make_partition(offsets_, lengths);
+        nseg_ = size(segs_);
+        scale_ = segs_.bounds().second/n;
+        assign(radii_, radii);
+        EXPECTS(size(radii_)==size(offsets_));
+    }
+
+    div_compartment operator()(size_type i) const {
+        using namespace math;
+
+        auto r1 = radius_at(locate(scale_*i));
+        auto rc = radius_at(locate(scale_*(i+0.5)));
+        auto r2 = radius_at(locate(scale_*(i+1)));
+
+        value_type semilength = 0.5*scale_;
+        return div_compartment(
+            i,
+            semi_compartment::frustrum(semilength, r1, rc),
+            semi_compartment::frustrum(semilength, rc, r2)
+        );
+    }
+
+protected:
+    struct sub_segment_index {
+        size_type i;   // index
+        value_type p;  // proportion [0,1] along sub-segment
+
+        sub_segment_index(size_type i_, value_type p_): i(i_), p(p_) {}
+        bool operator<(sub_segment_index x) const {
+            return i<x.i || (i==x.i && p<x.p);
+        }
+    };
+
+    sub_segment_index locate(value_type x) const {
+        EXPECTS(x>=0);
+
+        auto i = segs_.index(x);
+        if (i==segs_.npos) {
+            i = nseg_-1;
+        }
+
+        auto seg = segs_[i];
+        if (x>=seg.second) {
+            return sub_segment_index(i, 1);
+        }
+        return sub_segment_index(i, (x-seg.first)/(seg.second-seg.first));
+    }
+
+    value_type radius_at(sub_segment_index s) const {
+        return math::lerp(radii_[s.i], radii_[s.i+1], s.p);
+    }
+
+    size_type nseg_ = 0;
+    std::vector<value_type> offsets_;
+    std::vector<value_type> radii_;
+    value_type scale_ = 0;
+    decltype(util::partition_view(offsets_)) segs_;
+};
+
+/// Overrides operator() with a more accurate method
+class div_compartment_integrator: public div_compartment_sampler {
+public:
+    template <typename Radii, typename Lengths>
+    div_compartment_integrator(size_type n, const Radii& radii, const Lengths& lengths):
+        div_compartment_sampler(n, radii, lengths) {}
+
+    div_compartment operator()(size_type i) const {
+        using namespace math;
+
+        auto sleft = locate(scale_*i);
+        auto scentre = locate(scale_*(i+0.5));
+        auto sright = locate(scale_*(i+1));
+
+        return div_compartment(i, integrate(sleft, scentre), integrate(scentre, sright));
+    }
+
+protected:
+    semi_compartment sub_segment_frustrum(sub_segment_index a, sub_segment_index b) const {
+        EXPECTS(a.i==b.i && a.p<=b.p);
+
+        auto seg = segs_[a.i];
+        auto l = (b.p-a.p)*(seg.second-seg.first);
+        return semi_compartment::frustrum(l, radius_at(a), radius_at(b));
+    }
+
+    semi_compartment integrate(sub_segment_index a, sub_segment_index b) const {
+        sub_segment_index x = std::min(b, sub_segment_index(a.i, 1));
+
+        auto s = sub_segment_frustrum(a, x);
+        while (a.i<b.i) {
+            ++a.i;
+            a.p = 0;
+            x = std::min(b, sub_segment_index(a.i, 1));
+            s += sub_segment_frustrum(a, x);
+        }
+
+        return s;
+    }
+};
+
 } // namespace mc
 } // namespace nest
 
diff --git a/src/segment.hpp b/src/segment.hpp
index eedb5095f89166deb5f71735da28d0a74a3ab0cb..dd6e8fee88dc1cf9a2ab12896a1760d6a933a95b 100644
--- a/src/segment.hpp
+++ b/src/segment.hpp
@@ -3,13 +3,13 @@
 #include <cmath>
 #include <vector>
 
-#include "algorithms.hpp"
-#include "common_types.hpp"
-#include "compartment.hpp"
-#include "math.hpp"
-#include "parameter_list.hpp"
-#include "point.hpp"
-#include "util.hpp"
+#include <algorithms.hpp>
+#include <common_types.hpp>
+#include <compartment.hpp>
+#include <math.hpp>
+#include <parameter_list.hpp>
+#include <point.hpp>
+#include <util.hpp>
 
 namespace nest {
 namespace mc {
@@ -379,6 +379,11 @@ public:
         return lengths_;
     }
 
+    std::vector<value_type> const& radii() const
+    {
+        return radii_;
+    }
+
     cable_segment* as_cable() override
     {
         return this;
@@ -465,6 +470,18 @@ segment_ptr make_segment(Args&&... args)
     return segment_ptr(new SegmentType(std::forward<Args>(args)...));
 }
 
+/// Divided compartment adaptors for cable segments
+
+template <typename DivCompClass>
+DivCompClass div_compartments(const cable_segment* cable, unsigned ncomp) {
+    return DivCompClass(ncomp, cable->radii(), cable->lengths());
+}
+
+template <typename DivCompClass>
+DivCompClass div_compartments(const cable_segment* cable) {
+    return DivCompClass(cable->num_compartments(), cable->radii(), cable->lengths());
+}
+
 } // namespace mc
 } // namespace nest
 
diff --git a/tests/unit/test_compartments.cpp b/tests/unit/test_compartments.cpp
index 8bc034ade553675ac8f85ce19bc8bfccf5d14d5d..8701c2716f6fe40b44e0c2832a198bf1c8ca4f3b 100644
--- a/tests/unit/test_compartments.cpp
+++ b/tests/unit/test_compartments.cpp
@@ -1,12 +1,19 @@
 #include <cmath>
+#include <string>
 
 #include "gtest.h"
 
+#include <algorithms.hpp>
 #include <compartment.hpp>
+#include <math.hpp>
 #include <util.hpp>
+#include <util/span.hpp>
+#include <util/transform.hpp>
 
-using nest::mc::util::left;
-using nest::mc::util::right;
+using namespace nest::mc;
+using namespace nest::mc::algorithms;
+using namespace nest::mc::math;
+using namespace nest::mc::util;
 
 // not much to test here: just test that values passed into the constructor
 // are correctly stored in members
@@ -59,3 +66,250 @@ TEST(compartments, make_compartment_range)
     auto rng_empty = make_compartment_range(0, 1.0, 1.0, 0.);
     EXPECT_EQ(rng_empty.begin(), rng_empty.end());
 }
+
+// Divided compartments
+// (FVM-friendly compartment data)
+
+template <std::size_t N>
+struct pw_cable_data {
+    double radii[N+1];
+    double lengths[N];
+
+    static constexpr std::size_t nseg() { return N; }
+    double r1() const { return radii[0]; }
+    double r2() const { return radii[N]; }
+    double length() const { return sum(lengths); }
+
+    double area() const {
+        return sum(transform_view(make_span(0, N),
+            [&](unsigned i) { return area_frustrum(lengths[i], radii[i], radii[i+1]); }));
+    }
+
+    double volume() const {
+        return sum(transform_view(make_span(0, N),
+            [&](unsigned i) { return volume_frustrum(lengths[i], radii[i], radii[i+1]); }));
+    }
+};
+
+pw_cable_data<1> cable_one = {
+    {2.0, 5.0},
+    {10.0}
+};
+
+pw_cable_data<4> cable_linear = {
+    {2.0, 3.5, 6.0, 6.5, 6.75},
+    {3.0, 5.0, 1.0, 0.5}
+};
+
+pw_cable_data<4> cable_jumble = {
+    {2.0, 6.0, 3.5, 6.75, 6.5},
+    {3.0, 5.0, 1.0, 0.5}
+};
+
+void expect_equal_divs(const div_compartment& da, const div_compartment& db) {
+    EXPECT_EQ(da.index, db.index);
+
+    double eps = std::numeric_limits<double>::epsilon();
+    double e1 = std::min(da.length(), db.length())*8*eps;
+    double e2 = std::min(da.area(), db.area())*8*eps;
+    double e3 = std::min(da.volume(), db.volume())*8*eps;
+
+    EXPECT_NEAR(da.left.length, db.left.length, e1);
+    EXPECT_NEAR(da.left.area, db.left.area, e2);
+    EXPECT_NEAR(da.left.volume, db.left.volume, e3);
+    EXPECT_NEAR(da.left.radii.first, db.left.radii.first, e1);
+    EXPECT_NEAR(da.left.radii.second, db.left.radii.second, e1);
+
+    EXPECT_NEAR(da.right.length, db.right.length, e1);
+    EXPECT_NEAR(da.right.area, db.right.area, e2);
+    EXPECT_NEAR(da.right.volume, db.right.volume, e3);
+    EXPECT_NEAR(da.right.radii.first, db.right.radii.first, e1);
+    EXPECT_NEAR(da.right.radii.second, db.right.radii.second, e1);
+}
+
+TEST(compartments, div_ends) {
+    using namespace math;
+
+    {
+        div_compartment_by_ends divcomps{1, cable_one.radii, cable_one.lengths};
+
+        auto d = divcomps(0);
+        auto r1 = cable_one.radii[0];
+        auto r2 = cable_one.radii[1];
+        auto l = cable_one.lengths[0];
+
+        EXPECT_DOUBLE_EQ(r1, d.radii().first);
+        EXPECT_DOUBLE_EQ(r2, d.radii().second);
+        EXPECT_DOUBLE_EQ(l, d.length());
+        EXPECT_DOUBLE_EQ(area_frustrum(l, r2, r1), d.area());
+        EXPECT_DOUBLE_EQ(volume_frustrum(l, r2, r1), d.volume());
+
+        auto sl = l/2.0;
+        auto rc = mean(r1, r2);
+
+        div_compartment expected{
+            0,
+            semi_compartment{sl, area_frustrum(sl, r1, rc), volume_frustrum(sl, r1, rc), {r1, rc}},
+            semi_compartment{sl, area_frustrum(sl, rc, r2), volume_frustrum(sl, rc, r2), {rc, r2}}
+        };
+
+        SCOPED_TRACE("cable_one");
+        expect_equal_divs(expected, d);
+    }
+
+    {
+        // for a linear cable, expect this compartment maker to
+        // create consistent compartments
+
+        constexpr unsigned ncomp = 7;
+        div_compartment_by_ends divlin{ncomp, cable_linear.radii, cable_linear.lengths};
+
+        auto r1 = cable_linear.r1();
+        auto r2 = cable_linear.r2();
+        auto l = cable_linear.length();
+
+        pw_cable_data<1> one = { {r1, r2}, {l} };
+        div_compartment_by_ends divone{ncomp, one.radii, one.lengths};
+
+        for (unsigned i=0; i<ncomp; ++i) {
+            SCOPED_TRACE("cable_linear compartment "+std::to_string(i));
+            auto da = divlin(i);
+            auto db = divone(i);
+
+            EXPECT_DOUBLE_EQ(l/ncomp, da.length());
+            expect_equal_divs(da, db);
+        }
+    }
+}
+
+TEST(compartments, div_sample) {
+    using namespace math;
+
+    // expect by_ends and sampler to give same results on linear cable
+    {
+        constexpr unsigned ncomp = 7;
+        div_compartment_sampler divsampler{ncomp, cable_linear.radii, cable_linear.lengths};
+        div_compartment_by_ends divends{ncomp, cable_linear.radii, cable_linear.lengths};
+
+        auto l = cable_linear.length();
+        for (unsigned i=0; i<ncomp; ++i) {
+            SCOPED_TRACE("cable_linear compartment "+std::to_string(i));
+            auto da = divsampler(i);
+            auto db = divends(i);
+
+            EXPECT_DOUBLE_EQ(l/ncomp, da.length());
+            expect_equal_divs(da, db);
+        }
+    }
+
+    // expect (up to rounding) correct total area and volume if compartments
+    // align with sub-segments; when they don't align, expect error to decrease
+    // with ncomp
+    {
+        double area_expected = cable_jumble.area();
+        double volume_expected = cable_jumble.volume();
+        double eps = std::numeric_limits<double>::epsilon();
+
+        double common_dx = 0.5; // depends on cable_jumble.lengths;
+        // check our common_dx actually is ok
+        for (double l: cable_jumble.lengths) {
+            ASSERT_DOUBLE_EQ(l/common_dx, std::round(l/common_dx));
+        }
+
+        double length = cable_jumble.length();
+        unsigned nbase = std::round(length/common_dx);
+
+        for (unsigned m: {1u, 3u, 7u}) {
+            unsigned ncomp = m*nbase;
+            div_compartment_sampler divs{ncomp, cable_jumble.radii, cable_jumble.lengths};
+
+            double area = sum(transform_view(make_span(0, ncomp),
+                [&](unsigned i) { return divs(i).area(); }));
+
+            double volume = sum(transform_view(make_span(0, ncomp),
+                [&](unsigned i) { return divs(i).volume(); }));
+
+            double e2 = std::min(area, area_expected)*ncomp*eps;
+            double e3 = std::min(volume, volume_expected)*ncomp*eps;
+
+            SCOPED_TRACE("cable_jumble ncomp "+std::to_string(ncomp));
+
+            EXPECT_NEAR(area_expected, area, e2);
+            EXPECT_NEAR(volume_expected, volume, e3);
+        }
+
+        double coarse_area = area_frustrum(length, cable_jumble.r1(), cable_jumble.r2());
+        double coarse_volume = volume_frustrum(length, cable_jumble.r1(), cable_jumble.r2());
+        double area_error = std::abs(area_expected-coarse_area);
+        double volume_error = std::abs(volume_expected-coarse_volume);
+
+        for (unsigned m: {1u, 10u, 100u}) {
+            unsigned ncomp = m*nbase+1u;
+            div_compartment_sampler divs{ncomp, cable_jumble.radii, cable_jumble.lengths};
+
+            double area = sum(transform_view(make_span(0, ncomp),
+                [&](unsigned i) { return divs(i).area(); }));
+
+            double volume = sum(transform_view(make_span(0, ncomp),
+                [&](unsigned i) { return divs(i).volume(); }));
+
+            SCOPED_TRACE("cable_jumble ncomp "+std::to_string(ncomp));
+
+            double err = std::abs(area_expected-area);
+            EXPECT_LT(err, area_error);
+            area_error = err;
+
+            err = std::abs(volume_expected-volume);
+            EXPECT_LT(err, volume_error);
+            volume_error = err;
+        }
+    }
+}
+
+TEST(compartments, div_integrator) {
+    using namespace math;
+
+    // expect integrator and sampler to give same results on linear cable
+    {
+        constexpr unsigned ncomp = 7;
+        div_compartment_sampler divintegrator{ncomp, cable_linear.radii, cable_linear.lengths};
+        div_compartment_by_ends divends{ncomp, cable_linear.radii, cable_linear.lengths};
+
+        auto l = cable_linear.length();
+        for (unsigned i=0; i<ncomp; ++i) {
+            SCOPED_TRACE("cable_linear compartment "+std::to_string(i));
+            auto da = divintegrator(i);
+            auto db = divends(i);
+
+            EXPECT_DOUBLE_EQ(l/ncomp, da.length());
+            expect_equal_divs(da, db);
+        }
+    }
+
+    // expect integrator to give same (up to rounding) total areas and volumes
+    // as the cable
+    {
+        double area_expected = cable_jumble.area();
+        double volume_expected = cable_jumble.volume();
+        double eps = std::numeric_limits<double>::epsilon();
+
+        for (unsigned ncomp: make_span(1u, 23u)) {
+            div_compartment_integrator divs{ncomp, cable_jumble.radii, cable_jumble.lengths};
+
+            double area = sum(transform_view(make_span(0, ncomp),
+                [&](unsigned i) { return divs(i).area(); }));
+
+            double volume = sum(transform_view(make_span(0, ncomp),
+                [&](unsigned i) { return divs(i).volume(); }));
+
+            double e2 = std::min(area, area_expected)*ncomp*eps;
+            double e3 = std::min(volume, volume_expected)*ncomp*eps;
+
+            SCOPED_TRACE("cable_jumble ncomp "+std::to_string(ncomp));
+
+            EXPECT_NEAR(area_expected, area, e2);
+            EXPECT_NEAR(volume_expected, volume, e3);
+        }
+    }
+}
+