From cef0d1d4681c3e38d2b35d69b305f5c724cbb3ba Mon Sep 17 00:00:00 2001
From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com>
Date: Thu, 20 Oct 2022 15:18:15 +0200
Subject: [PATCH] Make decor mandatory and labels optional. (#1978)

* Make decor mandatory and labels optional.
* Sort morphology, decor, labels
* No more optional decor.
---
 arbor/cable_cell.cpp                          |  2 +-
 arbor/include/arbor/cable_cell.hpp            |  5 +--
 arborio/cableio.cpp                           |  2 +-
 doc/concepts/decor.rst                        |  2 +-
 doc/cpp/cable_cell.rst                        | 23 ++++++++------
 doc/cpp/morphology.rst                        |  5 +--
 doc/dev/debug.rst                             |  2 +-
 doc/python/cable_cell.rst                     |  6 ++--
 doc/python/morphology.rst                     |  3 +-
 doc/python/probe_sample.rst                   |  2 +-
 doc/scripts/gen-labels.py                     |  6 ++--
 example/diffusion/diffusion.cpp               |  2 +-
 example/dryrun/branch_cell.hpp                |  2 +-
 example/gap_junctions/gap_junctions.cpp       |  2 +-
 example/generators/generators.cpp             |  2 +-
 example/lfp/lfp.cpp                           |  2 +-
 example/plasticity/plasticity.cpp             |  2 +-
 example/probe-demo/probe-demo.cpp             |  2 +-
 example/ring/branch_cell.hpp                  |  2 +-
 example/single/single.cpp                     |  2 +-
 lmorpho/lmorpho.cpp                           |  2 +-
 python/cells.cpp                              | 22 ++++++++-----
 python/example/diffusion.py                   |  2 +-
 python/example/dynamic-catalogue.py           |  2 +-
 python/example/gap_junctions.py               |  2 +-
 python/example/network_ring.py                |  2 +-
 python/example/network_ring_gpu.py            |  2 +-
 python/example/network_ring_mpi.py            |  2 +-
 .../network_two_cells_gap_junctions.py        |  2 +-
 python/example/plasticity.py                  |  2 +-
 python/example/probe_lfpykit.py               |  2 +-
 python/example/single_cell_allen.py           |  2 +-
 python/example/single_cell_cable.py           |  2 +-
 python/example/single_cell_detailed.py        |  2 +-
 python/example/single_cell_detailed_recipe.py |  2 +-
 python/example/single_cell_model.py           |  2 +-
 python/example/single_cell_nml.py             |  2 +-
 python/example/single_cell_recipe.py          |  2 +-
 python/example/single_cell_stdp.py            |  2 +-
 python/example/single_cell_swc.py             |  2 +-
 python/test/fixtures.py                       |  2 +-
 python/test/unit/test_cable_probes.py         |  2 +-
 python/test/unit/test_catalogues.py           |  2 +-
 python/test/unit/test_multiple_connections.py |  4 +--
 .../test_domain_decompositions.py             |  4 +--
 test/common_cells.hpp                         |  2 +-
 test/unit-distributed/test_communicator.cpp   |  6 ++--
 test/unit/test_cable_cell.cpp                 |  2 +-
 test/unit/test_cv_geom.cpp                    | 22 ++++++-------
 test/unit/test_cv_layout.cpp                  | 10 +++---
 test/unit/test_cv_policy.cpp                  | 31 ++++++++++---------
 test/unit/test_diffusion.cpp                  |  2 +-
 test/unit/test_event_delivery.cpp             |  2 +-
 test/unit/test_fvm_layout.cpp                 | 10 +++---
 test/unit/test_fvm_lowered.cpp                |  8 ++---
 test/unit/test_probe.cpp                      | 24 +++++++-------
 test/unit/test_recipe.cpp                     |  4 +--
 test/unit/test_sde.cpp                        | 22 ++++++-------
 test/unit/test_spikes.cpp                     |  2 +-
 59 files changed, 152 insertions(+), 145 deletions(-)

diff --git a/arbor/cable_cell.cpp b/arbor/cable_cell.cpp
index 9f6a523f..8b7d0382 100644
--- a/arbor/cable_cell.cpp
+++ b/arbor/cable_cell.cpp
@@ -222,7 +222,7 @@ void cable_cell_impl::init(const decor& d) {
     }
 }
 
-cable_cell::cable_cell(const arb::morphology& m, const label_dict& dictionary, const decor& decorations):
+cable_cell::cable_cell(const arb::morphology& m, const decor& decorations, const label_dict& dictionary):
     impl_(make_impl(new cable_cell_impl(m, dictionary, decorations)))
 {}
 
diff --git a/arbor/include/arbor/cable_cell.hpp b/arbor/include/arbor/cable_cell.hpp
index 6556deb0..cb0570bc 100644
--- a/arbor/include/arbor/cable_cell.hpp
+++ b/arbor/include/arbor/cable_cell.hpp
@@ -266,10 +266,7 @@ public:
     }
 
     /// Construct from morphology, label and decoration descriptions.
-    cable_cell(const class morphology&, const label_dict&, const decor&);
-    cable_cell(const class morphology& m):
-        cable_cell(m, {}, {})
-    {}
+    cable_cell(const class morphology& m, const decor& d, const label_dict& l={});
 
     /// Access to labels
     const label_dict& labels() const;
diff --git a/arborio/cableio.cpp b/arborio/cableio.cpp
index 103246eb..8be039c6 100644
--- a/arborio/cableio.cpp
+++ b/arborio/cableio.cpp
@@ -352,7 +352,7 @@ cable_cell make_cable_cell(const std::vector<std::variant<morphology, label_dict
             [&](const decor & p){ dec = p; });
         std::visit(cable_cell_visitor, a);
     }
