From b7623d13e4d49146006cf4e2024e4433cb936350 Mon Sep 17 00:00:00 2001 From: Sam Yates <yates@cscs.ch> Date: Thu, 2 Nov 2017 17:18:51 +0100 Subject: [PATCH] Add mechanism parameter setting/new implementation. (#377) Fixes #350 * Replace parameter_list with mechanism_spec. * Add prototype for mechanism parameter schema checking. * Allow mechanism weights to be set after construction. * Combine range parameters on density mechanisms by linear contribution in CVs. * Cable segment electrical parameters are now member variables. * Publish mechanism parameter information through new method `mechanism::field_info`; note this will be replaced/improved in upcoming dynamic mechanism catalog work. * Access mechanism parameter scalars and range data via `mechanism::field_view_ptr` and `mechanism::field_value_ptr` methods. * Allow mechanism 'global' parameters to be set via a method of specializing mechanisms (and giving them corresponding aliases). * Extend recipe interface to allow querying of per-cell-kind global information for use by cell group implementations. * Add unit tests for above - note that linear density mechanism parameter test is tightly coupled with the FVM discretization scheme. --- miniapp/miniapp_recipes.cpp | 8 +- modcc/blocks.hpp | 19 +- modcc/cprinter.cpp | 117 +++++++- modcc/cudaprinter.cpp | 77 ++++++ modcc/parser.cpp | 72 ++++- modcc/parser.hpp | 3 + src/CMakeLists.txt | 1 - src/backends/fvm.hpp | 2 +- src/backends/gpu/fvm.hpp | 12 +- src/backends/multicore/fvm.hpp | 14 +- src/backends/multicore/stimulus.hpp | 3 - src/cell.hpp | 20 +- src/fvm_multicell.hpp | 319 +++++++++++++++------ src/mechanism.hpp | 57 +++- src/mechinfo.hpp | 108 ++++++++ src/memory/array.hpp | 19 -- src/parameter_list.cpp | 108 -------- src/parameter_list.hpp | 229 --------------- src/recipe.hpp | 8 +- src/segment.hpp | 161 +++-------- src/util/simple_table.hpp | 44 +++ tests/common_cells.hpp | 69 ++--- tests/simple_recipes.hpp | 14 + tests/unit/CMakeLists.txt | 2 +- tests/unit/common.hpp | 12 + tests/unit/test.cpp | 4 + tests/unit/test_cell.cpp | 3 +- tests/unit/test_fvm_multi.cpp | 369 +++++++++++++++++++++++-- tests/unit/test_matrix.cpp | 8 +- tests/unit/test_matrix.cu | 2 +- tests/unit/test_mechinfo.cpp | 45 +++ tests/unit/test_parameters.cpp | 41 --- tests/unit/test_synapses.cpp | 17 +- tests/validation/validate_kinetic.cpp | 13 +- tests/validation/validate_synapses.cpp | 2 +- 35 files changed, 1281 insertions(+), 721 deletions(-) create mode 100644 src/mechinfo.hpp delete mode 100644 src/parameter_list.cpp delete mode 100644 src/parameter_list.hpp create mode 100644 src/util/simple_table.hpp create mode 100644 tests/unit/test_mechinfo.cpp delete mode 100644 tests/unit/test_parameters.cpp diff --git a/miniapp/miniapp_recipes.cpp b/miniapp/miniapp_recipes.cpp index 91384d47..cae791fc 100644 --- a/miniapp/miniapp_recipes.cpp +++ b/miniapp/miniapp_recipes.cpp @@ -37,12 +37,12 @@ cell make_basic_cell( } if (segment->is_dendrite()) { - segment->add_mechanism(pas_parameters()); - segment->mechanism("membrane").set("r_L", 100); + segment->add_mechanism("pas"); + segment->rL = 100; } } - cell.soma()->add_mechanism(hh_parameters()); + cell.soma()->add_mechanism("hh"); cell.add_detector({0,0}, 20); auto distribution = std::uniform_real_distribution<float>(0.f, 1.0f); @@ -62,7 +62,7 @@ cell make_basic_cell( EXPECTS(!terminals.empty()); - arb::parameter_list syn_default(syn_type); + arb::mechanism_spec syn_default(syn_type); for (unsigned i=0; i<num_synapses; ++i) { unsigned id = terminals[i%terminals.size()]; cell.add_synapse({id, distribution(rng)}, syn_default); diff --git a/modcc/blocks.hpp b/modcc/blocks.hpp index 3ea27608..fc26c91d 100644 --- a/modcc/blocks.hpp +++ b/modcc/blocks.hpp @@ -33,6 +33,8 @@ struct Id { // string == no value unit_tokens units; + std::pair<Token, Token> range; // empty component => no range set + Id(Token const& t, std::string const& v, unit_tokens const& u) : token(t), value(v), units(u) {} @@ -40,7 +42,22 @@ struct Id { Id() {} bool has_value() const { - return value.size()>0; + return !value.empty(); + } + + bool has_range() const { + return !range.first.spelling.empty(); + } + + std::string unit_string() const { + std::string u; + for (auto& t: units) { + if (!u.empty()) { + u += ' '; + } + u += t.spelling; + } + return u; } std::string const& name() const { diff --git a/modcc/cprinter.cpp b/modcc/cprinter.cpp index 8e7ad9b0..7c935f61 100644 --- a/modcc/cprinter.cpp +++ b/modcc/cprinter.cpp @@ -1,5 +1,6 @@ #include <algorithm> #include <string> +#include <unordered_set> #include "cprinter.hpp" #include "lexer.hpp" @@ -19,6 +20,7 @@ std::string CPrinter::emit_source() { // and a list of all scalar types std::vector<VariableExpression*> scalar_variables; std::vector<VariableExpression*> array_variables; + for(auto& sym: module_->symbols()) { if(auto var = sym.second->is_variable()) { if(var->is_range()) { @@ -132,12 +134,21 @@ std::string CPrinter::emit_source() { if (module_->kind() == moduleKind::density) { text_.add_line("// add the user-supplied weights for converting from current density"); text_.add_line("// to per-compartment current in nA"); + text_.add_line("if (weights.size()) {"); + text_.increase_indentation(); if(optimize_) { text_.add_line("memory::copy(weights, view(weights_, size()));"); } else { text_.add_line("memory::copy(weights, weights_(0, size()));"); } + text_.decrease_indentation(); + text_.add_line("}"); + text_.add_line("else {"); + text_.increase_indentation(); + text_.add_line("memory::fill(weights_, 1.0);"); + text_.decrease_indentation(); + text_.add_line("}"); text_.add_line(); } @@ -179,10 +190,6 @@ std::string CPrinter::emit_source() { text_.add_line("}"); text_.add_line(); - text_.add_line("void set_params() override {"); - text_.add_line("}"); - text_.add_line(); - text_.add_line("std::string name() const override {"); text_.increase_indentation(); text_.add_line("return \"" + module_name + "\";"); @@ -200,6 +207,16 @@ std::string CPrinter::emit_source() { text_.add_line("}"); text_.add_line(); + // Override `set_weights` method only for density mechanisms. + if (module_->kind() == moduleKind::density) { + text_.add_line("void set_weights(array&& weights) override {"); + text_.increase_indentation(); + text_.add_line("memory::copy(weights, weights_(0, size()));"); + text_.decrease_indentation(); + text_.add_line("}"); + text_.add_line(); + } + // return true/false indicating if cell has dependency on k auto const& ions = module_->neuron_block().ions; auto find_ion = [&ions] (ionKind k) { @@ -351,6 +368,97 @@ std::string CPrinter::emit_source() { text_.add_line(); } + // TODO: replace field_info() generation implemenation with separate schema info generation + // as per #349. + auto field_info_string = [](const std::string& kind, const Id& id) { + return "field_spec{field_spec::" + kind + ", " + + "\"" + id.unit_string() + "\", " + + (id.has_value()? id.value: "0") + + (id.has_range()? ", " + id.range.first.spelling + "," + id.range.second.spelling: "") + + "}"; + }; + + std::unordered_set<std::string> scalar_set; + for (auto& v: scalar_variables) { + scalar_set.insert(v->name()); + } + + std::vector<Id> global_param_ids; + std::vector<Id> instance_param_ids; + + for (const Id& id: module_->parameter_block().parameters) { + auto var = id.token.spelling; + (scalar_set.count(var)? global_param_ids: instance_param_ids).push_back(id); + } + const std::vector<Id>& state_ids = module_->state_block().state_variables; + + text_.add_line("util::optional<field_spec> field_info(const char* id) const /* override */ {"); + text_.increase_indentation(); + text_.add_line("static const std::pair<const char*, field_spec> field_tbl[] = {"); + text_.increase_indentation(); + for (const auto& id: global_param_ids) { + auto var = id.token.spelling; + text_.add_line("{\""+var+"\", "+field_info_string("global",id )+"},"); + } + for (const auto& id: instance_param_ids) { + auto var = id.token.spelling; + text_.add_line("{\""+var+"\", "+field_info_string("parameter", id)+"},"); + } + for (const auto& id: state_ids) { + auto var = id.token.spelling; + text_.add_line("{\""+var+"\", "+field_info_string("state", id)+"},"); + } + text_.decrease_indentation(); + text_.add_line("};"); + text_.add_line(); + text_.add_line("auto* info = util::table_lookup(field_tbl, id);"); + text_.add_line("return info? util::just(*info): util::nothing;"); + text_.decrease_indentation(); + text_.add_line("}"); + text_.add_line(); + + if (!instance_param_ids.empty() || !state_ids.empty()) { + text_.add_line("view base::* field_view_ptr(const char* id) const override {"); + text_.increase_indentation(); + text_.add_line("static const std::pair<const char*, view "+class_name+"::*> field_tbl[] = {"); + text_.increase_indentation(); + for (const auto& id: instance_param_ids) { + auto var = id.token.spelling; + text_.add_line("{\""+var+"\", &"+class_name+"::"+var+"},"); + } + for (const auto& id: state_ids) { + auto var = id.token.spelling; + text_.add_line("{\""+var+"\", &"+class_name+"::"+var+"},"); + } + text_.decrease_indentation(); + text_.add_line("};"); + text_.add_line(); + text_.add_line("auto* pptr = util::table_lookup(field_tbl, id);"); + text_.add_line("return pptr? static_cast<view base::*>(*pptr): nullptr;"); + text_.decrease_indentation(); + text_.add_line("}"); + text_.add_line(); + } + + if (!global_param_ids.empty()) { + text_.add_line("value_type base::* field_value_ptr(const char* id) const override {"); + text_.increase_indentation(); + text_.add_line("static const std::pair<const char*, value_type "+class_name+"::*> field_tbl[] = {"); + text_.increase_indentation(); + for (const auto& id: global_param_ids) { + auto var = id.token.spelling; + text_.add_line("{\""+var+"\", &"+class_name+"::"+var+"},"); + } + text_.decrease_indentation(); + text_.add_line("};"); + text_.add_line(); + text_.add_line("auto* pptr = util::table_lookup(field_tbl, id);"); + text_.add_line("return pptr? static_cast<value_type base::*>(*pptr): nullptr;"); + text_.decrease_indentation(); + text_.add_line("}"); + text_.add_line(); + } + ////////////////////////////////////////////// ////////////////////////////////////////////// @@ -409,6 +517,7 @@ void CPrinter::emit_headers() { text_.add_line("#include <backends/event.hpp>"); text_.add_line("#include <backends/multi_event_stream_state.hpp>"); text_.add_line("#include <util/pprintf.hpp>"); + text_.add_line("#include <util/simple_table.hpp>"); text_.add_line(); } diff --git a/modcc/cudaprinter.cpp b/modcc/cudaprinter.cpp index 3eeecf84..1d9be13e 100644 --- a/modcc/cudaprinter.cpp +++ b/modcc/cudaprinter.cpp @@ -1,4 +1,6 @@ #include <algorithm> +#include <string> +#include <unordered_set> #include "cudaprinter.hpp" #include "lexer.hpp" @@ -46,6 +48,7 @@ CUDAPrinter::CUDAPrinter(Module &m, bool o) buffer().add_line("#include <backends/fvm_types.hpp>"); buffer().add_line("#include <backends/multi_event_stream_state.hpp>"); buffer().add_line("#include <backends/gpu/kernels/detail.hpp>"); + buffer().add_line("#include <util/simple_table.hpp>"); buffer().add_line(); buffer().add_line("namespace arb { namespace gpu{"); @@ -317,7 +320,16 @@ CUDAPrinter::CUDAPrinter(Module &m, bool o) if (m.kind() == moduleKind::density) { buffer().add_line("// add the user-supplied weights for converting from current density"); buffer().add_line("// to per-compartment current in nA"); + buffer().add_line("if (weights.size()) {"); + buffer().increase_indentation(); buffer().add_line("memory::copy(weights, weights_(0, size()));"); + buffer().decrease_indentation(); + buffer().add_line("}"); + buffer().add_line("else {"); + buffer().increase_indentation(); + buffer().add_line("memory::fill(weights_, 1.0);"); + buffer().decrease_indentation(); + buffer().add_line("}"); buffer().add_line(); } @@ -378,6 +390,16 @@ CUDAPrinter::CUDAPrinter(Module &m, bool o) buffer().add_line("}"); buffer().add_line(); + // Override `set_weights` method only for density mechanisms. + if (module_->kind() == moduleKind::density) { + buffer().add_line("void set_weights(array&& weights) override {"); + buffer().increase_indentation(); + buffer().add_line("memory::copy(weights, weights_(0, size()));"); + buffer().decrease_indentation(); + buffer().add_line("}"); + buffer().add_line(); + } + ////////////////////////////////////////////// // print ion channel interface ////////////////////////////////////////////// @@ -525,6 +547,61 @@ CUDAPrinter::CUDAPrinter(Module &m, bool o) } } + std::unordered_set<std::string> scalar_set; + for (auto& v: scalar_variables) { + scalar_set.insert(v->name()); + } + + std::vector<Id> global_param_ids; + std::vector<Id> instance_param_ids; + + for (const Id& id: module_->parameter_block().parameters) { + auto var = id.token.spelling; + (scalar_set.count(var)? global_param_ids: instance_param_ids).push_back(id); + } + const std::vector<Id>& state_ids = module_->state_block().state_variables; + + if (!instance_param_ids.empty() || !state_ids.empty()) { + buffer().add_line("view base::* field_view_ptr(const char* id) const override {"); + buffer().increase_indentation(); + buffer().add_line("static const std::pair<const char*, view "+class_name+"::*> field_tbl[] = {"); + buffer().increase_indentation(); + for (const auto& id: instance_param_ids) { + auto var = id.token.spelling; + buffer().add_line("{\""+var+"\", &"+class_name+"::"+var+"},"); + } + for (const auto& id: state_ids) { + auto var = id.token.spelling; + buffer().add_line("{\""+var+"\", &"+class_name+"::"+var+"},"); + } + buffer().decrease_indentation(); + buffer().add_line("};"); + buffer().add_line(); + buffer().add_line("auto* pptr = util::table_lookup(field_tbl, id);"); + buffer().add_line("return pptr? static_cast<view base::*>(*pptr): nullptr;"); + buffer().decrease_indentation(); + buffer().add_line("}"); + buffer().add_line(); + } + + if (!global_param_ids.empty()) { + buffer().add_line("value_type base::* field_value_ptr(const char* id) const override {"); + buffer().increase_indentation(); + buffer().add_line("static const std::pair<const char*, value_type "+class_name+"::*> field_tbl[] = {"); + buffer().increase_indentation(); + for (const auto& id: global_param_ids) { + auto var = id.token.spelling; + buffer().add_line("{\""+var+"\", &"+class_name+"::"+var+"},"); + } + buffer().decrease_indentation(); + buffer().add_line("};"); + buffer().add_line(); + buffer().add_line("auto* pptr = util::table_lookup(field_tbl, id);"); + buffer().add_line("return pptr? static_cast<value_type base::*>(*pptr): nullptr;"); + buffer().decrease_indentation(); + buffer().add_line("}"); + buffer().add_line(); + } ////////////////////////////////////////////// ////////////////////////////////////////////// diff --git a/modcc/parser.cpp b/modcc/parser.cpp index 84f79413..4c55321b 100644 --- a/modcc/parser.cpp +++ b/modcc/parser.cpp @@ -487,19 +487,14 @@ void Parser::parse_parameter_block() { // look for equality if(token_.type==tok::eq) { get_token(); // consume '=' - if(token_.type==tok::minus) { - parm.value = "-"; - get_token(); - } - if(token_.type != tok::integer && token_.type != tok::real) { + parm.value = value_literal(); + if(status_ == lexerStatus::error) { success = 0; goto parm_exit; } - parm.value += token_.spelling; // store value as a string - get_token(); } - // get the parameters + // get the units if(line==location_.line && token_.type == tok::lparen) { parm.units = unit_description(); if(status_ == lexerStatus::error) { @@ -508,6 +503,14 @@ void Parser::parse_parameter_block() { } } + // get the range + if(line==location_.line && token_.type == tok::lt) { + parm.range = range_description(); + if(status_ == lexerStatus::error) { + success = 0; + goto parm_exit; + } + } block.parameters.push_back(parm); } @@ -595,6 +598,26 @@ ass_exit: return; } +// Parse a value (integral or real) with possible preceding unary minus, +// and return as a string. +std::string Parser::value_literal() { + std::string value; + + if(token_.type==tok::minus) { + value = "-"; + get_token(); + } + if(token_.type != tok::integer && token_.type != tok::real) { + error(pprintf("numeric constant not an integer or real number '%'", token_)); + return ""; + } + else { + value += token_.spelling; + get_token(); + return value; + } +} + std::vector<Token> Parser::unit_description() { static const tok legal_tokens[] = {tok::identifier, tok::divide, tok::real, tok::integer}; int startline = location_.line; @@ -602,7 +625,7 @@ std::vector<Token> Parser::unit_description() { // check that we start with a left parenthesis if(token_.type != tok::lparen) { - error(pprintf("unit description must start with a parenthesis '%'", tokens)); + error(pprintf("unit description must start with a parenthesis '%'", token_)); goto unit_exit; } @@ -611,7 +634,7 @@ std::vector<Token> Parser::unit_description() { while(token_.type != tok::rparen) { // check for illegal tokens or a new line if( !is_in(token_.type,legal_tokens) || startline < location_.line ) { - error(pprintf("incorrect unit description '%'", tokens)); + error(pprintf("incorrect unit description '%'", token_)); goto unit_exit; } @@ -626,6 +649,35 @@ unit_exit: return tokens; } +std::pair<Token, Token> Parser::range_description() { + int startline = location_.line; + Token lb, ub; + + if(token_.type != tok::lt) { + error(pprintf("range description must start with a left angle bracket '%'", token_)); + return {}; + } + + get_token(); + lb = token_; + + if(token_.type != tok::comma) { + error(pprintf("range description must separate lower and upper bound with a comma '%'", token_)); + return {}; + } + + get_token(); + ub = token_; + + if(token_.type != tok::gt) { + error(pprintf("range description must end with a right angle bracket '%'", token_)); + return {}; + } + + get_token(); + return {lb, ub}; +} + // Returns a prototype expression for a function or procedure call // Takes an optional argument that allows the user to specify the // name of the prototype, which is used for prototypes where the name diff --git a/modcc/parser.hpp b/modcc/parser.hpp index 10a33e59..43fb323b 100644 --- a/modcc/parser.hpp +++ b/modcc/parser.hpp @@ -2,6 +2,7 @@ #include <memory> #include <string> +#include <utility> #include "expression.hpp" #include "lexer.hpp" @@ -60,6 +61,8 @@ private: std::vector<Token> comma_separated_identifiers(); std::vector<Token> unit_description(); + std::string value_literal(); + std::pair<Token, Token> range_description(); /// build the identifier list void add_variables_to_symbols(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 44488b94..6712d047 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,7 +11,6 @@ set(BASE_SOURCES hardware/power.cpp model.cpp morphology.cpp - parameter_list.cpp partition_load_balance.cpp profiling/memory_meter.cpp profiling/meter_manager.cpp diff --git a/src/backends/fvm.hpp b/src/backends/fvm.hpp index be7a1153..42ba619c 100644 --- a/src/backends/fvm.hpp +++ b/src/backends/fvm.hpp @@ -11,7 +11,7 @@ struct null_backend: public multicore::backend { return false; } - static mechanism make_mechanism( + static mechanism_ptr make_mechanism( const std::string&, size_type, const_iview, diff --git a/src/backends/gpu/fvm.hpp b/src/backends/gpu/fvm.hpp index ebdf9961..95b03c3f 100644 --- a/src/backends/gpu/fvm.hpp +++ b/src/backends/gpu/fvm.hpp @@ -65,12 +65,12 @@ struct backend { // mechanism infrastructure using ion_type = ion<backend>; - - using mechanism = mechanism_ptr<backend>; - using stimulus = gpu::stimulus<backend>; - static mechanism make_mechanism( + using mechanism = arb::mechanism<backend>; + using mechanism_ptr = std::unique_ptr<mechanism>; + + static mechanism_ptr make_mechanism( const std::string& name, size_type mech_id, const_iview vec_ci, @@ -137,11 +137,11 @@ struct backend { } private: - using maker_type = mechanism (*)(size_type, const_iview, const_view, const_view, const_view, view, view, array&&, iarray&&); + using maker_type = mechanism_ptr (*)(size_type, const_iview, const_view, const_view, const_view, view, view, array&&, iarray&&); static std::map<std::string, maker_type> mech_map_; template <template <typename> class Mech> - static mechanism maker(size_type mech_id, const_iview vec_ci, const_view vec_t, const_view vec_t_to, const_view vec_dt, view vec_v, view vec_i, array&& weights, iarray&& node_indices) { + static mechanism_ptr maker(size_type mech_id, const_iview vec_ci, const_view vec_t, const_view vec_t_to, const_view vec_dt, view vec_v, view vec_i, array&& weights, iarray&& node_indices) { return arb::make_mechanism<Mech<backend>> (mech_id, vec_ci, vec_t, vec_t_to, vec_dt, vec_v, vec_i, std::move(weights), std::move(node_indices)); } diff --git a/src/backends/multicore/fvm.hpp b/src/backends/multicore/fvm.hpp index 801f0744..af90096b 100644 --- a/src/backends/multicore/fvm.hpp +++ b/src/backends/multicore/fvm.hpp @@ -57,12 +57,12 @@ struct backend { // mechanism infrastructure // using ion_type = ion<backend>; - - using mechanism = mechanism_ptr<backend>; - using stimulus = multicore::stimulus<backend>; - static mechanism make_mechanism( + using mechanism = arb::mechanism<backend>; + using mechanism_ptr = std::unique_ptr<mechanism>; + + static mechanism_ptr make_mechanism( const std::string& name, size_type mech_id, const_iview vec_ci, @@ -75,7 +75,7 @@ struct backend { throw std::out_of_range("no mechanism in database : " + name); } - return mech_map_.find(name)->second(mech_id, vec_ci, vec_t, vec_t_to, vec_dt, vec_v, vec_i, array(weights), iarray(node_indices)); + return mech_map_.find(name)->second(mech_id, vec_ci, vec_t, vec_t_to, vec_dt, vec_v, vec_i, memory::make_const_view(weights), memory::make_const_view(node_indices)); } static bool has_mechanism(const std::string& name) { @@ -149,11 +149,11 @@ struct backend { } private: - using maker_type = mechanism (*)(value_type, const_iview, const_view, const_view, const_view, view, view, array&&, iarray&&); + using maker_type = mechanism_ptr (*)(value_type, const_iview, const_view, const_view, const_view, view, view, array&&, iarray&&); static std::map<std::string, maker_type> mech_map_; template <template <typename> class Mech> - static mechanism maker(value_type mech_id, const_iview vec_ci, const_view vec_t, const_view vec_t_to, const_view vec_dt, view vec_v, view vec_i, array&& weights, iarray&& node_indices) { + static mechanism_ptr maker(value_type mech_id, const_iview vec_ci, const_view vec_t, const_view vec_t_to, const_view vec_dt, view vec_v, view vec_i, array&& weights, iarray&& node_indices) { return arb::make_mechanism<Mech<backend>> (mech_id, vec_ci, vec_t, vec_t_to, vec_dt, vec_v, vec_i, std::move(weights), std::move(node_indices)); } diff --git a/src/backends/multicore/stimulus.hpp b/src/backends/multicore/stimulus.hpp index d43b3d7f..2318ff9f 100644 --- a/src/backends/multicore/stimulus.hpp +++ b/src/backends/multicore/stimulus.hpp @@ -38,9 +38,6 @@ public: return 0; } - void set_params() override { - } - std::string name() const override { return "stimulus"; } diff --git a/src/cell.hpp b/src/cell.hpp index 7879ab6a..28149edf 100644 --- a/src/cell.hpp +++ b/src/cell.hpp @@ -1,5 +1,6 @@ #pragma once +#include <map> #include <mutex> #include <stdexcept> #include <thread> @@ -29,6 +30,7 @@ int find_compartment_index( compartment_model const& graph ); +// Probe type for cell descriptions. struct cell_probe_address { enum probe_kind { membrane_voltage, membrane_current @@ -38,6 +40,20 @@ struct cell_probe_address { probe_kind kind; }; +// Global parameter type for cell descriptions. + +struct specialized_mechanism { + std::string mech_name; // underlying mechanism + + // parameters specify global constants for the specialized mechanism + std::vector<std::pair<std::string, double>> parameters; +}; + +struct cell_global_properties { + // Mechanisms specialized by mechanism-global parameter settings. + std::map<std::string, specialized_mechanism> special_mechs; +}; + // used in constructor below struct clone_cell_t {}; constexpr clone_cell_t clone_cell{}; @@ -52,7 +68,7 @@ public: struct synapse_instance { segment_location location; - parameter_list mechanism; + mechanism_spec mechanism; }; struct stimulus_instance { @@ -158,7 +174,7 @@ public: ////////////////// // synapses ////////////////// - void add_synapse(segment_location loc, parameter_list p) + void add_synapse(segment_location loc, mechanism_spec p) { synapses_.push_back(synapse_instance{loc, std::move(p)}); } diff --git a/src/fvm_multicell.hpp b/src/fvm_multicell.hpp index c7cbe89d..0fd6b4ee 100644 --- a/src/fvm_multicell.hpp +++ b/src/fvm_multicell.hpp @@ -191,6 +191,7 @@ public: /// mechanism type using mechanism = typename backend::mechanism; + using mechanism_ptr = typename backend::mechanism_ptr; /// stimulus type using stimulus = typename backend::stimulus; @@ -221,7 +222,7 @@ public: std::size_t size() const { return matrix_.size(); } /// return reference to in iterable container of the mechanisms - std::vector<mechanism>& mechanisms() { return mechanisms_; } + std::vector<mechanism_ptr>& mechanisms() { return mechanisms_; } /// return reference to list of ions std::map<ionKind, ion_type>& ions() { return ions_; } @@ -249,12 +250,12 @@ public: } /// Return reference to the mechanism that matches name. - /// The reference is const, because it this information should not be + /// The reference is const, because this information should not be /// modified by the caller, however it is needed for unit testing. - util::optional<const mechanism&> find_mechanism(const std::string& name) const { + util::optional<const mechanism_ptr&> find_mechanism(const std::string& name) const { auto it = std::find_if( std::begin(mechanisms_), std::end(mechanisms_), - [&name](const mechanism& m) {return m->name()==name;}); + [&name](const mechanism_ptr& m) {return m->name()==name;}); return it==mechanisms_.end() ? util::nothing: util::just(*it); } @@ -376,7 +377,7 @@ private: array voltage_; /// the set of mechanisms present in the cell - std::vector<mechanism> mechanisms_; + std::vector<mechanism_ptr> mechanisms_; /// the ion species std::map<ionKind, ion_type> ions_; @@ -420,6 +421,52 @@ private: std::vector<value_type>& tmp_cv_areas, std::vector<value_type>& cv_capacitance ); + + // TODO: This process should be simpler when we can deal with mechanism prototypes and have + // separate initialization. + // + // Create possibly-specialized mechanism and add to mechanism set. + // Weights are unset, and should be set specifically with mechanism::set_weights(). + mechanism& make_mechanism( + const std::string& name, + const std::map<std::string, specialized_mechanism>& special_mechs, + const std::vector<size_type>& node_indices) + { + std::string impl_name = name; + std::vector<std::pair<std::string, double>> global_params; + + if (special_mechs.count(name)) { + const auto& spec_mech = special_mechs.at(name); + impl_name = spec_mech.mech_name; + global_params = spec_mech.parameters; + } + + size_type mech_id = mechanisms_.size(); + auto m = backend::make_mechanism(impl_name, mech_id, cv_to_cell_, time_, time_to_, dt_comp_, voltage_, current_, {}, node_indices); + if (impl_name!=name) { + m->set_alias(name); + } + + for (const auto& pv: global_params) { + auto field = m->field_value_ptr(pv.first); + if (!field) { + throw std::invalid_argument("no scalar parameter "+pv.first+" in mechanism "+m->name()); + } + m.get()->*field = pv.second; + } + + mechanisms_.push_back(std::move(m)); + return *mechanisms_.back(); + } + + // Throwing-wrapper around mechanism (range) parameter look up. + static view mech_field(mechanism& m, const std::string& param_name) { + auto p = m.field_view_ptr(param_name); + if (!p) { + throw std::invalid_argument("no parameter "+param_name+" in mechanism "+m.name()); + } + return m.*p; + } }; //////////////////////////////////////////////////////////////////////////////// @@ -442,6 +489,9 @@ fvm_multicell<Backend>::compute_cv_area_capacitance( segment_cv_range cv_range; + auto cm = seg->cm; + auto rL = seg->rL; + if (auto soma = seg->as_soma()) { // confirm assumption that there is one compartment in soma if (ncomp!=1) { @@ -449,10 +499,9 @@ fvm_multicell<Backend>::compute_cv_area_capacitance( } auto i = comp_ival.first; auto area = math::area_sphere(soma->radius()); - auto c_m = soma->mechanism("membrane").get("c_m").value; tmp_cv_areas[i] += area; - cv_capacitance[i] += area*c_m; + cv_capacitance[i] += area*cm; cv_range.segment_cvs = {comp_ival.first, comp_ival.first+1}; cv_range.areas = {0.0, area}; @@ -488,9 +537,6 @@ fvm_multicell<Backend>::compute_cv_area_capacitance( // The first control volume of any cell corresponds to the soma // and the first half of the first cable compartment of that cell. - auto c_m = cable->mechanism("membrane").get("c_m").value; - auto r_L = cable->mechanism("membrane").get("r_L").value; - auto divs = div_compartments<div_compartment_integrator>(cable, ncomp); // assume that this segment has a parent, which is the case so long @@ -535,7 +581,7 @@ fvm_multicell<Backend>::compute_cv_area_capacitance( auto V2 = div.right.volume; auto h = h1+h2; - auto conductance = 1/r_L*h*V1*V2/(h2*h2*V1+h1*h1*V2); + auto conductance = 1/rL*h*V1*V2/(h2*h2*V1+h1*h1*V2); // the scaling factor of 10^2 is to convert the quantity // to micro Siemens [μS] face_conductance[i] = 1e2 * conductance / h; @@ -545,8 +591,8 @@ fvm_multicell<Backend>::compute_cv_area_capacitance( tmp_cv_areas[j] += al; tmp_cv_areas[i] += ar; - cv_capacitance[j] += al * c_m; - cv_capacitance[i] += ar * c_m; + cv_capacitance[j] += al * cm; + cv_capacitance[i] += ar * cm; } } else { @@ -576,6 +622,14 @@ void fvm_multicell<Backend>::initialize( ncell_ = size(gids); std::size_t targets_count = 0u; + // Handle any global parameters for these cell groups. + // (Currently: just specialized mechanisms). + std::map<std::string, specialized_mechanism> special_mechs; + util::any gprops = rec.get_global_properties(cable1d_neuron); + if (gprops.has_value()) { + special_mechs = util::any_cast<cell_global_properties&>(gprops).special_mechs; + } + // Take cell descriptions from recipe. These are used initially // to count compartments for allocation of data structures, and // then interrogated again for the details for each cell in turn. @@ -611,15 +665,36 @@ void fvm_multicell<Backend>::initialize( } memory::copy(cv_to_cell_tmp, cv_to_cell_); - // look up table: mechanism name -> list of cv_range objects - std::map<std::string, std::vector<segment_cv_range>> mech_to_cv_range; + // TODO: mechanism parameters are currently indexed by string keys; more efficient + // to use the mechanism member pointers, when these become easily accessible via + // the mechanism catalogue interface. + + // look up table: mechanism name -> list of cv_range objects and parameter settings. + + struct mech_area_contrib { + size_type index; + value_type area; + }; + + struct mech_info { + segment_cv_range cv_range; + // Note: owing to linearity constraints, the only parameters for which it is + // sensible to modify are those which make a linear contribution to currents + // (or ion fluxes, etc.) + std::map<std::string, value_type> param_map; + std::vector<mech_area_contrib> contributions; + }; + + std::map<std::string, std::vector<mech_info>> mech_map; - // look up table: point mechanism (synapse) name -> CV indices and target numbers. - struct syn_cv_and_target { + // look up table: point mechanism (synapse) name -> list of CV indices, target numbers, parameters. + struct syn_info { cell_lid_type cv; cell_lid_type target; + std::map<std::string, value_type> param_map; }; - std::map<std::string, std::vector<syn_cv_and_target>> syn_mech_map; + + std::map<std::string, std::vector<syn_info>> syn_mech_map; // initialize vector used for matrix creation. std::vector<size_type> group_parent_index(ncomp); @@ -631,9 +706,9 @@ void fvm_multicell<Backend>::initialize( // Create each cell: // Allocate scratch storage for calculating quantities used to build the - // linear system: these will later be copied into target-specific storage + // linear system: these will later be copied into target-specific storage. - // face_conductance_[i] = area_face / (r_L * delta_x); + // face_conductance_[i] = area_face / (rL * delta_x); std::vector<value_type> face_conductance(ncomp); // [µS] /// cv_capacitance_[i] is the capacitance of CV membrane std::vector<value_type> cv_capacitance(ncomp); // [µm^2*F*m^-2 = pF] @@ -681,26 +756,24 @@ void fvm_multicell<Backend>::initialize( face_conductance, tmp_cv_areas, cv_capacitance); for (const auto& mech: seg->mechanisms()) { - if (mech.name()!="membrane") { - mech_to_cv_range[mech.name()].push_back(cv_range); - } + mech_map[mech.name()].push_back({cv_range, mech.values()}); } } for (const auto& syn: c.synapses()) { const auto& name = syn.mechanism.name(); - auto& map_entry = syn_mech_map[name]; cell_lid_type syn_cv = comp_ival.first + find_cv_index(syn.location, graph); cell_lid_type target_index = targets_count++; - map_entry.push_back(syn_cv_and_target{syn_cv, target_index}); + syn_mech_map[name].push_back({syn_cv, target_index, syn.mechanism.values()}); } // // add the stimuli // + // TODO: use same process as for synapses! // step 1: pack the index and parameter information into flat vectors std::vector<size_type> stim_index; std::vector<value_type> stim_durations; @@ -726,7 +799,7 @@ void fvm_multicell<Backend>::initialize( cv_to_cell_, time_, time_to_, dt_comp_, voltage_, current_, memory::make_const_view(stim_index)); stim->set_parameters(stim_amplitudes, stim_durations, stim_delays); - mechanisms_.push_back(mechanism(stim)); + mechanisms_.push_back(mechanism_ptr(stim)); } // calculate spike detector handles are their corresponding compartment indices @@ -770,85 +843,177 @@ void fvm_multicell<Backend>::initialize( matrix_ = matrix_type( group_parent_index, cell_comp_bounds, cv_capacitance, face_conductance); - // For each density mechanism build the full node index, i.e the list of - // compartments with that mechanism, then build the mechanism instance. - std::vector<size_type> mech_cv_index(ncomp); - std::vector<value_type> mech_cv_weight(ncomp); + // Keep cv index list for each mechanism for ion set up below. std::map<std::string, std::vector<size_type>> mech_to_cv_index; - for (auto const& entry: mech_to_cv_range) { + + // Working vectors (re-used per mechanism). + std::vector<size_type> mech_cv(ncomp); + std::vector<value_type> mech_weight(ncomp); + + for (auto& entry: mech_map) { const auto& mech_name = entry.first; - const auto& seg_cv_ranges = entry.second; + auto& segments = entry.second; + + mech_cv.clear(); + mech_weight.clear(); + + // Three passes are performed over the segment list: + // 1. Compute the CVs and area contributions where the mechanism is instanced. + // 2. Build table of modified parameters together with default values. + // 3. Compute weights and parameters. + // The mechanism is instantiated after the first pass, in order to gain + // access to default mechanism parameter values. - // Clear the pre-allocated storage for mechanism indexes and weights. - // Reuse the same vectors each time to have only one malloc and free - // outside of the loop for each - mech_cv_index.clear(); - mech_cv_weight.clear(); + for (auto& seg: segments) { + const auto& rng = seg.cv_range; + seg.contributions.reserve(rng.size()); - for (auto& rng: seg_cv_ranges) { if (rng.has_parent()) { - // locate the parent cv in the partially constructed list of cv indexes - auto it = algorithms::binary_find(mech_cv_index, rng.parent_cv); - if (it == mech_cv_index.end()) { - mech_cv_index.push_back(rng.parent_cv); - mech_cv_weight.push_back(0); + auto cv = rng.parent_cv; + + auto it = algorithms::binary_find(mech_cv, cv); + size_type pos = it - mech_cv.begin(); + + if (it == mech_cv.end()) { + mech_cv.push_back(cv); } - auto pos = std::distance(std::begin(mech_cv_index), it); - // add area contribution to the parent cv for the segment - mech_cv_weight[pos] += rng.areas.first; + seg.contributions.push_back({pos, rng.areas.first}); } - util::append(mech_cv_index, make_span(rng.segment_cvs)); - util::append(mech_cv_weight, subrange_view(tmp_cv_areas, rng.segment_cvs)); - // adjust the last CV - mech_cv_weight.back() = rng.areas.second; + for (auto cv: make_span(rng.segment_cvs)) { + size_type pos = mech_cv.size(); + mech_cv.push_back(cv); + seg.contributions.push_back({pos, tmp_cv_areas[cv]}); + } - EXPECTS(mech_cv_weight.size()==mech_cv_index.size()); + // Last CV contribution may be only partial, so adjust. + seg.contributions.back().area = rng.areas.second; + } + + auto nindex = mech_cv.size(); + + EXPECTS(std::is_sorted(mech_cv.begin(), mech_cv.end())); + EXPECTS(nindex>0); + + auto& mech = make_mechanism(mech_name, special_mechs, mech_cv); + + // Save the indices for ion set up below. + + mech_to_cv_index[mech_name] = mech_cv; + + // Build modified (non-global) parameter table. + + struct param_tbl_entry { + std::vector<value_type> values; // staged for writing to mechanism + view data; // view to corresponding data in mechanism + value_type dflt; // default value for parameter + }; + + std::map<std::string, param_tbl_entry> param_tbl; + + for (const auto& seg: segments) { + for (const auto& pv: seg.param_map) { + if (param_tbl.count(pv.first)) { + continue; + } + + // Grab default value from mechanism data. + auto& entry = param_tbl[pv.first]; + entry.data = mech_field(mech, pv.first); + entry.dflt = entry.data[0]; + entry.values.assign(nindex, 0.); + } + } + + // Perform another pass of segment list to compute weights and (non-global) parameters. + + mech_weight.assign(nindex, 0.); + + for (const auto& seg: segments) { + for (auto cw: seg.contributions) { + mech_weight[cw.index] += cw.area; + + for (auto& entry: param_tbl) { + value_type v = entry.second.dflt; + const auto& name = entry.first; + + auto it = seg.param_map.find(name); + if (it != seg.param_map.end()) { + v = it->second; + } + + entry.second.values[cw.index] += cw.area*v; + } + } + } + + for (auto& entry: param_tbl) { + for (size_type i = 0; i<nindex; ++i) { + entry.second.values[i] /= mech_weight[i]; + } + memory::copy(entry.second.values, entry.second.data); } // Scale the weights to get correct units (see w_i^d in formulation docs) // The units for the density channel weights are [10^2 μm^2 = 10^-10 m^2], // which requires that we scale the areas [μm^2] by 10^-2 - for (auto& w: mech_cv_weight) { + + for (auto& w: mech_weight) { w *= 1e-2; } - - size_type mech_id = mechanisms_.size(); - mechanisms_.push_back( - backend::make_mechanism(mech_name, mech_id, cv_to_cell_, time_, time_to_, dt_comp_, voltage_, current_, mech_cv_weight, mech_cv_index)); - - // Save the indices for ion set up below. - mech_to_cv_index[mech_name] = mech_cv_index; + mech.set_weights(memory::make_const_view(mech_weight)); } target_handles.resize(targets_count); // Create point (synapse) mechanisms. - for (auto& syni: syn_mech_map) { + for (auto& map_entry: syn_mech_map) { size_type mech_id = mechanisms_.size(); - const auto& mech_name = syni.first; - auto& cv_assoc = syni.second; + const auto& mech_name = map_entry.first; + auto& syn_data = map_entry.second; + auto n_instance = syn_data.size(); - // Sort CV indices but keep track of their corresponding targets. - auto cv_index = [](syn_cv_and_target x) { return x.cv; }; - util::stable_sort_by(cv_assoc, cv_index); - std::vector<cell_lid_type> cv_indices = assign_from(transform_view(cv_assoc, cv_index)); + // Build permutation p such that p[j] is the index into + // syn_data for the jth synapse of this mechanism type as ordered by cv index. - // Create the mechanism. - // An empty weight vector is supplied, because there are no weights applied to point - // processes, because their currents are calculated with the target units of [nA] - mechanisms_.push_back( - backend::make_mechanism(mech_name, mech_id, cv_to_cell_, time_, time_to_, dt_comp_, voltage_, current_, {}, cv_indices)); + auto cv_of = [&](cell_lid_type i) { return syn_data[i].cv; }; + + std::vector<cell_lid_type> p(n_instance); + std::iota(p.begin(), p.end(), 0u); + util::sort_by(p, cv_of); + + std::vector<cell_lid_type> mech_cv; + mech_cv.reserve(n_instance); + + // Build mechanism cv index vector and targets. + for (auto i: make_span(0u, n_instance)) { + const auto& syn = syn_data[p[i]]; + mech_cv.push_back(syn.cv); + target_handles[syn.target] = target_handle(mech_id, i, cv_to_cell_tmp[syn.cv]); + } + + auto& mech = make_mechanism(mech_name, special_mechs, mech_cv); // Save the indices for ion set up below. - mech_to_cv_index[mech_name] = cv_indices; + mech_to_cv_index[mech_name] = mech_cv; - // Make the target handles. - cell_lid_type instance = 0; - for (auto entry: cv_assoc) { - target_handles[entry.target] = target_handle(mech_id, instance++, cv_to_cell_tmp[entry.cv]); + // Update the mechanism parameters. + std::map<std::string, std::vector<std::pair<cell_lid_type, fvm_value_type>>> param_assigns; + for (auto i: make_span(0u, n_instance)) { + for (const auto& pv: syn_data[p[i]].param_map) { + param_assigns[pv.first].push_back({i, pv.second}); + } + } + + for (const auto& pa: param_assigns) { + view field_data = mech_field(mech, pa.first); + host_array field_values = field_data; + for (const auto &iv: pa.second) { + field_values[iv.first] = iv.second; + } + memory::copy(field_values, field_data); } } @@ -866,7 +1031,7 @@ void fvm_multicell<Backend>::initialize( std::vector<size_type> indexes(index_set.begin(), index_set.end()); // create the ion state - if(indexes.size()) { + if (indexes.size()) { ions_[ion] = indexes; } diff --git a/src/mechanism.hpp b/src/mechanism.hpp index 2711c679..33d53872 100644 --- a/src/mechanism.hpp +++ b/src/mechanism.hpp @@ -4,16 +4,44 @@ #include <memory> #include <string> +#include <backends/fvm_types.hpp> #include <backends/event.hpp> #include <backends/multi_event_stream_state.hpp> #include <ion.hpp> -#include <parameter_list.hpp> #include <util/indirect.hpp> #include <util/meta.hpp> #include <util/make_unique.hpp> namespace arb { +struct field_spec { + enum field_kind { + parameter, // defined in 'PARAMETER' block and a 'RANGE' variable. + global, // defined in 'PARAMETER' block and a 'GLOBAL' variable. + state, // defined in 'STATE' block; run-time, read only values. + }; + enum field_kind kind = parameter; + + std::string units; + + fvm_value_type default_value = 0; + fvm_value_type lower_bound = std::numeric_limits<fvm_value_type>::lowest(); + fvm_value_type upper_bound = std::numeric_limits<fvm_value_type>::max(); + + // Until C++14, we need a ctor to provide default values instead of using + // default member initializers and aggregate initialization. + field_spec( + enum field_kind kind = parameter, + std::string units = "", + fvm_value_type default_value = 0., + fvm_value_type lower_bound = std::numeric_limits<fvm_value_type>::lowest(), + fvm_value_type upper_bound = std::numeric_limits<fvm_value_type>::max() + ): + kind(kind), units(units), default_value(default_value), lower_bound(lower_bound), upper_bound(upper_bound) + {} +}; + + enum class mechanismKind {point, density}; /// The mechanism type is templated on a memory policy type. @@ -60,7 +88,12 @@ public: return node_index_; } - virtual void set_params() = 0; + // Save pointers to data for use with GPU-side mechanisms; + // TODO: might be able to remove this method if we separate instantiation + // from initialization. + virtual void set_params() {} + virtual void set_weights(array&& weights) {} // override for density mechanisms + virtual std::string name() const = 0; virtual std::size_t memory() const = 0; virtual void nrn_init() = 0; @@ -71,6 +104,24 @@ public: virtual void set_ion(ionKind k, ion_type& i, const std::vector<size_type>& index) = 0; virtual mechanismKind kind() const = 0; + // Mechanism instances with different global parameter settings can be distinguished by alias. + std::string alias() const { + return alias_.empty()? name(): alias_; + } + + void set_alias(std::string alias) { + alias_ = std::move(alias); + } + + // For non-global fields: + virtual view mechanism::* field_view_ptr(const char* id) const { return nullptr; } + // For global fields: + virtual value_type mechanism::* field_value_ptr(const char* id) const { return nullptr; } + + // Convenience wrappers for field access methods with string parameter. + view mechanism::* field_view_ptr(const std::string& id) const { return field_view_ptr(id.c_str()); } + value_type mechanism::* field_value_ptr(const std::string& id) const { return field_value_ptr(id.c_str()); } + // net_receive() is used internally by deliver_events(), but // is exposed primarily for unit testing. virtual void net_receive(int, value_type) {}; @@ -94,6 +145,8 @@ public: view vec_i_; // Maps mechanism instance index to compartment index. iarray node_index_; + + std::string alias_; }; template <class Backend> diff --git a/src/mechinfo.hpp b/src/mechinfo.hpp new file mode 100644 index 00000000..cd40c387 --- /dev/null +++ b/src/mechinfo.hpp @@ -0,0 +1,108 @@ +#pragma once + +/* Mechanism schema classes, catalogue and parameter specification. + * + * Catalogue and schemata have placeholder implementations, to be + * completed in future work. + * + * The `mechanism_spec` class is the public interface for describing + * a mechanism and its (non-global) parameters in cable1d_cell + * recipes. It presents a map-like interface for accessing and querying + * parameter values, and parameter assignments will be validated + * against a corresponding schema when that infrastructure is in + * place. + */ + +#include <string> +#include <utility> +#include <vector> + +namespace arb { + +struct mechanism_schema_field { + void validate(double) const {} + double default_value = 0; +}; + +struct mechanism_schema { + const mechanism_schema_field* field(const std::string& key) const { + static mechanism_schema_field dummy_field; + return &dummy_field; + } + + static const mechanism_schema* dummy_schema() { + static mechanism_schema d; + return &d; + } +}; + +class mechanism_spec { +public: + struct field_proxy { + mechanism_spec* m; + std::string key; + + field_proxy& operator=(double v) { + m->set(key, v); + return *this; + } + + operator double() const { + return m->get(key); + } + }; + + // implicit + mechanism_spec(std::string name): name_(std::move(name)) { + // get schema pointer from global catalogue, or throw + schema_ = mechanism_schema::dummy_schema(); + if (!schema_) { + throw std::runtime_error("no mechanism "+name_); + } + } + + // implicit + mechanism_spec(const char* name): mechanism_spec(std::string(name)) {} + + mechanism_spec& set(std::string key, double value) { + auto field_schema = schema_->field(key); + if (!field_schema) { + throw std::runtime_error("no field "+key+" in mechanism "+name_); + } + + field_schema->validate(value); + param_[key] = value; + return *this; + } + + double operator[](const std::string& key) const { + return get(key); + } + + double get(const std::string& key) const { + auto field_schema = schema_->field(key); + if (!field_schema) { + throw std::runtime_error("no field "+key+" in mechanism "+name_); + } + + auto it = param_.find(key); + return it==param_.end()? field_schema->default_value: it->second; + } + + field_proxy operator[](const std::string& key) { + return {this, key}; + } + + const std::map<std::string, double>& values() const { + return param_; + } + + const std::string& name() const { return name_; } + +private: + std::string name_; + std::map<std::string, double> param_; + const mechanism_schema* schema_; // non-owning; schema must have longer lifetime +}; + +} // namespace arb diff --git a/src/memory/array.hpp b/src/memory/array.hpp index cdc97fe4..c5e358e6 100644 --- a/src/memory/array.hpp +++ b/src/memory/array.hpp @@ -223,25 +223,6 @@ public: std::copy(b, e, this->begin()); } - template <typename Seq> - array( - const Seq& seq, - arb::util::enable_if_t< - !std::is_convertible<Seq, std::size_t>::value - && !impl::is_array_t<Seq>::value >* = nullptr - ): - base(coordinator_type().allocate(arb::util::size(seq))) - { -#ifdef VERBOSE - std::cerr << util::green("array(iterator, iterator)") - << " " << util::type_printer<array>::print() - << "\n this " << util::pretty_printer<array>::print(*this) << "\n"; - //<< "\n other " << util::pretty_printer<Other>::print(other) << std::endl; -#endif - auto canon = arb::util::canonical_view(seq); - std::copy(std::begin(canon), std::end(canon), this->begin()); - } - // use the accessors provided by array_view // this enforces the requirement that accessing all of or a sub-array of an // array should return a view, not a new array. diff --git a/src/parameter_list.cpp b/src/parameter_list.cpp deleted file mode 100644 index 5b5d974c..00000000 --- a/src/parameter_list.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include <algorithm> -#include <iostream> - -#include "parameter_list.hpp" - -namespace arb { - -bool parameter_list::add_parameter(parameter p) { - if (has_parameter(p.name)) { - return false; - } - - parameters_.push_back(std::move(p)); - - return true; -} - -bool parameter_list::has_parameter(std::string const& n) const { - return find_by_name(n) != parameters_.end(); -} - -int parameter_list::num_parameters() const { - return parameters_.size(); -} - -// returns true if parameter was succesfully updated -// returns false if parameter was not updated, i.e. if -// - no parameter with name n exists -// - value is not in the valid range -bool parameter_list::set(std::string const& n, parameter_list::value_type v) { - auto p = find_by_name(n); - if(p!=parameters_.end()) { - if(p->is_in_range(v)) { - p->value = v; - return true; - } - } - return false; -} - -parameter& parameter_list::get(std::string const& n) { - auto it = find_by_name(n); - if (it==parameters_.end()) { - throw std::domain_error( - "parameter list does not contain parameter" - ); - } - return *it; -} - -const parameter& parameter_list::get(std::string const& n) const { - auto it = find_by_name(n); - if (it==parameters_.end()) { - throw std::domain_error( - "parameter list does not contain parameter" - ); - } - return *it; -} - -std::string const& parameter_list::name() const { - return mechanism_name_; -} - -std::vector<parameter> const& parameter_list::parameters() const { - return parameters_; -} - -auto parameter_list::find_by_name(std::string const& n) - -> decltype(parameters_.begin()) -{ - return - std::find_if( - parameters_.begin(), parameters_.end(), - [&n](parameter const& p) {return p.name == n;} - ); -} - -auto parameter_list::find_by_name(std::string const& n) const - -> decltype(parameters_.begin()) -{ - return - std::find_if( - parameters_.begin(), parameters_.end(), - [&n](parameter const& p) {return p.name == n;} - ); -} - -} // namespace arb - -std::ostream& -operator<<(std::ostream& o, arb::parameter const& p) { - return o - << "parameter(" - << "name " << p.name - << " : value " << p.value - << " : range " << p.range - << ")"; -} - -std::ostream& -operator<<(std::ostream& o, arb::parameter_list const& l) { - o << "parameters \"" << l.name() << "\" :\n"; - for(arb::parameter const& p : l.parameters()) { - o << " " << p << "\n"; - } - return o; -} diff --git a/src/parameter_list.hpp b/src/parameter_list.hpp deleted file mode 100644 index bc47ef0a..00000000 --- a/src/parameter_list.hpp +++ /dev/null @@ -1,229 +0,0 @@ -#pragma once - -#include <limits> -#include <ostream> -#include <stdexcept> -#include <string> -#include <vector> - -namespace arb { - - template <typename T> - struct value_range { - using value_type = T; - - value_range() - : min(std::numeric_limits<value_type>::quiet_NaN()), - max(std::numeric_limits<value_type>::quiet_NaN()) - { } - - value_range(value_type const& left, value_type const& right) - : min(left), - max(right) - { - if(left>right) { - throw std::out_of_range( - "parameter range must have left <= right" - ); - } - } - - bool has_lower_bound() const - { - return min==min; - } - - bool has_upper_bound() const - { - return max==max; - } - - bool is_in_range(value_type const& v) const - { - if(has_lower_bound()) { - if(v<min) return false; - } - if(has_upper_bound()) { - if(v>max) return false; - } - return true; - } - - value_type min; - value_type max; - }; - - struct parameter { - using value_type = double; - using range_type = value_range<value_type>; - - parameter() = delete; - - parameter(std::string n, value_type v, range_type r=range_type()) - : name(std::move(n)), - value(v), - range(r) - { - if(!is_in_range(v)) { - throw std::out_of_range( - "parameter value is out of permitted value range" - ); - } - } - - bool is_in_range(value_type v) const - { - return range.is_in_range(v); - } - - std::string name; - value_type value; - range_type range; - }; - - // Use a dumb container class for now - // might have to use a more sophisticated interface in the future if need be - class parameter_list { - public : - - using value_type = double; - - parameter_list(std::string n) - : mechanism_name_(std::move(n)) - { } - - bool has_parameter(std::string const& n) const; - bool add_parameter(parameter p); - - // returns true if parameter was succesfully updated - // returns false if parameter was not updated, i.e. if - // - no parameter with name n exists - // - value is not in the valid range - bool set(std::string const& n, value_type v); - parameter& get(std::string const& n); - const parameter& get(std::string const& n) const; - - std::string const& name() const; - - std::vector<parameter> const& parameters() const; - - int num_parameters() const; - - private: - - std::vector<parameter> parameters_; - std::string mechanism_name_; - - // need two versions for const and non-const applications - auto find_by_name(std::string const& n) - -> decltype(parameters_.begin()); - - auto find_by_name(std::string const& n) const - -> decltype(parameters_.begin()); - - }; - - /////////////////////////////////////////////////////////////////////////// - // predefined parameter sets - /////////////////////////////////////////////////////////////////////////// - - /// default set of parameters for the cell membrane that are added to every - /// segment when it is created. - class membrane_parameters - : public parameter_list - { - public: - - using base = parameter_list; - - using base::value_type; - - using base::set; - using base::get; - using base::parameters; - using base::has_parameter; - - membrane_parameters() - : base("membrane") - { - // from nrn/src/nrnoc/membdef.h - //#define DEF_cm 1. // uF/cm^2 - //#define DEF_Ra 35.4 // ohm-cm - //#define DEF_celsius 6.3 // deg-C - //#define DEF_vrest -65. // mV - // r_L is called Ra in Neuron - //base::add_parameter({"c_m", 10e-6, {0., 1e9}}); // typically 10 nF/mm^2 == 0.01 F/m^2 == 10^-6 F/cm^2 - base::add_parameter({"c_m", 0.01, {0., 1e9}}); // typically 10 nF/mm^2 == 0.01 F/m^2 == 10^-6 F/cm^2 - base::add_parameter({"r_L", 100.00, {0., 1e9}}); // equivalent to Ra in Neuron : Ohm.cm - } - }; - - /// parameters for the classic Hodgkin & Huxley model (1952) - class hh_parameters - : public parameter_list - { - public: - - using base = parameter_list; - - using base::value_type; - - using base::set; - using base::get; - using base::parameters; - using base::has_parameter; - - hh_parameters() - : base("hh") - { - base::add_parameter({"gnabar", 0.12, {0, 1e9}}); - base::add_parameter({"gkbar", 0.036, {0, 1e9}}); - base::add_parameter({"gl", 0.0003,{0, 1e9}}); - base::add_parameter({"el", -54.3}); - } - }; - - /// parameters for passive channel - class pas_parameters - : public parameter_list - { - public: - - using base = parameter_list; - - using base::value_type; - - using base::set; - using base::get; - using base::parameters; - using base::has_parameter; - - pas_parameters() - : base("pas") - { - base::add_parameter({"g", 0.001, {0, 1e9}}); - base::add_parameter({"e", -70}); - } - }; - -} // namespace arb - -template <typename T> -std::ostream& operator<<(std::ostream& o, arb::value_range<T> const& r) -{ - o << "["; - if(r.has_lower_bound()) - o << r.min; - else - o<< "-inf"; - o << ", "; - if(r.has_upper_bound()) - o << r.max; - else - o<< "inf"; - return o << "]"; -} - -std::ostream& operator<<(std::ostream& o, arb::parameter const& p); -std::ostream& operator<<(std::ostream& o, arb::parameter_list const& l); - diff --git a/src/recipe.hpp b/src/recipe.hpp index 52c8c377..106fac4b 100644 --- a/src/recipe.hpp +++ b/src/recipe.hpp @@ -14,6 +14,8 @@ namespace arb { struct probe_info { cell_member_type id; probe_tag tag; + + // Address type will be specific to cell kind of cell `id.gid`. util::any address; }; @@ -53,7 +55,8 @@ class recipe { public: virtual cell_size_type num_cells() const = 0; - virtual util::unique_any get_cell_description(cell_gid_type) const = 0; + // Cell description type will be specific to cell kind of cell with given gid. + virtual util::unique_any get_cell_description(cell_gid_type gid) const = 0; virtual cell_kind get_cell_kind(cell_gid_type) const = 0; virtual cell_size_type num_sources(cell_gid_type) const = 0; @@ -62,6 +65,9 @@ public: virtual std::vector<cell_connection> connections_on(cell_gid_type) const = 0; virtual probe_info get_probe(cell_member_type probe_id) const = 0; + + // Global property type will be specific to given cell kind. + virtual util::any get_global_properties(cell_kind) const { return util::any{}; }; }; } // namespace arb diff --git a/src/segment.hpp b/src/segment.hpp index bfdfa1f0..66534669 100644 --- a/src/segment.hpp +++ b/src/segment.hpp @@ -8,19 +8,12 @@ #include <compartment.hpp> #include <math.hpp> #include <morphology.hpp> -#include <parameter_list.hpp> +#include <mechinfo.hpp> #include <point.hpp> #include <util/make_unique.hpp> namespace arb { -template <typename T, - typename valid = typename std::is_floating_point<T>::type> -struct segment_properties { - T rL = 180.0; // resistivity [Ohm.cm] - T cm = 0.01; // capacitance [F/m^2] : 10 nF/mm^2 = 0.01 F/m^2 -}; - // forward declarations of segment specializations class soma_segment; class cable_segment; @@ -87,79 +80,44 @@ public: return false; } - segment_properties<value_type> properties; - - void add_mechanism(parameter_list p) - { - auto it = std::find_if( - mechanisms_.begin(), mechanisms_.end(), - [&p](parameter_list const& l){return l.name() == p.name();} - ); - if(it!=mechanisms_.end()) { - throw std::out_of_range( - "attempt to add a mechanism parameter set to a segment which has an existing mechanism with the same name" - ); - } - - mechanisms_.push_back(std::move(p)); + util::optional<mechanism_spec&> mechanism(const std::string& name) { + auto it = std::find_if(mechanisms_.begin(), mechanisms_.end(), + [&](mechanism_spec& m) { return m.name()==name; }); + return it==mechanisms_.end()? util::nothing: util::just(*it); } - parameter_list& mechanism(std::string const& n) - { - auto it = std::find_if( - mechanisms_.begin(), mechanisms_.end(), - [&n](parameter_list const& l){return l.name() == n;} - ); - if(it==mechanisms_.end()) { - throw std::out_of_range( - "attempt to access a parameter that is not defined in a segment" - ); + void add_mechanism(mechanism_spec mech) { + auto m = mechanism(mech.name()); + if (m) { + m.get() = std::move(mech); } - - return *it; - } - - const parameter_list& mechanism(std::string const& n) const - { - auto it = std::find_if( - mechanisms_.begin(), mechanisms_.end(), - [&n](parameter_list const& l){return l.name() == n;} - ); - if(it==mechanisms_.end()) { - throw std::out_of_range( - "attempt to access a parameter that is not defined in a segment" - ); + else { + mechanisms_.push_back(std::move(mech)); } - - return *it; } - std::vector<parameter_list>& mechanisms() - { + const std::vector<mechanism_spec>& mechanisms() { return mechanisms_; } - const std::vector<parameter_list>& mechanisms() const - { + const std::vector<mechanism_spec>& mechanisms() const { return mechanisms_; } + // common electrical properties + value_type rL = 100.0; // resistivity [Ohm.cm] + value_type cm = 0.01; // capacitance [F/m^2] : 10 nF/mm^2 = 0.01 F/m^2 + protected: + segment(section_kind kind): kind_(kind) {} + section_kind kind_; - std::vector<parameter_list> mechanisms_; + std::vector<mechanism_spec> mechanisms_; }; class placeholder_segment : public segment { public: - using base = segment; - using base::kind_; - using base::value_type; - using base::size_type; - - placeholder_segment() - { - kind_ = section_kind::none; - } + placeholder_segment(): segment(section_kind::none) {} std::unique_ptr<segment> clone() const override { // use default copy constructor @@ -191,26 +149,10 @@ public: class soma_segment : public segment { public: - using base = segment; - using base::kind_; - using base::value_type; - using base::point_type; - using base::size_type; - soma_segment() = delete; - soma_segment(value_type r): - radius_{r} - { - kind_ = section_kind::soma; - mechanisms_.push_back(membrane_parameters()); - } - - soma_segment(value_type r, point_type const& c): - soma_segment(r) - { - center_ = c; - } + explicit soma_segment(value_type r, point_type c = point_type{}): + segment(section_kind::soma), radius_{r}, center_(c) {} std::unique_ptr<segment> clone() const override { // use default copy constructor @@ -273,46 +215,24 @@ public: cable_segment() = delete; // constructors for a cable with no location information - cable_segment( - section_kind k, - std::vector<value_type> r, - std::vector<value_type> lens - ) { - kind_ = k; - assert(k==section_kind::dendrite || k==section_kind::axon); - - radii_ = std::move(r); - lengths_ = std::move(lens); - - // add default membrane parameters - mechanisms_.push_back(membrane_parameters()); + cable_segment(section_kind k, std::vector<value_type> r, std::vector<value_type> lens): + segment(k), radii_(std::move(r)), lengths_(std::move(lens)) + { + assert(kind_==section_kind::dendrite || kind_==section_kind::axon); } - cable_segment( - section_kind k, - value_type r1, - value_type r2, - value_type len - ) - : cable_segment{k, std::vector<value_type>{r1, r2}, std::vector<value_type>{len}} - { } + cable_segment(section_kind k, value_type r1, value_type r2, value_type len): + //cable_segment{k, std::vector<value_type>{r1, r2}, std::vector<value_type>{len}} + cable_segment{k, {r1, r2}, decltype(lengths_){len}} + {} // constructor that lets the user describe the cable as a // seriew of radii and locations - cable_segment( - section_kind k, - std::vector<value_type> r, - std::vector<point_type> p - ) { - kind_ = k; - assert(k==section_kind::dendrite || k==section_kind::axon); - - radii_ = std::move(r); - locations_ = std::move(p); + cable_segment(section_kind k, std::vector<value_type> r, std::vector<point_type> p): + segment(k), radii_(std::move(r)), locations_(std::move(p)) + { + assert(kind_==section_kind::dendrite || kind_==section_kind::axon); update_lengths(); - - // add default membrane parameters - mechanisms_.push_back(membrane_parameters()); } // helper that lets user specify with the radius and location @@ -324,9 +244,9 @@ public: value_type r2, point_type const& p1, point_type const& p2 - ) - : cable_segment(k, {r1, r2}, {p1, p2}) - { } + ): + cable_segment(k, {r1, r2}, {p1, p2}) + {} std::unique_ptr<segment> clone() const override { // use default copy constructor @@ -435,8 +355,7 @@ public: } private: - void update_lengths() - { + void update_lengths() { if (locations_.size()) { lengths_.resize(num_sub_segments()); for (size_type i=0; i<num_sub_segments(); ++i) { @@ -446,8 +365,8 @@ private: } size_type num_compartments_ = 1; - std::vector<value_type> lengths_; std::vector<value_type> radii_; + std::vector<value_type> lengths_; std::vector<point_type> locations_; }; diff --git a/src/util/simple_table.hpp b/src/util/simple_table.hpp new file mode 100644 index 00000000..32d6ee42 --- /dev/null +++ b/src/util/simple_table.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include <iterator> +#include <cstring> +#include <utility> + +// Linear lookup of values in a table indexed by a key. +// +// Tables are any sequence of pairs or tuples, with the key as the first element. + +namespace arb { +namespace util { + +namespace impl { + struct key_equal { + template <typename U, typename V> + bool operator()(U&& u, V&& v) const { + return std::forward<U>(u)==std::forward<V>(v); + } + + // special case for C strings: + bool operator()(const char* u, const char* v) const { + return !std::strcmp(u, v); + } + }; +}; + +// Return pointer to value (second element in entry) in table if key found, +// otherwise nullptr. + +template <typename PairSeq, typename Key, typename Eq = impl::key_equal> +auto table_lookup(PairSeq&& seq, const Key& key, Eq eq = Eq{}) + -> decltype(&std::get<1>(*std::begin(seq))) +{ + for (auto&& entry: seq) { + if (eq(std::get<0>(entry), key)) { + return &std::get<1>(entry); + } + } + return nullptr; +} + +} // namespace util +} // namespace arb diff --git a/tests/common_cells.hpp b/tests/common_cells.hpp index dc6d0d19..088967db 100644 --- a/tests/common_cells.hpp +++ b/tests/common_cells.hpp @@ -4,7 +4,7 @@ #include <recipe.hpp> #include <segment.hpp> #include <math.hpp> -#include <parameter_list.hpp> +#include <mechinfo.hpp> namespace arb { @@ -14,7 +14,7 @@ namespace arb { * Soma: * diameter: 18.8 µm * mechanisms: HH (default params) - * bulk resistivitiy: 100 Ω·cm + * bulk resistivitiy: 100 Ω·cm [default] * capacitance: 0.01 F/m² [default] * * Stimuli: @@ -25,8 +25,7 @@ inline cell make_cell_soma_only(bool with_stim = true) { cell c; auto soma = c.add_soma(18.8/2.0); - soma->mechanism("membrane").set("r_L", 100); - soma->add_mechanism(hh_parameters()); + soma->add_mechanism("hh"); if (with_stim) { c.add_stimulus({0,0.5}, {10., 100., 0.1}); @@ -39,7 +38,7 @@ inline cell make_cell_soma_only(bool with_stim = true) { * Create cell with a soma and unbranched dendrite: * * Common properties: - * bulk resistivity: 100 Ω·cm + * bulk resistivity: 100 Ω·cm [default] * capacitance: 0.01 F/m² [default] * * Soma: @@ -50,7 +49,6 @@ inline cell make_cell_soma_only(bool with_stim = true) { * mechanisms: passive (default params) * diameter: 1 µm * length: 200 µm - * bulk resistivity: 100 Ω·cm * compartments: 4 * * Stimulus: @@ -61,14 +59,13 @@ inline cell make_cell_ball_and_stick(bool with_stim = true) { cell c; auto soma = c.add_soma(12.6157/2.0); - soma->add_mechanism(hh_parameters()); + soma->add_mechanism("hh"); c.add_cable(0, section_kind::dendrite, 1.0/2, 1.0/2, 200.0); for (auto& seg: c.segments()) { - seg->mechanism("membrane").set("r_L", 100); if (seg->is_dendrite()) { - seg->add_mechanism(pas_parameters()); + seg->add_mechanism("pas"); seg->set_compartments(4); } } @@ -83,7 +80,7 @@ inline cell make_cell_ball_and_stick(bool with_stim = true) { * Create cell with a soma and unbranched tapered dendrite: * * Common properties: - * bulk resistivity: 100 Ω·cm + * bulk resistivity: 100 Ω·cm [default] * capacitance: 0.01 F/m² [default] * * Soma: @@ -106,14 +103,13 @@ inline cell make_cell_ball_and_taper(bool with_stim = true) { cell c; auto soma = c.add_soma(12.6157/2.0); - soma->add_mechanism(hh_parameters()); + soma->add_mechanism("hh"); c.add_cable(0, section_kind::dendrite, 1.0/2, 0.4/2, 200.0); for (auto& seg: c.segments()) { - seg->mechanism("membrane").set("r_L", 100); if (seg->is_dendrite()) { - seg->add_mechanism(pas_parameters()); + seg->add_mechanism("pas"); seg->set_compartments(4); } } @@ -127,12 +123,16 @@ inline cell make_cell_ball_and_taper(bool with_stim = true) { /* * Create cell with a soma and unbranched dendrite with varying diameter: * + * Common properties: + * bulk resistivity: 100 Ω·cm [default] + * capacitance: 0.01 F/m² [default] + * * Soma: * mechanisms: HH * diameter: 12.6157 µm * * Dendrite: - * mechanisms: none + * mechanisms: passive (default params) * length: 100 µm * membrane resistance: 100 Ω·cm * compartments: 4 @@ -145,7 +145,7 @@ inline cell make_cell_ball_and_squiggle(bool with_stim = true) { cell c; auto soma = c.add_soma(12.6157/2.0); - soma->add_mechanism(hh_parameters()); + soma->add_mechanism("hh"); std::vector<cell::value_type> radii; std::vector<cell::point_type> points; @@ -166,9 +166,8 @@ inline cell make_cell_ball_and_squiggle(bool with_stim = true) { c.add_cable(0, std::move(dendrite)); for (auto& seg: c.segments()) { - seg->mechanism("membrane").set("r_L", 100); if (seg->is_dendrite()) { - seg->add_mechanism(pas_parameters()); + seg->add_mechanism("pas"); seg->set_compartments(4); } } @@ -185,7 +184,7 @@ inline cell make_cell_ball_and_squiggle(bool with_stim = true) { * O----====== * * Common properties: - * bulk resistivity: 100 Ω·cm + * bulk resistivity: 100 Ω·cm [default] * capacitance: 0.01 F/m² [default] * * Soma: @@ -207,16 +206,15 @@ inline cell make_cell_ball_and_3stick(bool with_stim = true) { cell c; auto soma = c.add_soma(12.6157/2.0); - soma->add_mechanism(hh_parameters()); + soma->add_mechanism("hh"); c.add_cable(0, section_kind::dendrite, 0.5, 0.5, 100); c.add_cable(1, section_kind::dendrite, 0.5, 0.5, 100); c.add_cable(1, section_kind::dendrite, 0.5, 0.5, 100); for (auto& seg: c.segments()) { - seg->mechanism("membrane").set("r_L", 100); if (seg->is_dendrite()) { - seg->add_mechanism(pas_parameters()); + seg->add_mechanism("pas"); seg->set_compartments(4); } } @@ -238,7 +236,7 @@ inline cell make_cell_ball_and_3stick(bool with_stim = true) { * membrane reversal potential: -65 mV (default) * diameter: 1 µm * length: 1000 µm - * bulk resistivity: 100 Ω·cm + * bulk resistivity: 100 Ω·cm [default] * capacitance: 0.01 F/m² [default] * compartments: 4 * @@ -247,13 +245,6 @@ inline cell make_cell_ball_and_3stick(bool with_stim = true) { * * Note: zero-volume soma added with same mechanisms, as * work-around for some existing fvm modelling issues. - * - * TODO: Set the correct values when parameters are generally - * settable! - * - * We can't currently change leak parameters - * from defaults, so we scale other electrical parameters - * proportionally. */ inline cell make_cell_simple_cable(bool with_stim = true) { @@ -267,22 +258,13 @@ inline cell make_cell_simple_cable(bool with_stim = true) { double gbar = 0.000025; double I = 0.1; - // fudge factor! can't change passive membrane - // conductance from gbar0 = 0.001 - - double gbar0 = 0.001; - double f = gbar/gbar0; - - // scale everything else - r_L *= f; - c_m /= f; - I /= f; + mechanism_spec pas("pas"); + pas["g"] = gbar; for (auto& seg: c.segments()) { - seg->add_mechanism(pas_parameters()); - seg->mechanism("membrane").set("r_L", r_L); - seg->mechanism("membrane").set("c_m", c_m); - // seg->mechanism("pas").set("g", gbar); + seg->add_mechanism(pas); + seg->rL = r_L; + seg->cm = c_m; if (seg->is_dendrite()) { seg->set_compartments(4); @@ -296,5 +278,4 @@ inline cell make_cell_simple_cable(bool with_stim = true) { } return c; } - } // namespace arb diff --git a/tests/simple_recipes.hpp b/tests/simple_recipes.hpp index 338b0a55..5417c0fb 100644 --- a/tests/simple_recipes.hpp +++ b/tests/simple_recipes.hpp @@ -34,8 +34,22 @@ public: pvec_.push_back({probe_id, tag, std::move(address)}); } + void add_specialized_mechanism(std::string name, specialized_mechanism m) { + cell_gprop.special_mechs[name] = std::move(m); + } + + util::any get_global_properties(cell_kind k) const override { + switch (k) { + case cable1d_neuron: + return cell_gprop; + default: + return util::any{}; + } + } + protected: std::unordered_map<cell_gid_type, std::vector<probe_info>> probes_; + cell_global_properties cell_gprop; }; // Convenience derived recipe class for wrapping n copies of a single diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 11808a13..62eb4695 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -57,7 +57,7 @@ set(TEST_SOURCES test_multi_event_stream.cpp test_nop.cpp test_optional.cpp - test_parameters.cpp + test_mechinfo.cpp test_partition.cpp test_path.cpp test_point.cpp diff --git a/tests/unit/common.hpp b/tests/unit/common.hpp index af9833f5..445f5992 100644 --- a/tests/unit/common.hpp +++ b/tests/unit/common.hpp @@ -5,6 +5,9 @@ * more than one unit test. */ +#include <utility> +#include <cmath> + #include "../gtest.h" namespace testing { @@ -143,4 +146,13 @@ template <typename FPType, typename Seq1, typename Seq2> return ::testing::AssertionSuccess(); } +// Assert two floating point values are within a relative tolerance. +inline ::testing::AssertionResult near_relative(double a, double b, double relerr) { + double tol = relerr*std::max(std::abs(a), std::abs(b)); + if (std::abs(a-b)>tol) { + return ::testing::AssertionFailure() << "relative error between floating point numbers " << a << " and " << b << " exceeds tolerance " << relerr; + } + return ::testing::AssertionSuccess(); +} + } // namespace testing diff --git a/tests/unit/test.cpp b/tests/unit/test.cpp index d0a2d43a..d971b2f9 100644 --- a/tests/unit/test.cpp +++ b/tests/unit/test.cpp @@ -3,9 +3,13 @@ #include <numeric> #include <vector> +#include <communication/global_policy.hpp> + #include "../gtest.h" int main(int argc, char **argv) { + arb::communication::global_policy_guard g(argc, argv); + ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/tests/unit/test_cell.cpp b/tests/unit/test_cell.cpp index 8fbcabd4..5c197809 100644 --- a/tests/unit/test_cell.cpp +++ b/tests/unit/test_cell.cpp @@ -178,8 +178,7 @@ TEST(cell_type, clone) c.add_cable(1, section_kind::dendrite, 0.2, 0.15, 20); c.segment(2)->set_compartments(5); - parameter_list exp_default("expsyn"); - c.add_synapse({1, 0.3}, exp_default); + c.add_synapse({1, 0.3}, mechanism_spec("expsyn")); c.add_detector({0, 0.5}, 10.0); diff --git a/tests/unit/test_fvm_multi.cpp b/tests/unit/test_fvm_multi.cpp index eb7cafce..a9c57ec0 100644 --- a/tests/unit/test_fvm_multi.cpp +++ b/tests/unit/test_fvm_multi.cpp @@ -1,5 +1,7 @@ #include <cstddef> #include <fstream> +#include <set> +#include <vector> #include "../gtest.h" @@ -7,12 +9,17 @@ #include <cell.hpp> #include <common_types.hpp> #include <fvm_multicell.hpp> +#include <load_balance.hpp> +#include <model.hpp> #include <recipe.hpp> #include <sampler_map.hpp> +#include <sampling.hpp> +#include <schedule.hpp> +#include <segment.hpp> #include <util/meta.hpp> #include <util/rangeutil.hpp> -#include "../test_util.hpp" +#include "common.hpp" #include "../common_cells.hpp" #include "../simple_recipes.hpp" @@ -53,19 +60,13 @@ TEST(fvm_multi, init) const auto m = cell.model(); EXPECT_EQ(m.tree.num_segments(), 2u); - auto& soma_hh = cell.soma()->mechanism("hh"); + auto& soma_hh = (cell.soma()->mechanism("hh")).get(); soma_hh.set("gnabar", 0.12); soma_hh.set("gkbar", 0.036); soma_hh.set("gl", 0.0003); soma_hh.set("el", -54.3); - // check that parameter values were set correctly - EXPECT_EQ(cell.soma()->mechanism("hh").get("gnabar").value, 0.12); - EXPECT_EQ(cell.soma()->mechanism("hh").get("gkbar").value, 0.036); - EXPECT_EQ(cell.soma()->mechanism("hh").get("gl").value, 0.0003); - EXPECT_EQ(cell.soma()->mechanism("hh").get("el").value, -54.3); - cell.segment(1)->set_compartments(10); std::vector<fvm_cell::target_handle> targets; @@ -124,10 +125,10 @@ TEST(fvm_multi, multi_init) EXPECT_EQ(cells[1].segment(2)->num_compartments(), 4u); EXPECT_EQ(cells[1].segment(3)->num_compartments(), 4u); - cells[0].add_synapse({1, 0.4}, parameter_list("expsyn")); - cells[0].add_synapse({1, 0.4}, parameter_list("expsyn")); - cells[1].add_synapse({2, 0.4}, parameter_list("exp2syn")); - cells[1].add_synapse({3, 0.4}, parameter_list("expsyn")); + cells[0].add_synapse({1, 0.4}, "expsyn"); + cells[0].add_synapse({1, 0.4}, "expsyn"); + cells[1].add_synapse({2, 0.4}, "exp2syn"); + cells[1].add_synapse({3, 0.4}, "expsyn"); cells[1].add_detector({0, 0}, 3.3); @@ -265,7 +266,7 @@ TEST(fvm_multi, mechanism_indexes) cell c; auto soma = c.add_soma(12.6157/2.0); - soma->add_mechanism(hh_parameters()); + soma->add_mechanism("hh"); // add dendrite of length 200 um and diameter 1 um with passive channel c.add_cable(0, section_kind::dendrite, 0.5, 0.5, 100); @@ -273,13 +274,13 @@ TEST(fvm_multi, mechanism_indexes) c.add_cable(1, section_kind::dendrite, 0.5, 0.5, 100); auto& segs = c.segments(); - segs[1]->add_mechanism(pas_parameters()); - segs[2]->add_mechanism(hh_parameters()); - segs[3]->add_mechanism(pas_parameters()); + segs[1]->add_mechanism("pas"); + segs[2]->add_mechanism("hh"); + segs[3]->add_mechanism("pas"); for (auto& seg: segs) { if (seg->is_dendrite()) { - seg->mechanism("membrane").set("r_L", 100); + seg->rL = 100; seg->set_compartments(4); } } @@ -328,6 +329,338 @@ TEST(fvm_multi, mechanism_indexes) } } +namespace { + double wm_impl(double wa, double xa) { + return wa? xa/wa: 0; + } + + template <typename... R> + double wm_impl(double wa, double xa, double w, double x, R... rest) { + return wm_impl(wa+w, xa+w*x, rest...); + } + + // Computed weighted mean (w*x + ...) / (w + ...). + template <typename... R> + double wmean(double w, double x, R... rest) { + return wm_impl(w, w*x, rest...); + } +} + +// Test area-weighted linear combination of density mechanism parameters. + +TEST(fvm_multi, density_weights) { + using namespace arb; + + // Create a cell with 4 segments: + // - Soma (segment 0) plus three dendrites (1, 2, 3) meeting at a branch point. + // - HH mechanism on all segments. + // - Dendritic segments are given 3 compartments each. + // + // The CV corresponding to the branch point should comprise the terminal + // 1/6 of segment 1 and the initial 1/6 of segments 2 and 3. + // + // The HH mechanism current density parameters ('gnabar', 'gkbar' and 'gl') are set + // differently for each segment: + // + // soma: all default values (gnabar = 0.12, gkbar = .036, gl = .0003) + // segment 1: gl = .0002 + // segment 2: gkbar = .05 + // segment 3: gkbar = .07, gl = .0004 + // + // Geometry: + // segment 1: 100 µm long, 1 µm diameter cylinder. + // segment 2: 200 µm long, diameter linear taper from 1 µm to 0.2 µm. + // segment 3: 150 µm long, 0.8 µm diameter cylinder. + // + // Use divided compartment view on segments to compute area contributions. + + cell c; + auto soma = c.add_soma(12.6157/2.0); + + c.add_cable(0, section_kind::dendrite, 0.5, 0.5, 100); + c.add_cable(1, section_kind::dendrite, 0.5, 0.1, 200); + c.add_cable(1, section_kind::dendrite, 0.4, 0.4, 150); + + auto& segs = c.segments(); + + double dflt_gkbar = .036; + double dflt_gnabar = 0.12; + double dflt_gl = 0.0003; + + double seg1_gl = .0002; + double seg2_gkbar = .05; + double seg3_gkbar = .0004; + double seg3_gl = .0004; + + for (int i = 0; i<4; ++i) { + segment& seg = *segs[i]; + seg.set_compartments(3); + + mechanism_spec hh("hh"); + switch (i) { + case 1: + hh["gl"] = seg1_gl; + break; + case 2: + hh["gkbar"] = seg2_gkbar; + break; + case 3: + hh["gkbar"] = seg3_gkbar; + hh["gl"] = seg3_gl; + break; + default: ; + } + seg.add_mechanism(hh); + } + + int ncv = 10; + std::vector<double> expected_gkbar(ncv, dflt_gkbar); + std::vector<double> expected_gnabar(ncv, dflt_gnabar); + std::vector<double> expected_gl(ncv, dflt_gl); + + double soma_area = soma->area(); + auto seg1_divs = div_compartments<div_compartment_by_ends>(segs[1]->as_cable()); + auto seg2_divs = div_compartments<div_compartment_by_ends>(segs[2]->as_cable()); + auto seg3_divs = div_compartments<div_compartment_by_ends>(segs[3]->as_cable()); + + // CV 0: mix of soma and left of segment 1 + expected_gl[0] = wmean(soma_area, dflt_gl, seg1_divs(0).left.area, seg1_gl); + + expected_gl[1] = seg1_gl; + expected_gl[2] = seg1_gl; + + // CV 3: mix of right of segment 1 and left of segments 2 and 3. + expected_gkbar[3] = wmean(seg1_divs(2).right.area, dflt_gkbar, seg2_divs(0).left.area, seg2_gkbar, seg3_divs(0).left.area, seg3_gkbar); + expected_gl[3] = wmean(seg1_divs(2).right.area, seg1_gl, seg2_divs(0).left.area, dflt_gl, seg3_divs(0).left.area, seg3_gl); + + // CV 4-6: just segment 2 + expected_gkbar[4] = seg2_gkbar; + expected_gkbar[5] = seg2_gkbar; + expected_gkbar[6] = seg2_gkbar; + + // CV 7-9: just segment 3 + expected_gkbar[7] = seg3_gkbar; + expected_gkbar[8] = seg3_gkbar; + expected_gkbar[9] = seg3_gkbar; + expected_gl[7] = seg3_gl; + expected_gl[8] = seg3_gl; + expected_gl[9] = seg3_gl; + + // Generate the lowered fvm cell. + std::vector<fvm_cell::target_handle> targets; + probe_association_map<fvm_cell::probe_handle> probe_map; + + fvm_cell fvcell; + fvcell.initialize({0}, cable1d_recipe(c), targets, probe_map); + + // Check CV area assumptions. + // Note: area integrator used here and in `fvm_multicell` may differ, and so areas computed may + // differ some due to rounding area, even given that we're dealing with simple truncated cones + // for segments. Check relative error within a tolerance of (say) 10 epsilon. + auto cv_areas = fvcell.cv_areas(); + double area_relerr = 10*std::numeric_limits<double>::epsilon(); + EXPECT_TRUE(testing::near_relative(cv_areas[0], + soma_area+seg1_divs(0).left.area, area_relerr)); + EXPECT_TRUE(testing::near_relative(cv_areas[1], + seg1_divs(0).right.area+seg1_divs(1).left.area, area_relerr)); + EXPECT_TRUE(testing::near_relative(cv_areas[3], + seg1_divs(2).right.area+seg2_divs(0).left.area+seg3_divs(0).left.area, area_relerr)); + EXPECT_TRUE(testing::near_relative(cv_areas[6], + seg2_divs(2).right.area, area_relerr)); + + // Grab the HH parameters from the mechanism. + EXPECT_EQ(1u, fvcell.mechanisms().size()); + auto& hh_mech = *fvcell.mechanisms().front(); + + auto gnabar_field = hh_mech.field_view_ptr("gnabar"); + auto gkbar_field = hh_mech.field_view_ptr("gkbar"); + auto gl_field = hh_mech.field_view_ptr("gl"); + + EXPECT_TRUE(testing::seq_almost_eq<double>(expected_gnabar, hh_mech.*gnabar_field)); + EXPECT_TRUE(testing::seq_almost_eq<double>(expected_gkbar, hh_mech.*gkbar_field)); + EXPECT_TRUE(testing::seq_almost_eq<double>(expected_gl, hh_mech.*gl_field)); +} + +// Test specialized mechanism behaviour. + +TEST(fvm_multi, specialized_mechs) { + using namespace arb; + + // Create ball and stick cells with the 'test_kin1' mechanism, which produces + // a voltage-independent current density of the form a + exp(-t/tau) as a function + // of time t. + // + // 1. Default 'test_kin1': tau = 10 [ms]. + // + // 2. Specialized version 'custom_kin1' with tau = 20 [ms]. + // + // 3. Cell with both test_kin1 and custom_kin1. + + specialized_mechanism custom_kin1 = {"test_kin1", {{"tau", 20.0}}}; + + cell cells[3]; + + for (int i = 0; i<3; ++i) { + cell& c = cells[i]; + c.add_soma(6.0); + c.add_cable(0, section_kind::dendrite, 0.5, 0.5, 100); + + c.segment(1)->set_compartments(4); + for (auto& seg: c.segments()) { + if (!seg->is_soma()) { + seg->as_cable()->set_compartments(4); + } + + switch (i) { + case 0: + seg->add_mechanism("test_kin1"); + break; + case 1: + seg->add_mechanism("custom_kin1"); + break; + case 2: + seg->add_mechanism("test_kin1"); + seg->add_mechanism("custom_kin1"); + break; + } + } + } + + cable1d_recipe rec(cells); + rec.add_specialized_mechanism("custom_kin1", custom_kin1); + + cell_probe_address where{{1, 0.3}, cell_probe_address::membrane_current}; + rec.add_probe(0, 0, where); + rec.add_probe(1, 0, where); + rec.add_probe(2, 0, where); + + { + // Test initialization and global parameter values. + + std::vector<fvm_cell::target_handle> targets; + probe_association_map<fvm_cell::probe_handle> probe_map; + + fvm_cell fvcell; + fvcell.initialize({0, 1, 2}, rec, targets, probe_map); + + std::map<std::string, fvm_cell::mechanism*> mechmap; + for (auto& m: fvcell.mechanisms()) { + // (names of mechanisms should _all_ be 'test_kin1', but aliases will differ) + EXPECT_EQ("test_kin1", m->name()); + mechmap[m->alias()] = m.get(); + } + + ASSERT_EQ(2u, mechmap.size()); + EXPECT_NE(0u, mechmap.count("test_kin1")); + EXPECT_NE(0u, mechmap.count("custom_kin1")); + + // Both mechanisms are of the same type, so we can use the + // same member pointer. + auto fptr = mechmap.begin()->second->field_value_ptr("tau"); + + ASSERT_NE(nullptr, fptr); + + EXPECT_EQ(10.0, mechmap["test_kin1"]->*fptr); + EXPECT_EQ(20.0, mechmap["custom_kin1"]->*fptr); + } + + { + // Test dynamics: + // 1. Current at same point on cell 0 at time 10 ms should equal that + // on cell 1 at time 20 ms. + // 2. Current for cell 2 should be sum of currents for cells 0 and 1 at any given time. + + std::vector<double> samples[3]; + + sampler_function sampler = [&](cell_member_type pid, probe_tag, std::size_t n, const sample_record* records) { + for (std::size_t i = 0; i<n; ++i) { + double v = *util::any_cast<const double*>(records[i].data); + samples[pid.gid].push_back(v); + } + }; + + float times[] = {10.f, 20.f}; + + auto decomp = partition_load_balance(rec, hw::node_info{1u, 0u}); + model sim(rec, decomp); + sim.add_sampler(all_probes, explicit_schedule(times), sampler); + sim.run(30.0, 1.f/1024); + + ASSERT_EQ(2u, samples[0].size()); + ASSERT_EQ(2u, samples[1].size()); + ASSERT_EQ(2u, samples[2].size()); + + // Integration isn't exact: let's aim for one part in 10'000. + double relerr = 0.0001; + EXPECT_TRUE(testing::near_relative(samples[0][0], samples[1][1], relerr)); + EXPECT_TRUE(testing::near_relative(samples[0][0]+samples[1][0], samples[2][0], relerr)); + EXPECT_TRUE(testing::near_relative(samples[0][1]+samples[1][1], samples[2][1], relerr)); + } +} + +// Test synapses with differing parameter settings. + +TEST(fvm_multi, synapse_parameters) { + using namespace arb; + + cell c; + c.add_soma(6.0); + c.add_cable(0, section_kind::dendrite, 0.4, 0.4, 100.0); + c.segment(1)->set_compartments(4); + + // Add synapses out-of-order, with a parameter value a function of position + // on the segment, so we can test that parameters are properly associated + // after re-ordering. + + struct pset { + double x; // segment position + double tau1; + double tau2; + }; + + pset settings[] = { + {0.8, 1.5, 2.5}, + {0.1, 1.6, 3.7}, + {0.5, 1.7, 3.6}, + {0.6, 0.8, 2.5}, + {0.4, 0.9, 3.4}, + {0.9, 1.1, 2.3} + }; + + for (auto s: settings) { + mechanism_spec m("exp2syn"); + c.add_synapse({1, s.x}, m.set("tau1", s.tau1).set("tau2", s.tau2)); + } + + std::vector<fvm_cell::target_handle> targets; + probe_association_map<fvm_cell::probe_handle> probe_map; + + fvm_cell fvcell; + fvcell.initialize({0}, cable1d_recipe(c), targets, probe_map); + + EXPECT_EQ(1u, fvcell.mechanisms().size()); + auto& exp2syn_mech = *fvcell.mechanisms().front(); + + auto tau1_ptr = exp2syn_mech.field_view_ptr("tau1"); + auto tau2_ptr = exp2syn_mech.field_view_ptr("tau2"); + + // Compare tau1, tau2 values from settings and from mechanism, ignoring order. + std::set<std::pair<double, double>> expected; + for (auto s: settings) { + expected.insert({s.tau1, s.tau2}); + } + + unsigned n = exp2syn_mech.size(); + ASSERT_EQ(util::size(settings), n); + + std::set<std::pair<double, double>> values; + for (unsigned i = 0; i<n; ++i) { + values.insert({(exp2syn_mech.*tau1_ptr)[i], (exp2syn_mech.*tau2_ptr)[i]}); + } + + EXPECT_EQ(expected, values); +} + struct handle_info { unsigned cell; std::string mech; @@ -378,7 +711,7 @@ void run_target_handle_test(std::vector<handle_info> all_handles) { x.cv += 5; // offset for cell 1 } - cells[x.cell].add_synapse({seg_id, pos}, parameter_list(x.mech)); + cells[x.cell].add_synapse({seg_id, pos}, x.mech); handles[x.cell].push_back(x); } diff --git a/tests/unit/test_matrix.cpp b/tests/unit/test_matrix.cpp index 22f66f02..3bbab835 100644 --- a/tests/unit/test_matrix.cpp +++ b/tests/unit/test_matrix.cpp @@ -87,6 +87,8 @@ TEST(matrix, zero_diagonal) // elements should be ignored). // These submatrices should leave the rhs as-is when solved. + using memory::make_const_view; + // Three matrices, sizes 3, 3 and 2, with no branching. std::vector<size_type> p = {0, 0, 1, 3, 3, 5, 5}; std::vector<size_type> c = {0, 3, 5, 7}; @@ -96,9 +98,9 @@ TEST(matrix, zero_diagonal) EXPECT_EQ(3u, m.num_cells()); auto& A = m.state_; - A.d = vvec({2, 3, 2, 0, 0, 4, 5}); - A.u = vvec({0, -1, -1, 0, -1, 0, -2}); - A.rhs = vvec({3, 5, 7, 7, 8, 16, 32}); + A.d = make_const_view(vvec({2, 3, 2, 0, 0, 4, 5})); + A.u = make_const_view(vvec({0, -1, -1, 0, -1, 0, -2})); + A.rhs = make_const_view(vvec({3, 5, 7, 7, 8, 16, 32})); // Expected solution: std::vector<value_type> expected = {4, 5, 6, 7, 8, 9, 10}; diff --git a/tests/unit/test_matrix.cu b/tests/unit/test_matrix.cu index 195f82ea..c3156b62 100644 --- a/tests/unit/test_matrix.cu +++ b/tests/unit/test_matrix.cu @@ -291,7 +291,7 @@ TEST(matrix, assemble) std::generate(dt.begin(), dt.end(), [&](){return dt_dist(gen);}); // Voltage and current values - m_mc.assemble(host_array(dt), host_array(group_size, -64), host_array(group_size, 10)); + m_mc.assemble(on_host(dt), host_array(group_size, -64), host_array(group_size, 10)); m_mc.solve(); m_gpu.assemble(on_gpu(dt), gpu_array(group_size, -64), gpu_array(group_size, 10)); m_gpu.solve(); diff --git a/tests/unit/test_mechinfo.cpp b/tests/unit/test_mechinfo.cpp new file mode 100644 index 00000000..1e92ba08 --- /dev/null +++ b/tests/unit/test_mechinfo.cpp @@ -0,0 +1,45 @@ +#include <map> +#include <string> +#include <vector> + +#include "mechinfo.hpp" + +#include "../gtest.h" +#include "../test_util.hpp" + +// TODO: expand tests when we have exported mechanism schemata +// from modcc. + +using namespace arb; + +TEST(mechanism_spec, setting) { + mechanism_spec m("foo"); + + m.set("a", 3.2); + m.set("b", 4.3); + + auto dflt = m["c"]; + EXPECT_EQ(0., dflt); // note: 0 default is artefact of dummy schema + + EXPECT_EQ(3.2, m["a"]); + EXPECT_EQ(4.3, m["b"]); + + m["b"] = 5.4; + m["d"] = 6.5; + + EXPECT_EQ(3.2, m["a"]); + EXPECT_EQ(5.4, m["b"]); + EXPECT_EQ(6.5, m["d"]); + + // Check values() method is faithful: + auto p = m.values(); + EXPECT_EQ(3u, p.size()); + + EXPECT_TRUE(p.count("a")); + EXPECT_TRUE(p.count("b")); + EXPECT_TRUE(p.count("d")); + + EXPECT_EQ(p["a"], m["a"]); + EXPECT_EQ(p["b"], m["b"]); + EXPECT_EQ(p["d"], m["d"]); +} diff --git a/tests/unit/test_parameters.cpp b/tests/unit/test_parameters.cpp deleted file mode 100644 index f8e29ca3..00000000 --- a/tests/unit/test_parameters.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include <fstream> - -#include "../gtest.h" -#include "../test_util.hpp" - -#include "../src/parameter_list.hpp" - -// test out the parameter infrastructure -TEST(parameters, setting) -{ - arb::parameter_list list("test"); - EXPECT_EQ(list.name(), "test"); - EXPECT_EQ(list.num_parameters(), 0); - - arb::parameter p("a", 0.12, {0, 10}); - - // add_parameter() returns a bool that indicates whether - // it was able to successfull add the parameter - EXPECT_TRUE(list.add_parameter(std::move(p))); - EXPECT_EQ(list.num_parameters(), 1); - - // test in place construction of a parameter - EXPECT_TRUE(list.add_parameter({"b", -3.0})); - EXPECT_EQ(list.num_parameters(), 2); - - // check that adding a parameter that already exists returns false - // and does not increase the number of parameters - EXPECT_FALSE(list.add_parameter({"b", -3.0})); - EXPECT_EQ(list.num_parameters(), 2); - - auto& parms = list.parameters(); - EXPECT_EQ(parms[0].name, "a"); - EXPECT_EQ(parms[0].value, 0.12); - EXPECT_EQ(parms[0].range.min, 0); - EXPECT_EQ(parms[0].range.max, 10); - - EXPECT_EQ(parms[1].name, "b"); - EXPECT_EQ(parms[1].value, -3); - EXPECT_FALSE(parms[1].range.has_lower_bound()); - EXPECT_FALSE(parms[1].range.has_upper_bound()); -} diff --git a/tests/unit/test_synapses.cpp b/tests/unit/test_synapses.cpp index 804218f3..7951b4fb 100644 --- a/tests/unit/test_synapses.cpp +++ b/tests/unit/test_synapses.cpp @@ -16,14 +16,11 @@ TEST(synapses, add_to_cell) // Soma with diameter 12.6157 um and HH channel auto soma = cell.add_soma(12.6157/2.0); - soma->add_mechanism(hh_parameters()); + soma->add_mechanism("hh"); - parameter_list exp_default("expsyn"); - parameter_list exp2_default("exp2syn"); - - cell.add_synapse({0, 0.1}, exp_default); - cell.add_synapse({1, 0.2}, exp2_default); - cell.add_synapse({0, 0.3}, exp_default); + cell.add_synapse({0, 0.1}, "expsyn"); + cell.add_synapse({1, 0.2}, "exp2syn"); + cell.add_synapse({0, 0.3}, "expsyn"); EXPECT_EQ(3u, cell.synapses().size()); const auto& syns = cell.synapses(); @@ -44,6 +41,7 @@ TEST(synapses, add_to_cell) TEST(synapses, expsyn_basic_state) { using namespace arb; + using memory::make_const_view; using size_type = multicore::backend::size_type; using value_type = multicore::backend::value_type; @@ -62,7 +60,7 @@ TEST(synapses, expsyn_basic_state) synapse_type::array voltage(num_comp, -65.0); synapse_type::array current(num_comp, 1.0); - auto mech = make_mechanism<synapse_type>(0, cell_index, time, time_to, dt, voltage, current, weights, node_index); + auto mech = make_mechanism<synapse_type>(0, cell_index, time, time_to, dt, voltage, current, make_const_view(weights), make_const_view(node_index)); auto ptr = dynamic_cast<synapse_type*>(mech.get()); auto n = ptr->size(); @@ -105,6 +103,7 @@ TEST(synapses, expsyn_basic_state) TEST(synapses, exp2syn_basic_state) { using namespace arb; + using memory::make_const_view; using size_type = multicore::backend::size_type; using value_type = multicore::backend::value_type; @@ -123,7 +122,7 @@ TEST(synapses, exp2syn_basic_state) synapse_type::array voltage(num_comp, -65.0); synapse_type::array current(num_comp, 1.0); - auto mech = make_mechanism<synapse_type>(0, cell_index, time, time_to, dt, voltage, current, weights, node_index); + auto mech = make_mechanism<synapse_type>(0, cell_index, time, time_to, dt, voltage, current, make_const_view(weights), make_const_view(node_index)); auto ptr = dynamic_cast<synapse_type*>(mech.get()); auto n = ptr->size(); diff --git a/tests/validation/validate_kinetic.cpp b/tests/validation/validate_kinetic.cpp index 986d8cbc..b0077df1 100644 --- a/tests/validation/validate_kinetic.cpp +++ b/tests/validation/validate_kinetic.cpp @@ -23,6 +23,7 @@ void run_kinetic_dt( arb::backend_kind backend, arb::cell& c, + arb::cell_probe_address probe, float t_end, nlohmann::json meta, const std::string& ref_file) @@ -32,7 +33,7 @@ void run_kinetic_dt( float sample_dt = g_trace_io.sample_dt(); cable1d_recipe rec{c}; - rec.add_probe(0, 0, cell_probe_address{{0, 0.5}, cell_probe_address::membrane_voltage}); + rec.add_probe(0, 0, probe); probe_label plabels[1] = {"soma.mid", {0u, 0u}}; meta["sim"] = "arbor"; @@ -71,7 +72,8 @@ void validate_kinetic_kin1(arb::backend_kind backend) { // 20 µm diameter soma with single mechanism, current probe cell c; auto soma = c.add_soma(10); - soma->add_mechanism(std::string("test_kin1")); + soma->add_mechanism("test_kin1"); + cell_probe_address probe{{0, 0.5}, cell_probe_address::membrane_current}; nlohmann::json meta = { {"model", "test_kin1"}, @@ -79,7 +81,7 @@ void validate_kinetic_kin1(arb::backend_kind backend) { {"units", "nA"} }; - run_kinetic_dt(backend, c, 100.f, meta, "numeric_kin1.json"); + run_kinetic_dt(backend, c, probe, 100.f, meta, "numeric_kin1.json"); } void validate_kinetic_kinlva(arb::backend_kind backend) { @@ -89,7 +91,8 @@ void validate_kinetic_kinlva(arb::backend_kind backend) { cell c; auto soma = c.add_soma(10); c.add_stimulus({0,0.5}, {20., 130., -0.025}); - soma->add_mechanism(std::string("test_kinlva")); + soma->add_mechanism("test_kinlva"); + cell_probe_address probe{{0, 0.5}, cell_probe_address::membrane_voltage}; nlohmann::json meta = { {"model", "test_kinlva"}, @@ -97,7 +100,7 @@ void validate_kinetic_kinlva(arb::backend_kind backend) { {"units", "mV"} }; - run_kinetic_dt(backend, c, 300.f, meta, "numeric_kinlva.json"); + run_kinetic_dt(backend, c, probe, 300.f, meta, "numeric_kinlva.json"); } diff --git a/tests/validation/validate_synapses.cpp b/tests/validation/validate_synapses.cpp index 03c2ba70..55fc0e62 100644 --- a/tests/validation/validate_synapses.cpp +++ b/tests/validation/validate_synapses.cpp @@ -38,7 +38,7 @@ void run_synapse_test( }; cell c = make_cell_ball_and_stick(false); // no stimuli - parameter_list syn_default(syn_type); + mechanism_spec syn_default(syn_type); c.add_synapse({1, 0.5}, syn_default); // injected spike events -- GitLab