diff --git a/src/algorithms.hpp b/src/algorithms.hpp new file mode 100644 index 0000000000000000000000000000000000000000..db8b33988f9a12b8cb49d5f510c5661c5904f18f --- /dev/null +++ b/src/algorithms.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include <algorithm> +#include <type_traits> + +/* + * Some simple wrappers around stl algorithms to improve readability of code + * that uses them. + * + * For example, a simple sum() wrapper for finding the sum of a container, + * is much more readable than using std::accumulate() + * + */ + +namespace nest { +namespace mc { +namespace algorithms{ + + template <typename C> + typename C::value_type + sum(C const& c) + { + using value_type = typename C::value_type; + return std::accumulate(c.begin(), c.end(), value_type{0}); + } + + template <typename C> + typename C::value_type + mean(C const& c) + { + return sum(c)/c.size(); + } + + template <typename C> + C make_index(C const& c) + { + C out(c.size()+1); + out[0] = 0; + std::partial_sum(c.begin(), c.end(), out.begin()+1); + return out; + } + + /// works like std::is_sorted(), but with stronger condition that succesive + /// elements must be greater than those before them + template <typename C> + bool is_strictly_monotonic_increasing(C const& c) + { + using value_type = typename C::value_type; + return std::is_sorted( + c.begin(), + c.end(), + [] (value_type const& lhs, value_type const& rhs) { + return lhs <= rhs; + } + ); + } + + template <typename C> + bool is_strictly_monotonic_decreasing(C const& c) + { + using value_type = typename C::value_type; + return std::is_sorted( + c.begin(), + c.end(), + [] (value_type const& lhs, value_type const& rhs) { + return lhs >= rhs; + } + ); + } + + template <typename C> + bool is_minimal_degree(C const& c) + { + static_assert( + std::is_integral<typename C::value_type>::value, + "is_minimal_degree only applies to integral types" + ); + for(auto i=0; i<c.size(); ++i) { + if(i<c[i]) { + return false; + } + } + return true; + } + + template <typename C> + bool is_positive(C const& c) + { + static_assert( + std::is_integral<typename C::value_type>::value, + "is_positive only applies to integral types" + ); + for(auto i=0; i<c.size(); ++i) { + if(c[i]<1) { + return false; + } + } + return true; + } + +} // namespace algorithms +} // namespace mc +} // namespace nest diff --git a/src/cell.cpp b/src/cell.cpp index c0d51bc32741c17b73da8215d7076b5325b21cb9..a124eb384837e35215823bc8812f399905f56b68 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1,12 +1,15 @@ #include "cell.hpp" +#include "tree.hpp" -namespace nestmc { +namespace nest { +namespace mc { cell::cell() { // insert a placeholder segment for the soma segments_.push_back(make_segment<placeholder_segment>()); parents_.push_back(0); + stale_ = true; } int cell::num_segments() const @@ -33,6 +36,7 @@ void cell::add_soma(value_type radius, point_type center) else { segments_[0] = make_segment<soma_segment>(radius); } + stale_ = true; } void cell::add_cable(cell::index_type parent, segment_ptr&& cable) @@ -52,6 +56,7 @@ void cell::add_cable(cell::index_type parent, segment_ptr&& cable) } segments_.push_back(std::move(cable)); parents_.push_back(parent); + stale_ = true; } segment* cell::segment(int index) @@ -82,14 +87,21 @@ bool cell::has_soma() const soma_segment* cell::soma() { + stale_ = true; if(has_soma()) { return segment(0)->as_soma(); } return nullptr; } +// this breaks the use of stale_, because the user could modify a cable obtained from +// this call, without the stale_ tag being set. +// +// set stale_ to true, then expect the user to make any modifications before calling +// graph() etc. cable_segment* cell::cable(int index) { + stale_ = true; if(index>0 && index<num_segments()) { return segment(index)->as_cable(); } @@ -125,14 +137,43 @@ std::vector<segment_ptr> const& cell::segments() const return segments_; } +std::vector<int> cell::compartment_counts() const +{ + std::vector<int> comp_count; + comp_count.reserve(num_segments()); + for(auto const& s : segments()) { + comp_count.push_back(s->num_compartments()); + } + return comp_count; + +} + void cell::construct() const { - if(num_segments()) { + std::lock_guard<std::mutex> lock(mutex_); + + if(stale_) { tree_ = cell_tree(parents_); + auto counts = compartment_counts(); + parent_index_ = make_parent_index(tree_.graph(), counts); + segment_index_ = algorithms::make_index(counts); } + stale_ = false; +} + +std::vector<int> const& cell::parent_index() const +{ + construct(); + return parent_index_; +} + +std::vector<int> const& cell::segment_index() const +{ + construct(); + return segment_index_; } -cell_tree const& cell::graph() const +cell_tree const& cell::tree() const { construct(); return tree_; @@ -143,4 +184,5 @@ std::vector<int> const& cell::segment_parents() const return parents_; } -} // namespace nestmc +} // namespace mc +} // namespace nest diff --git a/src/cell.hpp b/src/cell.hpp index b3970ce0bbb2aa78fab1fcc1b3e9c6e375292e08..0a254e1ec006dec417b89ef36fa8f28631a283b7 100644 --- a/src/cell.hpp +++ b/src/cell.hpp @@ -8,108 +8,128 @@ #include "segment.hpp" #include "cell_tree.hpp" -namespace nestmc { +namespace nest { +namespace mc { - // high-level abstract representation of a cell and its segments - class cell { - public: +// high-level abstract representation of a cell and its segments +class cell { + public: - // types - using index_type = int; - using value_type = double; - using point_type = point<value_type>; + // types + using index_type = int; + using value_type = double; + using point_type = point<value_type>; - // constructor - cell(); + // constructor + cell(); - /// add a soma to the cell - /// radius must be specified - void add_soma(value_type radius, point_type center=point_type()); + /// add a soma to the cell + /// radius must be specified + 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 - /// cable is the segment that will be moved into the cell - void add_cable(index_type parent, segment_ptr&& cable); + /// 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); - - /// the number of segments in the cell - int num_segments() const; - - bool has_soma() const; - - class segment* segment(int index); - class segment const* segment(int index) const; - - /// access pointer to the soma - /// returns nullptr if the cell has no soma - soma_segment* soma(); - - /// access pointer to a cable segment - /// will throw an std::out_of_range exception if - /// the cable index is not valid - cable_segment* cable(int index); - - /// 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; - - /// 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: - - /// generate the internal representation of the connectivity - /// graph for the cell segments - void construct() const; - - - // - // 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_; - - // - // fixed cell description, which is computed from the layout description - // this computed whenever a call to the graph() method is made - // the graph method is const, so tree_ is mutable - // - // note: this isn't remotely thread safe! - - mutable cell_tree tree_; - }; - - // create a cable by forwarding cable construction parameters provided by the user + /// 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 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); + void add_cable(index_type parent, Args ...args); + + /// the number of segments in the cell + int num_segments() const; + + bool has_soma() const; + + class segment* segment(int index); + class segment const* segment(int index) const; + + /// access pointer to the soma + /// returns nullptr if the cell has no soma + soma_segment* soma(); + + /// access pointer to a cable segment + /// will throw an std::out_of_range exception if + /// the cable index is not valid + cable_segment* cable(int index); + + /// 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; + + /// the connectivity graph for the cell segments + cell_tree const& tree() const; + + /// return reference to array that enumerates the index of the parent of + /// each segment + std::vector<int> const& segment_parents() const; + + /// return a vector with the compartment count for each segment in the cell + std::vector<int> compartment_counts() const; + + /// return the parent index for the compartments + std::vector<int> const& parent_index() const; + + /// Return the segment index for the compartments + /// the segment index is an index into parent_index for looking + /// up the set of compartments associated with a segment. + /// i.e. the compartments for segment i are in the half open range + /// [segment_index()[i], segmend_index()[i+1]) + std::vector<int> const& segment_index() const; + + private: + + /// generate the internal representation of the connectivity + /// graph for the cell segments + void construct() const; + + // + // 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_; + + // used internally to mark whether derived data (tree_, parent_index_, etc) + // are out of date + mutable bool stale_ = true; + + // + // fixed cell description, which is computed from the layout description + // this computed whenever a call to the graph() method is made + // the graph method is const, so tree_ is mutable + // + + mutable std::mutex mutex_; + mutable cell_tree tree_; + mutable std::vector<int> parent_index_; + mutable std::vector<int> segment_index_; +}; + +// 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" + ); } + stale_ = true; + segments_.push_back(make_segment<cable_segment>(std::forward<Args>(args)...)); + parents_.push_back(parent); +} - -} // namespace nestmc +} // namespace mc +} // namespace nest diff --git a/src/cell_tree.hpp b/src/cell_tree.hpp index 16f7e43f8141375e64855b8bf99d8dd63eea7b59..43fac2429a657545251159a3fa97002b7243fe86 100644 --- a/src/cell_tree.hpp +++ b/src/cell_tree.hpp @@ -13,6 +13,9 @@ #include "tree.hpp" #include "util.hpp" +namespace nest { +namespace mc { + /// The tree data structure that describes the segments of a cell tree. /// A cell is represented as a tree where each node may have any number of /// children. Typically in a cell only the soma has more than two segments, @@ -25,8 +28,8 @@ /// sets it appears that the soma was always index 0, however we need more /// flexibility in choosing the root. class cell_tree { - public : - + using range = memory::Range; +public : // use a signed 16-bit integer for storage of indexes, which is reasonable given // that typical cells have at most 1000-2000 segments using int_type = int16_t; @@ -40,15 +43,12 @@ class cell_tree { cell_tree(std::vector<int> const& parent_index) { // handle the case of an empty parent list, which implies a single-compartment model - std::vector<int> segment_index; if(parent_index.size()>0) { - segment_index = tree_.init_from_parent_index(parent_index); + tree_ = tree(parent_index); } else { - segment_index = tree_.init_from_parent_index(std::vector<int>({0})); + tree_ = tree(std::vector<int>({0})); } - - // if needed, calculate meta-data like length[] and end[] arrays for data } /// construct from a tree @@ -93,6 +93,10 @@ class cell_tree { *this = std::move(other); } + tree const& graph() const { + return tree_; + } + int_type soma() const { return soma_; } @@ -169,8 +173,7 @@ class cell_tree { return depth; } - private : - +private : /// helper type for sub-tree computation /// use in balance() @@ -292,3 +295,6 @@ class cell_tree { int_type soma_ = 0; }; +} // namespace mc +} // namespace nest + diff --git a/src/compartment.hpp b/src/compartment.hpp index 350caed3277fadfe996922c2925649cf56e9c7d4..b92360a560ba5de30a036505d50d457b7b5a5a9c 100644 --- a/src/compartment.hpp +++ b/src/compartment.hpp @@ -1,127 +1,166 @@ #pragma once -#include <cmath> - -#include <vector> - -#include "point.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> -T constexpr pi() { - return 3.141592653589793238462643383279502; -} - -template <typename T> -class compartment { - using value_type = T; - using point_type = point<value_type>; - - constexpr compartment() - : p1_{point_type()}, - p2_{point_type()}, - radius1_{std::numeric_limits<value_type>::quiet_NaN()}, - radius2_{std::numeric_limits<value_type>::quiet_NaN()} - {} +#include <iterator> +#include <utility> + +namespace nest { +namespace mc { + +/// Defines the simplest type of compartment +/// The compartment is a conic frustrum +struct compartment { + using value_type = double; + using size_type = int; + using value_pair = std::pair<value_type, value_type>; - constexpr compartment( - point_type const& p1, - point_type const& p2, + compartment() = delete; + + compartment( + size_type idx, + value_type len, value_type r1, value_type r2 ) - : p1_{p1}, - p2_{p2}, - radius1_{r1}, - radius2_{r2} + : index{idx}, + radius{r1, r2}, + length{len} {} - value_type length() const { - return norm(p1_-p2_); + + size_type index; + std::pair<value_type, value_type> radius; + value_type length; +}; + +/// The simplest type of compartment iterator : +/// - divide a segment into n compartments of equal length +/// - assume that the radius varies linearly from one end of the segment +/// to the other +class compartment_iterator : + public std::iterator<std::forward_iterator_tag, compartment> +{ + + public: + + using base = std::iterator<std::forward_iterator_tag, compartment>; + using size_type = base::value_type::size_type; + using real_type = base::value_type::value_type; + + compartment_iterator() = delete; + + compartment_iterator( + size_type idx, + real_type len, + real_type rad, + real_type delta_rad + ) + : index_(idx), + radius_(rad), + delta_radius_(delta_rad), + length_(len) + { } + + compartment_iterator(compartment_iterator const& other) = default; + + compartment_iterator& operator++() + { + index_++; + radius_ += delta_radius_; + return *this; } - value_type area() const { - return volume_frustrum(length(), radius1_, radius2_); + compartment_iterator operator++(int) + { + compartment_iterator ret(*this); + operator++(); + return ret; } - value_type volume() const { - return volume_frustrum(length(), radius1_, radius2_); + compartment operator*() const + { + return + compartment( + index_, length_, radius_, radius_ + delta_radius_ + ); } - constexpr point_type midpoint() const { - return 0.5*(p1_+p2_); + bool operator==(compartment_iterator const& other) const + { + return other.index_ == index_; } - constexpr point_type midradius() const { - return 0.5*(radius1_+radius2_); + bool operator!=(compartment_iterator const& other) const + { + return !operator==(other); } private : - value_type area_frustrum( - value_type L, - value_type r1, - value_type r2 - ) const + size_type index_; + real_type radius_; + const real_type delta_radius_; + const real_type length_; +}; + +class compartment_range { + public: + + using size_type = compartment_iterator::size_type; + using real_type = compartment_iterator::real_type; + + compartment_range( + size_type num_compartments, + real_type radius_L, + real_type radius_R, + real_type length + ) + : num_compartments_(num_compartments), + radius_L_(radius_L), + radius_R_(radius_R), + length_(length) + {} + + compartment_iterator begin() const { - auto dr = r1 - r2; - return pi<double>() * (r1+r2) * std::sqrt(L*L + dr*dr); + return {0, compartment_length(), radius_L_, delta_radius()}; } - value_type volume_frustrum( - value_type L, - value_type r1, - value_type r2 - ) const + compartment_iterator cbegin() const { - auto meanr = (r1+r2) / 2.; - return pi<double>() * meanr * meanr * L; + return begin(); } - point_type p1_; - point_type p2_; - value_type radius1_; - value_type radius2_; -}; + /// With 0-based indexing compartment number "num_compartments_" is + /// one past the end + compartment_iterator end() const + { + return {num_compartments_, 0, 0, 0}; + } -class segment { + compartment_iterator cend() const + { + return end(); + } - private : + real_type delta_radius() const + { + return (radius_R_ - radius_L_) / num_compartments_; + } - double length_; - double radius_start_; - double radius_end_; - std::vector<compartment> compartments_; - segmentKind kind_; -}; + real_type compartment_length() const + { + return length_ / num_compartments_; + } -class abstract_cell { - abstract_cell() = default; + private: - private : + size_type num_compartments_; + real_type radius_L_; + real_type radius_R_; + real_type length_; }; -} // namespace nestmc +} // namespace mc +} // namespace nest diff --git a/src/fvm.hpp b/src/fvm.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8ddb2938f45b3cf7740becfcc94356b938a99ca9 --- /dev/null +++ b/src/fvm.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include <algorithm> + +#include "cell.hpp" +#include "segment.hpp" + +#include "math.hpp" +#include "util.hpp" +#include "algorithms.hpp" + +#include "../vector/include/Vector.hpp" + +namespace nest { +namespace mc { +namespace fvm { + +template <typename T, typename I> +class fvm_cell { + public : + + /// the real number type + using value_type = T; + /// the integral index type + using size_type = I; + + /// the container used for indexes + using index_type = memory::HostVector<size_type>; + /// view into index container + using index_view = typename index_type::view_type; + + fvm_cell(nest::mc::cell const& cell); +}; + +//////////////////////////////////////////////////////////// +// Implementation +//////////////////////////////////////////////////////////// + +template <typename T, typename I> +fvm_cell<T, I>::fvm_cell(nest::mc::cell const& cell) +{ + auto const& parent_index = cell.parent_index(); + auto const& segment_index = cell.segment_index(); + auto num_fv = segment_index.back(); + + // storage for the volume and surface area of the finite volumes + std::vector<value_type> fv_areas(num_fv); + std::vector<value_type> fv_volumes(num_fv); + + auto seg_idx = 0; + for(auto const& s : cell.segments()) { + if(auto soma = s->as_soma()) { + // make the assumption that the soma is at 0 + if(seg_idx!=0) { + throw std::domain_error( + "FVM lowering encountered soma with non-zero index" + ); + } + fv_volumes[0] += math::volume_sphere(soma->radius()); + fv_areas[0] += math::area_sphere (soma->radius()); + } + else if(auto cable = s->as_cable()) { + using util::left; + using util::right; + + for(auto c : cable->compartments()) { + auto rhs = segment_index[seg_idx] + c.index; + auto lhs = parent_index[rhs]; + + auto rad_C = math::mean(left(c.radius), right(c.radius)); + auto len = c.length/2; + + fv_volumes[lhs] += math::volume_frustrum(len, left(c.radius), rad_C); + fv_areas[lhs] += math::area_frustrum (len, left(c.radius), rad_C); + + fv_volumes[rhs] += math::volume_frustrum(len, right(c.radius), rad_C); + fv_areas[rhs] += math::area_frustrum (len, right(c.radius), rad_C); + } + } + else { + throw std::domain_error( + "FVM lowering encountered unsuported segment type" + ); + } + ++seg_idx; + } + + //std::cout << "volumes " << fv_volumes << " : " << sum(fv_volumes) << "\n"; + std::cout << "areas " << sum(fv_areas) << ", " << cell.volume() << "\n"; + std::cout << "volumes " << sum(fv_volumes) << ", " << cell.area() << "\n"; +} + +} // namespace fvm +} // namespace mc +} // namespace nest diff --git a/src/helpers.hpp b/src/helpers.hpp new file mode 100644 index 0000000000000000000000000000000000000000..cae1e30a5b7b43835cced5292d7f06e11b1c5d7f --- /dev/null +++ b/src/helpers.hpp @@ -0,0 +1,15 @@ +#include <algorithm> + +namespace nestmc { +namespace range{ + + template <typename C> + typename C::value_type + sum(C const& c) + { + using value_type = typename C::value_type; + return std::accumulate(c.begin(), c.end(), value_type{0}); + } + +} +} diff --git a/src/math.hpp b/src/math.hpp index c32c029eb5f03c9f1978b83caa836a8623bb9dc6..af0c402c40b603c8397fbeeb631695e68fa8a58b 100644 --- a/src/math.hpp +++ b/src/math.hpp @@ -2,7 +2,8 @@ #include <cmath> -namespace nestmc { +namespace nest { +namespace mc { namespace math { template <typename T> T constexpr pi() @@ -61,6 +62,7 @@ namespace math { return T(4) * pi<T>() * square(r); } -} // namespace nestmc::math -} // namespace nestmc +} // namespace math +} // namespace mc +} // namespace nest diff --git a/src/parameter_list.cpp b/src/parameter_list.cpp index 4394e800b32137501f3fc48b57ee573898926cad..a6d401b588c6eb8f543dd5f005201410cbaab827 100644 --- a/src/parameter_list.cpp +++ b/src/parameter_list.cpp @@ -3,7 +3,8 @@ #include "parameter_list.hpp" -namespace nestmc { +namespace nest { +namespace mc { bool parameter_list::add_parameter(parameter p) { @@ -75,25 +76,28 @@ namespace nestmc { [&n](parameter const& p) {return p.name == n;} ); } - - - std::ostream& operator<<(std::ostream& o, parameter const& p) - { - return o - << "parameter(" - << "name " << p.name - << " : value " << p.value - << " : range " << p.range - << ")"; - } - - std::ostream& operator<<(std::ostream& o, parameter_list const& l) - { - o << "parameters \"" << l.name() << "\" :\n"; - for(auto const& p : l.parameters()) { - o << " " << p << "\n"; - } - return o; +} // namespace mc +} // namespace nest + +/* +static std::ostream& +operator<<(std::ostream& o, nest::mc::parameter const& p) +{ + return o + << "parameter(" + << "name " << p.name + << " : value " << p.value + << " : range " << p.range + << ")"; +} + +static std::ostream& +operator<<(std::ostream& o, nest::mc::parameter_list const& l) +{ + o << "parameters \"" << l.name() << "\" :\n"; + for(nest::mc::parameter const& p : l.parameters()) { + o << " " << p << "\n"; } - -} // namespace nestmc + return o; +} +*/ diff --git a/src/parameter_list.hpp b/src/parameter_list.hpp index ef790a640d40264585c1dcdb25c9d917a72624bb..114a18d07198aaa2b8230206e517ff4817bda445 100644 --- a/src/parameter_list.hpp +++ b/src/parameter_list.hpp @@ -6,7 +6,8 @@ #include <string> #include <vector> -namespace nestmc { +namespace nest { +namespace mc { template <typename T> struct value_range { @@ -159,5 +160,6 @@ namespace nestmc { } }; -} // namespace nestmc +} // namespace mc +} // namespace nest diff --git a/src/point.hpp b/src/point.hpp index 00ded16ca2b70e3179cc18bb26a004344972c02b..cf74f3238d705e4e28c9430bb42cc85aa2b1ee14 100644 --- a/src/point.hpp +++ b/src/point.hpp @@ -3,6 +3,9 @@ #include <limits> #include <ostream> +namespace nest { +namespace mc { + template <typename T> struct point { using value_type = T; @@ -21,51 +24,61 @@ struct point { z(std::numeric_limits<T>::quiet_NaN()) {} - constexpr bool is_set() const { + constexpr bool is_set() const + { return (x==x && y==y && z==z); } }; template <typename T> -constexpr point<T> operator+ ( +constexpr point<T> +operator+ ( point<T> const& lhs, - point<T> const& rhs) -{ + point<T> const& rhs +) { return point<T>(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); } template <typename T> -constexpr point<T> operator- ( +constexpr point<T> +operator- ( point<T> const& lhs, - point<T> const& rhs) -{ + point<T> const& rhs +) { return point<T>(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); } template <typename T> -constexpr point<T> operator* ( +constexpr point<T> +operator* ( T alpha, - point<T> const& p) -{ + point<T> const& p +) { return point<T>(alpha*p.x, alpha*p.y, alpha*p.z); } template <typename T> -T norm(point<T> const& p) +T +norm(point<T> const& p) { return sqrt(p.x*p.x + p.y*p.y + p.z*p.z); } template <typename T> -constexpr T dot( +constexpr T +dot( point<T> const& lhs, - point<T> const& rhs) -{ + point<T> const& rhs +) { return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z; } +} // namespace mc +} // namespace nest + template <typename T> -std::ostream& operator << (std::ostream& o, point<T> const& p) { +std::ostream& operator << (std::ostream& o, nest::mc::point<T> const& p) +{ return o << "[" << p.x << ", " << p.y << ", " << p.z << "]"; } diff --git a/src/segment.hpp b/src/segment.hpp index ca7251b898ec8800ec71711777bcd44863e618f7..a73dd9219cba674f90fb03d4e11ea44e0ab29dfd 100644 --- a/src/segment.hpp +++ b/src/segment.hpp @@ -4,12 +4,15 @@ #include <vector> +#include "compartment.hpp" #include "math.hpp" #include "parameter_list.hpp" #include "point.hpp" +#include "algorithms.hpp" #include "util.hpp" -namespace nestmc { +namespace nest { +namespace mc { template <typename T, typename valid = typename std::is_floating_point<T>::type> @@ -55,6 +58,9 @@ class segment { return kind_==segmentKind::axon; } + virtual int num_compartments() const = 0; + virtual void set_compartments(int) = 0; + virtual value_type volume() const = 0; virtual value_type area() const = 0; @@ -140,6 +146,14 @@ class placeholder_segment : public segment { return true; } + + int num_compartments() const override + { + return 0; + } + + virtual void set_compartments(int) override + { } }; class soma_segment : public segment @@ -191,6 +205,15 @@ class soma_segment : public segment return this; } + /// soma has one and one only compartments + int num_compartments() const override + { + return 1; + } + + void set_compartments(int n) override + { } + private : // store the center and radius of the soma @@ -230,7 +253,7 @@ class cable_segment : public segment value_type r2, value_type len ) - : cable_segment{k, {r1, r2}, std::vector<value_type>{len}} + : cable_segment{k, {r1, r2}, {len}} { } // constructor that lets the user describe the cable as a @@ -268,7 +291,6 @@ class cable_segment : public segment sum += math::volume_frustrum(lengths_[i], radii_[i], radii_[i+1]); } return sum; - } value_type area() const override @@ -280,12 +302,19 @@ class cable_segment : public segment return sum; } - bool has_locations() const { + value_type length() const + { + return algorithms::sum(lengths_); + } + + bool has_locations() const + { return locations_.size() > 0; } // the number sub-segments that define the cable segment - int num_sub_segments() const { + int num_sub_segments() const + { return radii_.size()-1; } @@ -299,6 +328,53 @@ class cable_segment : public segment return this; } + int num_compartments() const override + { + return num_compartments_; + } + + void set_compartments(int n) override + { + if(n<1) { + throw std::out_of_range( + "number of compartments in a segment must be one or more" + ); + } + num_compartments_ = n; + } + + value_type radius(value_type loc) const + { + if(loc>=1.) return radii_.back(); + if(loc<=0.) return radii_.front(); + + auto len = length(); + value_type pos = loc*len; + + // This could be cached using a partial sum. + // In fact a lot of this stuff can be cached if + // we find ourselves having to do it over and over again. + // The time to cache it might be when update_lengths() is called. + auto sum = value_type(0); + auto i=0; + for(i=0; i<num_sub_segments(); ++i) { + if(sum+lengths_[i]>pos) { + break; + } + sum += lengths_[i]; + } + + auto rel = (len - sum)/lengths_[i]; + + return rel*radii_[i] + (1.-rel)*radii_[i+1]; + } + + /// iterable range type for simple compartment representation + compartment_range compartments() const + { + return {num_compartments(), radii_.front(), radii_.back(), length()}; + } + private : void update_lengths() @@ -311,17 +387,24 @@ class cable_segment : public segment } } + int num_compartments_ = 1; std::vector<value_type> lengths_; std::vector<value_type> radii_; std::vector<point_type> locations_; }; +/// Unique pointer wrapper for abstract segment base class using segment_ptr = std::unique_ptr<segment>; -template <typename T, typename... Args> -segment_ptr make_segment(Args&&... args) { - return segment_ptr(new T(std::forward<Args>(args)...)); +/// Helper for constructing segments in a segment_ptr unique pointer wrapper. +/// Forwards the supplied arguments to construct a segment of type SegmentType. +/// e.g. auto my_cable = make_segment<cable>(segmentKind::dendrite, ... ); +template <typename SegmentType, typename... Args> +segment_ptr make_segment(Args&&... args) +{ + return segment_ptr(new SegmentType(std::forward<Args>(args)...)); } -} // namespace nestmc +} // namespace mc +} // namespace nest diff --git a/src/tree.hpp b/src/tree.hpp index eca2fca6c7e8dd198974128784a6cc2b03b5db3b..27822daad87a1ed8480223e784bbe8ee6b0e8cf1 100644 --- a/src/tree.hpp +++ b/src/tree.hpp @@ -7,11 +7,15 @@ #include <cassert> #include "vector/include/Vector.hpp" +#include "algorithms.hpp" #include "util.hpp" -using range = memory::Range; +namespace nest { +namespace mc { class tree { + using range = memory::Range; + public : using int_type = int16_t; @@ -49,14 +53,21 @@ class tree { /// create the tree from a parent_index template <typename I> - std::vector<I> - init_from_parent_index(std::vector<I> const& parent_index) + tree(std::vector<I> const& parent_index) { + // validate the inputs + if(!algorithms::is_minimal_degree(parent_index)) { + throw std::domain_error( + "parent index used to build a tree did not satisfy minimal degree ordering" + ); + } + // n = number of compartment in cell auto n = parent_index.size(); - // On completion of this loop child_count[i] is the number of children of compartment i - // compensate count for compartment 0, which has itself as its own parent + // On completion of this loop child_count[i] is the number of children + // of compartment i compensate count for compartment 0, which has itself + // as its own parent index_type child_count(n, 0); child_count[0] = -1; for(auto i : parent_index) { @@ -137,9 +148,6 @@ class tree { child_index_[i+1] = child_index_[i]; } child_index_[0] = 0; - - // return the branch index to the caller for later use - return branch_index; } size_t num_children() const { @@ -344,8 +352,46 @@ class tree { // 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); + index_view children_ = data_(0, 0); + index_view child_index_= data_(0, 0); + index_view parents_ = data_(0, 0); }; +template <typename C> +std::vector<int> make_parent_index(tree const& t, C const& counts) +{ + using range = memory::Range; + + if( !algorithms::is_positive(counts) + || counts.size() != t.num_nodes() ) + { + throw std::domain_error( + "make_parent_index requires one non-zero count per segment" + ); + } + auto index = algorithms::make_index(counts); + auto num_compartments = index.back(); + std::vector<int> parent_index(num_compartments); + auto pos = 0; + for(auto i : range(0, t.num_nodes())) { + // get the parent of this segment + // taking care for the case where the root node has -1 as its parent + auto parent = t.parent(i); + parent = parent>=0 ? parent : 0; + parent_index[pos++] = index[parent]; + while(pos<index[i]) { + parent_index[pos] = pos-1; + pos++; + } + } + + // if one of these assertions is tripped, we have to improve + // the input validation above + assert(pos==num_compartments); + assert(algorithms::is_minimal_degree(parent_index)); + + return parent_index; +} + +} // namespace mc +} // namespace nest diff --git a/src/util.hpp b/src/util.hpp index 580981be6e10f4f676057423d1533da5def51a68..738c3d0f6302cdc467a28a467427c50b8a96a593 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -2,6 +2,7 @@ #include "vector/include/Vector.hpp" + /* using memory::util::red; using memory::util::yellow; @@ -13,6 +14,7 @@ using memory::util::cyan; #include <memory> #include <ostream> +#include <utility> #include <vector> template <typename T> @@ -27,12 +29,32 @@ operator << (std::ostream &o, std::vector<T>const& v) return o; } +namespace nest { +namespace mc { namespace util { + // just because we aren't using C++14, doesn't mean we shouldn't go // without make_unique template <typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args) ...)); } -} + + /// helper for taking the first value in a std::pair + template <typename L, typename R> + L const& left(std::pair<L, R> const& p) + { + return p.first; + } + + /// helper for taking the second value in a std::pair + template <typename L, typename R> + R const& right(std::pair<L, R> const& p) + { + return p.second; + } + +} // namespace util +} // namespace mc +} // namespace nest diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 53db3ff016a2765cecef53d807b20d09f5c8eaa1..15d3ec160e9f9a701564c802ee188ebd8845b48e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,12 +14,14 @@ set(TEST_SOURCES # unit tests test_cell.cpp + test_compartments.cpp test_point.cpp + test_algorithms.cpp + test_run.cpp test_segment.cpp test_stimulus.cpp test_swcio.cpp test_tree.cpp - test_run.cpp # unit test driver main.cpp diff --git a/tests/test_algorithms.cpp b/tests/test_algorithms.cpp new file mode 100644 index 0000000000000000000000000000000000000000..342c094ae50a31baf7083f1db89c60e056828a6d --- /dev/null +++ b/tests/test_algorithms.cpp @@ -0,0 +1,169 @@ +#include "gtest.h" + +#include <vector> + +#include "../src/algorithms.hpp" +#include "../src/util.hpp" + +TEST(algorithms, sum) +{ + // sum of 10 times 2 is 20 + std::vector<int> v1(10, 2); + EXPECT_EQ(10*2, nest::mc::algorithms::sum(v1)); + + // make an array 1:20 and sum it up using formula for arithmetic sequence + std::vector<int> v2(20); + std::iota(v2.begin(), v2.end(), 1); + auto n = 20; + EXPECT_EQ((n+1)*n/2, nest::mc::algorithms::sum(v2)); +} + +TEST(algorithms, make_index) +{ + { + std::vector<int> v(10, 1); + auto index = nest::mc::algorithms::make_index(v); + + EXPECT_EQ(index.size(), 11u); + EXPECT_EQ(index.back(), nest::mc::algorithms::sum(v)); + } + + { + std::vector<int> v(10); + std::iota(v.begin(), v.end(), 1); + auto index = nest::mc::algorithms::make_index(v); + + EXPECT_EQ(index.size(), 11u); + EXPECT_EQ(index.back(), nest::mc::algorithms::sum(v)); + } +} + +TEST(algorithms, minimal_degree) +{ + { + std::vector<int> v = {0}; + EXPECT_TRUE(nest::mc::algorithms::is_minimal_degree(v)); + } + + { + std::vector<int> v = {0, 1, 2, 3, 4}; + EXPECT_TRUE(nest::mc::algorithms::is_minimal_degree(v)); + } + + { + std::vector<int> v = {0, 0, 1, 2, 3, 4}; + EXPECT_TRUE(nest::mc::algorithms::is_minimal_degree(v)); + } + + { + std::vector<int> v = {0, 0, 1, 2, 0, 4}; + EXPECT_TRUE(nest::mc::algorithms::is_minimal_degree(v)); + } + + { + std::vector<int> v = {0, 0, 1, 2, 0, 4, 5, 4}; + EXPECT_TRUE(nest::mc::algorithms::is_minimal_degree(v)); + } + + { + std::vector<int> v = {1}; + EXPECT_FALSE(nest::mc::algorithms::is_minimal_degree(v)); + } + + { + std::vector<int> v = {0, 2}; + EXPECT_FALSE(nest::mc::algorithms::is_minimal_degree(v)); + } +} + +TEST(algorithms, is_strictly_monotonic_increasing) +{ + EXPECT_TRUE( + nest::mc::algorithms::is_strictly_monotonic_increasing( + std::vector<int>{0} + ) + ); + EXPECT_TRUE( + nest::mc::algorithms::is_strictly_monotonic_increasing( + std::vector<int>{0, 1, 2, 3} + ) + ); + EXPECT_TRUE( + nest::mc::algorithms::is_strictly_monotonic_increasing( + std::vector<int>{8, 20, 42, 89} + ) + ); + EXPECT_FALSE( + nest::mc::algorithms::is_strictly_monotonic_increasing( + std::vector<int>{0, 0} + ) + ); + EXPECT_FALSE( + nest::mc::algorithms::is_strictly_monotonic_increasing( + std::vector<int>{8, 20, 20, 89} + ) + ); + EXPECT_FALSE( + nest::mc::algorithms::is_strictly_monotonic_increasing( + std::vector<int>{3, 2, 1, 0} + ) + ); +} + +TEST(algorithms, is_strictly_monotonic_decreasing) +{ + EXPECT_TRUE( + nest::mc::algorithms::is_strictly_monotonic_decreasing( + std::vector<int>{0} + ) + ); + EXPECT_TRUE( + nest::mc::algorithms::is_strictly_monotonic_decreasing( + std::vector<int>{3, 2, 1, 0} + ) + ); + EXPECT_FALSE( + nest::mc::algorithms::is_strictly_monotonic_decreasing( + std::vector<int>{0, 1, 2, 3} + ) + ); + EXPECT_FALSE( + nest::mc::algorithms::is_strictly_monotonic_decreasing( + std::vector<int>{8, 20, 42, 89} + ) + ); + EXPECT_FALSE( + nest::mc::algorithms::is_strictly_monotonic_decreasing( + std::vector<int>{0, 0} + ) + ); + EXPECT_FALSE( + nest::mc::algorithms::is_strictly_monotonic_decreasing( + std::vector<int>{8, 20, 20, 89} + ) + ); +} + +TEST(algorithms, is_positive) +{ + EXPECT_TRUE( + nest::mc::algorithms::is_positive( + std::vector<int>{} + ) + ); + EXPECT_TRUE( + nest::mc::algorithms::is_positive( + std::vector<int>{3, 2, 1} + ) + ); + EXPECT_FALSE( + nest::mc::algorithms::is_positive( + std::vector<int>{3, 2, 1, 0} + ) + ); + EXPECT_FALSE( + nest::mc::algorithms::is_positive( + std::vector<int>{-1} + ) + ); +} diff --git a/tests/test_cell.cpp b/tests/test_cell.cpp index 68748512f594702d84a68d89c3eb72ec8010af25..09342b86e8d95a3185af68a903a7f59abdf77f2a 100644 --- a/tests/test_cell.cpp +++ b/tests/test_cell.cpp @@ -7,7 +7,7 @@ TEST(cell_type, soma) // test that insertion of a soma works // define with no centre point { - nestmc::cell c; + nest::mc::cell c; auto soma_radius = 2.1; EXPECT_EQ(c.has_soma(), false); @@ -22,7 +22,7 @@ TEST(cell_type, soma) // test that insertion of a soma works // define with centre point @ (0,0,1) { - nestmc::cell c; + nest::mc::cell c; auto soma_radius = 3.2; EXPECT_EQ(c.has_soma(), false); @@ -40,7 +40,7 @@ TEST(cell_type, soma) TEST(cell_type, add_segment) { - using namespace nestmc; + using namespace nest::mc; // add a pre-defined segment { cell c; @@ -103,7 +103,7 @@ TEST(cell_type, add_segment) TEST(cell_type, multiple_cables) { - using namespace nestmc; + using namespace nest::mc; // generate a cylindrical cable segment of length 1/pi and radius 1 // volume = 1 @@ -145,7 +145,7 @@ TEST(cell_type, multiple_cables) EXPECT_EQ(c.area(), 8. + math::area_sphere(soma_radius)); // construct the graph - auto const& con = c.graph(); + auto const& con = c.tree(); EXPECT_EQ(con.num_segments(), 5u); EXPECT_EQ(con.parent(0), -1); diff --git a/tests/test_compartments.cpp b/tests/test_compartments.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4a40f1f8bf69be50d7d17413d117dc4fc0b979c6 --- /dev/null +++ b/tests/test_compartments.cpp @@ -0,0 +1,131 @@ +#include "gtest.h" + +#include "../src/compartment.hpp" +#include "../src/util.hpp" + +using nest::mc::util::left; +using nest::mc::util::right; + +// not much to test here: just test that values passed into the constructor +// are correctly stored in members +TEST(compartments, compartment) +{ + + { + nest::mc::compartment c(100, 1.2, 2.1, 2.2); + EXPECT_EQ(c.index, 100); + EXPECT_EQ(c.length, 1.2); + EXPECT_EQ(left(c.radius), 2.1); + EXPECT_EQ(right(c.radius), 2.2); + + auto c2 = c; + EXPECT_EQ(c2.index, 100); + EXPECT_EQ(c2.length, 1.2); + EXPECT_EQ(left(c2.radius), 2.1); + EXPECT_EQ(right(c2.radius), 2.2); + } + + { + nest::mc::compartment c{100, 1, 2, 3}; + EXPECT_EQ(c.index, 100); + EXPECT_EQ(c.length, 1.); + EXPECT_EQ(left(c.radius), 2.); + EXPECT_EQ(right(c.radius), 3.); + } +} + +TEST(compartments, compartment_iterator) +{ + // iterator with arguments + // idx = 0 + // len = 2.5 + // rad = 1 + // delta_rad = 2 + nest::mc::compartment_iterator it(0, 2.5, 1, 2); + + // so each time the iterator is incremented + // idx is incremented by 1 + // len is unchanged + // rad is incremented by 2 + + // check the prefix increment + ++it; + { + auto c = *it; + EXPECT_EQ(c.index, 1); + EXPECT_EQ(left(c.radius), 3.0); + EXPECT_EQ(right(c.radius), 5.0); + EXPECT_EQ(c.length, 2.5); + } + + // check postfix increment + + // returned iterator should be unchanged + { + auto c = *(it++); + EXPECT_EQ(c.index, 1); + EXPECT_EQ(left(c.radius), 3.0); + EXPECT_EQ(right(c.radius), 5.0); + EXPECT_EQ(c.length, 2.5); + } + // while the iterator itself was updated + { + auto c = *it; + EXPECT_EQ(c.index, 2); + EXPECT_EQ(left(c.radius), 5.0); + EXPECT_EQ(right(c.radius), 7.0); + EXPECT_EQ(c.length, 2.5); + } + + // check that it can be copied and compared + { + // copy iterator + auto it2 = it; + auto c = *it2; + EXPECT_EQ(c.index, 2); + EXPECT_EQ(left(c.radius), 5.0); + EXPECT_EQ(right(c.radius), 7.0); + EXPECT_EQ(c.length, 2.5); + + // comparison + EXPECT_EQ(it2, it); + it2++; + EXPECT_NE(it2, it); + + // check the copy has updated correctly when incremented + c= *it2; + EXPECT_EQ(c.index, 3); + EXPECT_EQ(left(c.radius), 7.0); + EXPECT_EQ(right(c.radius), 9.0); + EXPECT_EQ(c.length, 2.5); + } +} + +TEST(compartments, compartment_range) +{ + { + nest::mc::compartment_range rng(10, 1.0, 2.0, 10.); + + EXPECT_EQ((*rng.begin()).index, 0); + EXPECT_EQ((*rng.end()).index, 10); + EXPECT_NE(rng.begin(), rng.end()); + + auto count = 0; + for(auto c : rng) { + EXPECT_EQ(c.index, count); + auto er = 1.0 + double(count)/10.; + EXPECT_TRUE(std::fabs(left(c.radius)-er)<1e-15); + EXPECT_TRUE(std::fabs(right(c.radius)-(er+0.1))<1e-15); + EXPECT_EQ(c.length, 1.0); + ++count; + } + EXPECT_EQ(count, 10); + } + + // test case of zero length range + { + nest::mc::compartment_range rng(0, 1.0, 1.0, 0.); + + EXPECT_EQ(rng.begin(), rng.end()); + } +} diff --git a/tests/test_fvm.cpp b/tests/test_fvm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/test_point.cpp b/tests/test_point.cpp index 57a8567b94e4a690ca40eb246987120c5721a8d3..9666a1387f895d408778f40afc6b07db5363bec6 100644 --- a/tests/test_point.cpp +++ b/tests/test_point.cpp @@ -5,6 +5,8 @@ #include "../src/point.hpp" +using namespace nest::mc; + TEST(point, construction) { { diff --git a/tests/test_run.cpp b/tests/test_run.cpp index 618d9e79560ea0764d7c22ec844749dc8ec51ffa..74b505c6e71c66bc32ffca346caae1ec57262b3c 100644 --- a/tests/test_run.cpp +++ b/tests/test_run.cpp @@ -1,19 +1,20 @@ #include "gtest.h" #include "../src/cell.hpp" +#include "../src/fvm.hpp" TEST(run, init) { - using namespace nestmc; + using namespace nest::mc; - nestmc::cell cell; + nest::mc::cell cell; cell.add_soma(12.6157/2.0); //auto& props = cell.soma()->properties; cell.add_cable(0, segmentKind::dendrite, 0.5, 0.5, 200); - EXPECT_EQ(cell.graph().num_segments(), 2u); + EXPECT_EQ(cell.tree().num_segments(), 2u); /* for(auto &s : cell.segments()) { @@ -41,19 +42,23 @@ TEST(run, init) EXPECT_EQ(cell.soma()->mechanism("hh").get("gl").value, 0.0003); EXPECT_EQ(cell.soma()->mechanism("hh").get("el").value, -54.3); + + cell.segment(1)->set_compartments(2); + + //using fvm_cell = fvm::fvm_cell<double, int>; + //fvm_cell fvcell(cell); // print out the parameters if you want... //std::cout << soma_hh << "\n"; - } // test out the parameter infrastructure TEST(run, parameters) { - nestmc::parameter_list list("test"); + nest::mc::parameter_list list("test"); EXPECT_EQ(list.name(), "test"); EXPECT_EQ(list.num_parameters(), 0); - nestmc::parameter p("a", 0.12, {0, 10}); + nest::mc::parameter p("a", 0.12, {0, 10}); // add_parameter() returns a bool that indicates whether // it was able to successfull add the parameter diff --git a/tests/test_segment.cpp b/tests/test_segment.cpp index 81f70aebe63cf0e97d3122f400c0d80e0ab6f593..9c3df625db9ef87110474d35a5ce88f3f4e07f09 100644 --- a/tests/test_segment.cpp +++ b/tests/test_segment.cpp @@ -7,8 +7,8 @@ TEST(segments, soma) { - using namespace nestmc; - using nestmc::math::pi; + using namespace nest::mc; + using nest::mc::math::pi; { auto s = make_segment<soma_segment>(1.0); @@ -29,8 +29,8 @@ TEST(segments, soma) TEST(segments, cable) { - using namespace nestmc; - using nestmc::math::pi; + using namespace nest::mc; + using nest::mc::math::pi; // take advantage of fact that a cable segment with constant radius 1 and // length 1 has volume=1. and area=2 @@ -63,8 +63,8 @@ TEST(segments, cable) TEST(segments, cable_positions) { - using namespace nestmc; - using nestmc::math::pi; + using namespace nest::mc; + using nest::mc::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) diff --git a/tests/test_tree.cpp b/tests/test_tree.cpp index 58435e15240344665921799dbf07ff8eca211f5c..36b273a064a9d3fe15102a745f732b05f7f71f99 100644 --- a/tests/test_tree.cpp +++ b/tests/test_tree.cpp @@ -11,6 +11,8 @@ using json = nlohmann::json; using range = memory::Range; +using namespace nest::mc; + TEST(cell_tree, from_parent_index) { // tree with single branch corresponding to the root node // this is equivalent to a single compartment model @@ -137,8 +139,7 @@ TEST(tree, change_root) { // | // 2 std::vector<int> parent_index = {0,0,0}; - tree t; - t.init_from_parent_index(parent_index); + tree t(parent_index); t.change_root(1); EXPECT_EQ(t.num_nodes(), 3u); @@ -156,8 +157,7 @@ TEST(tree, change_root) { // / \ | // 3 4 4 std::vector<int> parent_index = {0,0,0,1,1}; - tree t; - t.init_from_parent_index(parent_index); + tree t(parent_index); t.change_root(1u); EXPECT_EQ(t.num_nodes(), 5u); @@ -181,8 +181,7 @@ TEST(tree, change_root) { // / \. // 5 6 std::vector<int> parent_index = {0,0,0,1,1,4,4}; - tree t; - t.init_from_parent_index(parent_index); + tree t(parent_index); t.change_root(1); @@ -239,7 +238,8 @@ TEST(cell_tree, balance) { // this test doesn't test anything yet... it just loads each cell in turn // from a json file and creates a .dot file for it -TEST(cell_tree, json_load) { +TEST(cell_tree, json_load) +{ json cell_data; std::ifstream("../data/cells_small.json") >> cell_data; @@ -251,3 +251,13 @@ TEST(cell_tree, json_load) { } } +TEST(tree, make_parent_index) +{ + { + std::vector<int> parent_index = {0,0,1,2,3,4,3,6,0,8}; + std::vector<int> counts = {1,3,2,2,2}; + tree t(parent_index); + auto new_pid = make_parent_index(t, counts); + EXPECT_EQ(parent_index.size(), new_pid.size()); + } +}