-    return cable_cell(morpho, dict, dec);
+    return cable_cell(morpho, dec, dict);
 }
 version_tuple make_version(const std::string& v) {
     return version_tuple{v};
diff --git a/doc/concepts/decor.rst b/doc/concepts/decor.rst
index 62f2690e..d1b7f9fa 100644
--- a/doc/concepts/decor.rst
+++ b/doc/concepts/decor.rst
@@ -265,7 +265,7 @@ ion at the cell level using the Python interface:
     # Method 2: set directly using a string description.
     decor.set_ion(ion='ca', method='nernst/x=ca')
 
-    cell = arbor.cable_cell(morph, labels, decor)
+    cell = arbor.cable_cell(morph, decor)
 
 
 The NMODL code for the
diff --git a/doc/cpp/cable_cell.rst b/doc/cpp/cable_cell.rst
index b44e36dc..9013db7d 100644
--- a/doc/cpp/cable_cell.rst
+++ b/doc/cpp/cable_cell.rst
@@ -27,6 +27,8 @@ Properties shared by all cable cells, as returned by the recipe
 object of type :cpp:type:`cable_cell_global_properties`.
 
 
+
+
 The :cpp:type:`cable_cell` object
 ---------------------------------
 
@@ -55,14 +57,15 @@ are specified via the ``place`` method. See :ref:`cppcablecell-dynamics`, below.
 Cell dynamics
 -------------
 
-Each segment in a cell may have attached to it one or more density *mechanisms*,
-which describe biophysical processes. These are processes
-that are distributed in space, but whose behaviour is defined purely
-by the state of the cell and the process at any given point.
+Dynamics are imbued onto the cell by setting a :cpp:type:`decor` object during
+construction. Each segment in a cell may have attached to it one or more density
+*mechanisms*, which describe biophysical processes. These are processes that are
+distributed in space, but whose behaviour is defined purely by the state of the
+cell and the process at any given point.
 
-Cells may also have *point* mechanisms, describing the dynamics
-at post-synaptic sites. And *junction* mechanisms, describing the
-dynamics at each site of the two sites of a gap-junction connection.
+Cells may also have *point* mechanisms, describing the dynamics at post-synaptic
+sites. And *junction* mechanisms, describing the dynamics at each site of the
+two sites of a gap-junction connection.
 
 A fourth type of mechanism, which describes ionic reversal potential
 behaviour, can be specified for cells or the whole model via cell parameter
@@ -121,14 +124,14 @@ Relevant methods:
 
 Density mechanisms are associated with a cable cell object with:
 
-.. cpp:function:: void cable_cell::paint(const region&, density)
+.. cpp:function:: void decor::paint(const region&, density)
 
 Point mechanisms, which are associated with connection end points on a
 cable cell, are placed on a set of locations given by a locset. The group
 of generated items are given a label which can be used to create connections
 in the recipe. Point mechanisms are attached to a cell with:
 
-.. cpp:function:: void cable_cell::place(const locset&, synapse, cell_tag_type label)
+.. cpp:function:: void decor::place(const locset&, synapse, cell_tag_type label)
 
 Gap-junction mechanisms, which are associated with gap-junction connection
 end points on a cable cell, are placed on a single location given by a locset
@@ -136,7 +139,7 @@ end points on a cable cell, are placed on a single location given by a locset
 is given a label which can be used to create gap-junction connections in the
 recipe. Gap-junction mechanisms are attached to a cell with:
 
-.. cpp:function:: void cable_cell::place(const locset&, junction, cell_tag_type label)
+.. cpp:function:: void decor::place(const locset&, junction, cell_tag_type label)
 
 .. todo::
 
diff --git a/doc/cpp/morphology.rst b/doc/cpp/morphology.rst
index 9a21ea24..ba0c2473 100644
--- a/doc/cpp/morphology.rst
+++ b/doc/cpp/morphology.rst
@@ -190,9 +190,10 @@ by two stitches:
    builder.add({"dend", dend_end, 4}, "soma", 0.5);
 
    stitched_morphology stitched(std::move(builder));
-   cable_cell cell(stitched.morphology(), stitched.labels());
 
-   cell.paint("\"soma\"", density("hh"));
+   auto dec = decor{}.paint("\"soma\"", density("hh"));
+
+   cable_cell cell(stitched.morphology(), dec, stitched.labels());
 
 
 .. _locsets-and-regions:
diff --git a/doc/dev/debug.rst b/doc/dev/debug.rst
index e3ae77e5..a9705c4c 100644
--- a/doc/dev/debug.rst
+++ b/doc/dev/debug.rst
@@ -39,7 +39,7 @@ LLDB.
           # [...]
           decor = (arbor.decor()
               .place('"synapse_site"', arbor.synapse("expsyn"), "syn"))
-          return arbor.cable_cell(tree, labels, decor)
+          return arbor.cable_cell(tree, decor, labels)
 
       rec = Recipe()            # "Ok"
       sim = arb.simulation(rec) # ERROR here
diff --git a/doc/python/cable_cell.rst b/doc/python/cable_cell.rst
index 03603b98..7eb028b6 100644
--- a/doc/python/cable_cell.rst
+++ b/doc/python/cable_cell.rst
@@ -52,14 +52,14 @@ Cable cells
         decor.paint('"soma"', arbor.density('hh'))
 
         # Construct a cable cell.
-        cell = arbor.cable_cell(morph, labels, decor)
+        cell = arbor.cable_cell(morph, decor, labels)
 
-    .. method:: __init__(morphology, labels, decorations)
+    .. method:: __init__(morphology, decorations, labels)
 
         Constructor.
 
         :param morphology: the morphology of the cell
-        :type morphology: :py:class:`morphology`
+        :type morphology: :py:class:`morphology` or :py:class:`segment_tree`
         :param labels: dictionary of labeled regions and locsets
         :type labels: :py:class:`label_dict`
         :param decorations: the decorations on the cell
diff --git a/doc/python/morphology.rst b/doc/python/morphology.rst
index f72fb781..f0ee2045 100644
--- a/doc/python/morphology.rst
+++ b/doc/python/morphology.rst
@@ -761,8 +761,7 @@ Neurolucida
        asc = arbor.load_asc('granule.asc')
 
        # Construct a cable cell.
-       decor = arbor.decor()
-       cell = arbor.cable_cell(asc.morphology, asc.labels, decor)
+       cell = arbor.cable_cell(asc.morphology, arbor.decor(), asc.labels)
 
 
    :param str filename: the name of the input file.
diff --git a/doc/python/probe_sample.rst b/doc/python/probe_sample.rst
index 3ac24a0c..a6625298 100644
--- a/doc/python/probe_sample.rst
+++ b/doc/python/probe_sample.rst
@@ -79,7 +79,7 @@ Example
       .paint('"soma"', arbor.density("hh"))
       .place('"midpoint"', arbor.iclamp(10, 2, 0.8), "iclamp"))
 
-   cell = arbor.cable_cell(tree, labels, decor)
+   cell = arbor.cable_cell(tree, decor)
 
    class single_recipe(arbor.recipe):
       def __init__(self):
diff --git a/doc/scripts/gen-labels.py b/doc/scripts/gen-labels.py
index 69dd8e36..caab422f 100644
--- a/doc/scripts/gen-labels.py
+++ b/doc/scripts/gen-labels.py
@@ -223,7 +223,7 @@ labels = {**regions, **locsets}
 d = arbor.label_dict(labels)
 
 # Create a cell to concretise the region and locset definitions
-cell = arbor.cable_cell(label_morph, d, arbor.decor())
+cell = arbor.cable_cell(label_morph, None, d)
 
 ###############################################################################
 # Tutorial Example: single_cell_detailed
@@ -263,7 +263,7 @@ tutorial_labels = {**tutorial_regions, **tutorial_locsets}
 tutorial_dict = arbor.label_dict(tutorial_labels)
 
 # Create a cell to concretise the region and locset definitions
-tutorial_cell = arbor.cable_cell(tutorial_morph, tutorial_dict, arbor.decor())
+tutorial_cell = arbor.cable_cell(tutorial_morph, None, tutorial_dict)
 
 ###############################################################################
 # Tutorial Example: network_ring
@@ -298,7 +298,7 @@ tutorial_network_ring_dict = arbor.label_dict(tutorial_network_ring_labels)
 
 # Create a cell to concretise the region and locset definitions
 tutorial_network_ring_cell = arbor.cable_cell(
-    tutorial_network_ring_morph, tutorial_network_ring_dict, arbor.decor()
+    tutorial_network_ring_morph, None, tutorial_network_ring_dict
 )
 
 ################################################################################
diff --git a/example/diffusion/diffusion.cpp b/example/diffusion/diffusion.cpp
index 991a351d..6b0bb382 100644
--- a/example/diffusion/diffusion.cpp
+++ b/example/diffusion/diffusion.cpp
@@ -43,7 +43,7 @@ struct linear: public recipe {
         decor.set_default(ion_diffusivity{"na", b});
         decor.place("(location 0 0.5)"_ls, synapse("inject/x=na", {{"alpha", 200.0*l}}), "Zap");
         decor.paint("(all)"_reg, density("decay/x=na"));
-        return cable_cell({tree}, {}, decor);
+        return cable_cell({tree}, decor);
     }
 
     cable_cell_global_properties gprop;
diff --git a/example/dryrun/branch_cell.hpp b/example/dryrun/branch_cell.hpp
index cf208d6d..6b930d5d 100644
--- a/example/dryrun/branch_cell.hpp
+++ b/example/dryrun/branch_cell.hpp
@@ -121,5 +121,5 @@ arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& param
     for (unsigned i=1u; i<params.synapses; ++i) {
         decor.place(arb::mlocation{1, 0.5}, arb::synapse("expsyn"), "dummy_synapses");
     }
-    return arb::cable_cell{arb::morphology(tree), labels, decor};
+    return arb::cable_cell{arb::morphology(tree), decor, labels};
 }
diff --git a/example/gap_junctions/gap_junctions.cpp b/example/gap_junctions/gap_junctions.cpp
index 5d5d8c12..937cda15 100644
--- a/example/gap_junctions/gap_junctions.cpp
+++ b/example/gap_junctions/gap_junctions.cpp
@@ -298,7 +298,7 @@ arb::cable_cell gj_cell(cell_gid_type gid, unsigned ncell, double stim_duration)
     }
 
     // Create the cell and set its electrical properties.
-    return arb::cable_cell(tree, {}, decor);
+    return arb::cable_cell(tree, decor);
 }
 
 gap_params read_options(int argc, char** argv) {
diff --git a/example/generators/generators.cpp b/example/generators/generators.cpp
index c0b32d37..6ab8d905 100644
--- a/example/generators/generators.cpp
+++ b/example/generators/generators.cpp
@@ -66,7 +66,7 @@ public:
             // event_generators.
         .place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "syn");
 
-        return arb::cable_cell(tree, labels, decor);
+        return arb::cable_cell(tree, decor, labels);
     }
 
     cell_kind get_cell_kind(cell_gid_type gid) const override {
diff --git a/example/lfp/lfp.cpp b/example/lfp/lfp.cpp
index 95ead2c1..96c0f358 100644
--- a/example/lfp/lfp.cpp
+++ b/example/lfp/lfp.cpp
@@ -94,7 +94,7 @@ private:
             .paint("(tag 4)"_reg, density("pas/e=-70.0"))
             // Add exponential synapse at centre of soma.
             .place(synapse_location_, synapse("expsyn", {{"e", 0}, {"tau", 2}}), "syn");
-        cell_ = cable_cell(tree, {}, dec);
+        cell_ = cable_cell(tree, dec);
     }
 };
 
diff --git a/example/plasticity/plasticity.cpp b/example/plasticity/plasticity.cpp
index 37748caf..d72f24c4 100644
--- a/example/plasticity/plasticity.cpp
+++ b/example/plasticity/plasticity.cpp
@@ -67,7 +67,7 @@ struct recipe: public arb::recipe {
             .place(center, arb::synapse("expsyn"), syn)
             .place(center, arb::threshold_detector{-10.0}, det)
             .set_default(arb::cv_policy_every_segment());
-        return arb::cable_cell({tree}, {}, decor);
+        return arb::cable_cell({tree}, decor);
     }
 };
 
