diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 90c9d8f8c4446615bfd3e045cd64a082a0d1d016..457f38aa6b2e9d6c898a759e821fdf5e06326bd9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ set(HEADERS ) set(BASE_SOURCES swcio.cpp + cell.cpp ) add_library(cellalgo ${BASE_SOURCES} ${HEADERS}) diff --git a/src/cell.cpp b/src/cell.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5d9cec1b22e43d265f89ec3998f3c33e566f6ebc --- /dev/null +++ b/src/cell.cpp @@ -0,0 +1,115 @@ +#include "cell.hpp" + +namespace nestmc { + +int cell::num_segments() const +{ + return segments_.size(); +} + +void cell::add_soma(value_type radius, point_type center) +{ + 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) + ); + } +} + +// add a cable that is provided by the user as a segment_ptr +void cell::add_cable(cell::index_type parent, segment_ptr&& cable) +{ + // 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); +} + + +bool cell::has_soma() const +{ + return soma_ > -1; +} + +soma_segment* cell::soma() +{ + if(has_soma()) { + return segments_[soma_].get()->as_soma(); + } + return nullptr; +} + +cell::value_type cell::volume() const +{ + return + std::accumulate( + segments_.begin(), segments_.end(), + 0., + [](double value, segment_ptr const& seg) { + return seg->volume() + value; + } + ); +} + +cell::value_type cell::area() const +{ + return + std::accumulate( + segments_.begin(), segments_.end(), + 0., + [](double value, segment_ptr const& seg) { + return seg->area() + value; + } + ); +} + +std::vector<segment_ptr> const& cell::segments() const +{ + return segments_; +} + +void cell::construct() +{ + if(num_segments()) { + tree_ = cell_tree(parents_); + } +} + +cell_tree const& cell::graph() const +{ + return tree_; +} + +std::vector<int> const& cell::segment_parents() const +{ + return parents_; +} + +} // namespace nestmc diff --git a/src/cell.hpp b/src/cell.hpp index df22c1aa52d8a8c7e8ff2da302c07cfb1b7d772e..bc3d49e6289099b59e24e813c1a91e6b386c16c5 100644 --- a/src/cell.hpp +++ b/src/cell.hpp @@ -8,85 +8,56 @@ namespace nestmc { - // we probably need two cell types - // 1. the abstract cell type (which would be this one) - // 2. + // high-level abstract representation of a cell and its segments class cell { public: - using index_type = int16_t; + + // types + using index_type = int; using value_type = double; using point_type = point<value_type>; - int num_segments() const - { - return segments_.size(); - } + /// add a soma to the cell + /// radius must be specified + void add_soma(value_type radius, point_type center=point_type()); - 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); - } + /// add a cable + /// parent is the index of the parent segment for the cable section + /// cable is the segment that will be moved into the cell + void add_cable(index_type parent, segment_ptr&& cable); + /// add a cable by constructing it in place + /// parent is the index of the parent segment for the cable section + /// args are the arguments to be used to consruct the new cable 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); - } + void add_cable(index_type parent, Args ...args); - bool has_soma() const { return soma_ > -1; } + /// the number of segments in the cell + int num_segments() const; - soma_segment* soma() { - if(has_soma()) { - return segments_[soma_].get()->as_soma(); - } - return nullptr; - } + bool has_soma() const; + + /// access pointer to the soma + soma_segment* soma(); + + /// the volume of the cell + value_type volume() const; + + /// the surface area of the cell + value_type area() const; + + std::vector<segment_ptr> const& segments() const; + + /// generate the internal representation of the connectivity + /// graph for the cell segments + void construct(); + + /// the connectivity graph for the cell segments + cell_tree const& graph() const; + + /// return reference to array that enumerates the index of the parent of + /// each segment + std::vector<int> const& segment_parents() const; private: @@ -103,12 +74,27 @@ namespace nestmc { int soma_ = -1; // - // fixed cell description, which is computed from the - // rough layout description above + // fixed cell description, which is computed from the layout description + // above // - // cell_tree that describes the connection layout - //cell_tree tree_; + cell_tree tree_; }; + // create a cable by forwarding cable construction parameters provided by the user + template <typename... Args> + void cell::add_cable(cell::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); + } + + } // namespace nestmc + diff --git a/src/cell_tree.hpp b/src/cell_tree.hpp index c09f647bde9317dedb4ac924585c1fcf54463062..16f7e43f8141375e64855b8bf99d8dd63eea7b59 100644 --- a/src/cell_tree.hpp +++ b/src/cell_tree.hpp @@ -33,6 +33,9 @@ class cell_tree { using index_type = memory::HostVector<int_type>; using index_view = index_type::view_type; + /// default empty constructor + cell_tree() = default; + /// construct from a parent index cell_tree(std::vector<int> const& parent_index) { @@ -68,11 +71,27 @@ class cell_tree { soma_(other.soma()) { } + // assignment from rvalue + cell_tree& operator=(cell_tree&& other) + { + std::swap(other.tree_, tree_); + std::swap(other.soma_, soma_); + return *this; + } + + // assignment + cell_tree& operator=(cell_tree const& other) + { + tree_ = other.tree_; + soma_ = other.soma_; + return *this; + } + // move constructor cell_tree(cell_tree&& other) - : tree_(std::move(other.tree_)), - soma_(other.soma()) - { } + { + *this = std::move(other); + } int_type soma() const { return soma_; @@ -262,8 +281,14 @@ class cell_tree { } } - // storage for the tree structure of cell segments + ////////////////////////////////////////////////// + // state + ////////////////////////////////////////////////// + + /// storage for the tree structure of cell segments tree tree_; + /// index of the soma int_type soma_ = 0; }; + diff --git a/src/math.hpp b/src/math.hpp index e28c135d9e03d00516d36ad4f6e1254d40fa5f54..c32c029eb5f03c9f1978b83caa836a8623bb9dc6 100644 --- a/src/math.hpp +++ b/src/math.hpp @@ -5,22 +5,26 @@ namespace nestmc { namespace math { template <typename T> - T constexpr pi() { + T constexpr pi() + { return T(3.1415926535897932384626433832795); } template <typename T> - T constexpr mean(T a, T b) { + T constexpr mean(T a, T b) + { return (a+b) / T(2); } template <typename T> - T constexpr square(T a) { + T constexpr square(T a) + { return a*a; } template <typename T> - T constexpr cube(T a) { + T constexpr cube(T a) + { return a*a*a; } diff --git a/src/segment.hpp b/src/segment.hpp index dfb4cf49195f8d1f0774c901e62cf6db1ff03ccd..7710ed9714aa112dea731cf27bfd648027bcca16 100644 --- a/src/segment.hpp +++ b/src/segment.hpp @@ -8,28 +8,15 @@ #include "point.hpp" #include "util.hpp" -/* - We start with a high-level description of the cell - - list of branches of the cell - - soma, dendrites, axons - - spatial locations if provided - - bare minimum spatial information required is length and radius - at each end for each of the branches, and a soma radius - - model properties of each branch - - mechanisms - - clamps - - synapses - - list of compartments if they have been provided - - This description is not used for solving the system - From the description we can then build a cell solver - - e.g. the FVM formulation - - e.g. Green's functions - -*/ - namespace nestmc { +template <typename T, + typename valid = typename std::is_floating_point<T>::type> +struct segment_properties { + T rL = 180.0; // resistivity [Ohm.cm] + T cm = 0.01; // capacitance [F/m^2] : 10 nF/mm^2 = 0.01 F/m^2 +}; + enum class segmentKind { soma, dendrite, @@ -81,6 +68,8 @@ class segment { return nullptr; } + segment_properties<value_type> properties; + protected: segmentKind kind_; diff --git a/src/tree.hpp b/src/tree.hpp index 6b53aa6f329490a3c431817445f9b2be4dda25b1..eca2fca6c7e8dd198974128784a6cc2b03b5db3b 100644 --- a/src/tree.hpp +++ b/src/tree.hpp @@ -19,20 +19,34 @@ class tree { using index_type = memory::HostVector<int_type>; using index_view = index_type::view_type; + tree() = default; + + tree& operator=(tree&& other) { + std::swap(data_, other.data_); + std::swap(child_index_, other.child_index_); + std::swap(children_, other.children_); + std::swap(parents_, other.parents_); + return *this; + } + + tree& operator=(tree const& other) { + data_ = other.data_; + set_ranges(other.num_nodes()); + return *this; + } + + // copy constructors take advantage of the assignment operators + // defined above tree(tree const& other) - : data_(other.data_) { - set_ranges(other.num_nodes()); + *this = other; } tree(tree&& other) - : data_(std::move(other.data_)) { - set_ranges(other.num_nodes()); + *this = std::move(other); } - tree() = default; - /// create the tree from a parent_index template <typename I> std::vector<I> @@ -135,7 +149,10 @@ class tree { return child_index_[b+1] - child_index_[b]; } size_t num_nodes() const { - return child_index_.size() - 1; + // the number of nodes is the size of the child index minus 1 + // ... except for the case of an empty tree + auto sz = child_index_.size(); + return sz ? sz - 1 : 0; } /// return the child index @@ -219,19 +236,26 @@ class tree { } void set_ranges(int nnode) { - auto nchild = nnode - 1; - // data_ is partitioned as follows: - // data_ = [children_[nchild], child_index_[nnode+1], parents_[nnode]] - assert(data_.size() == unsigned(nchild + (nnode+1) + nnode)); - children_ = data_(0, nchild); - child_index_ = data_(nchild, nchild+nnode+1); - parents_ = data_(nchild+nnode+1, memory::end); - - // check that arrays have appropriate size - // this should be moved into a unit test - assert(children_.size() == unsigned(nchild)); - assert(child_index_.size() == unsigned(nnode+1)); - assert(parents_.size() == unsigned(nnode)); + if(nnode) { + auto nchild = nnode - 1; + // data_ is partitioned as follows: + // data_ = [children_[nchild], child_index_[nnode+1], parents_[nnode]] + assert(data_.size() == unsigned(nchild + (nnode+1) + nnode)); + children_ = data_(0, nchild); + child_index_ = data_(nchild, nchild+nnode+1); + parents_ = data_(nchild+nnode+1, memory::end); + + // check that arrays have appropriate size + // this should be moved into a unit test + assert(children_.size() == unsigned(nchild)); + assert(child_index_.size() == unsigned(nnode+1)); + assert(parents_.size() == unsigned(nnode)); + } + else { + children_ = data_(0, 0); + child_index_ = data_(0, 0); + parents_ = data_(0, 0); + } } /// Renumber the sub-tree with old_node as its root with new_node as @@ -304,15 +328,24 @@ class tree { } } if(add_parent_as_child) { - new_node = add_children(new_node, old_tree.parent(old_node), old_node, p, old_tree); + new_node = + add_children( + new_node, old_tree.parent(old_node), old_node, p, old_tree + ); } return new_node; } + ////////////////////////////////////////////////// + // state + ////////////////////////////////////////////////// index_type data_; - index_view children_; - index_view child_index_; - index_view parents_; + // provide default parameters so that tree type can + // be default constructed + index_view children_ = data_(0,0); + index_view child_index_= data_(0,0); + index_view parents_ = data_(0,0); }; + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d246268247d147eb7e608296af52bab191b616f7..3bfe22c59526798ea1108ad1862a7e418a734182 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,6 +18,7 @@ set(TEST_SOURCES test_segment.cpp test_swcio.cpp test_tree.cpp + test_run.cpp # unit test driver main.cpp diff --git a/tests/test_cell.cpp b/tests/test_cell.cpp index dfc3481712bab208104c9cbedb2de90ecb02c83a..d9cb96b8e816d1942438d98ff13a5ee3e1bba940 100644 --- a/tests/test_cell.cpp +++ b/tests/test_cell.cpp @@ -57,7 +57,7 @@ TEST(cell_type, add_segment) segmentKind::dendrite, cable_radius, cable_radius, cable_length ); - c.add_cable(std::move(seg), 0); + c.add_cable(0, std::move(seg)); EXPECT_EQ(c.num_segments(), 2); } @@ -100,3 +100,66 @@ TEST(cell_type, add_segment) EXPECT_EQ(c.num_segments(), 2); } } + +TEST(cell_type, multiple_cables) +{ + using namespace nestmc; + + // generate a cylindrical cable segment of length 1/pi and radius 1 + // volume = 1 + // area = 2 + auto seg = [](segmentKind k) { + return make_segment<cable_segment>( k, 1.0, 1.0, 1./math::pi<double>() ); + }; + + // add a pre-defined segment + { + cell c; + + auto soma_radius = std::pow(3./(4.*math::pi<double>()), 1./3.); + + // cell strucure as follows + // left : segment numbering + // right : segment type (soma, axon, dendrite) + // + // 0 s + // / \ / \. + // 1 2 d a + // / \ / \. + // 3 4 d d + + // add a soma + c.add_soma(soma_radius, {0,0,1}); + + // hook the dendrite and axons + c.add_cable(0, seg(segmentKind::dendrite)); + c.add_cable(0, seg(segmentKind::axon)); + c.add_cable(1, seg(segmentKind::dendrite)); + c.add_cable(1, seg(segmentKind::dendrite)); + + EXPECT_EQ(c.num_segments(), 5); + // each of the 5 segments has volume 1 by design + EXPECT_EQ(c.volume(), 5.); + // each of the 4 cables has volume 2., and the soma has an awkward area + // that isn't a round number + EXPECT_EQ(c.area(), 8. + math::area_sphere(soma_radius)); + + // construct the graph + c.construct(); + auto const& con = c.graph(); + + EXPECT_EQ(con.num_segments(), 5u); + EXPECT_EQ(con.parent(0), -1); + EXPECT_EQ(con.parent(1), 0); + EXPECT_EQ(con.parent(2), 0); + EXPECT_EQ(con.parent(3), 1); + EXPECT_EQ(con.parent(4), 1); + EXPECT_EQ(con.num_children(0), 2u); + EXPECT_EQ(con.num_children(1), 2u); + EXPECT_EQ(con.num_children(2), 0u); + EXPECT_EQ(con.num_children(3), 0u); + EXPECT_EQ(con.num_children(4), 0u); + + } +} + diff --git a/tests/test_run.cpp b/tests/test_run.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9ac528f7a5768c08b8043d019f9207b7339506e7 --- /dev/null +++ b/tests/test_run.cpp @@ -0,0 +1,17 @@ +#include "gtest.h" + +#include "../src/cell.hpp" + +TEST(run, init) +{ + using namespace nestmc; + + nestmc::cell cell; + + cell.add_soma(18.8); + auto& props = cell.soma()->properties; + + cell.construct(); + + EXPECT_EQ(cell.graph().num_segments(), 1u); +}