From 76120d16c00ae67128c3c69421ab712f985f3445 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Fri, 18 Oct 2024 13:30:35 +0200 Subject: [PATCH] Refactor discretization. (#2415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR rectifies a long-standing issue since the introduction of `decor`: CV discretization was treated as a defaultable value instead of a cell property, along with bio-physical properties. This lead to some awkward facts: - Intermingling of bio-physics and numerical settings - Discretization is treated like a default without override. - Discretization cannot be changed once set, e.g. if read from `.acc`, requiring the copy+update of a decor instead Thus: - Discretization can now be set during `cable_cell` construction - It can be read and updated later - Refactor `cv_policy` to use type erasure like other, similar constructions, e.g. schedules. - Hide internal `cv_policy` constructors. ⚠️ Breaks `.acc` by upgrading from 0.9 to 0.10 ⚠️ Closes #1408 --------- Co-authored-by: boeschf <48126478+boeschf@users.noreply.github.com> --- .github/workflows/sanitize.yml | 8 +- .github/workflows/test-matrix.yml | 4 +- CMakeLists.txt | 16 +- arbor/cable_cell.cpp | 18 +- arbor/cable_cell_param.cpp | 8 - arbor/cv_policy.cpp | 288 ++++++++++-------- arbor/fvm_layout.cpp | 12 +- arbor/include/arbor/cable_cell.hpp | 23 +- arbor/include/arbor/cable_cell_param.hpp | 5 +- arbor/include/arbor/cv_policy.hpp | 167 +++------- arbor/include/arbor/s_expr.hpp | 2 - arbor/include/arbor/util/expected.hpp | 2 +- arbor/morph/cv_data.cpp | 2 +- arbor/util/piecewise.hpp | 39 ++- arborio/cableio.cpp | 114 ++++--- arborio/cv_policy_parse.cpp | 39 +-- arborio/include/arborio/cv_policy_parse.hpp | 1 - doc/concepts/cable_cell.rst | 27 +- doc/concepts/discretization.rst | 55 ++++ doc/dependencies.csv | 2 +- doc/fileformat/cable_cell.rst | 10 +- doc/python/cable_cell.rst | 22 +- doc/python/decor.rst | 14 - doc/tutorial/single_cell_detailed.rst | 4 +- example/busyring/ring.cpp | 8 +- example/diffusion/diffusion.cpp | 2 +- example/dryrun/branch_cell.hpp | 5 +- example/lfp/lfp.cpp | 5 +- example/network_description/branch_cell.hpp | 6 +- example/ornstein_uhlenbeck/ou.cpp | 3 +- example/plasticity/branch_cell.hpp | 4 +- example/plasticity/plasticity.cpp | 5 +- example/ring/branch_cell.hpp | 6 +- python/cells.cpp | 97 +++--- python/example/diffusion.py | 3 +- .../network_two_cells_gap_junctions.py | 6 +- python/example/probe_lfpykit.py | 7 +- python/example/single_cell_allen.py | 4 +- .../l5pc/C060114A7_axon_replacement.acc | 2 +- .../l5pc/C060114A7_modified.acc | 2 +- .../single_cell_bluepyopt/l5pc/l5pc_decor.acc | 2 +- .../l5pc/l5pc_label_dict.acc | 2 +- .../simplecell/simple_cell_decor.acc | 2 +- .../simplecell/simple_cell_label_dict.acc | 2 +- python/example/single_cell_bluepyopt_l5pc.py | 41 ++- .../single_cell_bluepyopt_simplecell.py | 23 +- python/example/single_cell_cable.py | 3 +- python/example/single_cell_detailed.py | 7 +- python/example/single_cell_detailed_recipe.py | 8 +- python/example/single_cell_nml.py | 7 +- python/example/single_cell_swc.py | 9 +- python/strprintf.hpp | 2 +- python/test/unit/test_io.py | 6 +- test/common_cells.cpp | 27 +- test/common_cells.hpp | 7 +- test/ubench/CMakeLists.txt | 2 +- test/ubench/fvm_discretize.cpp | 19 +- test/ubench/mech_vec.cpp | 16 +- test/unit-distributed/test_communicator.cpp | 6 +- test/unit/CMakeLists.txt | 2 +- test/unit/test_cv_geom.cpp | 6 +- test/unit/test_cv_policy.cpp | 18 +- test/unit/test_diffusion.cpp | 2 +- test/unit/test_fvm_layout.cpp | 19 +- test/unit/test_fvm_lowered.cpp | 12 +- test/unit/test_probe.cpp | 45 +-- test/unit/test_s_expr.cpp | 26 +- test/unit/test_sde.cpp | 10 +- test/unit/test_spikes.cpp | 3 +- 69 files changed, 689 insertions(+), 692 deletions(-) create mode 100644 doc/concepts/discretization.rst diff --git a/.github/workflows/sanitize.yml b/.github/workflows/sanitize.yml index b75a1ed9a6..4cb6e0e729 100644 --- a/.github/workflows/sanitize.yml +++ b/.github/workflows/sanitize.yml @@ -19,7 +19,7 @@ permissions: jobs: build: name: "Sanitize" - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -27,8 +27,8 @@ jobs: sanitizer: ["address", "undefined", "thread"] simd: ["ON", "OFF"] env: - CC: clang-14 - CXX: clang++-14 + CC: clang-18 + CXX: clang++-18 ASAN_OPTIONS: detect_leaks=1 steps: - name: Get build dependencies @@ -43,8 +43,6 @@ jobs: uses: actions/checkout@v4 with: submodules: recursive - - name: Update pip - run: python -m pip install --upgrade pip # figure out vector extensions for ccache key - name: Check vector extensions run: | diff --git a/.github/workflows/test-matrix.yml b/.github/workflows/test-matrix.yml index a886f582f6..343978e16e 100644 --- a/.github/workflows/test-matrix.yml +++ b/.github/workflows/test-matrix.yml @@ -29,8 +29,8 @@ jobs: - { name: "Linux Min Clang", os: "ubuntu-22.04", - cc: "clang-12", - cxx: "clang++-12", + cc: "clang-13", + cxx: "clang++-13", py: "3.9", cmake: "3.19.x", mpi: "ON", diff --git a/CMakeLists.txt b/CMakeLists.txt index 355987a1b3..ac4f24c33e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,7 +274,7 @@ install(FILES mechanisms/BuildModules.cmake DESTINATION ${ARB_INSTALL_DATADIR}) # First make ourselves less chatty set(_saved_CMAKE_MESSAGE_LOG_LEVEL ${CMAKE_MESSAGE_LOG_LEVEL}) -set(CMAKE_MESSAGE_LOG_LEVEL WARNING) +set(CMAKE_MESSAGE_LOG_LEVEL STATUS) # in the event we can find hwloc, just add it find_package(hwloc QUIET) @@ -341,6 +341,8 @@ CPMAddPackage(NAME fmt VERSION 10.0.0 GIT_TAG 10.0.0) +add_library(ext-gtest INTERFACE) +add_library(ext-bench INTERFACE) if (BUILD_TESTING) CPMAddPackage(NAME benchmark GITHUB_REPOSITORY google/benchmark @@ -351,6 +353,18 @@ if (BUILD_TESTING) GIT_TAG release-1.12.1 VERSION 1.12.1 OPTIONS "INSTALL_GTEST OFF" "BUILD_GMOCK OFF") + if(benchmark_ADDED) + target_link_libraries(ext-bench INTERFACE benchmark) + else() + find_package(benchmark REQUIRED) + target_link_libraries(ext-bench INTERFACE benchmark::benchmark) + endif() + if(googletest_ADDED) + target_link_libraries(ext-gtest INTERFACE ) + else() + find_package(googletest REQUIRED) + target_link_libraries(ext-gtest INTERFACE gtest gtest_main) + endif() endif() CPMAddPackage(NAME units diff --git a/arbor/cable_cell.cpp b/arbor/cable_cell.cpp index b956b2db9c..a8098ab14c 100644 --- a/arbor/cable_cell.cpp +++ b/arbor/cable_cell.cpp @@ -83,18 +83,22 @@ struct cable_cell_impl { // The decorations on the cell. decor decorations; + // Discretization + std::optional discretization_; + // The placeable label to lid_range map dynamic_typed_map>::type> labeled_lid_ranges; - cable_cell_impl(const arb::morphology& m, const label_dict& labels, const decor& decorations): + cable_cell_impl(const arb::morphology& m, const label_dict& labels, const decor& decorations, const std::optional& cvp): provider(m, labels), dictionary(labels), - decorations(decorations) + decorations(decorations), + discretization_{cvp} { init(); } - cable_cell_impl(): cable_cell_impl({},{},{}) {} + cable_cell_impl(): cable_cell_impl({}, {}, {}, {}) {} cable_cell_impl(const cable_cell_impl& other) = default; @@ -203,6 +207,10 @@ struct cable_cell_impl { } }; +const std::optional& cable_cell::discretization() const { return impl_->discretization_; } +void cable_cell::discretization(cv_policy cvp) { impl_->discretization_ = std::move(cvp); } + + using impl_ptr = std::unique_ptr; impl_ptr make_impl(cable_cell_impl* c) { return impl_ptr(c, [](cable_cell_impl* p){delete p;}); @@ -232,8 +240,8 @@ void cable_cell_impl::init() { } } -cable_cell::cable_cell(const arb::morphology& m, const decor& decorations, const label_dict& dictionary): - impl_(make_impl(new cable_cell_impl(m, dictionary, decorations))) +cable_cell::cable_cell(const arb::morphology& m, const decor& decorations, const label_dict& dictionary, const std::optional& cvp): + impl_(make_impl(new cable_cell_impl(m, dictionary, decorations, cvp))) {} cable_cell::cable_cell(): impl_(make_impl(new cable_cell_impl())) {} diff --git a/arbor/cable_cell_param.cpp b/arbor/cable_cell_param.cpp index a5c4e20792..34fcd7e6e4 100644 --- a/arbor/cable_cell_param.cpp +++ b/arbor/cable_cell_param.cpp @@ -105,11 +105,6 @@ std::vector cable_cell_parameter_set::serialize() const { for (const auto& [name, mech]: reversal_potential_method) { D.push_back(ion_reversal_potential_method{name, mech}); } - - if (discretization) { - D.push_back(*discretization); - } - return D; } @@ -163,9 +158,6 @@ decor& decor::set_default(defaultable what) { else if constexpr (std::is_same_v) { defaults_.reversal_potential_method[p.ion] = p.method; } - else if constexpr (std::is_same_v) { - defaults_.discretization = std::forward(p); - } else if constexpr (std::is_same_v) { if (p.scale.type() != iexpr_type::scalar) throw cable_cell_error{"Default values cannot have a scale."}; auto s = p.scale.get_scalar(); diff --git a/arbor/cv_policy.cpp b/arbor/cv_policy.cpp index fcd8170547..039d9d9d9a 100644 --- a/arbor/cv_policy.cpp +++ b/arbor/cv_policy.cpp @@ -8,194 +8,244 @@ #include #include "util/rangeutil.hpp" -#include "util/span.hpp" - -// Discretization policy implementations: namespace arb { -static auto unique_sum = [](auto&&... lss) { - return ls::support(sum(std::forward(lss)...)); -}; +static std::string print_flag(cv_policy_flag flag) { + switch (flag) { + case arb::cv_policy_flag::none: return "(flag-none)"; + case arb::cv_policy_flag::interior_forks: return "(flag-interior-forks)"; + } + throw std::runtime_error("UNREACHABLE"); +} -// Combinators: -// cv_policy_plus_ represents the result of operator+, -// cv_policy_bar_ represents the result of operator|. +static bool has_flag(cv_policy_flag lhs, cv_policy_flag rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} -struct cv_policy_plus_: cv_policy_base { - cv_policy_plus_(const cv_policy& lhs, const cv_policy& rhs): - lhs_(lhs), rhs_(rhs) {} - cv_policy_base_ptr clone() const override { - return cv_policy_base_ptr(new cv_policy_plus_(*this)); - } +static auto unique_sum = [](auto&&... lss) { + return ls::support(sum(std::forward(lss)...)); +}; - locset cv_boundary_points(const cable_cell& c) const override { +struct cvp_cv_policy_plus { + locset cv_boundary_points(const cable_cell& c) const { return unique_sum(lhs_.cv_boundary_points(c), rhs_.cv_boundary_points(c)); } - region domain() const override { return join(lhs_.domain(), rhs_.domain()); } + region domain() const { return join(lhs_.domain(), rhs_.domain()); } - std::ostream& print(std::ostream& os) override { + std::ostream& format(std::ostream& os) const { os << "(join " << lhs_ << ' ' << rhs_ << ')'; return os; } + // TODO This is needed seemingly only for older compilers + cvp_cv_policy_plus(cv_policy lhs, cv_policy rhs): lhs_{std::move(lhs)}, rhs_(std::move(rhs)) {} + cv_policy lhs_, rhs_; }; ARB_ARBOR_API cv_policy operator+(const cv_policy& lhs, const cv_policy& rhs) { - return cv_policy_plus_(lhs, rhs); + return cv_policy{cvp_cv_policy_plus(lhs, rhs)}; } -struct cv_policy_bar_: cv_policy_base { - cv_policy_bar_(const cv_policy& lhs, const cv_policy& rhs): - lhs_(lhs), rhs_(rhs) {} - - cv_policy_base_ptr clone() const override { - return cv_policy_base_ptr(new cv_policy_bar_(*this)); +struct cvp_cv_policy_bar { + locset cv_boundary_points(const cable_cell& c) const { + return unique_sum(ls::restrict_to(lhs_.cv_boundary_points(c), + complement(rhs_.domain())), + rhs_.cv_boundary_points(c)); } - locset cv_boundary_points(const cable_cell& c) const override { - return unique_sum(ls::restrict_to(lhs_.cv_boundary_points(c), complement(rhs_.domain())), rhs_.cv_boundary_points(c)); - } - - region domain() const override { return join(lhs_.domain(), rhs_.domain()); } + region domain() const { return join(lhs_.domain(), rhs_.domain()); } - std::ostream& print(std::ostream& os) override { + std::ostream& format(std::ostream& os) const { os << "(replace " << lhs_ << ' ' << rhs_ << ')'; return os; } + // TODO This is needed seemingly only for older compilers + cvp_cv_policy_bar(cv_policy lhs, cv_policy rhs): lhs_{std::move(lhs)}, rhs_(std::move(rhs)) {} + cv_policy lhs_, rhs_; }; ARB_ARBOR_API cv_policy operator|(const cv_policy& lhs, const cv_policy& rhs) { - return cv_policy_bar_(lhs, rhs); + return cv_policy{cvp_cv_policy_bar(lhs, rhs)}; +} + +struct cvp_cv_policy_max_extent { + double max_extent_; + region domain_; + cv_policy_flag flags_; + + std::ostream& format(std::ostream& os) const { + os << "(max-extent " << max_extent_ << ' ' << domain_ << ' ' << print_flag(flags_) << ')'; + return os; + } + + locset cv_boundary_points(const cable_cell& cell) const { + const unsigned nbranch = cell.morphology().num_branches(); + const auto& embed = cell.embedding(); + if (!nbranch || max_extent_<=0) return ls::nil(); + + std::vector points; + double oomax_extent = 1./max_extent_; + auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); + + for (auto& comp: comps) { + for (mcable c: comp) { + double cable_length = embed.integrate_length(c); + unsigned ncv = std::ceil(cable_length*oomax_extent); + double scale = (c.dist_pos-c.prox_pos)/ncv; + + if (has_flag(flags_, cv_policy_flag::interior_forks)) { + for (unsigned i = 0; i points; - double oomax_extent = 1./max_extent_; - auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); - - for (auto& comp: comps) { - for (mcable c: comp) { - double cable_length = embed.integrate_length(c); - unsigned ncv = std::ceil(cable_length*oomax_extent); - double scale = (c.dist_pos-c.prox_pos)/ncv; + std::ostream& format(std::ostream& os) const { + os << "(single " << domain_ << ')'; + return os; + } - if (flags_&cv_policy_flag::interior_forks) { - for (unsigned i = 0; i points; - double ooncv = 1./cv_per_branch_; - auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); + std::vector points; + double ooncv = 1./cv_per_branch_; + auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); - for (auto& comp: comps) { - for (mcable c: comp) { - double scale = (c.dist_pos-c.prox_pos)*ooncv; + for (auto& comp: comps) { + for (mcable c: comp) { + double scale = (c.dist_pos-c.prox_pos)*ooncv; - if (flags_&cv_policy_flag::interior_forks) { - for (unsigned i = 0; icv_boundary_points(cell): - global_dflt.discretization? global_dflt.discretization->cv_boundary_points(cell): - default_cv_policy().cv_boundary_points(cell)); + const auto& cvp = cell.discretization().value_or(global_dflt.discretization.value_or(default_cv_policy())); + D.geometry = cv_geometry(cell, cvp.cv_boundary_points(cell)); if (D.geometry.empty()) return D; diff --git a/arbor/include/arbor/cable_cell.hpp b/arbor/include/arbor/cable_cell.hpp index 8295823160..3fd84f7b4b 100644 --- a/arbor/include/arbor/cable_cell.hpp +++ b/arbor/include/arbor/cable_cell.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -237,9 +238,16 @@ using location_assignment = mlocation_map>; using cable_cell_region_map = static_typed_map; + density, + voltage_process, + init_membrane_potential, + axial_resistivity, + temperature, + membrane_capacitance, + init_int_concentration, + ion_diffusivity, + init_ext_concentration, + init_reversal_potential>; using cable_cell_location_map = static_typed_map; @@ -265,7 +273,10 @@ struct ARB_SYMBOL_VISIBLE cable_cell { } /// Construct from morphology, label and decoration descriptions. - cable_cell(const class morphology& m, const decor& d, const label_dict& l={}); + cable_cell(const class morphology& m, + const decor& d, + const label_dict& l={}, + const std::optional& = {}); /// Access to labels const label_dict& labels() const; @@ -306,6 +317,10 @@ struct ARB_SYMBOL_VISIBLE cable_cell { // The decorations on the cell. const decor& decorations() const; + // The current cv_policy of this cell + const std::optional& discretization() const; + void discretization(cv_policy); + // The default parameter and ion settings on the cell. const cable_cell_parameter_set& default_parameters() const; diff --git a/arbor/include/arbor/cable_cell_param.hpp b/arbor/include/arbor/cable_cell_param.hpp index d68c0c73e2..621d36b6fc 100644 --- a/arbor/include/arbor/cable_cell_param.hpp +++ b/arbor/include/arbor/cable_cell_param.hpp @@ -8,8 +8,8 @@ #include #include -#include #include +#include #include #include #include @@ -380,8 +380,7 @@ using defaultable = init_int_concentration, init_ext_concentration, init_reversal_potential, - ion_reversal_potential_method, - cv_policy>; + ion_reversal_potential_method>; // Cable cell ion and electrical defaults. diff --git a/arbor/include/arbor/cv_policy.hpp b/arbor/include/arbor/cv_policy.hpp index a56a5c51d9..e06cd0c737 100644 --- a/arbor/include/arbor/cv_policy.hpp +++ b/arbor/include/arbor/cv_policy.hpp @@ -69,146 +69,77 @@ struct cv_policy_base { virtual std::ostream& print(std::ostream&) = 0; }; -using cv_policy_base_ptr = std::unique_ptr; - struct ARB_SYMBOL_VISIBLE cv_policy { - cv_policy(const cv_policy_base& ref) { // implicit - policy_ptr = ref.clone(); - } - - cv_policy(const cv_policy& other): - policy_ptr(other.policy_ptr->clone()) {} - + // construct from anything except other policies + template , cv_policy>>> + explicit cv_policy(const Impl& impl): impl_(std::make_unique>(impl)) {} + template , cv_policy>>> + explicit cv_policy(Impl&& impl): impl_(std::make_unique>(std::move(impl))) {} + // move + cv_policy(cv_policy&&) = default; + cv_policy& operator=(cv_policy&&) = default; + // copy + cv_policy(const cv_policy& other): impl_(other.impl_->clone()) {} cv_policy& operator=(const cv_policy& other) { - policy_ptr = other.policy_ptr->clone(); + impl_ = other.impl_->clone(); return *this; } - cv_policy(cv_policy&&) = default; - cv_policy& operator=(cv_policy&&) = default; + // interface + locset cv_boundary_points(const cable_cell& cell) const { return impl_->cv_boundary_points(cell); } + region domain() const { return impl_->domain(); } + std::ostream& format(std::ostream& os) const { return impl_->format(os); } - locset cv_boundary_points(const cable_cell& cell) const { - return policy_ptr->cv_boundary_points(cell); - } - - region domain() const { - return policy_ptr->domain(); - } - - friend std::ostream& operator<<(std::ostream& o, const cv_policy& p) { - return p.policy_ptr->print(o); - } + friend ARB_ARBOR_API std::ostream& operator<<(std::ostream& os, const cv_policy& cvp) { return cvp.format(os); } private: - cv_policy_base_ptr policy_ptr; -}; - -ARB_ARBOR_API cv_policy operator+(const cv_policy&, const cv_policy&); -ARB_ARBOR_API cv_policy operator|(const cv_policy&, const cv_policy&); - - -// Common flags for CV policies; bitwise composable. -namespace cv_policy_flag { - using value = unsigned; - enum : unsigned { - none = 0, - interior_forks = 1<<0 + struct iface { + virtual locset cv_boundary_points(const cable_cell& cell) const = 0; + virtual region domain() const = 0; + virtual std::unique_ptr clone() const = 0; + virtual ~iface() {} + virtual std::ostream& format(std::ostream&) const = 0; }; -} - -struct ARB_ARBOR_API cv_policy_explicit: cv_policy_base { - explicit cv_policy_explicit(locset locs, region domain = reg::all()): - locs_(std::move(locs)), domain_(std::move(domain)) {} - - cv_policy_base_ptr clone() const override; - locset cv_boundary_points(const cable_cell&) const override; - region domain() const override; - std::ostream& print(std::ostream& os) override { - os << "(explicit " << locs_ << ' ' << domain_ << ')'; - return os; - } -private: - locset locs_; - region domain_; -}; - -struct ARB_ARBOR_API cv_policy_single: cv_policy_base { - explicit cv_policy_single(region domain = reg::all()): - domain_(domain) {} + using iface_ptr = std::unique_ptr; - cv_policy_base_ptr clone() const override; - locset cv_boundary_points(const cable_cell&) const override; - region domain() const override; - std::ostream& print(std::ostream& os) override { - os << "(single " << domain_ << ')'; - return os; - } + template + struct wrap: iface { + explicit wrap(const Impl& impl): inner_(impl) {} + explicit wrap(Impl&& impl): inner_(std::move(impl)) {} -private: - region domain_; -}; + locset cv_boundary_points(const cable_cell& cell) const override { return inner_.cv_boundary_points(cell); } + region domain() const override { return inner_.domain(); }; + iface_ptr clone() const override { return std::make_unique>(inner_); } + std::ostream& format(std::ostream& os) const override { return inner_.format(os); }; -struct ARB_ARBOR_API cv_policy_max_extent: cv_policy_base { - cv_policy_max_extent(double max_extent, region domain, cv_policy_flag::value flags = cv_policy_flag::none): - max_extent_(max_extent), domain_(std::move(domain)), flags_(flags) {} - - explicit cv_policy_max_extent(double max_extent, cv_policy_flag::value flags = cv_policy_flag::none): - max_extent_(max_extent), domain_(reg::all()), flags_(flags) {} - - cv_policy_base_ptr clone() const override; - locset cv_boundary_points(const cable_cell&) const override; - region domain() const override; - std::ostream& print(std::ostream& os) override { - os << "(max-extent " << max_extent_ << ' ' << domain_ << ' ' << flags_ << ')'; - return os; - } + Impl inner_; + }; -private: - double max_extent_; - region domain_; - cv_policy_flag::value flags_; + iface_ptr impl_; }; -struct ARB_ARBOR_API cv_policy_fixed_per_branch: cv_policy_base { - cv_policy_fixed_per_branch(unsigned cv_per_branch, region domain, cv_policy_flag::value flags = cv_policy_flag::none): - cv_per_branch_(cv_per_branch), domain_(std::move(domain)), flags_(flags) {} +// Common flags for CV policies; bitwise composable. +enum class cv_policy_flag: unsigned { + none = 0, + interior_forks = 1<<0 +}; - explicit cv_policy_fixed_per_branch(unsigned cv_per_branch, cv_policy_flag::value flags = cv_policy_flag::none): - cv_per_branch_(cv_per_branch), domain_(reg::all()), flags_(flags) {} +ARB_ARBOR_API cv_policy operator+(const cv_policy&, const cv_policy&); +ARB_ARBOR_API cv_policy operator|(const cv_policy&, const cv_policy&); - cv_policy_base_ptr clone() const override; - locset cv_boundary_points(const cable_cell&) const override; - region domain() const override; - std::ostream& print(std::ostream& os) override { - os << "(fixed-per-branch " << cv_per_branch_ << ' ' << domain_ << ' ' << flags_ << ')'; - return os; - } +ARB_ARBOR_API cv_policy cv_policy_explicit(locset, region = reg::all()); -private: - unsigned cv_per_branch_; - region domain_; - cv_policy_flag::value flags_; -}; +ARB_ARBOR_API cv_policy cv_policy_max_extent(double, region, cv_policy_flag = cv_policy_flag::none); +ARB_ARBOR_API cv_policy cv_policy_max_extent(double, cv_policy_flag = cv_policy_flag::none); -struct ARB_ARBOR_API cv_policy_every_segment: cv_policy_base { - explicit cv_policy_every_segment(region domain = reg::all()): - domain_(std::move(domain)) {} +ARB_ARBOR_API cv_policy cv_policy_fixed_per_branch(unsigned, region, cv_policy_flag = cv_policy_flag::none); +ARB_ARBOR_API cv_policy cv_policy_fixed_per_branch(unsigned, cv_policy_flag = cv_policy_flag::none); - cv_policy_base_ptr clone() const override; - locset cv_boundary_points(const cable_cell&) const override; - region domain() const override; - std::ostream& print(std::ostream& os) override { - os << "(every-segment " << domain_ << ')'; - return os; - } +ARB_ARBOR_API cv_policy cv_policy_single(region domain = reg::all()); -private: - region domain_; -}; +ARB_ARBOR_API cv_policy cv_policy_every_segment(region domain = reg::all()); -inline cv_policy default_cv_policy() { - return cv_policy_fixed_per_branch(1); -} +inline cv_policy default_cv_policy() { return cv_policy_fixed_per_branch(1); } } // namespace arb diff --git a/arbor/include/arbor/s_expr.hpp b/arbor/include/arbor/s_expr.hpp index c5c4f51fa1..ce23a18cec 100644 --- a/arbor/include/arbor/s_expr.hpp +++ b/arbor/include/arbor/s_expr.hpp @@ -7,9 +7,7 @@ #include #include #include -#include #include -#include #include diff --git a/arbor/include/arbor/util/expected.hpp b/arbor/include/arbor/util/expected.hpp index 9d860f6536..1af88eb42f 100644 --- a/arbor/include/arbor/util/expected.hpp +++ b/arbor/include/arbor/util/expected.hpp @@ -486,7 +486,7 @@ struct expected { // Swap ops. void swap(expected& other) { - data_.swap(other.data); + data_.swap(other.data_); } // Accessors. diff --git a/arbor/morph/cv_data.cpp b/arbor/morph/cv_data.cpp index ec4d68e762..6fdd996bbb 100644 --- a/arbor/morph/cv_data.cpp +++ b/arbor/morph/cv_data.cpp @@ -138,7 +138,7 @@ arb_size_type cell_cv_data::size() const { } ARB_ARBOR_API std::optional cv_data(const cable_cell& cell) { - if (auto policy = cell.decorations().defaults().discretization) { + if (const auto& policy = cell.discretization()) { return cell_cv_data(cell, policy->cv_boundary_points(cell)); } return {}; diff --git a/arbor/util/piecewise.hpp b/arbor/util/piecewise.hpp index f047b2bbdd..9fbd936efd 100644 --- a/arbor/util/piecewise.hpp +++ b/arbor/util/piecewise.hpp @@ -1,6 +1,5 @@ #pragma once - // Create/manipulate 1-d piecewise defined objects. // // A `pw_element` describes a _value_ of type `A` and an _extent_ of @@ -398,7 +397,6 @@ struct pw_elements { void push_back(double left, double right, U&& v) { if (!empty() && left != vertex_.back()) throw std::runtime_error("noncontiguous element"); if (right(v)); if (vertex_.empty()) vertex_.push_back(left); @@ -407,10 +405,7 @@ struct pw_elements { template void push_back(double right, U&& v) { - if (empty()) { - throw std::runtime_error("require initial left vertex for element"); - } - + if (empty()) throw std::runtime_error("require initial left vertex for element"); push_back(vertex_.back(), right, std::forward(v)); } @@ -424,37 +419,41 @@ struct pw_elements { using std::begin; using std::end; + // Invariant, see below + // empty() || value_.size() + 1 = vertex_.size() + auto vs = std::size(vertices); + auto es = std::size(values); + // check invariant + if (!((es == 0 && es == vs) + || (es != 0 && es + 1 == vs))) { + // TODO(fmt): Make a better error w/ format. + throw std::runtime_error{"Vertices and values need to have matching lengths"}; + } + + // clean-up + clear(); + + // We know that invariant holds from here on. + if (es == 0) return; + auto vi = begin(vertices); - auto ve = end(vertices); auto ei = begin(values); auto ee = end(values); - if (ei == ee) { // empty case - if (vi != ve) throw std::runtime_error{"Vertices and values need to have same length; values too long."}; - clear(); - return; - } - clear(); - if (vi == ve) throw std::runtime_error{"Vertices and values need to have same length; values too short."}; - - reserve(vertices.size()); + reserve(vs); double left = *vi++; double right = *vi++; push_back(left, right, *ei++); - while (ei != ee) { - if (vi == ve) throw std::runtime_error{"Vertices and values need to have same length; values too short."}; double right = *vi++; push_back(right, *ei++); } - if (vi != ve) throw std::runtime_error{"Vertices and values need to have same length; values too long."}; } private: // Consistency requirements: // 1. empty() || value_.size()+1 = vertex_.size() // 2. vertex_[i]<=vertex_[j] for i<=j. - std::vector vertex_; std::vector value_; }; diff --git a/arborio/cableio.cpp b/arborio/cableio.cpp index 4ca05cc64a..61dd4af5ae 100644 --- a/arborio/cableio.cpp +++ b/arborio/cableio.cpp @@ -19,7 +19,7 @@ namespace arborio { using namespace arb; -ARB_ARBORIO_API std::string acc_version() {return "0.9-dev";} +ARB_ARBORIO_API std::string acc_version() {return "0.10-dev";} cableio_parse_error::cableio_parse_error(const std::string& msg, const arb::src_location& loc): arb::arbor_exception(msg+" at :"+ @@ -116,12 +116,6 @@ s_expr mksexp(const mpoint& p) { s_expr mksexp(const msegment& seg) { return slist("segment"_symbol, (int)seg.id, mksexp(seg.prox), mksexp(seg.dist), seg.tag); } -// This can be removed once cv_policy is removed from the decor. -s_expr mksexp(const cv_policy& c) { - std::stringstream s; - s << c; - return slist("cv-policy"_symbol, parse_s_expr(s.str())); -} s_expr mksexp(const decor& d) { auto round_trip = [](auto& x) { std::stringstream s; @@ -145,6 +139,11 @@ s_expr mksexp(const decor& d) { } return {"decor"_symbol, slist_range(decorations)}; } +s_expr mksexp(const cv_policy& c) { + std::stringstream s; + s << c; + return slist("cv-policy"_symbol, parse_s_expr(s.str())); +} s_expr mksexp(const label_dict& dict) { auto round_trip = [](auto& x) { std::stringstream s; @@ -202,8 +201,10 @@ ARB_ARBORIO_API std::ostream& write_component(std::ostream& o, const morphology& return o << s_expr{"arbor-component"_symbol, slist(mksexp(m), mksexp(x))}; } ARB_ARBORIO_API std::ostream& write_component(std::ostream& o, const cable_cell& x, const meta_data& m) { - if (m.version != acc_version()) { - throw cableio_version_error(m.version); + if (m.version != acc_version()) throw cableio_version_error(m.version); + if (const auto& cvp = x.discretization()) { + auto cell = s_expr{"cable-cell"_symbol, slist(mksexp(x.morphology()), mksexp(x.labels()), mksexp(x.decorations()), mksexp(*cvp))}; + return o << s_expr{"arbor-component"_symbol, slist(mksexp(m), cell)}; } auto cell = s_expr{"cable-cell"_symbol, slist(mksexp(x.morphology()), mksexp(x.labels()), mksexp(x.decorations()))}; return o << s_expr{"arbor-component"_symbol, slist(mksexp(m), cell)}; @@ -359,20 +360,36 @@ morphology make_morphology(const std::vector>& args) } // Define cable-cell maker -// Accepts the morphology, decor and label_dict arguments in any order as a vector -cable_cell make_cable_cell(const std::vector>& args) { +// Accepts the morphology, decor, label_dict and cv_policy arguments in any order as a vector +cable_cell make_cable_cell4(const std::vector>& args) { decor dec; label_dict dict; morphology morpho; + std::optional cvp; for(const auto& a: args) { auto cable_cell_visitor = arb::util::overload( - [&](const morphology & p) { morpho = p; }, - [&](const label_dict & p) { dict = p; }, - [&](const decor & p){ dec = p; }); + [&](const morphology& q) { morpho = q; }, + [&](const label_dict& q) { dict = q; }, + [&](const cv_policy& q) { cvp = q; }, + [&](const decor& q){ dec = q; }); std::visit(cable_cell_visitor, a); } - return cable_cell(morpho, dec, dict); + return cable_cell(morpho, dec, dict, cvp); } +cable_cell make_cable_cell3(const std::vector>& args) { + decor dec; + label_dict dict; + morphology morpho; + for(const auto& a: args) { + auto cable_cell_visitor = arb::util::overload( + [&](const morphology& q) { morpho = q; }, + [&](const label_dict& q) { dict = q; }, + [&](const decor& q){ dec = q; }); + std::visit(cable_cell_visitor, a); + } + return cable_cell(morpho, dec, dict, {}); +} + version_tuple make_version(const std::string& v) { return version_tuple{v}; } @@ -592,6 +609,16 @@ parse_hopefully eval(const s_expr& e, const eval_map& map, const eval_ auto& hd = e.head(); if (hd.is_atom()) { auto& atom = hd.atom(); + if (atom.spelling == "cv-policy") { + // NOTE tail produces a single item list? + auto res = parse_cv_policy_expression(e.tail().head()); + if (res) { + return res.value(); + } + else { + return res.error(); + } + } // If this is not a symbol, parse as a tuple if (atom.kind != tok::symbol) { auto args = eval_args(e, map, vec); @@ -641,9 +668,6 @@ parse_hopefully eval(const s_expr& e, const eval_map& map, const eval_ if (match(l->type())) return eval_cast(l.value()); } - // Or it could be a cv-policy expression - if (auto p = parse_cv_policy_expression(e)) return p.value(); - // Unable to find a match: try to return a helpful error message. const auto nc = std::distance(matches.first, matches.second); std::string msg = "No matches for found for "+name+" with "+std::to_string(args->size())+" arguments.\n" @@ -673,27 +697,26 @@ eval_map named_evals{ ARBIO_ADD_ION_EVAL("ion-diffusivity", make_ion_diffusivity), {"quantity", make_call(make_quantity, "'quantity' with a value:real and a unit:string")}, {"envelope", make_arg_vec_call(make_envelope, - "'envelope' with one or more pairs of start time and amplitude (start:real amplitude:real)")}, + "'envelope' with one or more pairs of start time and amplitude (start:real amplitude:real)")}, {"envelope-pulse", make_call(make_envelope_pulse, - "'envelope-pulse' with 3 arguments (delay:real duration:real amplitude:real)")}, + "'envelope-pulse' with 3 arguments (delay:real duration:real amplitude:real)")}, {"current-clamp", make_call, double, double>(make_i_clamp, - "'current-clamp' with 3 arguments (env:envelope freq:real phase:real)")}, + "'current-clamp' with 3 arguments (env:envelope freq:real phase:real)")}, {"current-clamp", make_call(make_i_clamp_pulse, - "'current-clamp' with 3 arguments (env:envelope_pulse freq:real phase:real)")}, + "'current-clamp' with 3 arguments (env:envelope_pulse freq:real phase:real)")}, {"threshold-detector", make_call(arb::threshold_detector::from_raw_millivolts, - "'threshold-detector' with 1 argument (threshold:real)")}, + "'threshold-detector' with 1 argument (threshold:real)")}, {"mechanism", make_mech_call("'mechanism' with a name argument, and 0 or more parameter settings" - "(name:string (param:string val:real))")}, + "(name:string (param:string val:real))")}, {"ion-reversal-potential-method", make_call( make_ion_reversal_potential_method, - "'ion-reversal-potential-method' with 2 arguments (ion:string mech:mechanism)")}, - {"cv-policy", make_call(make_cv_policy, - "'cv-policy' with 1 argument (p:policy)")}, + "'ion-reversal-potential-method' with 2 arguments (ion:string mech:mechanism)")}, + {"cv-policy", make_call(make_cv_policy, "'cv-policy' with 1 argument (p:policy)")}, {"junction", make_call(make_wrapped_mechanism, "'junction' with 1 argumnet (m: mechanism)")}, {"synapse", make_call(make_wrapped_mechanism, "'synapse' with 1 argumnet (m: mechanism)")}, {"density", make_call(make_wrapped_mechanism, "'density' with 1 argumnet (m: mechanism)")}, {"voltage-process", make_call(make_wrapped_mechanism, "'voltage-process' with 1 argumnet (m: mechanism)")}, {"scaled-mechanism", make_scaled_mechanism_call("'scaled_mechanism' with a density argument, and 0 or more parameter scaling expressions" - "(d:density (param:string val:iexpr))")}, + "(d:density (param:string val:iexpr))")}, {"place", make_call(make_place, "'place' with 3 arguments (ls:locset c:current-clamp name:string)")}, {"place", make_call(make_place, "'place' with 3 arguments (ls:locset t:threshold-detector name:string)")}, {"place", make_call(make_place, "'place' with 3 arguments (ls:locset gj:junction name:string)")}, @@ -719,39 +742,42 @@ eval_map named_evals{ {"default", make_call(make_default, "'default' with 1 argument (v:ion-diffusivity)")}, {"default", make_call(make_default, "'default' with 1 argument (v:ion-reversal-potential)")}, {"default", make_call(make_default, "'default' with 1 argument (v:ion-reversal-potential-method)")}, - {"default", make_call(make_default, "'default' with 1 argument (v:cv-policy)")}, {"locset-def", make_call(make_locset_pair, - "'locset-def' with 2 arguments (name:string ls:locset)")}, + "'locset-def' with 2 arguments (name:string ls:locset)")}, {"region-def", make_call(make_region_pair, - "'region-def' with 2 arguments (name:string reg:region)")}, + "'region-def' with 2 arguments (name:string reg:region)")}, {"iexpr-def", make_call(make_iexpr_pair, - "'iexpr-def' with 2 arguments (name:string e:iexpr)")}, + "'iexpr-def' with 2 arguments (name:string e:iexpr)")}, {"point", make_call(make_point, - "'point' with 4 arguments (x:real y:real z:real radius:real)")}, + "'point' with 4 arguments (x:real y:real z:real radius:real)")}, {"segment", make_call(make_segment, - "'segment' with 4 arguments (parent:int prox:point dist:point tag:int)")}, + "'segment' with 4 arguments (parent:int prox:point dist:point tag:int)")}, {"branch", make_branch_call( - "'branch' with 2 integers and 1 or more segment arguments (id:int parent:int s0:segment s1:segment ..)")}, + "'branch' with 2 integers and 1 or more segment arguments (id:int parent:int s0:segment s1:segment ..)")}, {"decor", make_arg_vec_call(make_decor, - "'decor' with 1 or more `paint`, `place` or `default` arguments")}, + "'decor' with 1 or more `paint`, `place` or `default` arguments")}, {"label-dict", make_arg_vec_call(make_label_dict, - "'label-dict' with 1 or more `locset-def` or `region-def` or `iexpr-def` arguments")}, + "'label-dict' with 1 or more `locset-def` or `region-def` or `iexpr-def` arguments")}, {"morphology", make_arg_vec_call(make_morphology, - "'morphology' 1 or more `branch` arguments")}, + "'morphology' 1 or more `branch` arguments")}, - {"cable-cell", make_unordered_call(make_cable_cell, - "'cable-cell' with 3 arguments: `morphology`, `label-dict`, and `decor` in any order")}, + {"cable-cell", + make_unordered_call(make_cable_cell4, + "'cable-cell' with 4 arguments: `morphology`, `label-dict`, `decor`, and `cv_policy` in any order")}, + {"cable-cell", + make_unordered_call(make_cable_cell3, + "'cable-cell' with 3 arguments: `morphology`, `label-dict`, and `decor` in any order")}, {"version", make_call(make_version, "'version' with one argment (val:std::string)")}, {"meta-data", make_call(make_meta_data, "'meta-data' with one argument (v:version)")}, - { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:decor)")}, - { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:label_dict)")}, - { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:morphology)")}, - { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:cable_cell)")} + {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:decor)")}, + {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:label_dict)")}, + {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:morphology)")}, + {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:cable_cell)")} }; #undef ARBIO_ADD_EVAL diff --git a/arborio/cv_policy_parse.cpp b/arborio/cv_policy_parse.cpp index 7e5bc2fe42..b371806ce0 100644 --- a/arborio/cv_policy_parse.cpp +++ b/arborio/cv_policy_parse.cpp @@ -1,6 +1,4 @@ #include -#include -#include #include #include @@ -10,7 +8,6 @@ #include #include - #include "parse_helpers.hpp" namespace arborio { @@ -29,43 +26,43 @@ template using parse_hopefully = arb::util::expected eval_map {{"default", - make_call<>([] () { return arb::cv_policy{arb::default_cv_policy()}; }, + make_call<>([] () { return arb::default_cv_policy(); }, "'default' with no arguments")}, {"every-segment", - make_call<>([] () { return arb::cv_policy{arb::cv_policy_every_segment()}; }, + make_call<>([] () { return arb::cv_policy_every_segment(); }, "'every-segment' with no arguments")}, {"every-segment", - make_call([] (const region& r) { return arb::cv_policy{arb::cv_policy_every_segment(r) }; }, + make_call([] (const region& r) { return arb::cv_policy_every_segment(r); }, "'every-segment' with one argument (every-segment (reg:region))")}, {"fixed-per-branch", - make_call([] (int i) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i) }; }, + make_call([] (int i) { return arb::cv_policy_fixed_per_branch(i); }, "'every-segment' with one argument (fixed-per-branch (count:int))")}, {"fixed-per-branch", - make_call([] (int i, const region& r) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i, r) }; }, + make_call([] (int i, const region& r) { return arb::cv_policy_fixed_per_branch(i, r); }, "'every-segment' with two arguments (fixed-per-branch (count:int) (reg:region))")}, {"fixed-per-branch", - make_call([] (int i, const region& r, int f) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i, r, f) }; }, - "'fixed-per-branch' with three arguments (fixed-per-branch (count:int) (reg:region) (flags:int))")}, + make_call([] (int i, const region& r, cv_policy_flag f) { return arb::cv_policy_fixed_per_branch(i, r, f); }, + "'fixed-per-branch' with three arguments (fixed-per-branch (count:int) (reg:region) (flags:flag))")}, {"max-extent", - make_call([] (double i) { return arb::cv_policy{arb::cv_policy_max_extent(i) }; }, + make_call([] (double i) { return arb::cv_policy_max_extent(i); }, "'max-extent' with one argument (max-extent (length:double))")}, {"max-extent", - make_call([] (double i, const region& r) { return arb::cv_policy{arb::cv_policy_max_extent(i, r) }; }, + make_call([] (double i, const region& r) { return arb::cv_policy_max_extent(i, r); }, "'max-extent' with two arguments (max-extent (length:double) (reg:region))")}, {"max-extent", - make_call([] (double i, const region& r, int f) { return arb::cv_policy{arb::cv_policy_max_extent(i, r, f) }; }, - "'max-extent' with three arguments (max-extent (length:double) (reg:region) (flags:int))")}, + make_call([] (double i, const region& r, cv_policy_flag f) { return arb::cv_policy_max_extent(i, r, f); }, + "'max-extent' with three arguments (max-extent (length:double) (reg:region) (flags:flag))")}, {"single", - make_call<>([] () { return arb::cv_policy{arb::cv_policy_single()}; }, + make_call<>([] () { return arb::cv_policy_single(); }, "'single' with no arguments")}, {"single", - make_call([] (const region& r) { return arb::cv_policy{arb::cv_policy_single(r) }; }, + make_call([] (const region& r) { return arb::cv_policy_single(r); }, "'single' with one argument (single (reg:region))")}, {"explicit", - make_call([] (const locset& l) { return arb::cv_policy{arb::cv_policy_explicit(l) }; }, + make_call([] (const locset& l) { return arb::cv_policy_explicit(l); }, "'explicit' with one argument (explicit (ls:locset))")}, {"explicit", - make_call([] (const locset& l, const region& r) { return arb::cv_policy{arb::cv_policy_explicit(l, r) }; }, + make_call([] (const locset& l, const region& r) { return arb::cv_policy_explicit(l, r); }, "'explicit' with two arguments (explicit (ls:locset) (reg:region))")}, {"join", make_fold([](cv_policy l, cv_policy r) { return l + r; }, @@ -73,6 +70,12 @@ eval_map {{"default", {"replace", make_fold([](cv_policy l, cv_policy r) { return l | r; }, "'replace' with at least 2 arguments: (replace cv_policy cv_policy ...)")}, + {"flag-none", + make_call<>([] () { return cv_policy_flag::none; }, + "'flag:none' with no arguments")}, + {"flag-interior-forks", + make_call<>([] () { return cv_policy_flag::interior_forks; }, + "'flag-interior-forks' with no arguments")}, }; parse_hopefully eval(const s_expr& e); diff --git a/arborio/include/arborio/cv_policy_parse.hpp b/arborio/include/arborio/cv_policy_parse.hpp index 70836105c7..6df5858778 100644 --- a/arborio/include/arborio/cv_policy_parse.hpp +++ b/arborio/include/arborio/cv_policy_parse.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include diff --git a/doc/concepts/cable_cell.rst b/doc/concepts/cable_cell.rst index 3364f20fb4..5ed43566f9 100644 --- a/doc/concepts/cable_cell.rst +++ b/doc/concepts/cable_cell.rst @@ -7,17 +7,27 @@ An Arbor *cable cell* is a full :ref:`description ` of a cell with morphology and cell dynamics like ion species and their properties, ion channels, synapses, gap junction mechanisms, stimuli and threshold detectors. -Cable cells are constructed from three components: - -* :ref:`Morphology `: a description of the geometry and branching structure of the cell shape. -* :ref:`Label dictionary `: a set of definitions and a :abbr:`DSL (domain specific language)` that refer to regions and locations on the cell morphology. -* :ref:`Decor `: a description of the dynamics on the cell, placed according to the named rules in the dictionary. It can reference :ref:`mechanisms` from mechanism catalogues. +Cable cells are constructed from four components: + +* :ref:`Morphology `: a description of the geometry and branching + structure of the cell shape. +* :ref:`Label dictionary `: a set of definitions and a :abbr:`DSL + (domain specific language)` that refer to regions and locations on the cell + morphology. +* :ref:`Decor `: a description of the dynamics on the + cell, placed according to the named rules in the dictionary. It can reference + :ref:`mechanisms` from mechanism catalogues. +* :ref:`Discretization `, :ref:`decoration `, :ref:`morphology `, or :ref:`cable cell ` object. These are denoted as arbor-components. Arbor-components need to be accompanied by *meta-data* -specifying the version of the format being used. The only version currently supported is ``0.9-dev``. +specifying the version of the format being used. The only version currently supported is ``0.10-dev``. .. label:: (version val:string) @@ -418,7 +418,7 @@ Label-dict .. code:: lisp (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (label-dict (region-def "my_soma" (tag 1)) (locset-def "root" (root)))) @@ -429,7 +429,7 @@ Decoration .. code:: lisp (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (decor (default (membrane-potential -55.000000)) (place (locset "root") (synapse (mechanism "expsyn")) "root_synapse") @@ -441,7 +441,7 @@ Morphology .. code:: lisp (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (morphology (branch 0 -1 (segment 0 (point 0 0 0 2) (point 4 0 0 2) 1) @@ -454,7 +454,7 @@ Cable-cell .. code:: lisp (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (cable-cell (label-dict (region-def "my_soma" (tag 1)) diff --git a/doc/python/cable_cell.rst b/doc/python/cable_cell.rst index b78a753ebd..267160bb5d 100644 --- a/doc/python/cable_cell.rst +++ b/doc/python/cable_cell.rst @@ -48,7 +48,7 @@ Cable cells # Construct a cable cell. cell = arbor.cable_cell(lmrf.morphology, decor, lmrf.labels) - .. method:: __init__(morphology, decorations, labels) + .. method:: __init__(morphology, decorations, labels=None, discretization=None) Constructor. @@ -58,15 +58,23 @@ Cable cells :type decorations: :py:class:`decor` :param labels: dictionary of labeled regions and locsets :type labels: :py:class:`label_dict` + :param discretization: discretization policy + :type discretization: :py:class:`cv_policy` - .. method:: placed_lid_range(index) + .. method:: discretization(policy) - Returns the range of local indexes assigned to a placement in the decorations as a tuple of two integers, - that define the range of indexes as a half open interval. + Set the cv_policy used to discretise the cell into control volumes for simulation. + + :param policy: The cv_policy. + :type policy: :py:class:`cv_policy` + + .. method:: discretization(policy) + :noindex: + + Set the cv_policy used to discretise the cell into control volumes for simulation. + + :param str policy: :ref:`string representation ` of a cv_policy. - :param index: the unique index of the placement. - :type index: int - :rtype: tuple(int, int) .. py:class:: ion diff --git a/doc/python/decor.rst b/doc/python/decor.rst index 8573fc47c4..1f8744e9af 100644 --- a/doc/python/decor.rst +++ b/doc/python/decor.rst @@ -173,20 +173,6 @@ Cable cell decoration :type d: :py:class:`threshold_detector` :param str label: the label of the group of detectors on the locset. - .. method:: discretization(policy) - - Set the cv_policy used to discretise the cell into control volumes for simulation. - - :param policy: The cv_policy. - :type policy: :py:class:`cv_policy` - - .. method:: discretization(policy) - :noindex: - - Set the cv_policy used to discretise the cell into control volumes for simulation. - - :param str policy: :ref:`string representation ` of a cv_policy. - .. method:: paintings() Returns a list of tuples ``(region, painted_object)`` for inspection. diff --git a/doc/tutorial/single_cell_detailed.rst b/doc/tutorial/single_cell_detailed.rst index 5fe68d0126..e7f6d6baa4 100644 --- a/doc/tutorial/single_cell_detailed.rst +++ b/doc/tutorial/single_cell_detailed.rst @@ -30,8 +30,8 @@ geometry and dynamics which is constructed from 3 components: 2. A **label dictionary** storing labelled expressions which define regions and locations of interest on the cell. 3. A **decor** defining various properties and dynamics on these regions and locations. - The decor also includes hints about how the cell is to be modelled under the hood, by - splitting it into discrete control volumes (CV). +4. Finally, the cell needs to know how we will be splitting it into discrete + control volumes (CV). The morphology ^^^^^^^^^^^^^^^ diff --git a/example/busyring/ring.cpp b/example/busyring/ring.cpp index 57a7e3384c..b4903f84c9 100644 --- a/example/busyring/ring.cpp +++ b/example/busyring/ring.cpp @@ -421,9 +421,7 @@ arb::cable_cell complex_cell(arb::cell_gid_type gid, const cell_parameters& para if (params.synapses>1) decor.place(syns, arb::synapse("expsyn"), "s"); - decor.set_default(arb::cv_policy_every_segment()); - - return {arb::morphology(tree), decor}; + return {arb::morphology(tree), decor, {}, arb::cv_policy_every_segment()}; } arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& params) { @@ -453,7 +451,5 @@ arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& param } // Make a CV between every sample in the sample tree. - decor.set_default(arb::cv_policy_every_segment()); - - return {arb::morphology(tree), decor}; + return {arb::morphology(tree), decor, {}, arb::cv_policy_every_segment()}; } diff --git a/example/diffusion/diffusion.cpp b/example/diffusion/diffusion.cpp index 0d2952baca..80cb3fe34d 100644 --- a/example/diffusion/diffusion.cpp +++ b/example/diffusion/diffusion.cpp @@ -26,7 +26,7 @@ namespace U = arb::units; struct linear: public recipe { linear(double ext, double dx, double Xi, double beta): l{ext}, d{dx}, i{Xi}, b{beta} { gprop.default_parameters = neuron_parameter_defaults; - gprop.default_parameters.discretization = cv_policy_max_extent{d}; + gprop.default_parameters.discretization = cv_policy_max_extent(d); gprop.add_ion("bla", 1, 23*U::mM, 42*U::mM, 0*U::mV, b*U::m2/U::s); } diff --git a/example/dryrun/branch_cell.hpp b/example/dryrun/branch_cell.hpp index 665e4f53ed..d0ac1ff1ba 100644 --- a/example/dryrun/branch_cell.hpp +++ b/example/dryrun/branch_cell.hpp @@ -118,12 +118,11 @@ inline arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters .place(arb::mlocation{0,0}, arb::threshold_detector{10*U::mV}, "detector") // Add spike threshold detector at the soma. - .place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "synapse") // Add a synapse to the mid point of the first dendrite. - .set_default(arb::cv_policy_every_segment()); // Make a CV between every sample in the sample tree. + .place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "synapse"); // Add a synapse to the mid point of the first dendrite. // Add additional synapses that will not be connected to anything. for (unsigned i=1u; i= d.size()) throw py::index_error("index out of range"); - return d.cables(index); - }, - "index"_a, "Return a list of cables representing the CV at the given index.") - .def("children", - [](const arb::cell_cv_data& d, unsigned index) { - if (index >= d.size()) throw py::index_error("index out of range"); - return d.children(index); - }, - "index"_a, - "Return a list of indices of the CVs representing the children of the CV at the given index.") - .def("parent", - [](const arb::cell_cv_data& d, unsigned index) { - if (index >= d.size()) throw py::index_error("index out of range"); - return d.parent(index); - }, - "index"_a, - "Return the index of the CV representing the parent of the CV at the given index.") - .def("__str__", [](const arb::cell_cv_data& p){return "";}) - .def("__repr__", [](const arb::cell_cv_data& p){return "";}); + .def_property_readonly("num_cv", [](const arb::cell_cv_data& data){return data.size();}, + "Return the number of CVs in the cell.") + .def("cables", + [](const arb::cell_cv_data& d, unsigned index) { + if (index >= d.size()) throw py::index_error("index out of range"); + return d.cables(index); + }, + "index"_a, "Return a list of cables representing the CV at the given index.") + .def("children", + [](const arb::cell_cv_data& d, unsigned index) { + if (index >= d.size()) throw py::index_error("index out of range"); + return d.children(index); + }, + "index"_a, + "Return a list of indices of the CVs representing the children of the CV at the given index.") + .def("parent", + [](const arb::cell_cv_data& d, unsigned index) { + if (index >= d.size()) throw py::index_error("index out of range"); + return d.parent(index); + }, + "index"_a, + "Return the index of the CV representing the parent of the CV at the given index.") + .def("__str__", [](const arb::cell_cv_data& p){return "";}) + .def("__repr__", [](const arb::cell_cv_data& p){return "";}); m.def("cv_data", [](const arb::cable_cell& cell) { return arb::cv_data(cell);}, "cell"_a, "the cable cell", @@ -913,34 +913,22 @@ void register_cells(py::module& m) { }, "locations"_a, "detector"_a, "label"_a, "Add a voltage spike detector at each location in locations." - "The group of spike detectors has the label 'label', used for forming connections between cells.") - .def("discretization", - [](arb::decor& dec, const arb::cv_policy& p) { return dec.set_default(p); }, - py::arg("policy"), - "A cv_policy used to discretise the cell into compartments for simulation") - .def("discretization", - [](arb::decor& dec, const std::string& p) { - return dec.set_default(arborio::parse_cv_policy_expression(p).unwrap()); - }, - py::arg("policy"), - "An s-expression string representing a cv_policy used to discretise the " - "cell into compartments for simulation"); - + "The group of spike detectors has the label 'label', used for forming connections between cells."); cable_cell .def(py::init( - [](const arb::morphology& m, const arb::decor& d, const std::optional& l) { - if (l) return arb::cable_cell(m, d, l->dict); - return arb::cable_cell(m, d); + [](const arb::morphology& m, const arb::decor& d, const std::optional& l, const std::optional& p) { + if (l) return arb::cable_cell(m, d, l->dict, p); + return arb::cable_cell(m, d, {}, p); }), - "morphology"_a, "decor"_a, "labels"_a=py::none(), - "Construct with a morphology, decor, and label dictionary.") + "morphology"_a, "decor"_a, "labels"_a=py::none(), "discretization"_a=py::none(), + "Construct with a morphology, decor, label dictionary, and cv policy.") .def(py::init( - [](const arb::segment_tree& t, const arb::decor& d, const std::optional& l) { - if (l) return arb::cable_cell({t}, d, l->dict); - return arb::cable_cell({t}, d); + [](const arb::segment_tree& t, const arb::decor& d, const std::optional& l, const std::optional& p) { + if (l) return arb::cable_cell({t}, d, l->dict, p); + return arb::cable_cell({t}, d, {}, p); }), - "segment_tree"_a, "decor"_a, "labels"_a=py::none(), - "Construct with a morphology derived from a segment tree, decor, and label dictionary.") + "segment_tree"_a, "decor"_a, "labels"_a=py::none(), "discretization"_a=py::none(), + "Construct with a morphology derived from a segment tree, decor, label dictionary, and cv policy.") .def_property_readonly("num_branches", [](const arb::cable_cell& c) {return c.morphology().num_branches();}, "The number of unbranched cable sections in the morphology.") @@ -952,6 +940,21 @@ void register_cells(py::module& m) { .def("cables", [](arb::cable_cell& c, const char* label) {return c.concrete_region(arborio::parse_region_expression(label).unwrap()).cables();}, "label"_a, "The cable segments of the cell morphology for a region label.") + // Discretization + .def("discretization", + [](const arb::cable_cell& c) { return c.discretization(); }, + "The cv_policy used to discretise the cell into compartments for simulation") + .def("discretization", + [](arb::cable_cell& c, const arb::cv_policy& p) { return c.discretization(p); }, + py::arg("policy"), + "A cv_policy used to discretise the cell into compartments for simulation") + .def("discretization", + [](arb::cable_cell& c, const std::string& p) { + return c.discretization(arborio::parse_cv_policy_expression(p).unwrap()); + }, + py::arg("policy"), + "An s-expression string representing a cv_policy used to discretise the " + "cell into compartments for simulation") // Stringification .def("__repr__", [](const arb::cable_cell&){return "";}) .def("__str__", [](const arb::cable_cell&){return "";}); diff --git a/python/example/diffusion.py b/python/example/diffusion.py index 8dda5e78f7..b4281815ae 100644 --- a/python/example/diffusion.py +++ b/python/example/diffusion.py @@ -42,7 +42,6 @@ def event_generators(self, _): .set_ion("na", int_con=0.0 * U.mM, diff=0.005 * U.m2 / U.s) .place("(location 0 0.5)", A.synapse("inject/x=na", {"alpha": 200.0}), "Zap") .paint("(all)", A.density("decay/x=na")) - .discretization(A.cv_policy("(max-extent 5)")) # Set up ion diffusion .set_ion( "na", @@ -57,7 +56,7 @@ def event_generators(self, _): prb = [ A.cable_probe_ion_diff_concentration_cell("na", "nad"), ] -cel = A.cable_cell(tree, dec) +cel = A.cable_cell(tree, dec, discretization=A.cv_policy("(max-extent 5)")) rec = recipe(cel, prb) sim = A.simulation(rec) hdl = (sim.sample((0, "nad"), A.regular_schedule(0.1 * U.ms)),) diff --git a/python/example/network_two_cells_gap_junctions.py b/python/example/network_two_cells_gap_junctions.py index c129823072..3a99f078c4 100755 --- a/python/example/network_two_cells_gap_junctions.py +++ b/python/example/network_two_cells_gap_junctions.py @@ -60,11 +60,11 @@ def cell_description(self, gid): ) if self.max_extent is not None: - decor.discretization(A.cv_policy_max_extent(self.max_extent)) + cvp = A.cv_policy_max_extent(self.max_extent) else: - decor.discretization(A.cv_policy_single()) + cvp = A.cv_policy_single() - return A.cable_cell(tree, decor, labels) + return A.cable_cell(tree, decor, labels, cvp) def gap_junctions_on(self, gid): return [A.gap_junction_connection(((gid + 1) % 2, "gj"), "gj", 1)] diff --git a/python/example/probe_lfpykit.py b/python/example/probe_lfpykit.py index 795b325d01..45a0ba5a92 100644 --- a/python/example/probe_lfpykit.py +++ b/python/example/probe_lfpykit.py @@ -88,15 +88,16 @@ def probes(self, _): .paint("(all)", A.density("pas/e=-65", g=0.0001)) # attach the stimulus .place(str(clamp_location), iclamp, "iclamp") - # use a fixed 3 CVs per branch - .discretization(A.cv_policy_fixed_per_branch(3)) ) +# use a fixed 3 CVs per branch +cvp = A.cv_policy_fixed_per_branch(3) + # place_pwlin can be queried with region/locset expressions to obtain # geometrical objects, like points and segments, essentially recovering # geometry from morphology. ppwl = A.place_pwlin(morphology) -cell = A.cable_cell(morphology, decor) +cell = A.cable_cell(morphology, decor, discretization=cvp) # instantiate recipe with cell rec = Recipe(cell) diff --git a/python/example/single_cell_allen.py b/python/example/single_cell_allen.py index b3d30c441d..2cbcb2da0a 100644 --- a/python/example/single_cell_allen.py +++ b/python/example/single_cell_allen.py @@ -131,10 +131,10 @@ def make_cell(base, swc, fit): decor.place('"midpoint"', A.threshold_detector(-40 * U.mV), "sd") # (10) discretisation strategy: max compartment length - decor.discretization(A.cv_policy_max_extent(20)) + cvp = A.cv_policy_max_extent(20) # (11) Create cell - return A.cable_cell(morphology, decor, labels), offset + return A.cable_cell(morphology, decor, labels, cvp), offset # (12) Create cell, model diff --git a/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc b/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc index ad5cfa7de8..0370e97543 100644 --- a/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc +++ b/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc @@ -1,6 +1,6 @@ (arbor-component (meta-data - (version "0.9-dev")) + (version "0.10-dev")) (morphology (branch 0 -1 (segment 0 diff --git a/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc b/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc index 317282f7d5..3ec66000d5 100644 --- a/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc +++ b/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc @@ -1,6 +1,6 @@ (arbor-component (meta-data - (version "0.9-dev")) + (version "0.10-dev")) (morphology (branch 0 -1 (segment 0 diff --git a/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc b/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc index 29831b6715..73007e7fff 100644 --- a/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc +++ b/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (decor (default (membrane-potential -65 (scalar 1.0))) (default (temperature-kelvin 307.14999999999998 (scalar 1.0))) diff --git a/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc b/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc index c15ec60578..a6ff3884a7 100644 --- a/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc +++ b/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (label-dict (region-def "all" (all)) (region-def "apic" (tag 4)) diff --git a/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc b/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc index c46b2578ef..bb67b8b826 100644 --- a/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc +++ b/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (decor (paint (region "soma") (membrane-capacitance 0.01 (scalar 1))) (paint (region "soma") (density (mechanism "default::hh" ("gnabar" 0.10299326453483033) ("gkbar" 0.027124836082684685)))))) diff --git a/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc b/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc index c15ec60578..a6ff3884a7 100644 --- a/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc +++ b/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (label-dict (region-def "all" (all)) (region-def "apic" (tag 4)) diff --git a/python/example/single_cell_bluepyopt_l5pc.py b/python/example/single_cell_bluepyopt_l5pc.py index 0a66b2e479..9639202e6c 100755 --- a/python/example/single_cell_bluepyopt_l5pc.py +++ b/python/example/single_cell_bluepyopt_l5pc.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import arbor +import arbor as A import pandas import seaborn import sys @@ -12,7 +12,6 @@ exit(-42) # (1) Read the cell JSON description referencing morphology, label dictionary and decor. - if len(sys.argv) < 2: print("No JSON file passed to the program") sys.exit(0) @@ -21,7 +20,6 @@ cell_json, morpho, decor, labels = ephys.create_acc.read_acc(cell_json_filename) # (2) Define labels for stimuli and voltage recordings. - labels["soma_center"] = "(location 0 0.5)" labels["dend1"] = ( '(restrict-to (distal-translate (proximal (region "apic")) 660)' @@ -29,42 +27,39 @@ ) # (3) Define stimulus and spike detector, adjust discretization - decor.place( - '"soma_center"', arbor.iclamp(tstart=295, duration=5, current=1.9), "soma_iclamp" + '"soma_center"', A.iclamp(tstart=295, duration=5, current=1.9), "soma_iclamp" ) # Add spike detector -decor.place('"soma_center"', arbor.threshold_detector(-10), "detector") +decor.place('"soma_center"', A.threshold_detector(-10), "detector") # Adjust discretization (single CV on soma, default everywhere else) -decor.discretization(arbor.cv_policy_max_extent(1.0) | arbor.cv_policy_single('"soma"')) +cvp = A.cv_policy_max_extent(1.0) | A.cv_policy_single('"soma"') # (4) Create the cell. - -cell = arbor.cable_cell(morpho, decor, labels) +cell = A.cable_cell(morpho, decor, labels, cvp) # (5) Declare a probe. - -probe = arbor.cable_probe_membrane_voltage('"dend1"') +probe = A.cable_probe_membrane_voltage('"dend1"') -# (6) Create a class that inherits from arbor.recipe -class single_recipe(arbor.recipe): +# (6) Create a class that inherits from A.recipe +class single_recipe(A.recipe): # (6.1) Define the class constructor def __init__(self, cell, probes): # The base C++ class constructor must be called first, to ensure that # all memory in the C++ class is initialized correctly. - arbor.recipe.__init__(self) + super().__init__() self.the_cell = cell self.the_probes = probes - self.the_props = arbor.neuron_cable_properties() + self.the_props = A.neuron_cable_properties() # Add catalogues with explicit qualifiers - self.the_props.catalogue = arbor.catalogue() - self.the_props.catalogue.extend(arbor.default_catalogue(), "default::") - self.the_props.catalogue.extend(arbor.bbp_catalogue(), "BBP::") + self.the_props.catalogue = A.catalogue() + self.the_props.catalogue.extend(A.default_catalogue(), "default::") + self.the_props.catalogue.extend(A.bbp_catalogue(), "BBP::") # (6.2) Override the num_cells method def num_cells(self): @@ -72,7 +67,7 @@ def num_cells(self): # (6.3) Override the cell_kind method def cell_kind(self, gid): - return arbor.cell_kind.cable + return A.cell_kind.cable # (6.4) Override the cell_description method def cell_description(self, gid): @@ -92,13 +87,13 @@ def global_properties(self, gid): recipe = single_recipe(cell, [probe]) # (7) Create a simulation (using defaults for context and partition_load_balance) -sim = arbor.simulation(recipe) +sim = A.simulation(recipe) # Instruct the simulation to record the spikes and sample the probe -sim.record(arbor.spike_recording.all) +sim.record(A.spike_recording.all) -probe_id = arbor.cell_member(0, 0) -handle = sim.sample(probe_id, arbor.regular_schedule(0.02)) +probe_id = A.cell_member(0, 0) +handle = sim.sample(probe_id, A.regular_schedule(0.02)) # (8) Run the simulation sim.run(tfinal=600, dt=0.025) diff --git a/python/example/single_cell_bluepyopt_simplecell.py b/python/example/single_cell_bluepyopt_simplecell.py index bffcdce503..78676d716b 100755 --- a/python/example/single_cell_bluepyopt_simplecell.py +++ b/python/example/single_cell_bluepyopt_simplecell.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import arbor +import arbor as A import pandas import seaborn import sys @@ -12,7 +12,6 @@ exit(-42) # (1) Read the cell JSON description referencing morphology, label dictionary and decor. - if len(sys.argv) < 2: print("No JSON file passed to the program") sys.exit(0) @@ -21,33 +20,29 @@ cell_json, morpho, decor, labels = ephys.create_acc.read_acc(cell_json_filename) # (2) Define labels for stimuli and voltage recordings. - labels["soma_center"] = "(location 0 0.5)" # (3) Define stimulus and spike detector, adjust discretization - decor.place( - '"soma_center"', arbor.iclamp(tstart=100, duration=50, current=0.05), "soma_iclamp" + '"soma_center"', A.iclamp(tstart=100, duration=50, current=0.05), "soma_iclamp" ) # Add spike detector -decor.place('"soma_center"', arbor.threshold_detector(-10), "detector") +decor.place('"soma_center"', A.threshold_detector(-10), "detector") # Adjust discretization (single CV on soma, default everywhere else) -decor.discretization(arbor.cv_policy_max_extent(1.0) | arbor.cv_policy_single('"soma"')) +cvp = A.cv_policy_max_extent(1.0) | A.cv_policy_single('"soma"') # (4) Create the cell. - -cell = arbor.cable_cell(morpho, decor, labels) +cell = A.cable_cell(morpho, decor, labels, cvp) # (5) Make the single cell model. - -m = arbor.single_cell_model(cell) +m = A.single_cell_model(cell) # Add catalogues with qualifiers -m.properties.catalogue = arbor.catalogue() -m.properties.catalogue.extend(arbor.default_catalogue(), "default::") -m.properties.catalogue.extend(arbor.bbp_catalogue(), "BBP::") +m.properties.catalogue = A.catalogue() +m.properties.catalogue.extend(A.default_catalogue(), "default::") +m.properties.catalogue.extend(A.bbp_catalogue(), "BBP::") # (6) Attach voltage probe that samples at 50 kHz. m.probe("voltage", where='"soma_center"', frequency=50) diff --git a/python/example/single_cell_cable.py b/python/example/single_cell_cable.py index fc6cceb156..02da846431 100755 --- a/python/example/single_cell_cable.py +++ b/python/example/single_cell_cable.py @@ -103,9 +103,8 @@ def cell_description(self, _): ) policy = A.cv_policy_max_extent(self.cv_policy_max_extent) - decor.discretization(policy) - return A.cable_cell(tree, decor, labels) + return A.cable_cell(tree, decor, labels, policy) def probes(self, _): return self.the_probes diff --git a/python/example/single_cell_detailed.py b/python/example/single_cell_detailed.py index 1e4b7659be..7ea7220f10 100755 --- a/python/example/single_cell_detailed.py +++ b/python/example/single_cell_detailed.py @@ -71,12 +71,13 @@ .place('"root"', A.iclamp(30 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp1") .place('"root"', A.iclamp(50 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp2") .place('"axon_terminal"', A.threshold_detector(-10 * U.mV), "detector") - # Set discretisation: Soma as one CV, 1um everywhere else - .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) +# Set discretisation: Soma as one CV, 1um everywhere else +cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') + # (4) Create the cell. -cell = A.cable_cell(morph, decor, labels) +cell = A.cable_cell(morph, decor, labels, cvp) # (5) Construct the model model = A.single_cell_model(cell) diff --git a/python/example/single_cell_detailed_recipe.py b/python/example/single_cell_detailed_recipe.py index 65013797a7..b45f2076cb 100644 --- a/python/example/single_cell_detailed_recipe.py +++ b/python/example/single_cell_detailed_recipe.py @@ -67,13 +67,13 @@ .place('"root"', A.iclamp(30 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp1") .place('"root"', A.iclamp(50 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp2") .place('"axon_terminal"', A.threshold_detector(-10 * U.mV), "detector") - # Set discretisation: Soma as one CV, 1um everywhere else - .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) +# Set discretisation: Soma as one CV, 1um everywhere else +cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') -# (4) Create the cell. -cell = A.cable_cell(lmrf.morphology, decor, labels) +# (4) Create the cell +cell = A.cable_cell(lmrf.morphology, decor, labels, cvp) # (5) Create a class that inherits from A.recipe diff --git a/python/example/single_cell_nml.py b/python/example/single_cell_nml.py index 99f74e666a..86eff50525 100755 --- a/python/example/single_cell_nml.py +++ b/python/example/single_cell_nml.py @@ -60,12 +60,13 @@ .place('"stim_site"', A.iclamp(8 * U.ms, 1 * U.ms, current=4 * U.nA), "iclamp3") # Detect spikes at the soma with a voltage threshold of -10 mV. .place('"axon_end"', A.threshold_detector(-10 * U.mV), "detector") - # Set discretisation: Soma as one CV, 1um everywhere else - .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) +# Set discretisation: Soma as one CV, 1um everywhere else +cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') + # Combine morphology with region and locset definitions to make a cable cell. -cell = A.cable_cell(morpho, decor, labels) +cell = A.cable_cell(morpho, decor, labels, cvp) print(cell.locations('"axon_end"')) diff --git a/python/example/single_cell_swc.py b/python/example/single_cell_swc.py index 6455850aed..5186b7e1e5 100755 --- a/python/example/single_cell_swc.py +++ b/python/example/single_cell_swc.py @@ -60,13 +60,14 @@ .place('"stim_site"', A.iclamp(8 * U.ms, 1 * U.ms, current=4 * U.nA), "iclamp3") # Detect spikes at the soma with a voltage threshold of -10 mV. .place('"axon_end"', A.threshold_detector(-10 * U.mV), "detector") - # Create the policy used to discretise the cell into CVs. - # Use a single CV for the soma, and CVs of maximum length 1 μm elsewhere. - .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) +# Create the policy used to discretise the cell into CVs. +# Use a single CV for the soma, and CVs of maximum length 1 μm elsewhere. +cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') + # Combine morphology with region and locset definitions to make a cable cell. -cell = A.cable_cell(morpho, decor, labels) +cell = A.cable_cell(morpho, decor, labels, cvp) # Make single cell model. m = A.single_cell_model(cell) diff --git a/python/strprintf.hpp b/python/strprintf.hpp index 6380167726..2ccf12c1a3 100644 --- a/python/strprintf.hpp +++ b/python/strprintf.hpp @@ -168,7 +168,7 @@ namespace impl { for (auto& x: s.seq_) { if (!first) o << s.sep_; first = false; - o << s.f(x); + o << s.f_(x); } return o; } diff --git a/python/test/unit/test_io.py b/python/test/unit/test_io.py index f9e4049aa1..6a78beaa73 100644 --- a/python/test/unit/test_io.py +++ b/python/test/unit/test_io.py @@ -9,7 +9,7 @@ acc = """(arbor-component (meta-data - (version "0.9-dev")) + (version "0.10-dev")) (cable-cell (morphology (branch 0 -1 @@ -231,9 +231,7 @@ def cell_description(self, _): dec = A.decor() dec.paint("(all)", A.density("pas")) - dec.discretization(A.cv_policy("(max-extent 1)")) - - return A.cable_cell(tree, dec) + return A.cable_cell(tree, dec, discretization=A.cv_policy("(max-extent 1)")) def global_properties(self, _): return self.the_props diff --git a/test/common_cells.cpp b/test/common_cells.cpp index 903e4e0a43..d275de09b1 100644 --- a/test/common_cells.cpp +++ b/test/common_cells.cpp @@ -97,11 +97,9 @@ mcable soma_cell_builder::cable(mcable cab) const { // Add a new branch that is attached to parent_branch. // Returns the id of the new branch. -msize_t soma_cell_builder::add_branch( - msize_t parent_branch, - double len, double r1, double r2, int ncomp, - const std::string& region) -{ +msize_t soma_cell_builder::add_branch(msize_t parent_branch, + double len, double r1, double r2, int ncomp, + const std::string& region) { // Get tag id of region (add a new tag if region does not already exist). int tag = get_tag(region); @@ -147,18 +145,13 @@ cable_cell_description soma_cell_builder::make_cell() const { // Make label dictionary with one entry for each tag. label_dict dict; - for (auto& tag: tag_map) { - dict.set(tag.first, reg::tagged(tag.second)); + for (auto& [k, v]: tag_map) { + dict.set(k, reg::tagged(v)); } auto boundaries = cv_boundaries; - for (auto& b: boundaries) { - b = location(b); - } - decor decorations; - decorations.set_default(cv_policy_explicit(boundaries)); - // Construct cable_cell from sample tree, dictionary and decorations. - return {std::move(tree), std::move(dict), std::move(decorations)}; + for (auto& b: boundaries) b = location(b); + return {std::move(tree), std::move(dict), {}, cv_policy_explicit(boundaries)}; } /* @@ -185,7 +178,7 @@ cable_cell_description make_cell_soma_only(bool with_stim) { "cc"); } - return {c.morph, c.labels, c.decorations}; + return c; } /* @@ -222,7 +215,7 @@ cable_cell_description make_cell_ball_and_stick(bool with_stim) { "cc"); } - return {c.morph, c.labels, c.decorations}; + return c; } /* @@ -265,7 +258,7 @@ cable_cell_description make_cell_ball_and_3stick(bool with_stim) { "cc1"); } - return {c.morph, c.labels, c.decorations}; + return c; } } // namespace arb diff --git a/test/common_cells.hpp b/test/common_cells.hpp index 6e9cda85a7..cf72da9850 100644 --- a/test/common_cells.hpp +++ b/test/common_cells.hpp @@ -14,10 +14,9 @@ struct cable_cell_description { morphology morph; label_dict labels; decor decorations; + std::optional discretization; - operator cable_cell() const { - return cable_cell(morph, decorations, labels); - } + operator cable_cell() const { return cable_cell(morph, decorations, labels, discretization); } }; class soma_cell_builder { @@ -38,7 +37,7 @@ class soma_cell_builder { // Add a new branch that is attached to parent_branch. // Returns the id of the new branch. msize_t add_branch(msize_t parent_branch, double len, double r1, double r2, int ncomp, - const std::string& region); + const std::string& region); mlocation location(mlocation) const; mcable cable(mcable) const; diff --git a/test/ubench/CMakeLists.txt b/test/ubench/CMakeLists.txt index 56bf7b4048..9628987655 100644 --- a/test/ubench/CMakeLists.txt +++ b/test/ubench/CMakeLists.txt @@ -26,7 +26,7 @@ foreach(bench_src ${bench_sources}) string(REGEX REPLACE "\\.[^.]*$" "" bench_exe ${bench_src}) add_executable(${bench_exe} EXCLUDE_FROM_ALL "${bench_src}") - target_link_libraries(${bench_exe} arbor arborio arbor-private-headers benchmark) + target_link_libraries(${bench_exe} arbor arborio arbor-private-headers ext-bench) target_compile_options(${bench_exe} PRIVATE ${ARB_CXX_FLAGS_TARGET_FULL}) target_compile_definitions(${bench_exe} PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../swc\"") list(APPEND bench_exe_list ${bench_exe}) diff --git a/test/ubench/fvm_discretize.cpp b/test/ubench/fvm_discretize.cpp index ce9cc719cc..e78558ac29 100644 --- a/test/ubench/fvm_discretize.cpp +++ b/test/ubench/fvm_discretize.cpp @@ -60,7 +60,6 @@ void run_cv_geom_explicit(benchmark::State& state) { while (state.KeepRunning()) { auto ends = cv_policy_every_segment().cv_boundary_points(c); auto ends2 = cv_policy_explicit(std::move(ends)).cv_boundary_points(c); - benchmark::DoNotOptimize(cv_geometry(c, ends2)); } } @@ -68,38 +67,28 @@ void run_cv_geom_explicit(benchmark::State& state) { void run_discretize(benchmark::State& state) { auto gdflt = neuron_parameter_defaults; const std::size_t ncv_per_branch = state.range(0); - - decor dec; auto morpho = from_swc(SWCFILE); - dec.set_default(cv_policy_fixed_per_branch(ncv_per_branch)); - while (state.KeepRunning()) { - benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, dec}, gdflt)); + benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, {}, {}, cv_policy_fixed_per_branch(ncv_per_branch)}, gdflt)); } } void run_discretize_every_segment(benchmark::State& state) { auto gdflt = neuron_parameter_defaults; - - decor dec; auto morpho = from_swc(SWCFILE); - dec.set_default(cv_policy_every_segment()); - while (state.KeepRunning()) { - benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, dec}, gdflt)); + benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, {}, {}, cv_policy_every_segment()}, gdflt)); } } void run_discretize_explicit(benchmark::State& state) { auto gdflt = neuron_parameter_defaults; - decor dec; auto morpho = from_swc(SWCFILE); - auto ends = cv_policy_every_segment().cv_boundary_points(cable_cell{morpho, {}}); - dec.set_default(cv_policy_explicit(std::move(ends))); + auto ends = cv_policy_every_segment().cv_boundary_points(cable_cell{morpho, {}, {}, {}}); while (state.KeepRunning()) { - benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, dec}, gdflt)); + benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, {}, {}, cv_policy_explicit(ends)}, gdflt)); } } diff --git a/test/ubench/mech_vec.cpp b/test/ubench/mech_vec.cpp index 880774fda9..d8ac020167 100644 --- a/test/ubench/mech_vec.cpp +++ b/test/ubench/mech_vec.cpp @@ -63,7 +63,6 @@ class recipe_expsyn_1_branch: public recipe { arb::decor decor; decor.paint(arb::reg::tagged(1), arb::density("pas")); - decor.set_default(arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)); auto distribution = std::uniform_real_distribution(0.f, 1.0f); for(unsigned i = 0; i < num_synapse_; i++) { @@ -71,7 +70,7 @@ class recipe_expsyn_1_branch: public recipe { decor.place(arb::mlocation{0, distribution(gen)}, arb::synapse("expsyn"), "syn"); } - return arb::cable_cell{arb::morphology(tree), decor}; + return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -110,9 +109,7 @@ class recipe_pas_1_branch: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("pas")); - decor.set_default(arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)); - - return arb::cable_cell {arb::morphology(tree), decor}; + return arb::cable_cell {arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -153,9 +150,8 @@ class recipe_pas_3_branches: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("pas")); - decor.set_default(arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)); - return arb::cable_cell{arb::morphology(tree), decor}; + return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -194,9 +190,8 @@ class recipe_hh_1_branch: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("hh")); - decor.set_default(arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)); - return arb::cable_cell{arb::morphology(tree), decor}; + return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -237,9 +232,8 @@ class recipe_hh_3_branches: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("hh")); - decor.set_default(arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)); - return arb::cable_cell{arb::morphology(tree), decor}; + return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { diff --git a/test/unit-distributed/test_communicator.cpp b/test/unit-distributed/test_communicator.cpp index 0e46459e04..546bfe875b 100644 --- a/test/unit-distributed/test_communicator.cpp +++ b/test/unit-distributed/test_communicator.cpp @@ -202,10 +202,9 @@ namespace { arb::segment_tree tree; tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); arb::decor decor; - decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(arb::mlocation{0, 0.5}, arb::threshold_detector{10*arb::units::mV}, "src"); decor.place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "tgt"); - return arb::cable_cell(arb::morphology(tree), decor); + return arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10)); } return arb::lif_cell("src", "tgt"); } @@ -274,10 +273,9 @@ namespace { arb::segment_tree tree; tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); arb::decor decor; - decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(arb::mlocation{0, 0.5}, arb::threshold_detector{10*arb::units::mV}, "src"); decor.place(arb::ls::uniform(arb::reg::all(), 0, size_, gid), arb::synapse("expsyn"), "tgt"); - return arb::cable_cell(arb::morphology(tree), decor); + return arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10)); } cell_kind get_cell_kind(cell_gid_type gid) const override { return cell_kind::cable; diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index c419b8ebc1..070ebb783c 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -191,7 +191,7 @@ make_catalogue_standalone( target_link_libraries(dummy-catalogue PRIVATE arbor-private-deps) add_dependencies(unit dummy-catalogue) -target_link_libraries(unit PRIVATE arbor-private-deps) +target_link_libraries(unit PRIVATE arbor-private-deps ext-gtest) target_compile_definitions(unit PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../swc\"") target_compile_definitions(unit PRIVATE "-DLIBDIR=\"${PROJECT_BINARY_DIR}/lib\"") target_include_directories(unit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/test/unit/test_cv_geom.cpp b/test/unit/test_cv_geom.cpp index 7d49151b4d..4aec031496 100644 --- a/test/unit/test_cv_geom.cpp +++ b/test/unit/test_cv_geom.cpp @@ -584,8 +584,7 @@ TEST(region_cv, custom_geometry) { { decor d; // Discretize by segment - d.set_default(cv_policy_every_segment()); - auto cell = cable_cell(m, d, l); + auto cell = cable_cell(m, d, l, cv_policy_every_segment()); auto geom = cv_data(cell); EXPECT_TRUE(geom); @@ -642,8 +641,7 @@ TEST(region_cv, custom_geometry) { {2, 0.2}, {2, 1} }); - d.set_default(cv_policy_explicit(ls)); - auto cell = cable_cell(m, d, l); + auto cell = cable_cell(m, d, l, cv_policy_explicit(ls)); auto geom = cv_data(cell); EXPECT_TRUE(geom); diff --git a/test/unit/test_cv_policy.cpp b/test/unit/test_cv_policy.cpp index 95f2259710..9182859199 100644 --- a/test/unit/test_cv_policy.cpp +++ b/test/unit/test_cv_policy.cpp @@ -1,6 +1,3 @@ -#include -#include -#include #include #include @@ -43,8 +40,7 @@ TEST(cv_policy, single) { for (region reg: {reg::all(), reg::branch(2), reg::cable(3, 0.25, 1.), join(reg::cable(1, 0.75, 1), reg::branch(3), reg::cable(2, 0, 0.5)), - join(reg::cable(2, 0, 0.5), reg::branch(3), reg::cable(4, 0, 0.5))}) - { + join(reg::cable(2, 0, 0.5), reg::branch(3), reg::cable(4, 0, 0.5))}) { locset expected = ls::cboundary(reg); EXPECT_TRUE(locset_eq(cell.provider(), ls::cboundary(reg), cv_policy_single(reg).cv_boundary_points(cell))); } @@ -94,9 +90,7 @@ TEST(cv_policy, explicit_policy) { TEST(cv_policy, empty_morphology) { // Any policy applied to an empty morphology should give an empty locset. - - using namespace cv_policy_flag; - + using enum cv_policy_flag; cv_policy policies[] = { cv_policy_fixed_per_branch(3), cv_policy_fixed_per_branch(3, interior_forks), @@ -116,7 +110,7 @@ TEST(cv_policy, empty_morphology) { } TEST(cv_policy, fixed_per_branch) { - using namespace cv_policy_flag; + using enum cv_policy_flag; using L = mlocation; // Root branch only: @@ -200,7 +194,7 @@ TEST(cv_policy, fixed_per_branch) { } TEST(cv_policy, max_extent) { - using namespace cv_policy_flag; + using enum cv_policy_flag; using L = mlocation; // Root branch only: @@ -268,7 +262,7 @@ TEST(cv_policy, max_extent) { } TEST(cv_policy, every_segment) { - using namespace cv_policy_flag; + using enum cv_policy_flag; // Cell with root branch and two child branches, with multiple samples per branch. // Fork is at (0., 0., 4.0). @@ -317,7 +311,7 @@ TEST(cv_policy, every_segment) { } TEST(cv_policy, domain) { - using namespace cv_policy_flag; + using enum cv_policy_flag; region reg1 = join(reg::branch(1), reg::cable(2, 0, 0.5)); region reg2 = join(reg::branch(1), reg::cable(2, 0.5, 1), reg::cable(4, 0, 1)); diff --git a/test/unit/test_diffusion.cpp b/test/unit/test_diffusion.cpp index 264450ce79..8f51e38faa 100644 --- a/test/unit/test_diffusion.cpp +++ b/test/unit/test_diffusion.cpp @@ -37,7 +37,7 @@ constexpr int with_gpu = -1; struct linear: public recipe { linear(double x, double d, double c): extent{x}, diameter{d}, cv_length{c} { gprop.default_parameters = arb::neuron_parameter_defaults; - gprop.default_parameters.discretization = arb::cv_policy_max_extent{cv_length}; + gprop.default_parameters.discretization = arb::cv_policy_max_extent(cv_length); // Stick morphology // -----x----- segment_tree tree; diff --git a/test/unit/test_fvm_layout.cpp b/test/unit/test_fvm_layout.cpp index 58ab02a6e2..7d7a031cd4 100644 --- a/test/unit/test_fvm_layout.cpp +++ b/test/unit/test_fvm_layout.cpp @@ -1402,7 +1402,7 @@ TEST(fvm_layout, density_norm_area_partial) { hh_end["gkbar"] = end_gkbar; auto desc = builder.make_cell(); - desc.decorations.set_default(cv_policy_fixed_per_branch(1)); + desc.discretization = cv_policy_fixed_per_branch(1); desc.decorations.paint(builder.cable({1, 0., 0.3}), density(hh_begin)); desc.decorations.paint(builder.cable({1, 0.4, 1.}), density(hh_end)); @@ -1693,8 +1693,7 @@ TEST(fvm_layout, vinterp_cable) { // CV midpoints at branch pos 0.1, 0.3, 0.5, 0.7, 0.9. // Expect voltage reference locations to be CV modpoints. - d.set_default(cv_policy_fixed_per_branch(5)); - cable_cell cell{m, d}; + cable_cell cell{m, d, {}, cv_policy_fixed_per_branch(5)}; fvm_cv_discretization D = fvm_cv_discretize(cell, neuron_parameter_defaults); // Test locations, either side of CV midpoints plus extrema, CV boundaries. @@ -1753,8 +1752,7 @@ TEST(fvm_layout, vinterp_forked) { // CV 0 contains branch 0 and the fork point; CV 1 and CV 2 have CV 0 as parent, // and contain branches 1 and 2 respectively, excluding the fork point. mlocation_list cv_ends{{1, 0.}, {2, 0.}}; - d.set_default(cv_policy_explicit(cv_ends)); - cable_cell cell{m, d}; + cable_cell cell{m, d, {}, cv_policy_explicit(cv_ends)}; fvm_cv_discretization D = fvm_cv_discretize(cell, neuron_parameter_defaults); // Points in branch 0 should only get CV 0 for interpolation. @@ -1806,12 +1804,10 @@ TEST(fvm_layout, iinterp) { if (p.second.empty()) continue; decor d; - d.set_default(cv_policy_fixed_per_branch(3)); - cells.emplace_back(cable_cell{p.second, d}); + cells.emplace_back(cable_cell{p.second, d, {}, cv_policy_fixed_per_branch(3)}); label.push_back(p.first+": forks-at-end"s); - d.set_default(cv_policy_fixed_per_branch(3, cv_policy_flag::interior_forks)); - cells.emplace_back(cable_cell{p.second, d}); + cells.emplace_back(cable_cell{p.second, d, {}, cv_policy_fixed_per_branch(3, cv_policy_flag::interior_forks)}); label.push_back(p.first+": interior-forks"s); } @@ -1859,8 +1855,7 @@ TEST(fvm_layout, iinterp) { // CV 0 contains branch 0 and the fork point; CV 1 and CV 2 have CV 0 as parent, // and contain branches 1 and 2 respectively, excluding the fork point. mlocation_list cv_ends{{1, 0.}, {2, 0.}}; - d.set_default(cv_policy_explicit(cv_ends)); - cable_cell cell{m, d}; + cable_cell cell{m, d, {}, cv_policy_explicit(cv_ends)}; D = fvm_cv_discretize(cell, neuron_parameter_defaults); // Expect axial current interpolations on branches 1 and 2 to match CV 1 and 2 @@ -1925,7 +1920,7 @@ TEST(fvm_layout, inhomogeneous_parameters) { auto param = neuron_parameter_defaults; param.membrane_capacitance = 42.0; - param.discretization = cv_policy_fixed_per_branch{30}; + param.discretization = cv_policy_fixed_per_branch(30); auto expected = std::vector{{8.37758,385.369,2}, // constant {8.37758,385.369,2}, diff --git a/test/unit/test_fvm_lowered.cpp b/test/unit/test_fvm_lowered.cpp index 2529b9841d..3772f223b0 100644 --- a/test/unit/test_fvm_lowered.cpp +++ b/test/unit/test_fvm_lowered.cpp @@ -227,9 +227,9 @@ TEST(fvm_lowered, target_handles) { // (in increasing target order) descriptions[0].decorations.place(mlocation{0, 0.7}, synapse("expsyn"), "syn0"); descriptions[0].decorations.place(mlocation{0, 0.3}, synapse("expsyn"), "syn1"); + descriptions[1].decorations.place(mlocation{2, 0.2}, synapse("exp2syn"), "syn2"); descriptions[1].decorations.place(mlocation{2, 0.8}, synapse("expsyn"), "syn3"); - descriptions[1].decorations.place(mlocation{0, 0}, threshold_detector{3.3*arb::units::mV}, "detector"); cable_cell cells[] = {descriptions[0], descriptions[1]}; @@ -268,7 +268,6 @@ TEST(fvm_lowered, target_handles) { fvm_cell fvcell1(*context); auto fvm_info1 = fvcell1.initialize({0, 1}, cable1d_recipe(cells, false)); test_target_handles(fvcell1); - } TEST(fvm_lowered, stimulus) { @@ -825,7 +824,6 @@ TEST(fvm_lowered, post_events_shared_state) { tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); arb::decor decor; - decor.set_default(arb::cv_policy_fixed_per_branch(ncv_)); auto ndetectors = detectors_per_cell_[gid]; auto offset = 1.0 / ndetectors; @@ -834,7 +832,7 @@ TEST(fvm_lowered, post_events_shared_state) { } decor.place(arb::mlocation{0, 0.5}, synapse_, "syanpse"); - return arb::cable_cell(arb::morphology(tree), decor); + return arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(ncv_)); } cell_kind get_cell_kind(cell_gid_type gid) const override { @@ -921,22 +919,20 @@ TEST(fvm_lowered, label_data) { tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); { arb::decor decor; - decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(uniform(all(), 0, 3, 42), arb::synapse("expsyn"), "4_synapses"); decor.place(uniform(all(), 4, 4, 42), arb::synapse("expsyn"), "1_synapse"); decor.place(uniform(all(), 5, 5, 42), arb::threshold_detector{10*arb::units::mV}, "1_detector"); - cells_.push_back(arb::cable_cell(arb::morphology(tree), decor)); + cells_.push_back(arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10))); } { arb::decor decor; - decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(uniform(all(), 0, 2, 24), arb::threshold_detector{10*arb::units::mV}, "3_detectors"); decor.place(uniform(all(), 3, 4, 24), arb::threshold_detector{10*arb::units::mV}, "2_detectors"); decor.place(uniform(all(), 5, 6, 24), arb::junction("gj"), "2_gap_junctions"); decor.place(uniform(all(), 7, 7, 24), arb::junction("gj"), "1_gap_junction"); - cells_.push_back(arb::cable_cell(arb::morphology(tree), decor)); + cells_.push_back(arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10))); } } diff --git a/test/unit/test_probe.cpp b/test/unit/test_probe.cpp index 43449d0173..99a6b249a1 100644 --- a/test/unit/test_probe.cpp +++ b/test/unit/test_probe.cpp @@ -108,7 +108,7 @@ void run_v_i_probe_test(context ctx) { builder.add_branch(0, 200, 1.0/2, 1.0/2, 1, "dend"); auto bs = builder.make_cell(); - bs.decorations.set_default(cv_policy_fixed_per_branch(1)); + bs.discretization = cv_policy_fixed_per_branch(1); auto stim = i_clamp::box(0.*U::ms, 100*U::ms, 0.3*U::nA); bs.decorations.place(mlocation{1, 1}, stim, "clamp"); @@ -209,12 +209,11 @@ void run_v_cell_probe_test(context ctx) { {"interior fork", cv_policy_fixed_per_branch(3, cv_policy_flag::interior_forks)}, }; - for (auto& testcase: test_policies) { - SCOPED_TRACE(testcase.first); + for (auto& [name, policy]: test_policies) { + SCOPED_TRACE(name); decor d; - d.set_default(testcase.second); - cable_cell cell(m, d); + cable_cell cell(m, d, {}, policy); cable1d_recipe rec(cell, false); rec.add_probe(0, "U_m", cable_probe_membrane_voltage_cell{}); @@ -236,7 +235,7 @@ void run_v_cell_probe_test(context ctx) { // Independetly discretize the cell so we can follow cable–CV relationship. - cv_geometry geom(cell, testcase.second.cv_boundary_points(cell)); + cv_geometry geom(cell, policy.cv_boundary_points(cell)); // For each cable in metadata, get CV from geom and confirm raw handle is // state voltage + CV. @@ -271,7 +270,7 @@ void run_expsyn_g_probe_test(context ctx) { auto bs = builder.make_cell(); bs.decorations.place(loc0, synapse("expsyn"), "syn0"); bs.decorations.place(loc1, synapse("expsyn"), "syn1"); - bs.decorations.set_default(cv_policy_fixed_per_branch(2)); + bs.discretization = cv_policy_fixed_per_branch(2); auto run_test = [&](bool coalesce_synapses) { cable1d_recipe rec(cable_cell(bs), coalesce_synapses); @@ -362,7 +361,6 @@ void run_expsyn_g_cell_probe_test(context ctx) { auto policy = cv_policy_fixed_per_branch(3); arb::decor decor; - decor.set_default(policy); std::unordered_map expsyn_target_loc_map; unsigned n_expsyn = 0; for (unsigned bid = 0; bid<3u; ++bid) { @@ -376,7 +374,7 @@ void run_expsyn_g_cell_probe_test(context ctx) { } } - std::vector cells(2, arb::cable_cell(make_y_morphology(), decor)); + std::vector cells(2, arb::cable_cell(make_y_morphology(), decor, {}, policy)); // Weight for target (gid, lid) auto weight = [](auto gid, auto tgt) -> float { return tgt + 100*gid; }; @@ -492,11 +490,9 @@ void run_ion_density_probe_test(context ctx) { auto m = make_stick_morphology(); decor d; - d.set_default(cv_policy_fixed_per_branch(3)); // Calcium ions everywhere, half written by write_ca1, half by write_ca2. // Sodium ions only on distal half. - d.paint(mcable{0, 0., 0.5}, density("write_ca1")); d.paint(mcable{0, 0.5, 1.}, density("write_ca2")); d.paint(mcable{0, 0.5, 1.}, density("write_na3")); @@ -507,7 +503,7 @@ void run_ion_density_probe_test(context ctx) { mlocation loc1{0, 0.5}; mlocation loc2{0, 0.9}; - cable1d_recipe rec(cable_cell(m, d)); + cable1d_recipe rec(cable_cell(m, d, {}, cv_policy_fixed_per_branch(3))); rec.catalogue() = cat; rec.add_probe(0, "cai-l0", cable_probe_ion_int_concentration{loc0, "ca"}); @@ -648,13 +644,8 @@ void run_partial_density_probe_test(context ctx) { cable_cell cells[2]; // Each cell is a simple constant diameter cable, with 3 CVs each. - auto m = make_stick_morphology(); - decor d0, d1; - d0.set_default(cv_policy_fixed_per_branch(3)); - d1.set_default(cv_policy_fixed_per_branch(3)); - // Paint the mechanism on every second 10% interval of each cell. // Expected values on a CV are the weighted mean of the parameter values // over the intersections of the support and the CV. @@ -675,20 +666,21 @@ void run_partial_density_probe_test(context ctx) { auto mk_mech = [](double param) { return density(mechanism_desc("param_as_state").set("p", param)); }; + decor d0; d0.paint(mcable{0, 0.0, 0.1}, mk_mech(2)); d0.paint(mcable{0, 0.2, 0.3}, mk_mech(3)); d0.paint(mcable{0, 0.4, 0.5}, mk_mech(4)); d0.paint(mcable{0, 0.6, 0.7}, mk_mech(5)); d0.paint(mcable{0, 0.8, 0.9}, mk_mech(6)); + cells[0] = cable_cell(m, d0, {}, cv_policy_fixed_per_branch(3)); + decor d1; d1.paint(mcable{0, 0.1, 0.2}, mk_mech(7)); d1.paint(mcable{0, 0.3, 0.4}, mk_mech(8)); d1.paint(mcable{0, 0.5, 0.6}, mk_mech(9)); d1.paint(mcable{0, 0.7, 0.8}, mk_mech(10)); d1.paint(mcable{0, 0.9, 1.0}, mk_mech(11)); - - cells[0] = cable_cell(m, d0); - cells[1] = cable_cell(m, d1); + cells[1] = cable_cell(m, d1, {}, cv_policy_fixed_per_branch(3)); // Place probes in the middle of each 10% interval, i.e. at 0.05, 0.15, etc. struct test_probe { @@ -771,7 +763,6 @@ void run_axial_and_ion_current_sampled_probe_test(context ctx) { const unsigned n_cv = 3; cv_policy policy = cv_policy_fixed_per_branch(n_cv); - d.set_default(policy); d.place(mlocation{0, 0}, i_clamp(0.3*U::nA), "clamp"); @@ -783,7 +774,7 @@ void run_axial_and_ion_current_sampled_probe_test(context ctx) { d.set_default(membrane_capacitance{0.01*U::F/U::m2}); // [F/m²] auto tau = 0.1*U::ms; - cable1d_recipe rec(cable_cell(m, d)); + cable1d_recipe rec(cable_cell(m, d, {}, policy)); rec.catalogue() = cat; cable_cell cell(m, d); @@ -972,7 +963,7 @@ void run_v_sampled_probe_test(context ctx) { builder.add_branch(0, 200, 1.0/2, 1.0/2, 1, "dend"); auto bs = builder.make_cell(); - bs.decorations.set_default(cv_policy_fixed_per_branch(1)); + bs.discretization = cv_policy_fixed_per_branch(1); auto d0 = bs.decorations; auto d1 = bs.decorations; @@ -1057,9 +1048,7 @@ void run_total_current_probe_test(context ctx) { auto run_cells = [&](bool interior_forks) { auto flags = interior_forks? cv_policy_flag::interior_forks: cv_policy_flag::none; cv_policy policy = cv_policy_fixed_per_branch(n_cv_per_branch, flags); - d0.set_default(policy); - d1.set_default(policy); - std::vector cells = {{m, d0}, {m, d1}}; + std::vector cells = {{m, d0, {}, policy}, {m, d1, {}, policy}}; for (unsigned i = 0; i<2; ++i) { @@ -1163,17 +1152,15 @@ void run_stimulus_probe_test(context ctx) { cv_policy policy = cv_policy_fixed_per_branch(3); decor d0, d1; - d0.set_default(policy); d0.place(mlocation{0, 0.5}, i_clamp::box(stim_from, stim_until, 10.*U::nA), "clamp0"); d0.place(mlocation{0, 0.5}, i_clamp::box(stim_from, stim_until, 20.*U::nA), "clamp1"); double expected_stim0 = 30; - d1.set_default(policy); d1.place(mlocation{0, 1}, i_clamp::box(stim_from, stim_until, 30.*U::nA), "clamp0"); d1.place(mlocation{0, 1}, i_clamp::box(stim_from, stim_until, -10.*U::nA), "clamp1"); double expected_stim1 = 20; - std::vector cells = {{m, d0}, {m, d1}}; + std::vector cells = {{m, d0, {}, policy}, {m, d1, {}, policy}}; // Sample the cells during the stimulus, and after. diff --git a/test/unit/test_s_expr.cpp b/test/unit/test_s_expr.cpp index f08607ce5e..c45701fe42 100644 --- a/test/unit/test_s_expr.cpp +++ b/test/unit/test_s_expr.cpp @@ -235,11 +235,10 @@ std::string round_trip_network_value(const char* in) { } } - TEST(cv_policies, round_tripping) { auto literals = {"(every-segment (tag 42))", - "(fixed-per-branch 23 (segment 0) 1)", - "(max-extent 23.1 (segment 0) 1)", + "(fixed-per-branch 23 (segment 0) (flag-interior-forks))", + "(max-extent 23.1 (segment 0) (flag-interior-forks))", "(single (segment 0))", "(explicit (terminal) (segment 0))", "(join (every-segment (tag 42)) (single (segment 0)))", @@ -252,8 +251,8 @@ TEST(cv_policies, round_tripping) { TEST(cv_policies, literals) { EXPECT_NO_THROW("(every-segment (tag 42))"_cvp); - EXPECT_NO_THROW("(fixed-per-branch 23 (segment 0) 1)"_cvp); - EXPECT_NO_THROW("(max-extent 23.1 (segment 0) 1)"_cvp); + EXPECT_NO_THROW("(fixed-per-branch 23 (segment 0) (flag-interior-forks))"_cvp); + EXPECT_NO_THROW("(max-extent 23.1 (segment 0) (flag-interior-forks))"_cvp); EXPECT_NO_THROW("(single (segment 0))"_cvp); EXPECT_NO_THROW("(explicit (terminal) (segment 0))"_cvp); EXPECT_NO_THROW("(join (every-segment (tag 42)) (single (segment 0)))"_cvp); @@ -860,8 +859,7 @@ TEST(decor_literals, round_tripping) { "(scaled-mechanism (density (mechanism \"pas\" (\"g\" 0.02))) (\"g\" (exp (add (radius 2.1) (scalar 3.2)))))", }; auto default_literals = { - "(ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\"))", - "(cv-policy (single (segment 0)))" + "(ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\"))" }; auto place_literals = { "(current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 10 0.25)", @@ -923,8 +921,7 @@ TEST(decor_expressions, round_tripping) { "(default (ion-internal-concentration \"ca\" 5 (scalar 75.1)))", "(default (ion-external-concentration \"h\" 6 (scalar -50.1)))", "(default (ion-reversal-potential \"na\" 7 (scalar 30)))", - "(default (ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\")))", - "(default (cv-policy (max-extent 2 (region \"soma\") 2)))" + "(default (ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\")))" }; auto decorate_place_literals = { "(place (location 3 0.2) (current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 0.5 0.25) \"clamp\")", @@ -1004,11 +1001,6 @@ TEST(decor, round_tripping) { " (default \n" " (ion-reversal-potential-method \"na\" \n" " (mechanism \"nernst\")))\n" - " (default \n" - " (cv-policy \n" - " (fixed-per-branch 10 \n" - " (all)\n" - " 1)))\n" " (paint \n" " (region \"dend\")\n" " (density \n" @@ -1274,7 +1266,11 @@ TEST(cable_cell, round_tripping) { " (110.000000 0.500000)\n" " (110.000000 0.000000))\n" " 0.000000 0.000000)\n" - " \"iclamp\"))))"; + " \"iclamp\"))\n" + " (cv-policy \n" + " (fixed-per-branch 10 \n" + " (all)\n" + " (flag-interior-forks)))))"; EXPECT_EQ(component_str, round_trip_component(component_str.c_str())); diff --git a/test/unit/test_sde.cpp b/test/unit/test_sde.cpp index ed9b1a1efd..30637dfebd 100644 --- a/test/unit/test_sde.cpp +++ b/test/unit/test_sde.cpp @@ -254,15 +254,13 @@ class simple_sde_recipe: public simple_recipe_base { // set cvs explicitly double const cv_size = 1.0; - dec.set_default(cv_policy_max_extent(cv_size)); - // generate cells unsigned const n1 = ncvs/2; for (unsigned int i=0; i