diff --git a/example/probe-demo/probe-demo.cpp b/example/probe-demo/probe-demo.cpp
index 16bcef34..d7f21181 100644
--- a/example/probe-demo/probe-demo.cpp
+++ b/example/probe-demo/probe-demo.cpp
@@ -125,7 +125,7 @@ struct cable_recipe: public arb::recipe {
             .place(arb::mlocation{0, 0.}, arb::i_clamp{1.}, "iclamp")           // Inject a 1 nA current indefinitely.
             .place(arb::mlocation{0, 0.}, arb::synapse("expsyn"), "synapse1")   // a synapse
             .place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "synapse2"); // another synapse
-        return arb::cable_cell(tree, {}, decor);
+        return arb::cable_cell(tree, decor);
     }
 
     virtual std::vector<arb::event_generator> event_generators(arb::cell_gid_type) const override {
diff --git a/example/ring/branch_cell.hpp b/example/ring/branch_cell.hpp
index 503827c0..61740a99 100644
--- a/example/ring/branch_cell.hpp
+++ b/example/ring/branch_cell.hpp
@@ -124,7 +124,7 @@ arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& param
     // Make a CV between every sample in the sample tree.
     decor.set_default(arb::cv_policy_every_segment());
 
-    arb::cable_cell cell(arb::morphology(tree), labels, decor);
+    arb::cable_cell cell(arb::morphology(tree), decor, labels);
 
     return cell;
 }
diff --git a/example/single/single.cpp b/example/single/single.cpp
index 26afc366..94131025 100644
--- a/example/single/single.cpp
+++ b/example/single/single.cpp
@@ -72,7 +72,7 @@ struct single_recipe: public arb::recipe {
             // Add synapse to last branch.
             .place(arb::mlocation{ morpho.num_branches()-1, 1. }, arb::synapse("exp2syn"), "synapse");
 
-        return arb::cable_cell(morpho, dict, decor);
+        return arb::cable_cell(morpho, decor, dict);
     }
 
     arb::morphology morpho;
diff --git a/lmorpho/lmorpho.cpp b/lmorpho/lmorpho.cpp
index d6ed240f..cbcba1a3 100644
--- a/lmorpho/lmorpho.cpp
+++ b/lmorpho/lmorpho.cpp
@@ -87,7 +87,7 @@ int main(int argc, char** argv) {
             labels.set("apical", "(tag 4)"_reg);
             arb::decor decor;
 
-            arb::cable_cell cell(morph, labels, decor);
+            arb::cable_cell cell(morph, decor, labels);
 
             if(acc_file) {
                 std::string filename = acc_file.value();
diff --git a/python/cells.cpp b/python/cells.cpp
index d79059cf..7d9159e5 100644
--- a/python/cells.cpp
+++ b/python/cells.cpp
@@ -923,17 +923,23 @@ void register_cells(pybind11::module& m) {
         "tree of one-dimensional cable segments.");
     cable_cell
         .def(pybind11::init(
-            [](const arb::morphology& m, const label_dict_proxy& labels, const arb::decor& d) {
-                return arb::cable_cell(m, labels.dict, d);
+            [](const arb::morphology& m, const arb::decor& d, const std::optional<label_dict_proxy>& l) {
+                if (l) return arb::cable_cell(m, d, l->dict);
+                return arb::cable_cell(m, d);
             }),
-            "morphology"_a, "labels"_a, "decor"_a,
-            "Construct with a morphology, label dictionary and decor.")
+            "morphology"_a,
+             "decor"_a,
+             pybind11::arg_v("labels", pybind11::none(), "Labels"),
+            "Construct with a morphology, decor, and label dictionary.")
         .def(pybind11::init(
-            [](const arb::segment_tree& t, const label_dict_proxy& labels, const arb::decor& d) {
-                return arb::cable_cell(arb::morphology(t), labels.dict, d);
+            [](const arb::segment_tree& t, const arb::decor& d, const std::optional<label_dict_proxy>& l) {
+                if (l) return arb::cable_cell({t}, d, l->dict);
+                return arb::cable_cell({t}, d);
             }),
-            "segment_tree"_a, "labels"_a, "decor"_a,
-            "Construct with a morphology derived from a segment tree, label dictionary and decor.")
+            "segment_tree"_a,
+             "decor"_a,
+             pybind11::arg_v("labels", pybind11::none(), "Labels"),
+            "Construct with a morphology derived from a segment tree, decor, and label dictionary.")
         .def_property_readonly("num_branches",
             [](const arb::cable_cell& c) {return c.morphology().num_branches();},
             "The number of unbranched cable sections in the morphology.")
diff --git a/python/example/diffusion.py b/python/example/diffusion.py
index 438ae0d5..7ef2d210 100644
--- a/python/example/diffusion.py
+++ b/python/example/diffusion.py
@@ -49,7 +49,7 @@ dec.paint("(tag 1)", ion_name="na", int_con=100.0, diff=0.01)
 prb = [
     A.cable_probe_ion_diff_concentration_cell("na"),
 ]
-cel = A.cable_cell(tree, A.label_dict(), dec)
+cel = A.cable_cell(tree, dec)
 rec = recipe(cel, prb)
 sim = A.simulation(rec)
 hdl = (sim.sample((0, 0), A.regular_schedule(0.1)),)
diff --git a/python/example/dynamic-catalogue.py b/python/example/dynamic-catalogue.py
index 64c74cc9..6ed1a83f 100644
--- a/python/example/dynamic-catalogue.py
+++ b/python/example/dynamic-catalogue.py
@@ -15,7 +15,7 @@ class recipe(arb.recipe):
         self.props = arb.neuron_cable_properties()
         self.props.catalogue = arb.load_catalogue(cat)
         d = arb.decor().paint("(all)", "dummy").set_property(Vm=0.0)
-        self.cell = arb.cable_cell(self.tree, arb.label_dict(), d)
+        self.cell = arb.cable_cell(self.tree, d)
 
     def global_properties(self, _):
         return self.props
diff --git a/python/example/gap_junctions.py b/python/example/gap_junctions.py
index dcdbbfa5..a06bc8de 100644
--- a/python/example/gap_junctions.py
+++ b/python/example/gap_junctions.py
@@ -55,7 +55,7 @@ def make_cable_cell(gid):
         .place('"root"', arbor.threshold_detector(-10), "detector")
     )
 
-    return arbor.cable_cell(tree, labels, decor)
+    return arbor.cable_cell(tree, decor, labels)
 
 
 # Create a recipe that generates connected chains of cells
diff --git a/python/example/network_ring.py b/python/example/network_ring.py
index 3e0f3de6..d30e83a4 100755
--- a/python/example/network_ring.py
+++ b/python/example/network_ring.py
@@ -68,7 +68,7 @@ def make_cable_cell(gid):
         .place('"root"', arbor.threshold_detector(-10), "detector")
     )
 
-    return arbor.cable_cell(tree, labels, decor)
+    return arbor.cable_cell(tree, decor, labels)
 
 
 # (5) Create a recipe that generates a network of connected cells.
diff --git a/python/example/network_ring_gpu.py b/python/example/network_ring_gpu.py
index 61b7be3e..22a9b43d 100644
--- a/python/example/network_ring_gpu.py
+++ b/python/example/network_ring_gpu.py
@@ -69,7 +69,7 @@ def make_cable_cell(gid):
     # Attach a detector with threshold of -10 mV.
     decor.place('"root"', arbor.threshold_detector(-10), "detector")
 
-    cell = arbor.cable_cell(tree, labels, decor)
+    cell = arbor.cable_cell(tree, decor, labels)
 
     return cell
 
diff --git a/python/example/network_ring_mpi.py b/python/example/network_ring_mpi.py
index dc1b0d2c..73621685 100644
--- a/python/example/network_ring_mpi.py
+++ b/python/example/network_ring_mpi.py
@@ -70,7 +70,7 @@ def make_cable_cell(gid):
         .place('"root"', arbor.threshold_detector(-10), "detector")
     )
 
-    return arbor.cable_cell(tree, labels, decor)
+    return arbor.cable_cell(tree, decor, labels)
 
 
 # (5) Create a recipe that generates a network of connected cells.
diff --git a/python/example/network_two_cells_gap_junctions.py b/python/example/network_two_cells_gap_junctions.py
index 95fd830e..1eeec49f 100755
--- a/python/example/network_two_cells_gap_junctions.py
+++ b/python/example/network_two_cells_gap_junctions.py
@@ -98,7 +98,7 @@ class TwoCellsWithGapJunction(arbor.recipe):
         else:
             decor.discretization(arbor.cv_policy_single())
 
-        return arbor.cable_cell(tree, labels, decor)
+        return arbor.cable_cell(tree, decor, labels)
 
     def gap_junctions_on(self, gid):
         # create a bidirectional gap junction from cell 0 at label "gj_label" to cell 1 at label "gj_label" and back.
