Skip to content
Snippets Groups Projects
Commit 6738e37f authored by Benjamin Cumming's avatar Benjamin Cumming
Browse files

basic abstract cell

parent 74f32058
Branches
Tags
No related merge requests found
...@@ -3,6 +3,7 @@ set(HEADERS ...@@ -3,6 +3,7 @@ set(HEADERS
) )
set(BASE_SOURCES set(BASE_SOURCES
swcio.cpp swcio.cpp
cell.cpp
) )
add_library(cellalgo ${BASE_SOURCES} ${HEADERS}) add_library(cellalgo ${BASE_SOURCES} ${HEADERS})
#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
...@@ -8,85 +8,56 @@ ...@@ -8,85 +8,56 @@
namespace nestmc { namespace nestmc {
// we probably need two cell types // high-level abstract representation of a cell and its segments
// 1. the abstract cell type (which would be this one)
// 2.
class cell { class cell {
public: public:
using index_type = int16_t;
// types
using index_type = int;
using value_type = double; using value_type = double;
using point_type = point<value_type>; using point_type = point<value_type>;
int num_segments() const /// add a soma to the cell
{ /// radius must be specified
return segments_.size(); void add_soma(value_type radius, point_type center=point_type());
}
void add_soma(value_type radius, point_type center=point_type()) /// add a cable
{ /// parent is the index of the parent segment for the cable section
if(has_soma()) { /// cable is the segment that will be moved into the cell
throw std::domain_error( void add_cable(index_type parent, segment_ptr&& cable);
"attempt to add a soma to a cell that already has one"
);
}
// soma has intself as its own parent /// add a cable by constructing it in place
soma_ = num_segments(); /// parent is the index of the parent segment for the cable section
parents_.push_back(num_segments()); /// args are the arguments to be used to consruct the new cable
template <typename... Args>
void add_cable(index_type parent, Args ...args);
// add segment for the soma /// the number of segments in the cell
if(center.is_set()) { int num_segments() const;
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) bool has_soma() const;
{
// 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 /// access pointer to the soma
if(parent>num_segments()) { soma_segment* soma();
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> /// the volume of the cell
void add_cable(index_type parent, Args ...args) value_type volume() const;
{
// 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; } /// the surface area of the cell
value_type area() const;
soma_segment* soma() { std::vector<segment_ptr> const& segments() const;
if(has_soma()) {
return segments_[soma_].get()->as_soma(); /// generate the internal representation of the connectivity
} /// graph for the cell segments
return nullptr; 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: private:
...@@ -103,12 +74,27 @@ namespace nestmc { ...@@ -103,12 +74,27 @@ namespace nestmc {
int soma_ = -1; int soma_ = -1;
// //
// fixed cell description, which is computed from the // fixed cell description, which is computed from the layout description
// rough layout description above // 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 } // namespace nestmc
...@@ -33,6 +33,9 @@ class cell_tree { ...@@ -33,6 +33,9 @@ class cell_tree {
using index_type = memory::HostVector<int_type>; using index_type = memory::HostVector<int_type>;
using index_view = index_type::view_type; using index_view = index_type::view_type;
/// default empty constructor
cell_tree() = default;
/// construct from a parent index /// construct from a parent index
cell_tree(std::vector<int> const& parent_index) cell_tree(std::vector<int> const& parent_index)
{ {
...@@ -68,11 +71,27 @@ class cell_tree { ...@@ -68,11 +71,27 @@ class cell_tree {
soma_(other.soma()) 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 // move constructor
cell_tree(cell_tree&& other) cell_tree(cell_tree&& other)
: tree_(std::move(other.tree_)), {
soma_(other.soma()) *this = std::move(other);
{ } }
int_type soma() const { int_type soma() const {
return soma_; return soma_;
...@@ -262,8 +281,14 @@ class cell_tree { ...@@ -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_; tree tree_;
/// index of the soma
int_type soma_ = 0; int_type soma_ = 0;
}; };
...@@ -5,22 +5,26 @@ ...@@ -5,22 +5,26 @@
namespace nestmc { namespace nestmc {
namespace math { namespace math {
template <typename T> template <typename T>
T constexpr pi() { T constexpr pi()
{
return T(3.1415926535897932384626433832795); return T(3.1415926535897932384626433832795);
} }
template <typename T> template <typename T>
T constexpr mean(T a, T b) { T constexpr mean(T a, T b)
{
return (a+b) / T(2); return (a+b) / T(2);
} }
template <typename T> template <typename T>
T constexpr square(T a) { T constexpr square(T a)
{
return a*a; return a*a;
} }
template <typename T> template <typename T>
T constexpr cube(T a) { T constexpr cube(T a)
{
return a*a*a; return a*a*a;
} }
......
...@@ -8,28 +8,15 @@ ...@@ -8,28 +8,15 @@
#include "point.hpp" #include "point.hpp"
#include "util.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 { 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 { enum class segmentKind {
soma, soma,
dendrite, dendrite,
...@@ -81,6 +68,8 @@ class segment { ...@@ -81,6 +68,8 @@ class segment {
return nullptr; return nullptr;
} }
segment_properties<value_type> properties;
protected: protected:
segmentKind kind_; segmentKind kind_;
......
...@@ -19,20 +19,34 @@ class tree { ...@@ -19,20 +19,34 @@ class tree {
using index_type = memory::HostVector<int_type>; using index_type = memory::HostVector<int_type>;
using index_view = index_type::view_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) tree(tree const& other)
: data_(other.data_)
{ {
set_ranges(other.num_nodes()); *this = other;
} }
tree(tree&& 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 /// create the tree from a parent_index
template <typename I> template <typename I>
std::vector<I> std::vector<I>
...@@ -135,7 +149,10 @@ class tree { ...@@ -135,7 +149,10 @@ class tree {
return child_index_[b+1] - child_index_[b]; return child_index_[b+1] - child_index_[b];
} }
size_t num_nodes() const { 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 /// return the child index
...@@ -219,6 +236,7 @@ class tree { ...@@ -219,6 +236,7 @@ class tree {
} }
void set_ranges(int nnode) { void set_ranges(int nnode) {
if(nnode) {
auto nchild = nnode - 1; auto nchild = nnode - 1;
// data_ is partitioned as follows: // data_ is partitioned as follows:
// data_ = [children_[nchild], child_index_[nnode+1], parents_[nnode]] // data_ = [children_[nchild], child_index_[nnode+1], parents_[nnode]]
...@@ -233,6 +251,12 @@ class tree { ...@@ -233,6 +251,12 @@ class tree {
assert(child_index_.size() == unsigned(nnode+1)); assert(child_index_.size() == unsigned(nnode+1));
assert(parents_.size() == unsigned(nnode)); 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 /// Renumber the sub-tree with old_node as its root with new_node as
/// the new index of old_node. This is a helper function for the /// the new index of old_node. This is a helper function for the
...@@ -304,15 +328,24 @@ class tree { ...@@ -304,15 +328,24 @@ class tree {
} }
} }
if(add_parent_as_child) { 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; return new_node;
} }
//////////////////////////////////////////////////
// state
//////////////////////////////////////////////////
index_type data_; index_type data_;
index_view children_; // provide default parameters so that tree type can
index_view child_index_; // be default constructed
index_view parents_; index_view children_ = data_(0,0);
index_view child_index_= data_(0,0);
index_view parents_ = data_(0,0);
}; };
...@@ -18,6 +18,7 @@ set(TEST_SOURCES ...@@ -18,6 +18,7 @@ set(TEST_SOURCES
test_segment.cpp test_segment.cpp
test_swcio.cpp test_swcio.cpp
test_tree.cpp test_tree.cpp
test_run.cpp
# unit test driver # unit test driver
main.cpp main.cpp
......
...@@ -57,7 +57,7 @@ TEST(cell_type, add_segment) ...@@ -57,7 +57,7 @@ TEST(cell_type, add_segment)
segmentKind::dendrite, segmentKind::dendrite,
cable_radius, cable_radius, cable_length 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); EXPECT_EQ(c.num_segments(), 2);
} }
...@@ -100,3 +100,66 @@ TEST(cell_type, add_segment) ...@@ -100,3 +100,66 @@ TEST(cell_type, add_segment)
EXPECT_EQ(c.num_segments(), 2); 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);
}
}
#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);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment