diff --git a/src/cell.hpp b/src/cell.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..df22c1aa52d8a8c7e8ff2da302c07cfb1b7d772e
--- /dev/null
+++ b/src/cell.hpp
@@ -0,0 +1,114 @@
+#pragma once
+
+#include <vector>
+#include <stdexcept>
+
+#include "segment.hpp"
+#include "cell_tree.hpp"
+
+namespace nestmc {
+
+    // we probably need two cell types
+    //  1. the abstract cell type (which would be this one)
+    //  2.
+    class cell {
+        public:
+        using index_type = int16_t;
+        using value_type = double;
+        using point_type = point<value_type>;
+
+        int num_segments() const
+        {
+            return segments_.size();
+        }
+
+        void add_soma(value_type radius, point_type center=point_type())
+        {
+            if(has_soma()) {
+                throw std::domain_error(
+                    "attempt to add a soma to a cell that already has one"
+                );
+            }
+
+            // soma has intself as its own parent
+            soma_ = num_segments();
+            parents_.push_back(num_segments());
+
+            // add segment for the soma
+            if(center.is_set()) {
+                segments_.push_back(
+                    make_segment<soma_segment>(radius, center)
+                );
+            }
+            else {
+                segments_.push_back(
+                    make_segment<soma_segment>(radius)
+                );
+            }
+        }
+
+        void add_cable(segment_ptr&& cable, index_type parent)
+        {
+            // check for a valid parent id
+            if(cable->is_soma()) {
+                throw std::domain_error(
+                    "attempt to add a soma as a segment"
+                );
+            }
+
+            // check for a valid parent id
+            if(parent>num_segments()) {
+                throw std::out_of_range(
+                    "parent index of cell segment is out of range"
+                );
+            }
+            segments_.push_back(std::move(cable));
+            parents_.push_back(parent);
+        }
+
+        template <typename... Args>
+        void add_cable(index_type parent, Args ...args)
+        {
+            // check for a valid parent id
+            if(parent>num_segments()) {
+                throw std::out_of_range(
+                    "parent index of cell segment is out of range"
+                );
+            }
+            segments_.push_back(make_segment<cable_segment>(std::forward<Args>(args)...));
+            parents_.push_back(parent);
+        }
+
+        bool has_soma() const { return soma_ > -1; }
+
+        soma_segment* soma() {
+            if(has_soma()) {
+                return segments_[soma_].get()->as_soma();
+            }
+            return nullptr;
+        }
+
+        private:
+
+        //
+        // the local description of the cell which can be modified by the user
+        // in a ad-hoc manner (adding segments, modifying segments, etc)
+        //
+
+        // storage for connections
+        std::vector<index_type> parents_;
+        // the segments
+        std::vector<segment_ptr> segments_;
+        // index of the soma
+        int soma_ = -1;
+
+        //
+        // fixed cell description, which is computed from the
+        // rough layout description above
+        //
+
+        // cell_tree that describes the connection layout
+        //cell_tree tree_;
+    };
+
+} // namespace nestmc
diff --git a/src/cell_tree.hpp b/src/cell_tree.hpp
index 0d4c496f459821c59caa80319d1536bba71b3671..c09f647bde9317dedb4ac924585c1fcf54463062 100644
--- a/src/cell_tree.hpp
+++ b/src/cell_tree.hpp
@@ -50,13 +50,15 @@ class cell_tree {
 
     /// construct from a tree
     // copy constructor
-    cell_tree(tree const& t)
-    : tree_(t)
+    cell_tree(tree const& t, int s)
+    : tree_(t),
+      soma_(s)
     { }
 
     // move constructor
-    cell_tree(tree&& t)
-    : tree_(std::move(t))
+    cell_tree(tree&& t, int s)
+    : tree_(std::move(t)),
+      soma_(s)
     { }
 
     /// construct from a cell tree
@@ -166,12 +168,11 @@ class cell_tree {
         }
 
         std::string to_string() const {
-            std::string s;
-
-            s += "[" + std::to_string(root) + ","
-                + std::to_string(diameter)  + "," + std::to_string(depth) + "]";
-
-            return s;
+            return
+               "[" + std::to_string(root) + ","
+                   + std::to_string(diameter)  + ","
+                   + std::to_string(depth) +
+               "]";
         }
 
         int root;
diff --git a/src/math.hpp b/src/math.hpp
index d936824966c59d18758f232bd4b351ce77cdeca1..e28c135d9e03d00516d36ad4f6e1254d40fa5f54 100644
--- a/src/math.hpp
+++ b/src/math.hpp
@@ -3,24 +3,60 @@
 #include <cmath>
 
 namespace nestmc {
-
+namespace math {
     template <typename T>
     T constexpr pi() {
-        return 3.141592653589793238462643383279502;
+        return T(3.1415926535897932384626433832795);
+    }
+
+    template <typename T>
+    T constexpr mean(T a, T b) {
+        return (a+b) / T(2);
+    }
+
+    template <typename T>
+    T constexpr square(T a) {
+        return a*a;
+    }
+
+    template <typename T>
+    T constexpr cube(T a) {
+        return a*a*a;
     }
 
+    // volume of a conic frustrum
     template <typename T>
-    T area_frustrum(T L, T r1, T r2)
+    T constexpr area_frustrum(T L, T r1, T r2)
     {
-        auto dr = r1 - r2;
-        return pi<double>() * (r1+r2) * sqrt(L*L + dr*dr);
+        return pi<T>() * (r1+r2) * sqrt(square(L) + square(r1-r2));
     }
 
+    // surface area of conic frustrum, not including the area of the
+    // circles at either end (i.e. just the area of the surface of rotation)
     template <typename T>
-    T volume_frustrum(T L, T r1, T r2)
+    T constexpr volume_frustrum(T L, T r1, T r2)
     {
-        auto meanr = (r1+r2) / 2.;
-        return pi<double>() * meanr * meanr * L;
+        // this formulation uses one less multiplication
+        return pi<T>()/T(3) * (square(r1+r2) - r1*r2) * L;
+        //return pi<T>()/T(3) * (square(r1) + r1*r2 + square(r2)) * L;
     }
 
+    // volume of a sphere
+    //   four-thirds pi r-cubed
+    template <typename T>
+    T constexpr volume_sphere(T r)
+    {
+        return T(4)/T(3) * pi<T>() * cube(r);
+    }
+
+    // surface area of a sphere
+    //   four pi r-squared
+    template <typename T>
+    T constexpr area_sphere(T r)
+    {
+        return T(4) * pi<T>() * square(r);
+    }
+
+} // namespace nestmc::math
 } // namespace nestmc
+
diff --git a/src/point.hpp b/src/point.hpp
index fc3f0daea1f6d44779ecf9a36784ab3e48580eae..00ded16ca2b70e3179cc18bb26a004344972c02b 100644
--- a/src/point.hpp
+++ b/src/point.hpp
@@ -11,7 +11,7 @@ struct point {
     value_type z;
 
     constexpr point(T a, T b, T c)
-    : x(a), y(b), z(c)
+    : x{a}, y{b}, z{c}
     {}
 
     // initialize to NaN by default
@@ -20,6 +20,10 @@ struct point {
       y(std::numeric_limits<T>::quiet_NaN()),
       z(std::numeric_limits<T>::quiet_NaN())
     {}
+
+    constexpr bool is_set() const {
+        return (x==x && y==y && z==z);
+    }
 };
 
 template <typename T>
diff --git a/src/segment.hpp b/src/segment.hpp
index 6cefbd78cd45b8013b69a2f07a5ce18211fd8ed7..dfb4cf49195f8d1f0774c901e62cf6db1ff03ccd 100644
--- a/src/segment.hpp
+++ b/src/segment.hpp
@@ -36,6 +36,11 @@ enum class segmentKind {
     axon
 };
 
+// forward declarations of segment specializations
+class soma_segment;
+class cable_segment;
+
+// abstract base class for a cell segment
 class segment {
     public:
 
@@ -46,17 +51,42 @@ class segment {
         return kind_;
     }
 
+    bool is_soma() const
+    {
+        return kind_==segmentKind::soma;
+    }
+
+    bool is_dendrite() const
+    {
+        return kind_==segmentKind::dendrite;
+    }
+
+    bool is_axon() const
+    {
+        return kind_==segmentKind::axon;
+    }
+
     virtual value_type volume() const = 0;
-    virtual value_type area()  const = 0;
+    virtual value_type area()   const = 0;
 
     virtual ~segment() = default;
 
+    virtual cable_segment* as_cable()
+    {
+        return nullptr;
+    }
+
+    virtual soma_segment* as_soma()
+    {
+        return nullptr;
+    }
+
     protected:
 
     segmentKind kind_;
 };
 
-class spherical_segment : public segment
+class soma_segment : public segment
 {
     public :
 
@@ -65,16 +95,16 @@ class spherical_segment : public segment
     using base::value_type;
     using base::point_type;
 
-    spherical_segment() = delete;
+    soma_segment() = delete;
 
-    spherical_segment(value_type r)
+    soma_segment(value_type r)
     :   radius_{r}
     {
         kind_ = segmentKind::soma;
     }
 
-    spherical_segment(value_type r, point_type const &c)
-    :   spherical_segment(r)
+    soma_segment(value_type r, point_type const &c)
+    :   soma_segment(r)
     {
         center_ = c;
         kind_ = segmentKind::soma;
@@ -82,15 +112,28 @@ class spherical_segment : public segment
 
     value_type volume() const override
     {
-        return 4./3. * pi<value_type>() * radius_ * radius_ * radius_;
+        return math::volume_sphere(radius_);
     }
 
     value_type area() const override
     {
-        return 4. * pi<value_type>() * radius_ * radius_;
+        return math::area_sphere(radius_);
+    }
+
+    value_type radius() const
+    {
+        return radius_;
     }
 
-    virtual ~spherical_segment() = default;
+    point_type const& center() const
+    {
+        return center_;
+    }
+
+    soma_segment* as_soma() override
+    {
+        return this;
+    }
 
     private :
 
@@ -100,61 +143,121 @@ class spherical_segment : public segment
     point_type center_;
 };
 
-class frustrum_segment : public segment
+class cable_segment : public segment
 {
     public :
 
-    using segment::kind_;
     using base = segment;
+    using base::kind_;
     using base::value_type;
     using base::point_type;
 
-    frustrum_segment() = delete;
+    // delete the default constructor
+    cable_segment() = delete;
 
-    frustrum_segment(
+    // constructors for a cable with no location information
+    cable_segment(
+        segmentKind k,
+        std::vector<value_type> r,
+        std::vector<value_type> lens
+    ) {
+        kind_ = k;
+        assert(k==segmentKind::dendrite || k==segmentKind::axon);
+
+        radii_   = std::move(r);
+        lengths_ = std::move(lens);
+    }
+
+    cable_segment(
         segmentKind k,
         value_type r1,
         value_type r2,
         value_type len
+    )
+    : cable_segment{k, {r1, r2}, std::vector<value_type>{len}}
+    { }
+
+    // constructor that lets the user describe the cable as a
+    // seriew of radii and locations
+    cable_segment(
+        segmentKind k,
+        std::vector<value_type> r,
+        std::vector<point_type> p
     ) {
-        r1_ = r1;
-        r2_ = r2;
-        length_ = len;
         kind_ = k;
-        assert(k==segmentKind::dendrite || k ==segmentKind::axon);
+        assert(k==segmentKind::dendrite || k==segmentKind::axon);
+
+        radii_     = std::move(r);
+        locations_ = std::move(p);
+        update_lengths();
     }
 
-    frustrum_segment(
+    // helper that lets user specify with the radius and location
+    // of just the end points of the cable
+    //  i.e.    describing the cable as a single frustrum
+    cable_segment(
         segmentKind k,
         value_type r1,
         value_type r2,
         point_type const& p1,
         point_type const& p2
     )
-    : frustrum_segment(k, r1, r2, norm(p1-p2))
+    :   cable_segment(k, {r1, r2}, {p1, p2})
+    { }
+
+    value_type volume() const override
     {
-        p1_ = p1;
-        p2_ = p2;
+        auto sum = value_type{0};
+        for(auto i=0; i<num_sub_segments(); ++i) {
+            sum += math::volume_frustrum(lengths_[i], radii_[i], radii_[i+1]);
+        }
+        return sum;
+
     }
 
-    value_type volume() const override
+    value_type area() const override
     {
-        return volume_frustrum(length_, r1_, r2_);
+        auto sum = value_type{0};
+        for(auto i=0; i<num_sub_segments(); ++i) {
+            sum += math::area_frustrum(lengths_[i], radii_[i], radii_[i+1]);
+        }
+        return sum;
+    }
+
+    bool has_locations() const {
+        return locations_.size() > 0;
+    }
+
+    // the number sub-segments that define the cable segment
+    int num_sub_segments() const {
+        return radii_.size()-1;
     }
-    value_type area()   const override
+
+    std::vector<value_type> const& lengths() const
     {
-        return area_frustrum(length_, r1_, r2_);
+        return lengths_;
     }
 
-    virtual ~frustrum_segment() = default;
+    cable_segment* as_cable() override
+    {
+        return this;
+    }
 
     private :
 
-    value_type length_;
-    value_type r1_;
-    value_type r2_;
-    point_type p1_;
-    point_type p2_;
+    void update_lengths()
+    {
+        if(locations_.size()) {
+            lengths_.resize(num_sub_segments());
+            for(auto i=0; i<num_sub_segments(); ++i) {
+                lengths_[i] = norm(locations_[i] - locations_[i+1]);
+            }
+        }
+    }
+
+    std::vector<value_type> lengths_;
+    std::vector<value_type> radii_;
+    std::vector<point_type> locations_;
 };
 
 using segment_ptr = std::unique_ptr<segment>;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 4c921c290c5459bce2f48306843d307f2a6470c7..d246268247d147eb7e608296af52bab191b616f7 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,10 +1,11 @@
 set(HEADERS
-    ../src/swcio.hpp
-    ../src/tree.hpp
+    ../src/cell.hpp
     ../src/cell_tree.hpp
-    ../src/point.hpp
     ../src/math.hpp
+    ../src/point.hpp
     ../src/segment.hpp
+    ../src/swcio.hpp
+    ../src/tree.hpp
 )
 
 set(TEST_SOURCES
@@ -12,10 +13,11 @@ set(TEST_SOURCES
     gtest-all.cpp
 
     # unit tests
-    test_swcio.cpp
-    test_tree.cpp
+    test_cell.cpp
     test_point.cpp
     test_segment.cpp
+    test_swcio.cpp
+    test_tree.cpp
 
     # unit test driver
     main.cpp
diff --git a/tests/test_cell.cpp b/tests/test_cell.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dfc3481712bab208104c9cbedb2de90ecb02c83a
--- /dev/null
+++ b/tests/test_cell.cpp
@@ -0,0 +1,102 @@
+#include "gtest.h"
+
+#include "../src/cell.hpp"
+
+TEST(cell_type, soma)
+{
+    // test that insertion of a soma works
+    //      define with no centre point
+    {
+        nestmc::cell c;
+        auto soma_radius = 2.1;
+
+        EXPECT_EQ(c.has_soma(), false);
+        c.add_soma(soma_radius);
+        EXPECT_EQ(c.has_soma(), true);
+
+        auto s = c.soma();
+        EXPECT_EQ(s->radius(), soma_radius);
+        EXPECT_EQ(s->center().is_set(), false);
+    }
+
+    // test that insertion of a soma works
+    //      define with centre point @ (0,0,1)
+    {
+        nestmc::cell c;
+        auto soma_radius = 3.2;
+
+        EXPECT_EQ(c.has_soma(), false);
+        c.add_soma(soma_radius, {0,0,1});
+        EXPECT_EQ(c.has_soma(), true);
+
+        auto s = c.soma();
+        EXPECT_EQ(s->radius(), soma_radius);
+        EXPECT_EQ(s->center().is_set(), true);
+
+        // add expression template library for points
+        //EXPECT_EQ(s->center(), point<double>(0,0,1));
+    }
+}
+
+TEST(cell_type, add_segment)
+{
+    using namespace nestmc;
+    //  add a pre-defined segment
+    {
+        cell c;
+
+        auto soma_radius  = 2.1;
+        auto cable_radius = 0.1;
+        auto cable_length = 8.3;
+
+        // add a soma because we need something to attach the first dendrite to
+        c.add_soma(soma_radius, {0,0,1});
+
+        auto seg =
+            make_segment<cable_segment>(
+                segmentKind::dendrite,
+                cable_radius, cable_radius, cable_length
+            );
+        c.add_cable(std::move(seg), 0);
+
+        EXPECT_EQ(c.num_segments(), 2);
+    }
+
+    //  add segment on the fly
+    {
+        cell c;
+
+        auto soma_radius  = 2.1;
+        auto cable_radius = 0.1;
+        auto cable_length = 8.3;
+
+        // add a soma because we need something to attach the first dendrite to
+        c.add_soma(soma_radius, {0,0,1});
+
+        c.add_cable(
+            0,
+            segmentKind::dendrite, cable_radius, cable_radius, cable_length
+        );
+
+        EXPECT_EQ(c.num_segments(), 2);
+    }
+    {
+        cell c;
+
+        auto soma_radius  = 2.1;
+        auto cable_radius = 0.1;
+        auto cable_length = 8.3;
+
+        // add a soma because we need something to attach the first dendrite to
+        c.add_soma(soma_radius, {0,0,1});
+
+        c.add_cable(
+            0,
+            segmentKind::dendrite,
+            std::vector<double>{cable_radius, cable_radius, cable_radius, cable_radius},
+            std::vector<double>{cable_length, cable_length, cable_length}
+        );
+
+        EXPECT_EQ(c.num_segments(), 2);
+    }
+}
diff --git a/tests/test_point.cpp b/tests/test_point.cpp
index 9fbb62a2fd6819caf1e1b97a2c62013d98886f24..f795b72de29ff6bac8c931745f1d02a3d5866a5a 100644
--- a/tests/test_point.cpp
+++ b/tests/test_point.cpp
@@ -17,7 +17,7 @@ TEST(point, construction)
 
     {
         // initializer list
-        point<float> p = {1, 2, 3};
+        point<float> p{1, 2, 3};
         EXPECT_EQ(p.x, 1.);
         EXPECT_EQ(p.y, 2.);
         EXPECT_EQ(p.z, 3.);
diff --git a/tests/test_segment.cpp b/tests/test_segment.cpp
index 94dabca0b3f113124116dfa19b454aed98b47222..81f70aebe63cf0e97d3122f400c0d80e0ab6f593 100644
--- a/tests/test_segment.cpp
+++ b/tests/test_segment.cpp
@@ -4,29 +4,95 @@
 
 #include "../src/segment.hpp"
 
-TEST(segments, sphere)
+
+TEST(segments, soma)
 {
     using namespace nestmc;
+    using nestmc::math::pi;
 
     {
-        auto s = make_segment<spherical_segment>(1.0);
+        auto s = make_segment<soma_segment>(1.0);
 
-        EXPECT_EQ(s->volume(), nestmc::pi<double>()*4./3.);
-        EXPECT_EQ(s->area(),   nestmc::pi<double>()*4.);
-        EXPECT_EQ(s->kind(),   nestmc::segmentKind::soma);
+        EXPECT_EQ(s->volume(), pi<double>()*4./3.);
+        EXPECT_EQ(s->area(),   pi<double>()*4.);
+        EXPECT_EQ(s->kind(),   segmentKind::soma);
     }
 
     {
-        auto s = make_segment<spherical_segment>(1.0, point<double>(0., 1., 2.));
+        auto s = make_segment<soma_segment>(1.0, point<double>(0., 1., 2.));
 
-        EXPECT_EQ(s->volume(), nestmc::pi<double>()*4./3.);
-        EXPECT_EQ(s->area(),   nestmc::pi<double>()*4.);
-        EXPECT_EQ(s->kind(),   nestmc::segmentKind::soma);
+        EXPECT_EQ(s->volume(), pi<double>()*4./3.);
+        EXPECT_EQ(s->area(),   pi<double>()*4.);
+        EXPECT_EQ(s->kind(),   segmentKind::soma);
     }
 }
 
-TEST(segments, frustrum)
+TEST(segments, cable)
 {
+    using namespace nestmc;
+    using nestmc::math::pi;
+
+    // take advantage of fact that a cable segment with constant radius 1 and
+    // length 1 has volume=1. and area=2
+    auto length = 1./pi<double>();
+    auto radius = 1.;
+
+    // single cylindrical frustrum
     {
+        auto s = make_segment<cable_segment>(segmentKind::dendrite, radius, radius, length);
+
+        EXPECT_EQ(s->volume(), 1.0);
+        EXPECT_EQ(s->area(),   2.0);
+        EXPECT_EQ(s->kind(),   segmentKind::dendrite);
+    }
+
+    // cable made up of three identical cylindrical frustrums
+    {
+        auto s =
+            make_segment<cable_segment>(
+                segmentKind::axon,
+                std::vector<double>{radius, radius, radius, radius},
+                std::vector<double>{length, length, length}
+            );
+
+        EXPECT_EQ(s->volume(), 3.0);
+        EXPECT_EQ(s->area(),   6.0);
+        EXPECT_EQ(s->kind(),   segmentKind::axon);
+    }
+}
+
+TEST(segments, cable_positions)
+{
+    using namespace nestmc;
+    using nestmc::math::pi;
+
+    // single frustrum of length 1 and radii 1 and 2
+    // the centre of each end are at the origin (0,0,0) and (0,1,0)
+    {
+        auto s =
+            make_segment<cable_segment>(
+                segmentKind::dendrite,
+                1, 2,
+                point<double>(0,0,0), point<double>(0,1,0)
+            );
+
+        EXPECT_EQ(s->volume(), math::volume_frustrum(1., 1., 2.));
+        EXPECT_EQ(s->area(),   math::area_frustrum  (1., 1., 2.));
+        EXPECT_EQ(s->kind(),   segmentKind::dendrite);
+    }
+
+    // cable made up of three frustrums
+    // that emulate the frustrum from the previous single-frustrum case
+    {
+        auto s =
+            make_segment<cable_segment>(
+                segmentKind::axon,
+                std::vector<double>{1, 1.5, 2},
+                std::vector<point<double>>{ {0,0,0}, {0,0.5,0}, {0,1,0} }
+            );
+
+        EXPECT_EQ(s->volume(), math::volume_frustrum(1., 1., 2.));
+        EXPECT_EQ(s->area(),   math::area_frustrum(1., 1., 2.));
+        EXPECT_EQ(s->kind(),   segmentKind::axon);
     }
 }