diff --git a/python/example/plasticity.py b/python/example/plasticity.py
index cd8b8a2b..ab57d099 100644
--- a/python/example/plasticity.py
+++ b/python/example/plasticity.py
@@ -56,7 +56,7 @@ class recipe(A.recipe):
         #   - detector for reporting spikes on the cable cells.
         decor.place("(location 0 0.5)", A.threshold_detector(-10.0), "detector")
         # return the cable cell description
-        return A.cable_cell(tree, A.label_dict(), decor)
+        return A.cable_cell(tree, decor)
 
     def connections_on(self, gid):
         # If we have added a connection to this cell, return it, else nothing.
diff --git a/python/example/probe_lfpykit.py b/python/example/probe_lfpykit.py
index 8519b933..0db4733f 100644
--- a/python/example/probe_lfpykit.py
+++ b/python/example/probe_lfpykit.py
@@ -109,7 +109,7 @@ def make_cable_cell(morphology, clamp_location):
     p = arbor.place_pwlin(morphology)
 
     # create cell and set properties
-    cell = arbor.cable_cell(morphology, labels, decor)
+    cell = arbor.cable_cell(morphology, decor, labels)
 
     return p, cell
 
diff --git a/python/example/single_cell_allen.py b/python/example/single_cell_allen.py
index 1296a29b..dade963a 100644
--- a/python/example/single_cell_allen.py
+++ b/python/example/single_cell_allen.py
@@ -114,7 +114,7 @@ def make_cell(swc, fit):
     decor.discretization(arbor.cv_policy_max_extent(20))
 
     # (11) Create cell
-    return arbor.cable_cell(morphology, labels, decor), offset
+    return arbor.cable_cell(morphology, decor, labels), offset
 
 
 # (12) Create cell, model
diff --git a/python/example/single_cell_cable.py b/python/example/single_cell_cable.py
index 8cb5d4cf..25f6aa14 100755
--- a/python/example/single_cell_cable.py
+++ b/python/example/single_cell_cable.py
@@ -108,7 +108,7 @@ class Cable(arbor.recipe):
         policy = arbor.cv_policy_max_extent(self.cv_policy_max_extent)
         decor.discretization(policy)
 
-        return arbor.cable_cell(tree, labels, decor)
+        return arbor.cable_cell(tree, decor, labels)
 
 
 def get_rm(g):
diff --git a/python/example/single_cell_detailed.py b/python/example/single_cell_detailed.py
index 37873eba..25495c51 100755
--- a/python/example/single_cell_detailed.py
+++ b/python/example/single_cell_detailed.py
@@ -67,7 +67,7 @@ decor.discretization('(replace (single (region "soma")) (max-extent 1.0))')
 
 # (4) Create the cell.
 
-cell = arbor.cable_cell(morph, labels, decor)
+cell = arbor.cable_cell(morph, decor, labels)
 
 # (5) Construct the model
 
diff --git a/python/example/single_cell_detailed_recipe.py b/python/example/single_cell_detailed_recipe.py
index ac01e73a..b5056254 100644
--- a/python/example/single_cell_detailed_recipe.py
+++ b/python/example/single_cell_detailed_recipe.py
@@ -68,7 +68,7 @@ decor = (
 
 # (4) Create the cell.
 
-cell = arbor.cable_cell(morph, labels, decor)
+cell = arbor.cable_cell(morph, decor, labels)
 
 
 # (5) Create a class that inherits from arbor.recipe
diff --git a/python/example/single_cell_model.py b/python/example/single_cell_model.py
index 229862ab..0737983f 100755
--- a/python/example/single_cell_model.py
+++ b/python/example/single_cell_model.py
@@ -23,7 +23,7 @@ decor = (
 )
 
 # (4) Create cell and the single cell model based on it
-cell = arbor.cable_cell(tree, labels, decor)
+cell = arbor.cable_cell(tree, decor, labels)
 
 # (5) Make single cell model.
 m = arbor.single_cell_model(cell)
diff --git a/python/example/single_cell_nml.py b/python/example/single_cell_nml.py
index 0061e72b..f69b195f 100755
--- a/python/example/single_cell_nml.py
+++ b/python/example/single_cell_nml.py
@@ -69,7 +69,7 @@ decor = (
 )
 
 # Combine morphology with region and locset definitions to make a cable cell.
-cell = arbor.cable_cell(morpho, labels, decor)
+cell = arbor.cable_cell(morpho, decor, labels)
 
 print(cell.locations('"axon_end"'))
 
diff --git a/python/example/single_cell_recipe.py b/python/example/single_cell_recipe.py
index 6b780009..447266e5 100644
--- a/python/example/single_cell_recipe.py
+++ b/python/example/single_cell_recipe.py
@@ -26,7 +26,7 @@ decor = (
     .place('"midpoint"', arbor.threshold_detector(-10), "detector")
 )
 
-cell = arbor.cable_cell(tree, labels, decor)
+cell = arbor.cable_cell(tree, decor, labels)
 
 # (4) Define a recipe for a single cell and set of probes upon it.
 # This constitutes the corresponding generic recipe version of
diff --git a/python/example/single_cell_stdp.py b/python/example/single_cell_stdp.py
index a2079748..9799d69f 100755
--- a/python/example/single_cell_stdp.py
+++ b/python/example/single_cell_stdp.py
@@ -41,7 +41,7 @@ class single_recipe(arbor.recipe):
             )
         )
 
-        return arbor.cable_cell(tree, labels, decor)
+        return arbor.cable_cell(tree, decor, labels)
 
     def event_generators(self, gid):
         """two stimuli: one that makes the cell spike, the other to monitor STDP"""
diff --git a/python/example/single_cell_swc.py b/python/example/single_cell_swc.py
index 43e76cdb..4edaec7b 100755
--- a/python/example/single_cell_swc.py
+++ b/python/example/single_cell_swc.py
@@ -59,7 +59,7 @@ decor = (
 )
 
 # Combine morphology with region and locset definitions to make a cable cell.
-cell = arbor.cable_cell(morpho, labels, decor)
+cell = arbor.cable_cell(morpho, decor, labels)
 
 print(cell.locations('"axon_end"'))
 
diff --git a/python/test/fixtures.py b/python/test/fixtures.py
index 72633215..19f88ae9 100644
--- a/python/test/fixtures.py
+++ b/python/test/fixtures.py
@@ -218,7 +218,7 @@ class art_spiker_recipe(arbor.recipe):
             )
         else:
             tree, labels, decor = self._cable_cell_elements()
-            return arbor.cable_cell(tree, labels, decor)
+            return arbor.cable_cell(tree, decor, labels)
 
 
 @_fixture
diff --git a/python/test/unit/test_cable_probes.py b/python/test/unit/test_cable_probes.py
index e49b3930..daa0ffd2 100644
--- a/python/test/unit/test_cable_probes.py
+++ b/python/test/unit/test_cable_probes.py
@@ -24,7 +24,7 @@ class cc_recipe(A.recipe):
         dec.place("(location 0 0.1)", A.iclamp(20.0), "iclamp")
         dec.paint("(all)", A.density("hh"))
 
-        self.cell = A.cable_cell(st, A.label_dict(), dec)
+        self.cell = A.cable_cell(st, dec)
 
         self.props = A.neuron_cable_properties()
         self.props.catalogue = A.default_catalogue()
diff --git a/python/test/unit/test_catalogues.py b/python/test/unit/test_catalogues.py
index 7e9608df..38175fda 100644
--- a/python/test/unit/test_catalogues.py
+++ b/python/test/unit/test_catalogues.py
@@ -23,7 +23,7 @@ class recipe(arb.recipe):
         d = arb.decor()
         d.paint("(all)", arb.density("pas"))
         d.set_property(Vm=0.0)
-        self.cell = arb.cable_cell(self.tree, arb.label_dict(), d)
+        self.cell = arb.cable_cell(self.tree, d)
 
     def global_properties(self, _):
         return self.props
diff --git a/python/test/unit/test_multiple_connections.py b/python/test/unit/test_multiple_connections.py
index b23326c4..1f7dab5a 100644
--- a/python/test/unit/test_multiple_connections.py
+++ b/python/test/unit/test_multiple_connections.py
@@ -111,7 +111,7 @@ class TestMultipleConnections(unittest.TestCase):
                     "postsyn_target",
                 )  # place synapse for input from another presynaptic neuron at the center of the soma
                 # (using the same label as above!)
-                return arb.cable_cell(tree, labels, decor)
+                return arb.cable_cell(tree, decor, labels)
 
         art_spiker_recipe.cell_description = types.MethodType(
             cell_description, art_spiker_recipe
@@ -352,7 +352,7 @@ class TestMultipleConnections(unittest.TestCase):
                 )  # place synapse for input from another presynaptic neuron at the center of the soma
                 # (using another label as above!)
 
-                return arb.cable_cell(tree, labels, decor)
+                return arb.cable_cell(tree, decor, labels)
 
         art_spiker_recipe.cell_description = types.MethodType(
             cell_description, art_spiker_recipe
diff --git a/python/test/unit_distributed/test_domain_decompositions.py b/python/test/unit_distributed/test_domain_decompositions.py
index e19bd55c..b8fbbfb8 100644
--- a/python/test/unit_distributed/test_domain_decompositions.py
+++ b/python/test/unit_distributed/test_domain_decompositions.py
@@ -47,7 +47,7 @@ class hetero_recipe(arb.recipe):
         tree.append(arb.mnpos, arb.mpoint(-3, 0, 0, 3), arb.mpoint(3, 0, 0, 3), tag=1)
         decor = arb.decor()
         decor.place("(location 0 0.5)", arb.gap_junction_site(), "gj")
-        return arb.cable_cell(tree, arb.label_dict(), decor)
+        return arb.cable_cell(tree, decor)
 
     def cell_kind(self, gid):
         if gid % 2:
@@ -132,7 +132,7 @@ class gj_non_symmetric(arb.recipe):
         tree.append(arb.mnpos, arb.mpoint(-3, 0, 0, 3), arb.mpoint(3, 0, 0, 3), tag=1)
         decor = arb.decor()
         decor.place("(location 0 0.5)", arb.gap_junction_site(), "gj")
-        return arb.cable_cell(tree, arb.label_dict(), decor)
+        return arb.cable_cell(tree, decor)
 
     def cell_kind(self, gid):
         return arb.cell_kind.cable
diff --git a/test/common_cells.hpp b/test/common_cells.hpp
index 44212794..9b22af70 100644
--- a/test/common_cells.hpp
+++ b/test/common_cells.hpp
@@ -18,7 +18,7 @@ struct cable_cell_description {
     decor decorations;
 
     operator cable_cell() const {
-        return cable_cell(morph, labels, decorations);
+        return cable_cell(morph, decorations, labels);
     }
 };
 
diff --git a/test/unit-distributed/test_communicator.cpp b/test/unit-distributed/test_communicator.cpp
index 7836fc54..5884a311 100644
--- a/test/unit-distributed/test_communicator.cpp
+++ b/test/unit-distributed/test_communicator.cpp
@@ -201,7 +201,7 @@ namespace {
                 decor.set_default(arb::cv_policy_fixed_per_branch(10));
                 decor.place(arb::mlocation{0, 0.5}, arb::threshold_detector{10}, "src");
                 decor.place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "tgt");
-                return arb::cable_cell(arb::morphology(tree), {}, decor);
+                return arb::cable_cell(arb::morphology(tree), decor);
             }
             return arb::lif_cell("src", "tgt");
         }
@@ -274,7 +274,7 @@ namespace {
             decor.set_default(arb::cv_policy_fixed_per_branch(10));
             decor.place(arb::mlocation{0, 0.5}, arb::threshold_detector{10}, "src");
             decor.place(arb::ls::uniform(arb::reg::all(), 0, size_, gid), arb::synapse("expsyn"), "tgt");
-            return arb::cable_cell(arb::morphology(tree), {}, decor);
+            return arb::cable_cell(arb::morphology(tree), decor);
         }
         cell_kind get_cell_kind(cell_gid_type gid) const override {
             return cell_kind::cable;
@@ -352,7 +352,7 @@ namespace {
                 decor.place(arb::ls::uniform(arb::reg::all(), 0, 2, gid), arb::threshold_detector{10}, "detectors_0");
                 decor.place(arb::ls::uniform(arb::reg::all(), 3, 3, gid), arb::threshold_detector{10}, "detectors_1");
             }
-            return arb::cable_cell(arb::morphology(tree), {}, decor);
+            return arb::cable_cell(arb::morphology(tree), decor);
         }
 
         cell_kind get_cell_kind(cell_gid_type gid) const override {
diff --git a/test/unit/test_cable_cell.cpp b/test/unit/test_cable_cell.cpp
index 2898a617..1e59691b 100644
--- a/test/unit/test_cable_cell.cpp
+++ b/test/unit/test_cable_cell.cpp
@@ -39,7 +39,7 @@ TEST(cable_cell, lid_ranges) {
     decorations.place(three_sites, synapse("expsyn"), "t3");
     decorations.place("term"_lab, synapse("exp2syn"), "t3");
 
-    cable_cell cell(morph, dict, decorations);
+    cable_cell cell(morph, decorations, dict);
 
     // Get the assigned lid ranges for each placement
     const auto& src_ranges = cell.detector_ranges();
diff --git a/test/unit/test_cv_geom.cpp b/test/unit/test_cv_geom.cpp
index cfca8a58..7d49151b 100644
--- a/test/unit/test_cv_geom.cpp
+++ b/test/unit/test_cv_geom.cpp
@@ -73,7 +73,7 @@ std::ostream& operator<<(std::ostream& out, ::arb::cv_prefer::type p) {
 TEST(cv_geom, empty) {
     using namespace common_morphology;
 
-    cable_cell empty_cell{m_empty};
+    cable_cell empty_cell{m_empty, {}};
     cv_geometry geom(empty_cell, ls::nil());
     EXPECT_TRUE(verify_cv_children(geom));
 
@@ -96,7 +96,7 @@ TEST(cv_geom, trivial) {
         if (p.second.empty()) continue;
 
         SCOPED_TRACE(p.first);
-        cable_cell cell{p.second};
+        cable_cell cell{p.second, {}};
         auto& m = cell.morphology();
 
         // Equivalent ways of specifying one CV comprising whole cell:
@@ -137,7 +137,7 @@ TEST(cv_geom, one_cv_per_branch) {
         if (p.second.empty()) continue;
         SCOPED_TRACE(p.first);
 
-        cable_cell cell{p.second};
+        cable_cell cell{p.second, {}};
         auto& m = cell.morphology();
 
         auto cell_cv_geom = cv_geometry(cell, sum(ls::on_branches(0), ls::on_branches(1)));
@@ -192,7 +192,7 @@ TEST(cv_geom, midpoints) {
         if (p.second.empty()) continue;
         SCOPED_TRACE(p.first);
 
-        cable_cell cell{p.second};
+        cable_cell cell{p.second, {}};
         auto& m = cell.morphology();
 
         cv_geometry geom(cell, ls::on_branches(0.5));
@@ -285,7 +285,7 @@ TEST(cv_geom, weird) {
     using C = mcable;
     using testing::seq_eq;
 
-    cable_cell cell{common_morphology::m_reg_b6};
+    cable_cell cell{common_morphology::m_reg_b6, {}};
     cv_geometry geom(cell, mlocation_list{{1, 0}, {4,0}});
 
     EXPECT_TRUE(verify_cv_children(geom));
@@ -304,7 +304,7 @@ TEST(cv_geom, weird) {
 TEST(cv_geom, location_cv) {
     using namespace common_morphology;
 
-    cable_cell cell{m_reg_b6};
+    cable_cell cell{m_reg_b6, {}};
     auto& m = cell.morphology();
 
     auto cv_extent = [](const cv_geometry& geom, auto cv) {
@@ -451,7 +451,7 @@ TEST(cv_geom, multicell) {
     using namespace common_morphology;
     using index_type = cv_geometry::index_type;
 
-    cable_cell cell = cable_cell(m_reg_b6);
+    cable_cell cell = cable_cell(m_reg_b6, {});
 
     cv_geometry geom(cell, ls::on_branches(0.5));
     unsigned n_cv = geom.size();
@@ -489,7 +489,7 @@ TEST(cv_geom, multicell) {
 TEST(region_cv, empty) {
     using namespace common_morphology;
 
-    cable_cell empty_cell{m_empty};
+    cable_cell empty_cell{m_empty, {}};
     cell_cv_data cv_data(empty_cell, ls::nil());
 
     auto all_cv = intersect_region(reg::all(), cv_data);
@@ -506,7 +506,7 @@ TEST(region_cv, trivial) {
         if (p.second.empty()) continue;
 
         SCOPED_TRACE(p.first);
-        cable_cell cell{p.second};
+        cable_cell cell{p.second, {}};
 
         // One CV comprising whole cell:
         cell_cv_data cell_geom1(cell, ls::nil());
@@ -585,7 +585,7 @@ TEST(region_cv, custom_geometry) {
         decor d;
         // Discretize by segment
         d.set_default(cv_policy_every_segment());
-        auto cell = cable_cell(m, l, d);
+        auto cell = cable_cell(m, d, l);
         auto geom = cv_data(cell);
         EXPECT_TRUE(geom);
 
@@ -643,7 +643,7 @@ TEST(region_cv, custom_geometry) {
           {2, 1}
         });
         d.set_default(cv_policy_explicit(ls));
-        auto cell = cable_cell(m, l, d);
+        auto cell = cable_cell(m, d, l);
         auto geom = cv_data(cell);
         EXPECT_TRUE(geom);
 
diff --git a/test/unit/test_cv_layout.cpp b/test/unit/test_cv_layout.cpp
index d4fbe24d..8852f61a 100644
--- a/test/unit/test_cv_layout.cpp
+++ b/test/unit/test_cv_layout.cpp
@@ -19,7 +19,7 @@ using util::make_span;
 TEST(cv_layout, empty) {
     using namespace common_morphology;
 
-    cable_cell empty_cell{m_empty, {}, {}};
+    cable_cell empty_cell{m_empty, {}};
     fvm_cv_discretization D = fvm_cv_discretize(empty_cell, neuron_parameter_defaults);
 
     EXPECT_TRUE(D.empty());
@@ -50,7 +50,7 @@ TEST(cv_layout, trivial) {
         // they are not 'connected', and will generate multiple CVs.
         if (p.second.branch_children(mnpos).size()>1u) continue;
 
-        cells.emplace_back(p.second, label_dict{}, decor{});
+        cells.emplace_back(p.second, decor{});
         n_cv += !p.second.empty(); // one cv per non-empty cell
     }
 
@@ -95,7 +95,7 @@ TEST(cv_layout, cable) {
     decs.paint(reg::cable(0, 0.0, 0.2), init_membrane_potential{10});
     decs.paint(reg::cable(0, 0.2, 0.7), init_membrane_potential{20});
     decs.paint(reg::cable(0, 0.7, 1.0), init_membrane_potential{30});
-    cable_cell c(morph, {}, decs);
+    cable_cell c(morph, decs);
 
     params.discretization = cv_policy_explicit(ls::nil());
     fvm_cv_discretization D = fvm_cv_discretize(c, params);
@@ -118,7 +118,7 @@ TEST(cv_layout, cable_conductance) {
     auto params = neuron_parameter_defaults;
     params.axial_resistivity = rho;
 
-    cable_cell c(morph, {}, {});
+    cable_cell c(morph, {});
     double radius = c.embedding().radius(mlocation{0, 0.5});
     double length = c.embedding().branch_length(0);
 
@@ -140,7 +140,7 @@ TEST(cv_layout, zero_size_cv) {
     // Six branches; branches 0, 1 and 2 meet at (0, 1); branches
     // 2, 3, 4, and 5 meet at (2, 1). Terminal branches are 1, 3, 4, and 5.
     auto morph = common_morphology::m_reg_b6;
-    cable_cell cell(morph, {}, {});
+    cable_cell cell(morph, {});
 
     auto params = neuron_parameter_defaults;
     const double rho = 5.; // [Ω·cm]
diff --git a/test/unit/test_cv_policy.cpp b/test/unit/test_cv_policy.cpp
index 89279ffc..95f22597 100644
--- a/test/unit/test_cv_policy.cpp
+++ b/test/unit/test_cv_policy.cpp
@@ -38,7 +38,8 @@ TEST(cv_policy, single) {
     // the extremal points of the completions of the components of the
     // supplied region.
 
-    cable_cell cell(m_mlt_b6);
+
+    cable_cell cell(m_mlt_b6, {});
     for (region reg:
             {reg::all(), reg::branch(2), reg::cable(3, 0.25, 1.),
              join(reg::cable(1, 0.75, 1), reg::branch(3), reg::cable(2, 0, 0.5)),
@@ -57,7 +58,7 @@ TEST(cv_policy, explicit_policy) {
 
     cv_policy pol = cv_policy_explicit(lset);
     for (auto& m: {m_reg_b6, m_mlt_b6}) {
-        cable_cell cell(m);
+        cable_cell cell(m, {});
 
         locset result = pol.cv_boundary_points(cell);
         locset expected = join(ls::boundary(reg::all()), lset);
@@ -71,7 +72,7 @@ TEST(cv_policy, explicit_policy) {
     region b12 = join(reg::branch(1), reg::branch(2));
     pol = cv_policy_explicit(lset, b12);
     for (auto& m: {m_reg_b6, m_mlt_b6}) {
-        cable_cell cell(m);
+        cable_cell cell(m, {});
 
         locset result = pol.cv_boundary_points(cell);
         locset expected = as_locset(L{1, 0}, L{1, 0.5}, L{1, 1}, L{2, 0}, L{2, 1});
@@ -83,7 +84,7 @@ TEST(cv_policy, explicit_policy) {
 
     pol = cv_policy_explicit(lset, reg::complete(b12));
     for (auto& m: {m_mlt_b6}) {
-        cable_cell cell(m);
+        cable_cell cell(m, {});
 
         locset result = pol.cv_boundary_points(cell);
         locset expected = as_locset(L{0, 1}, L{1, 0.5}, L{1, 1}, L{2, 1});
@@ -106,7 +107,8 @@ TEST(cv_policy, empty_morphology) {
         cv_policy_explicit(ls::location(0, 0))
     };
 
-    cable_cell cell(m_empty);
+
+    cable_cell cell(m_empty, {});
 
     for (auto& pol: policies) {
         EXPECT_TRUE(locset_eq(cell.provider(), ls::nil(), pol.cv_boundary_points(cell)));
@@ -119,7 +121,7 @@ TEST(cv_policy, fixed_per_branch) {
 
     // Root branch only:
     {
-        cable_cell cell(m_reg_b1);
+        cable_cell cell(m_reg_b1, {});
         {
             // boundary fork points
             cv_policy pol = cv_policy_fixed_per_branch(4);
@@ -138,8 +140,7 @@ TEST(cv_policy, fixed_per_branch) {
     // Multiple top level branches:
     // top level branches are 0 and 3, terminal branches are 1, 2, 4 and 5.
     {
-        cable_cell cell(m_mlt_b6);
-
+        cable_cell cell(m_mlt_b6, {});
         {
             // With boundary fork points:
             cv_policy pol = cv_policy_fixed_per_branch(2);
@@ -167,7 +168,7 @@ TEST(cv_policy, fixed_per_branch) {
     // Restrict to an incomplete subtree (distal half of branch 0 and all of branch 2)
     // in m_mlt_b6 morphology.
     {
-        cable_cell cell(m_mlt_b6);
+        cable_cell cell(m_mlt_b6, {});
         region reg = mcable_list{{0, 0.5, 1.}, {2, 0., 1.}};
         {
             // With two per branch and fork points as boundaries, expect to see:
@@ -204,7 +205,7 @@ TEST(cv_policy, max_extent) {
 
     // Root branch only:
     {
-        cable_cell cell(m_reg_b1);
+        cable_cell cell(m_reg_b1, {});
         ASSERT_EQ(1.0, cell.embedding().branch_length(0));
 
         {
@@ -238,7 +239,7 @@ TEST(cv_policy, max_extent) {
 
     // Cell with varying branch lengths; extent not exact fraction:
     {
-        cable_cell cell(m_mlt_b6);
+        cable_cell cell(m_mlt_b6, {});
         ASSERT_EQ(1.0, cell.embedding().branch_length(0));
         ASSERT_EQ(1.0, cell.embedding().branch_length(1));
         ASSERT_EQ(2.0, cell.embedding().branch_length(2));
@@ -285,7 +286,7 @@ TEST(cv_policy, every_segment) {
 
     // Including all samples:
     {
-        cable_cell cell(m);
+        cable_cell cell(m, {});
         cv_policy pol = cv_policy_every_segment();
 
         mlocation_list expected = {
@@ -298,7 +299,7 @@ TEST(cv_policy, every_segment) {
     }
     // Restricting to the two child branches (disconnected):
     {
-        cable_cell cell(m);
+        cable_cell cell(m, {});
         region reg = join(reg::branch(1), reg::branch(2));
         cv_policy pol = cv_policy_every_segment(reg);
 
@@ -321,7 +322,7 @@ TEST(cv_policy, domain) {
     region reg1 = join(reg::branch(1), reg::cable(2, 0, 0.5));
     region reg2 = join(reg::branch(1), reg::cable(2, 0.5, 1), reg::cable(4, 0, 1));
 
-    cable_cell cell(m_mlt_b6);
+    cable_cell cell(m_mlt_b6, {});
 
     EXPECT_TRUE(region_eq(cell.provider(), reg1, cv_policy_single(reg1).domain()));
     EXPECT_TRUE(region_eq(cell.provider(), reg1, cv_policy_fixed_per_branch(3, reg1).domain()));
@@ -337,7 +338,7 @@ TEST(cv_policy, domain) {
 TEST(cv_policy, combinators) {
     auto unique_sum = [](auto... a) { return ls::support(sum(locset(a)...)); };
 
-    cable_cell cell(m_reg_b6);
+    cable_cell cell(m_reg_b6, {});
     auto eval_locset_eq = [&cell](const locset& a, const cv_policy& p) {
         return locset_eq(cell.provider(), a, p.cv_boundary_points(cell));
     };
diff --git a/test/unit/test_diffusion.cpp b/test/unit/test_diffusion.cpp
index f6d89396..de56e11a 100644
--- a/test/unit/test_diffusion.cpp
+++ b/test/unit/test_diffusion.cpp
@@ -50,7 +50,7 @@ struct linear: public recipe {
     arb::cell_kind get_cell_kind(arb::cell_gid_type)            const override { return arb::cell_kind::cable; }
     std::any get_global_properties(arb::cell_kind)              const override { return gprop; }
     std::vector<arb::probe_info> get_probes(arb::cell_gid_type) const override { return {arb::cable_probe_ion_diff_concentration_cell{"na"}}; }
-    util::unique_any get_cell_description(arb::cell_gid_type)   const override { return arb::cable_cell(morph, {}, decor); }
+    util::unique_any get_cell_description(arb::cell_gid_type)   const override { return arb::cable_cell(morph, decor); }
 
     std::vector<arb::event_generator> event_generators(arb::cell_gid_type gid) const override {
         std::vector<arb::event_generator> result;
diff --git a/test/unit/test_event_delivery.cpp b/test/unit/test_event_delivery.cpp
index ecc135e2..89718a19 100644
--- a/test/unit/test_event_delivery.cpp
+++ b/test/unit/test_event_delivery.cpp
@@ -39,7 +39,7 @@ struct test_recipe: public n_cable_cell_recipe {
         decorations.place(mlocation{0, 0.5}, synapse("expsyn"), "synapse");
         decorations.place(mlocation{0, 0.5}, threshold_detector{-64}, "detector");
         decorations.place(mlocation{0, 0.5}, junction("gj"), "gapjunction");
-        cable_cell c(st, labels, decorations);
+        cable_cell c(st, decorations, labels);
 
         return c;
     }
diff --git a/test/unit/test_fvm_layout.cpp b/test/unit/test_fvm_layout.cpp
index f81c76fd..e937d02d 100644
--- a/test/unit/test_fvm_layout.cpp
+++ b/test/unit/test_fvm_layout.cpp
@@ -1665,7 +1665,7 @@ TEST(fvm_layout, vinterp_cable) {
     // CV midpoints at branch pos 0.1, 0.3, 0.5, 0.7, 0.9.
     // Expect voltage reference locations to be CV modpoints.
     d.set_default(cv_policy_fixed_per_branch(5));
-    cable_cell cell{m, {}, d};
+    cable_cell cell{m, d};
     fvm_cv_discretization D = fvm_cv_discretize(cell, neuron_parameter_defaults);
 
     // Test locations, either side of CV midpoints plus extrema, CV boundaries.
@@ -1725,7 +1725,7 @@ TEST(fvm_layout, vinterp_forked) {
     // and contain branches 1 and 2 respectively, excluding the fork point.
     mlocation_list cv_ends{{1, 0.}, {2, 0.}};
     d.set_default(cv_policy_explicit(cv_ends));
-    cable_cell cell{m, {}, d};
+    cable_cell cell{m, d};
     fvm_cv_discretization D = fvm_cv_discretize(cell, neuron_parameter_defaults);
 
     // Points in branch 0 should only get CV 0 for interpolation.
@@ -1778,11 +1778,11 @@ TEST(fvm_layout, iinterp) {
         decor d;
 
         d.set_default(cv_policy_fixed_per_branch(3));
-        cells.emplace_back(cable_cell{p.second, {}, d});
+        cells.emplace_back(cable_cell{p.second, d});
         label.push_back(p.first+": forks-at-end"s);
 
         d.set_default(cv_policy_fixed_per_branch(3, cv_policy_flag::interior_forks));
-        cells.emplace_back(cable_cell{p.second, {}, d});
+        cells.emplace_back(cable_cell{p.second, d});
         label.push_back(p.first+": interior-forks"s);
     }
 
@@ -1831,7 +1831,7 @@ TEST(fvm_layout, iinterp) {
     // and contain branches 1 and 2 respectively, excluding the fork point.
     mlocation_list cv_ends{{1, 0.}, {2, 0.}};
     d.set_default(cv_policy_explicit(cv_ends));
-    cable_cell cell{m, {}, d};
+    cable_cell cell{m, d};
     D = fvm_cv_discretize(cell, neuron_parameter_defaults);
 
     // Expect axial current interpolations on branches 1 and 2 to match CV 1 and 2
diff --git a/test/unit/test_fvm_lowered.cpp b/test/unit/test_fvm_lowered.cpp
index 0b2cbcaa..a11be575 100644
--- a/test/unit/test_fvm_lowered.cpp
+++ b/test/unit/test_fvm_lowered.cpp
@@ -367,7 +367,7 @@ TEST(fvm_lowered, ac_stimulus) {
 
     // Envelope is linear ramp from 0 to max_time.
     dec.place(mlocation{0, 0}, i_clamp({{0, 0}, {max_time, max_amplitude}, {max_time, 0}}, freq, phase), "clamp");
-    std::vector<cable_cell> cells = {cable_cell(tree, {}, dec)};
+    std::vector<cable_cell> cells = {cable_cell(tree, dec)};
 
     cable_cell_global_properties gprop;
     gprop.default_parameters = neuron_parameter_defaults;
@@ -877,7 +877,7 @@ TEST(fvm_lowered, post_events_shared_state) {
             }
             decor.place(arb::mlocation{0, 0.5}, synapse_, "syanpse");
 
-            return arb::cable_cell(arb::morphology(tree), {}, decor);
+            return arb::cable_cell(arb::morphology(tree), decor);
         }
 
         cell_kind get_cell_kind(cell_gid_type gid) const override {
@@ -969,7 +969,7 @@ TEST(fvm_lowered, label_data) {
                 decor.place(uniform(all(), 4, 4, 42), arb::synapse("expsyn"), "1_synapse");
                 decor.place(uniform(all(), 5, 5, 42), arb::threshold_detector{10}, "1_detector");
 
-                cells_.push_back(arb::cable_cell(arb::morphology(tree), {}, decor));
+                cells_.push_back(arb::cable_cell(arb::morphology(tree), decor));
             }
             {
                 arb::decor decor;
@@ -979,7 +979,7 @@ TEST(fvm_lowered, label_data) {
                 decor.place(uniform(all(), 5, 6, 24), arb::junction("gj"), "2_gap_junctions");
                 decor.place(uniform(all(), 7, 7, 24), arb::junction("gj"), "1_gap_junction");
 
-                cells_.push_back(arb::cable_cell(arb::morphology(tree), {}, decor));
+                cells_.push_back(arb::cable_cell(arb::morphology(tree), decor));
             }
         }
 
diff --git a/test/unit/test_probe.cpp b/test/unit/test_probe.cpp
index a6b3ed6e..6377298d 100644
--- a/test/unit/test_probe.cpp
+++ b/test/unit/test_probe.cpp
@@ -218,7 +218,7 @@ void run_v_cell_probe_test(context ctx) {
         decor d;
         d.set_default(testcase.second);
 
-        cable_cell cell(m, {}, d);
+        cable_cell cell(m, d);
 
         cable1d_recipe rec(cell, false);
         rec.add_probe(0, 0, cable_probe_membrane_voltage_cell{});
@@ -384,7 +384,7 @@ void run_expsyn_g_cell_probe_test(context ctx) {
         }
     }
 
-    std::vector<cable_cell> cells(2, arb::cable_cell(m, {}, d));
+    std::vector<cable_cell> cells(2, arb::cable_cell(m, d));
 
     auto run_test = [&](bool coalesce_synapses) {
         cable1d_recipe rec(cells, coalesce_synapses);
@@ -514,7 +514,7 @@ void run_ion_density_probe_test(context ctx) {
     mlocation loc1{0, 0.5};
     mlocation loc2{0, 0.9};
 
-    cable1d_recipe rec(cable_cell(m, {}, d));
+    cable1d_recipe rec(cable_cell(m, d));
     rec.catalogue() = cat;
 
     // Probe (0, 0): ca internal on CV 0.
@@ -707,8 +707,8 @@ void run_partial_density_probe_test(context ctx) {
     d1.paint(mcable{0, 0.7, 0.8}, mk_mech(10));
     d1.paint(mcable{0, 0.9, 1.0}, mk_mech(11));
 
-    cells[0] = cable_cell(m, {}, d0);
-    cells[1] = cable_cell(m, {}, d1);
+    cells[0] = cable_cell(m, d0);
+    cells[1] = cable_cell(m, d1);
 
     // Place probes in the middle of each 10% interval, i.e. at 0.05, 0.15, etc.
     struct test_probe {
@@ -798,10 +798,10 @@ void run_axial_and_ion_current_sampled_probe_test(context ctx) {
     d.set_default(membrane_capacitance{0.01}); // [F/m²]
     const double tau = 0.1; // [ms]
 
-    cable1d_recipe rec(cable_cell(m, {}, d));
+    cable1d_recipe rec(cable_cell(m, d));
     rec.catalogue() = cat;
 
-    cable_cell cell(m, {}, d);
+    cable_cell cell(m, d);
 
     // Place axial current probes at CV boundaries and make cell-wide probes for
     // total ionic membrane current and stimulus currents.
@@ -950,7 +950,7 @@ void run_multi_probe_test(context ctx) {
     d.paint(reg::branch(2), density("param_as_state", {{"p", 20.}}));
     d.paint(reg::branch(5), density("param_as_state", {{"p", 50.}}));
 
-    auto tracev = run_simple_sampler<double, mlocation>(ctx, 0.1, {cable_cell{m, {}, d}}, 0, cable_probe_density_state{ls::terminal(), "param_as_state", "s"}, {0.});
+    auto tracev = run_simple_sampler<double, mlocation>(ctx, 0.1, {cable_cell{m, d}}, 0, cable_probe_density_state{ls::terminal(), "param_as_state", "s"}, {0.});
 
     // Expect to have received a sample on each of the terminals of branches 1, 2, and 5.
     ASSERT_EQ(3u, tracev.size());
@@ -989,7 +989,7 @@ void run_v_sampled_probe_test(context ctx) {
     d1.place(mlocation{1, 1}, i_clamp::box(0, 1.0, 1.), "clamp1");
     mlocation probe_loc{1, 0.2};
 
-    std::vector<cable_cell> cells = {{bs.morph, bs.labels, d0}, {bs.morph, bs.labels, d1}};
+    std::vector<cable_cell> cells = {{bs.morph, d0, bs.labels}, {bs.morph, d1, bs.labels}};
 
     const double t_end = 1.; // [ms]
     std::vector<double> when = {0.3, 0.6}; // Sample at 0.3 and 0.6 ms.
@@ -1060,7 +1060,7 @@ void run_total_current_probe_test(context ctx) {
         cv_policy policy = cv_policy_fixed_per_branch(n_cv_per_branch, flags);
         d0.set_default(policy);
         d1.set_default(policy);
-        std::vector<cable_cell> cells = {{m, {}, d0}, {m, {}, d1}};
+        std::vector<cable_cell> cells = {{m, d0}, {m, d1}};
 
 
         for (unsigned i = 0; i<2; ++i) {
@@ -1166,7 +1166,7 @@ void run_stimulus_probe_test(context ctx) {
     d1.place(mlocation{0, 1}, i_clamp::box(0., stim_until, -10.), "clamp1");
     double expected_stim1 = 20;
 
-    std::vector<cable_cell> cells = {{m, {}, d0}, {m, {}, d1}};
+    std::vector<cable_cell> cells = {{m, d0}, {m, d1}};
 
     // Sample the cells during the stimulus, and after.
 
@@ -1357,7 +1357,7 @@ TEST(probe, get_probe_metadata) {
     d.paint(reg::branch(2), density("param_as_state", {{"p", 20.}}));
     d.paint(reg::branch(5), density("param_as_state", {{"p", 50.}}));
 
-    cable1d_recipe rec(cable_cell{m, {}, d}, false);
+    cable1d_recipe rec(cable_cell{m, d}, false);
     rec.catalogue() = make_unit_test_catalogue(global_default_catalogue());
     rec.add_probe(0, 7, cable_probe_density_state{ls::terminal(), "param_as_state", "s"});
 
diff --git a/test/unit/test_recipe.cpp b/test/unit/test_recipe.cpp
index ec51e718..27b04cb7 100644
--- a/test/unit/test_recipe.cpp
+++ b/test/unit/test_recipe.cpp
@@ -69,7 +69,7 @@ namespace {
         tree.append(arb::mnpos, {0,0,0,10}, {0,0,20,10}, 1); // soma
         tree.append(0, {0,0, 20, 2}, {0,0, 320, 2}, 3);  // dendrite
 
-        arb::cable_cell cell(tree);
+        arb::cable_cell cell(tree, {});
 
         arb::decor decorations;
 
@@ -88,7 +88,7 @@ namespace {
             decorations.place(arb::mlocation{0,(double)i/num_gj}, arb::junction("gj"), "gapjunction"+std::to_string(i));
         }
 
-        return arb::cable_cell(tree, {}, decorations);
+        return arb::cable_cell(tree, decorations);
     }
 }
 
diff --git a/test/unit/test_sde.cpp b/test/unit/test_sde.cpp
index b43169ce..da527499 100644
--- a/test/unit/test_sde.cpp
+++ b/test/unit/test_sde.cpp
@@ -264,7 +264,7 @@ public:
             segment_tree tree;
             tree.append(mnpos, {i*20., 0, 0.0, 4.0}, {i*20., 0, n1*cv_size, 4.0}, 1);
             tree.append(0, {i*20., 0, ncvs*cv_size, 4.0}, 2);
-            cells_.push_back(cable_cell(morphology(tree), labels, dec));
+            cells_.push_back(cable_cell(morphology(tree), dec, labels));
         }
     }
 
@@ -276,7 +276,7 @@ public:
 
     std::any get_global_properties(cell_kind) const override { return cell_gprop_; }
     
-    sde_recipe& add_probe(probe_tag tag, std::any address) {
+    sde_recipe& add_probe_all_gids(probe_tag tag, std::any address) {
         for (unsigned i=0; i<cells_.size(); ++i) {
             simple_recipe_base::add_probe(i, tag, address);
         }
@@ -541,7 +541,7 @@ TEST(sde, solver) {
     dec.place(*labels.locset("locs"), synapse(m4), "m4");
 
     // a basic sampler: stores result in a vector
-    auto sampler_ = [ncells, nsteps] (std::vector<arb_value_type>& results, unsigned count,
+    auto sampler_ = [nsteps] (std::vector<arb_value_type>& results, unsigned count,
         probe_metadata pm, std::size_t n, sample_record const * samples) {
 
         auto* point_info_ptr = arb::util::any_cast<const std::vector<arb::cable_probe_point_info>*>(pm.meta);
@@ -589,10 +589,10 @@ TEST(sde, solver) {
     sde_recipe rec(ncells, ncvs, labels, dec, false);
 
     // add probes
-    rec.add_probe(1, cable_probe_point_state_cell{m1, "S"});
-    rec.add_probe(2, cable_probe_point_state_cell{m2, "S"});
-    rec.add_probe(3, cable_probe_point_state_cell{m3, "S"});
-    rec.add_probe(4, cable_probe_point_state_cell{m4, "S"});
+    rec.add_probe_all_gids(1, cable_probe_point_state_cell{m1, "S"});
+    rec.add_probe_all_gids(2, cable_probe_point_state_cell{m2, "S"});
+    rec.add_probe_all_gids(3, cable_probe_point_state_cell{m3, "S"});
+    rec.add_probe_all_gids(4, cable_probe_point_state_cell{m4, "S"});
 
     // results are accumulated for each time step
     std::vector<accumulator> stats_m1(nsteps);
@@ -694,7 +694,7 @@ TEST(sde, coupled) {
     dec.place(*labels.locset("locs"), synapse(m1), "m1");
 
     // a basic sampler: stores result in a vector
-    auto sampler_ = [ncells, nsteps] (std::vector<arb_value_type>& results, unsigned count,
+    auto sampler_ = [nsteps] (std::vector<arb_value_type>& results, unsigned count,
         probe_metadata pm, std::size_t n, sample_record const * samples) {
 
         auto* point_info_ptr = arb::util::any_cast<const std::vector<arb::cable_probe_point_info>*>(pm.meta);
@@ -732,8 +732,8 @@ TEST(sde, coupled) {
     sde_recipe rec(ncells, ncvs, labels, dec, false);
 
     // add probes
-    rec.add_probe(1, cable_probe_point_state_cell{m1, "P"});
-    rec.add_probe(2, cable_probe_point_state_cell{m1, "sigma"});
+    rec.add_probe_all_gids(1, cable_probe_point_state_cell{m1, "P"});
+    rec.add_probe_all_gids(2, cable_probe_point_state_cell{m1, "sigma"});
 
     // results are accumulated for each time step
     std::vector<accumulator> stats_P(nsteps);
@@ -854,7 +854,7 @@ public:
             segment_tree tree;
             tree.append(mnpos, {i*20., 0, 0.0, 4.0}, {i*20., 0, n1*cv_size, 4.0}, 1);
             tree.append(0, {i*20., 0, ncvs*cv_size, 4.0}, 2);
-            cells_.push_back(cable_cell(morphology(tree), labels, dec));
+            cells_.push_back(cable_cell(morphology(tree), dec, labels));
         }
     }
 
diff --git a/test/unit/test_spikes.cpp b/test/unit/test_spikes.cpp
index 4e923d79..987dd99f 100644
--- a/test/unit/test_spikes.cpp
+++ b/test/unit/test_spikes.cpp
@@ -230,7 +230,7 @@ TEST(SPIKES_TEST_CLASS, threshold_watcher_interpolation) {
         decor.place("mid"_lab, arb::i_clamp::box(0.01+i*dt, duration, 0.5), "clamp");
         decor.place("mid"_lab, arb::synapse("expsyn"), "synapse");
 
-        arb::cable_cell cell(morpho, dict, decor);
+        arb::cable_cell cell(morpho, decor, dict);
         cable1d_recipe rec({cell});
 
         auto decomp = arb::partition_load_balance(rec, context);
-- 
GitLab