diff --git a/arbor/CMakeLists.txt b/arbor/CMakeLists.txt index 2af505ec75acb2918946940b2505dd8532780dd0..997128750b383b6e84e16880ad0179a7abd9b1da 100644 --- a/arbor/CMakeLists.txt +++ b/arbor/CMakeLists.txt @@ -31,7 +31,6 @@ set(arbor_sources memory/util.cpp morph/embed_pwlin.cpp morph/label_dict.cpp - morph/label_parse.cpp morph/locset.cpp morph/morphexcept.cpp morph/morphology.cpp diff --git a/arbor/cv_policy.cpp b/arbor/cv_policy.cpp index 65deddcede9cec00a801933c99b9bdbfbff9890a..11135cf7b16d1f41f7e6d827db61b1f64edbb2fb 100644 --- a/arbor/cv_policy.cpp +++ b/arbor/cv_policy.cpp @@ -1,4 +1,5 @@ #include <utility> +#include <ostream> #include <vector> #include <arbor/cable_cell.hpp> @@ -35,6 +36,11 @@ struct cv_policy_plus_: cv_policy_base { region domain() const override { return join(lhs_.domain(), rhs_.domain()); } + std::ostream& print(std::ostream& os) override { + os << "(join " << lhs_ << ' ' << rhs_ << ')'; + return os; + } + cv_policy lhs_, rhs_; }; @@ -56,6 +62,11 @@ struct cv_policy_bar_: cv_policy_base { region domain() const override { return join(lhs_.domain(), rhs_.domain()); } + std::ostream& print(std::ostream& os) override { + os << "(replace " << lhs_ << ' ' << rhs_ << ')'; + return os; + } + cv_policy lhs_, rhs_; }; diff --git a/arbor/include/arbor/cv_policy.hpp b/arbor/include/arbor/cv_policy.hpp index 4ddbdfcb95e7d8b26d326d1b6e764aac97789240..917610a1d18ff5ff1ced7f04ab28604453747080 100644 --- a/arbor/include/arbor/cv_policy.hpp +++ b/arbor/include/arbor/cv_policy.hpp @@ -65,6 +65,7 @@ struct cv_policy_base { virtual region domain() const = 0; virtual std::unique_ptr<cv_policy_base> clone() const = 0; virtual ~cv_policy_base() {} + virtual std::ostream& print(std::ostream&) = 0; }; using cv_policy_base_ptr = std::unique_ptr<cv_policy_base>; @@ -93,6 +94,10 @@ struct cv_policy { return policy_ptr->domain(); } + friend std::ostream& operator<<(std::ostream& o, const cv_policy& p) { + return p.policy_ptr->print(o); + } + private: cv_policy_base_ptr policy_ptr; }; @@ -117,6 +122,10 @@ struct cv_policy_explicit: cv_policy_base { cv_policy_base_ptr clone() const override; locset cv_boundary_points(const cable_cell&) const override; region domain() const override; + std::ostream& print(std::ostream& os) override { + os << "(explicit " << locs_ << ' ' << domain_ << ')'; + return os; + } private: locset locs_; @@ -130,6 +139,10 @@ struct cv_policy_single: cv_policy_base { cv_policy_base_ptr clone() const override; locset cv_boundary_points(const cable_cell&) const override; region domain() const override; + std::ostream& print(std::ostream& os) override { + os << "(single " << domain_ << ')'; + return os; + } private: region domain_; @@ -145,6 +158,10 @@ struct cv_policy_max_extent: cv_policy_base { cv_policy_base_ptr clone() const override; locset cv_boundary_points(const cable_cell&) const override; region domain() const override; + std::ostream& print(std::ostream& os) override { + os << "(max-extent " << max_extent_ << ' ' << domain_ << ' ' << flags_ << ')'; + return os; + } private: double max_extent_; @@ -162,6 +179,10 @@ struct cv_policy_fixed_per_branch: cv_policy_base { cv_policy_base_ptr clone() const override; locset cv_boundary_points(const cable_cell&) const override; region domain() const override; + std::ostream& print(std::ostream& os) override { + os << "(fixed-per-branch " << cv_per_branch_ << ' ' << domain_ << ' ' << flags_ << ')'; + return os; + } private: unsigned cv_per_branch_; @@ -176,6 +197,10 @@ struct cv_policy_every_segment: cv_policy_base { cv_policy_base_ptr clone() const override; locset cv_boundary_points(const cable_cell&) const override; region domain() const override; + std::ostream& print(std::ostream& os) override { + os << "(every-segment " << domain_ << ')'; + return os; + } private: region domain_; diff --git a/arbor/include/arbor/morph/label_parse.hpp b/arbor/include/arbor/morph/label_parse.hpp deleted file mode 100644 index db3b21c18e0930645b7e0a1147b8259390b27f74..0000000000000000000000000000000000000000 --- a/arbor/include/arbor/morph/label_parse.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include <any> - -#include <arbor/arbexcept.hpp> -#include <arbor/morph/region.hpp> -#include <arbor/s_expr.hpp> -#include <arbor/util/expected.hpp> - -namespace arb { - -struct label_parse_error: arb::arbor_exception { - label_parse_error(const std::string& msg); -}; - -template <typename T> -using parse_hopefully = arb::util::expected<T, label_parse_error>; - -parse_hopefully<std::any> parse_label_expression(const std::string&); -parse_hopefully<std::any> parse_label_expression(const s_expr&); - -parse_hopefully<arb::region> parse_region_expression(const std::string& s); -parse_hopefully<arb::locset> parse_locset_expression(const std::string& s); - -} // namespace arb diff --git a/arbor/include/arbor/morph/locset.hpp b/arbor/include/arbor/morph/locset.hpp index b66f268cdfa2f2ffba309f1ead5237134f49d206..71a60778434e19bcb3b35cd3f8c052098bd9580d 100644 --- a/arbor/include/arbor/morph/locset.hpp +++ b/arbor/include/arbor/morph/locset.hpp @@ -25,7 +25,8 @@ public: explicit locset(Impl&& impl): impl_(new wrap<Impl>(std::forward<Impl>(impl))) {} - template <typename Impl> + template <typename Impl, + typename = std::enable_if_t<std::is_base_of<locset_tag, std::decay_t<Impl>>::value>> explicit locset(const Impl& impl): impl_(new wrap<Impl>(impl)) {} @@ -48,10 +49,6 @@ public: locset(mlocation other); locset(mlocation_list other); - // Implicitly convert string to named locset expression. - locset(const std::string& label); - locset(const char* label); - template <typename Impl, typename = std::enable_if_t<std::is_base_of<locset_tag, std::decay_t<Impl>>::value>> locset& operator=(Impl&& other) { @@ -164,7 +161,7 @@ locset on_branches(double pos); // s.t. dist(h, L) = r * max {dist(h, t) | t is a distal point in C}. locset on_components(double relpos, region reg); -// Support of a locset (x s.t. x in locset). +// Set of locations in the locset with duplicates removed, i.e. the support of the input multiset locset support(locset); } // namespace ls diff --git a/arbor/include/arbor/morph/region.hpp b/arbor/include/arbor/morph/region.hpp index 62afa9e0541013cf2b0ed2520c4672ac8779ca99..0f048860b3ca2b954bf68c0d63504e3b2a80fcb2 100644 --- a/arbor/include/arbor/morph/region.hpp +++ b/arbor/include/arbor/morph/region.hpp @@ -23,7 +23,8 @@ public: explicit region(Impl&& impl): impl_(new wrap<Impl>(std::forward<Impl>(impl))) {} - template <typename Impl> + template <typename Impl, + typename = std::enable_if_t<std::is_base_of<region_tag, std::decay_t<Impl>>::value>> explicit region(const Impl& impl): impl_(new wrap<Impl>(impl)) {} @@ -47,7 +48,8 @@ public: return *this; } - template <typename Impl> + template <typename Impl, + typename = std::enable_if_t<std::is_base_of<region_tag, std::decay_t<Impl>>::value>> region& operator=(const Impl& other) { impl_ = new wrap<Impl>(other); return *this; @@ -58,10 +60,6 @@ public: region(mextent); region(mcable_list); - // Implicitly convert string to named region expression. - region(const std::string& label); - region(const char* label); - friend mextent thingify(const region& r, const mprovider& m) { return r.impl_->thingify(m); } diff --git a/arbor/include/arbor/string_literals.hpp b/arbor/include/arbor/string_literals.hpp deleted file mode 100644 index 056c25231abd0078387c15b12d0cbc2207885c41..0000000000000000000000000000000000000000 --- a/arbor/include/arbor/string_literals.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include <string> - -namespace arb::literals { - -inline -std::string operator "" _lab(const char* s, std::size_t) { - return std::string("\"") + s + "\""; -} - -} // namespace arb diff --git a/arbor/include/arbor/util/expected.hpp b/arbor/include/arbor/util/expected.hpp index 947783bc5c970e78a8576e962614e3a55bb36824..45ab99bc06c435007b49deb87b6064d15705d554 100644 --- a/arbor/include/arbor/util/expected.hpp +++ b/arbor/include/arbor/util/expected.hpp @@ -288,7 +288,6 @@ struct expected { if (*this) return std::get<0>(data_); throw bad_expected_access(error()); } - T&& value() && { if (*this) return std::get<0>(std::move(data_)); throw bad_expected_access(error()); @@ -298,6 +297,23 @@ struct expected { throw bad_expected_access(error()); } + T& unwrap() & { + if (*this) return std::get<0>(data_); + throw error(); + } + const T& unwrap() const& { + if (*this) return std::get<0>(data_); + throw error(); + } + T&& unwrap() && { + if (*this) return std::get<0>(std::move(data_)); + throw error(); + } + const T&& unwrap() const&& { + if (*this) return std::get<0>(std::move(data_)); + throw error(); + } + const E& error() const& { return std::get<1>(data_).value(); } E& error() & { return std::get<1>(data_).value(); } @@ -505,7 +521,5 @@ template <typename T1, typename E1, typename T2, typename E2, inline bool operator!=(const expected<T1, E1>& a, const expected<T2, E2>& b) { return a? !b || !std::is_void_v<T2> || !std::is_void_v<T1>: b || a.error()!=b.error(); } - - } // namespace util } // namespace arb diff --git a/arbor/morph/locset.cpp b/arbor/morph/locset.cpp index ea37fb008f41dc4eebdc478753917c475a1ecd46..8b15a2954df8deed91c616449ad27671cca6c118 100644 --- a/arbor/morph/locset.cpp +++ b/arbor/morph/locset.cpp @@ -3,7 +3,6 @@ #include <numeric> #include <arbor/math.hpp> -#include <arbor/morph/label_parse.hpp> #include <arbor/morph/locset.hpp> #include <arbor/morph/morphexcept.hpp> #include <arbor/morph/morphology.hpp> @@ -152,7 +151,10 @@ std::ostream& operator<<(std::ostream& o, const segments_& x) { // Proportional location on every branch. -struct on_branches_ { double pos; }; +struct on_branches_: locset_tag { + explicit on_branches_(double p): pos{p} {} + double pos; +}; locset on_branches(double pos) { return locset{on_branches_{pos}}; @@ -397,7 +399,10 @@ std::ostream& operator<<(std::ostream& o, const on_components_& x) { // Uniform locset. -struct uniform_ { +struct uniform_: locset_tag { + uniform_(const arb::region& reg_, unsigned left_, unsigned right_, uint64_t seed_): + reg{reg_}, left{left_}, right{right_}, seed{seed_} + {} region reg; unsigned left; unsigned right; @@ -525,7 +530,8 @@ std::ostream& operator<<(std::ostream& o, const lsup_& x) { // Restrict a locset on to a region: returns all locations in the locset that // are also in the region. -struct lrestrict_ { +struct lrestrict_: locset_tag { + explicit lrestrict_(const locset& l, const region& r): ls{l}, reg{r} {} locset ls; region reg; }; @@ -588,15 +594,4 @@ locset::locset(mlocation_list ll) { *this = ls::location_list(std::move(ll)); } -locset::locset(const std::string& desc) { - if (auto r=parse_locset_expression(desc)) { - *this = *r; - } - else { - throw r.error(); - } -} - -locset::locset(const char* label): locset(std::string(label)) {} - } // namespace arb diff --git a/arbor/morph/region.cpp b/arbor/morph/region.cpp index 64234f554fb3cd00cd36fe7394b4a9bb247c6457..539b4683c94025bf586bd73f38f09d1940a1371f 100644 --- a/arbor/morph/region.cpp +++ b/arbor/morph/region.cpp @@ -4,7 +4,6 @@ #include <unordered_set> #include <vector> -#include <arbor/morph/label_parse.hpp> #include <arbor/morph/locset.hpp> #include <arbor/morph/primitives.hpp> #include <arbor/morph/morphexcept.hpp> @@ -215,7 +214,8 @@ std::ostream& operator<<(std::ostream& o, const all_& t) { // Region comprising points up to `distance` distal to a point in `start`. -struct distal_interval_ { +struct distal_interval_: region_tag { + distal_interval_(const locset& ls, double d): start{ls}, distance{d} {} locset start; double distance; //um }; @@ -289,7 +289,8 @@ std::ostream& operator<<(std::ostream& o, const distal_interval_& d) { // Region comprising points up to `distance` proximal to a point in `end`. -struct proximal_interval_ { +struct proximal_interval_: region_tag { + proximal_interval_(const locset& ls, double d): end{ls}, distance{d} {} locset end; double distance; //um }; @@ -361,7 +362,8 @@ mextent radius_cmp(const mprovider& p, region r, double val, comp_op op) { } // Region with all segments with radius less than r -struct radius_lt_ { +struct radius_lt_: region_tag { + radius_lt_(const region& rg, double d): reg{rg}, val{d} {} region reg; double val; //um }; @@ -379,7 +381,8 @@ std::ostream& operator<<(std::ostream& o, const radius_lt_& r) { } // Region with all segments with radius less than r -struct radius_le_ { +struct radius_le_: region_tag { + radius_le_(const region& rg, double d): reg{rg}, val{d} {} region reg; double val; //um }; @@ -397,7 +400,8 @@ std::ostream& operator<<(std::ostream& o, const radius_le_& r) { } // Region with all segments with radius greater than r -struct radius_gt_ { +struct radius_gt_: region_tag { + radius_gt_(const region& rg, double d): reg{rg}, val{d} {} region reg; double val; //um }; @@ -415,7 +419,8 @@ std::ostream& operator<<(std::ostream& o, const radius_gt_& r) { } // Region with all segments with radius greater than or equal to r -struct radius_ge_ { +struct radius_ge_: region_tag { + radius_ge_(const region& rg, double d): reg{rg}, val{d} {} region reg; double val; //um }; @@ -445,7 +450,8 @@ mextent projection_cmp(const mprovider& p, double v, comp_op op) { } // Region with all segments with projection less than val -struct projection_lt_{ +struct projection_lt_: region_tag { + projection_lt_(double d): val{d} {} double val; //um }; @@ -462,7 +468,8 @@ std::ostream& operator<<(std::ostream& o, const projection_lt_& r) { } // Region with all segments with projection less than or equal to val -struct projection_le_{ +struct projection_le_: region_tag { + projection_le_(double d): val{d} {} double val; //um }; @@ -479,7 +486,8 @@ std::ostream& operator<<(std::ostream& o, const projection_le_& r) { } // Region with all segments with projection greater than val -struct projection_gt_ { +struct projection_gt_: region_tag { + projection_gt_(double d): val{d} {} double val; //um }; @@ -496,7 +504,8 @@ std::ostream& operator<<(std::ostream& o, const projection_gt_& r) { } // Region with all segments with projection greater than val -struct projection_ge_ { +struct projection_ge_: region_tag { + projection_ge_(double d): val{d} {} double val; //um }; @@ -559,7 +568,8 @@ std::ostream& operator<<(std::ostream& o, const named_& x) { // Adds all cover points to a region. // Ensures that all valid representations of all fork points in the region are included. -struct super_ { +struct super_: region_tag { + explicit super_(const region& rg): reg{rg} {} region reg; }; @@ -745,17 +755,6 @@ region::region() { // Implicit constructors/converters. -region::region(const std::string& desc) { - if (auto r=parse_region_expression(desc)) { - *this = *r; - } - else { - throw r.error(); - } -} - -region::region(const char* label): region(std::string(label)) {} - region::region(mcable c) { *this = reg::cable(c.branch, c.prox_pos, c.dist_pos); } diff --git a/arborio/CMakeLists.txt b/arborio/CMakeLists.txt index 63855f42a7029aa50681f925d4d6ba2a775564b0..104afdacefb5553d3cb727ad51dc9adcff7376a0 100644 --- a/arborio/CMakeLists.txt +++ b/arborio/CMakeLists.txt @@ -3,6 +3,8 @@ set(arborio-sources neurolucida.cpp swcio.cpp cableio.cpp + cv_policy_parse.cpp + label_parse.cpp ) if(ARB_WITH_NEUROML) list(APPEND arborio-sources diff --git a/arborio/cableio.cpp b/arborio/cableio.cpp index 998dff37682fc0ee4b8afe954443127da810fd4b..4440cfa743a97a9e19e87165ec52f582ebf9038f 100644 --- a/arborio/cableio.cpp +++ b/arborio/cableio.cpp @@ -2,13 +2,14 @@ #include <sstream> #include <unordered_map> -#include <arbor/morph/label_parse.hpp> #include <arbor/s_expr.hpp> #include <arbor/util/pp_util.hpp> #include <arbor/util/any_visitor.hpp> +#include <arborio/label_parse.hpp> #include <arborio/cableio.hpp> +#include "parse_helpers.hpp" #include "parse_s_expr.hpp" namespace arborio { @@ -32,7 +33,6 @@ cableio_version_error::cableio_version_error(const std::string& version): arb::arbor_exception("Unsupported cable-cell format version `" + version + "`") {} -struct nil_tag {}; // Define s-expr makers for various types s_expr mksexp(const init_membrane_potential& p) { @@ -86,7 +86,9 @@ s_expr mksexp(const msegment& seg) { } // This can be removed once cv_policy is removed from the decor. s_expr mksexp(const cv_policy& c) { - return s_expr(); + std::stringstream s; + s << c; + return parse_s_expr(s.str()); } s_expr mksexp(const decor& d) { auto round_trip = [](auto& x) { @@ -179,36 +181,6 @@ std::ostream& write_component(std::ostream& o, const cable_cell_component& x) { // Anonymous namespace containing helper functions and types for parsing s-expr namespace { -// Test whether a value wrapped in std::any can be converted to a target type -template <typename T> -bool match(const std::type_info& info) { - return info == typeid(T); -} -template <> -bool match<double>(const std::type_info& info) { - return info == typeid(double) || info == typeid(int); -} - -// Convert a value wrapped in a std::any to target type. -template <typename T> -T eval_cast(std::any arg) { - return std::move(std::any_cast<T&>(arg)); -} -template <> -double eval_cast<double>(std::any arg) { - if (arg.type()==typeid(int)) return std::any_cast<int>(arg); - return std::any_cast<double>(arg); -} - -// Convert a value wrapped in a std::any to an optional std::variant type -template <typename T, std::size_t I=0> -std::optional<T> eval_cast_variant(const std::any& a) { - if constexpr (I<std::variant_size_v<T>) { - using var_type = std::variant_alternative_t<I, T>; - return match<var_type>(a.type())? eval_cast<var_type>(a): eval_cast_variant<T, I+1>(a); - } - return std::nullopt; -} // Useful tuple aliases using envelope_tuple = std::tuple<double,double>; @@ -358,142 +330,6 @@ cable_cell_component make_component(const meta_data& m, const T& d) { return cable_cell_component{m, d}; } -// Evaluator: member of make_call, make_arg_vec_call, make_mech_call, make_branch_call, make_unordered_call -struct evaluator { - using any_vec = std::vector<std::any>; - using eval_fn = std::function<std::any(any_vec)>; - using args_fn = std::function<bool(const any_vec&)>; - - eval_fn eval; - args_fn match_args; - const char* message; - - evaluator(eval_fn f, args_fn a, const char* m): - eval(std::move(f)), - match_args(std::move(a)), - message(m) - {} - - std::any operator()(any_vec args) { - return eval(std::move(args)); - } -}; - -// Test whether a list of arguments passed as a std::vector<std::any> can be converted -// to the types in Args. -// -// For example, the following would return true: -// -// call_match<int, int, string>(vector<any(4), any(12), any(string("hello"))>) -template <typename... Args> -struct call_match { - template <std::size_t I, typename T, typename Q, typename... Rest> - bool match_args_impl(const std::vector<std::any>& args) const { - return match<T>(args[I].type()) && match_args_impl<I+1, Q, Rest...>(args); - } - - template <std::size_t I, typename T> - bool match_args_impl(const std::vector<std::any>& args) const { - return match<T>(args[I].type()); - } - - template <std::size_t I> - bool match_args_impl(const std::vector<std::any>& args) const { - return true; - } - - bool operator()(const std::vector<std::any>& args) const { - const auto nargs_in = args.size(); - const auto nargs_ex = sizeof...(Args); - return nargs_in==nargs_ex? match_args_impl<0, Args...>(args): false; - } -}; -// Evaluate a call to a function where the arguments are provided as a std::vector<std::any>. -// The arguments are expanded and converted to the correct types, as specified by Args. -template <typename... Args> -struct call_eval { - using ftype = std::function<std::any(Args...)>; - ftype f; - call_eval(ftype f): f(std::move(f)) {} - - template<std::size_t... I> - std::any expand_args_then_eval(const std::vector<std::any>& args, std::index_sequence<I...>) { - return f(eval_cast<Args>(std::move(args[I]))...); - } - - std::any operator()(const std::vector<std::any>& args) { - return expand_args_then_eval(std::move(args), std::make_index_sequence<sizeof...(Args)>()); - } -}; -// Wrap call_match and call_eval in an evaluator -template <typename... Args> -struct make_call { - evaluator state; - - template <typename F> - make_call(F&& f, const char* msg="call"): - state(call_eval<Args...>(std::forward<F>(f)), call_match<Args...>(), msg) - {} - operator evaluator() const { - return state; - } -}; - -// Test whether a list of arguments passed as a std::vector<std::any> can be converted -// to a std::vector<std::variant<Args...>>. -// -// For example, the following would return true: -// -// call_match<int, string>(vector<any(4), any(12), any(string("hello"))>) -template <typename... Args> -struct arg_vec_match { - template <typename T, typename Q, typename... Rest> - bool match_args_impl(const std::any& arg) const { - return match<T>(arg.type()) || match_args_impl<Q, Rest...>(arg); - } - - template <typename T> - bool match_args_impl(const std::any& arg) const { - return match<T>(arg.type()); - } - - bool operator()(const std::vector<std::any>& args) const { - for (const auto& a: args) { - if (!match_args_impl<Args...>(a)) return false; - } - return true; - } -}; -// Evaluate a call to a function where the arguments are provided as a std::vector<std::any>. -// The arguments are converted to std::variant<Args...> and passed to the function as a std::vector. -template <typename... Args> -struct arg_vec_eval { - using ftype = std::function<std::any(std::vector<std::variant<Args...>>)>; - ftype f; - arg_vec_eval(ftype f): f(std::move(f)) {} - - std::any operator()(const std::vector<std::any>& args) { - std::vector<std::variant<Args...>> vars; - for (const auto& a: args) { - vars.push_back(eval_cast_variant<std::variant<Args...>>(a).value()); - } - return f(vars); - } -}; -// Wrap arg_vec_match and arg_vec_eval in an evaluator -template <typename... Args> -struct make_arg_vec_call { - evaluator state; - - template <typename F> - make_arg_vec_call(F&& f, const char* msg="argument vector"): - state(arg_vec_eval<Args...>(std::forward<F>(f)), arg_vec_match<Args...>(), msg) - {} - operator evaluator() const { - return state; - } -}; - // Test whether a list of arguments passed as a std::vector<std::any> can be converted // to a string followed by any number of std::pair<std::string, double> struct mech_match { @@ -654,26 +490,9 @@ parse_hopefully<std::vector<std::any>> eval_args(const s_expr& e, const eval_map parse_hopefully<std::any> eval(const s_expr& e, const eval_map& map, const eval_vec& vec) { if (e.is_atom()) { - auto& t = e.atom(); - switch (t.kind) { - case tok::integer: - return {std::stoi(t.spelling)}; - case tok::real: - return {std::stod(t.spelling)}; - case tok::nil: - return {nil_tag()}; - case tok::string: - return std::any{std::string(t.spelling)}; - // An arbitrary symbol in a region/locset expression is an error, and is - // often a result of not quoting a label correctly. - case tok::symbol: - return util::unexpected(cableio_parse_error("Unexpected symbol "+e.atom().spelling, location(e))); - case tok::error: - return util::unexpected(cableio_parse_error("Unexpected term "+e.atom().spelling, location(e))); - default: - return util::unexpected(cableio_parse_error("Unexpected term "+e.atom().spelling, location(e))); - } + return eval_atom<cableio_parse_error>(e); } + if (e.head().is_atom()) { // If this is not a symbol, parse as a tuple if (e.head().atom().kind != tok::symbol) { diff --git a/arborio/cv_policy_parse.cpp b/arborio/cv_policy_parse.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c013efe751499b4c34b3b0f1dba052cdb6ad8656 --- /dev/null +++ b/arborio/cv_policy_parse.cpp @@ -0,0 +1,204 @@ +#include <any> +#include <limits> +#include <optional> + +#include <arborio/label_parse.hpp> +#include <arborio/cv_policy_parse.hpp> + +#include <arbor/arbexcept.hpp> +#include <arbor/cv_policy.hpp> +#include <arbor/s_expr.hpp> +#include <arbor/util/expected.hpp> + + +#include "parse_helpers.hpp" + +namespace arborio { + +cv_policy_parse_error::cv_policy_parse_error(const std::string& msg, const arb::src_location& loc): + arb::arbor_exception(concat("error in CV policy description: ", msg," at :", loc.line, ":", loc.column)) +{} + +cv_policy_parse_error::cv_policy_parse_error(const std::string& msg): + arb::arbor_exception(concat("error in CV policy description: ", msg)) +{} + +namespace { + +template<typename T> using parse_hopefully = arb::util::expected<T, cv_policy_parse_error>; + +std::unordered_multimap<std::string, evaluator> +eval_map {{"default", + make_call<>([] () { return arb::cv_policy{arb::default_cv_policy()}; }, + "'default' with no arguments")}, + {"every-segment", + make_call<>([] () { return arb::cv_policy{arb::cv_policy_every_segment()}; }, + "'every-segment' with no arguments")}, + {"every-segment", + make_call<region>([] (const region& r) { return arb::cv_policy{arb::cv_policy_every_segment(r) }; }, + "'every-segment' with one argument (every-segment (reg:region))")}, + {"fixed-per-branch", + make_call<int>([] (int i) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i) }; }, + "'every-segment' with one argument (fixed-per-branch (count:int))")}, + {"fixed-per-branch", + make_call<int, region>([] (int i, const region& r) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i, r) }; }, + "'every-segment' with two arguments (fixed-per-branch (count:int) (reg:region))")}, + {"fixed-per-branch", + make_call<int, region, int>([] (int i, const region& r, int f) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i, r, f) }; }, + "'fixed-per-branch' with three arguments (fixed-per-branch (count:int) (reg:region) (flags:int))")}, + {"max-extent", + make_call<double>([] (double i) { return arb::cv_policy{arb::cv_policy_max_extent(i) }; }, + "'max-extent' with one argument (max-extent (length:double))")}, + {"max-extent", + make_call<double, region>([] (double i, const region& r) { return arb::cv_policy{arb::cv_policy_max_extent(i, r) }; }, + "'max-extent' with two arguments (max-extent (length:double) (reg:region))")}, + {"max-extent", + make_call<double, region, int>([] (double i, const region& r, int f) { return arb::cv_policy{arb::cv_policy_max_extent(i, r, f) }; }, + "'max-extent' with three arguments (max-extent (length:double) (reg:region) (flags:int))")}, + {"single", + make_call<>([] () { return arb::cv_policy{arb::cv_policy_single()}; }, + "'single' with no arguments")}, + {"single", + make_call<region>([] (const region& r) { return arb::cv_policy{arb::cv_policy_single(r) }; }, + "'single' with one argument (single (reg:region))")}, + {"explicit", + make_call<locset>([] (const locset& l) { return arb::cv_policy{arb::cv_policy_explicit(l) }; }, + "'explicit' with one argument (explicit (ls:locset))")}, + {"explicit", + make_call<locset, region>([] (const locset& l, const region& r) { return arb::cv_policy{arb::cv_policy_explicit(l, r) }; }, + "'explicit' with two arguments (explicit (ls:locset) (reg:region))")}, + {"join", + make_fold<cv_policy>([](cv_policy l, cv_policy r) { return l + r; }, + "'join' with at least 2 arguments: (join cv_policy cv_policy ...)")}, + {"replace", + make_fold<cv_policy>([](cv_policy l, cv_policy r) { return l | r; }, + "'replace' with at least 2 arguments: (replace cv_policy cv_policy ...)")}, +}; + +parse_hopefully<std::any> eval(const s_expr& e); + +parse_hopefully<std::vector<std::any>> eval_args(const s_expr& e) { + if (!e) return {std::vector<std::any>{}}; // empty argument list + std::vector<std::any> args; + for (auto& h: e) { + if (auto arg=eval(h)) { + args.push_back(std::move(*arg)); + } + else { + return util::unexpected(std::move(arg.error())); + } + } + return args; +} + +// Generate a string description of a function evaluation of the form: +// Example output: +// 'foo' with 1 argument: (real) +// 'bar' with 0 arguments +// 'cat' with 3 arguments: (locset region integer) +// Where 'foo', 'bar' and 'cat' are the name of the function, and the +// types (integer, real, region, locset) are inferred from the arguments. +std::string eval_description(const char* name, const std::vector<std::any>& args) { + auto type_string = [](const std::type_info& t) -> const char* { + if (t==typeid(int)) return "integer"; + if (t==typeid(double)) return "real"; + if (t==typeid(region)) return "region"; + if (t==typeid(locset)) return "locset"; + if (t==typeid(cv_policy)) return "cv_policy"; + return "unknown"; + }; + + const auto nargs = args.size(); + std::string msg = concat("'", name, "' with ", nargs, " argument", nargs!=1u ? "s" : "", ":"); + if (nargs) { + msg += " ("; + bool append_sep = false; + for (auto& a: args) { + if (append_sep) { + msg += " "; + } + msg += type_string(a.type()); + append_sep = true; + } + msg += ")"; + } + return msg; +} + +// Evaluate an s expression. +// On success the result is wrapped in std::any, where the result is one of: +// int: an integer atom +// double: a real atom +// cv_policy: a discretization policy expression +// +// If there invalid input is detected, hopefully return value contains +// a cv_policy_error_state with an error string and location. +// +// If there was an unexpected/fatal error, an exception will be thrown. +parse_hopefully<std::any> eval(const s_expr& e) { + if (e.is_atom()) { + return eval_atom<cv_policy_parse_error>(e); + } + + if (e.head().is_atom()) { + // This must be a function evaluation, where head is the function name, and + // tail is a list of arguments. + + // Evaluate the arguments, and return error state if an error occurred. + auto args = eval_args(e.tail()); + if (!args) { + return args.error(); + } + + // Find all candidate functions that match the name of the function. + auto& name = e.head().atom().spelling; + auto matches = eval_map.equal_range(name); + + // if no matches found, maybe this is a morphology expression? + if (matches.first == matches.second) { + auto lbl = parse_label_expression(e); + if (lbl.has_value()) { + return { lbl.value() }; + } else { + return util::unexpected(cv_policy_parse_error(lbl.error().what(), location(e))); + } + } else { + // Search for a candidate that matches the argument list. + for (auto i=matches.first; i!=matches.second; ++i) { + if (i->second.match_args(*args)) { // found a match: evaluate and return. + return i->second.eval(*args); + } + } + + // Unable to find a match: try to return a helpful error message. + const auto nc = std::distance(matches.first, matches.second); + auto msg = concat("No matches for ", eval_description(name.c_str(), *args), "\n There are ", nc, " potential candiates", nc ? ":" : "."); + int count = 0; + for (auto i=matches.first; i!=matches.second; ++i) { + msg += concat("\n Candidate ", ++count, ": ", i->second.message); + } + + return util::unexpected(cv_policy_parse_error(msg, location(e))); + } + } + + return util::unexpected(cv_policy_parse_error( + concat("'", e, "' is not either integer, real expression of the form (op <args>)"), + location(e))); +} +} + +parse_cv_policy_hopefully parse_cv_policy_expression(const std::string& s) { + if (auto e = eval(parse_s_expr(s))) { + if (e->type() == typeid(cv_policy)) { + return {std::move(std::any_cast<cv_policy&>(*e))}; + } + return util::unexpected( + cv_policy_parse_error(concat("Invalid description: '", s, "' is not a valid CV policy expression."))); + } + else { + return util::unexpected(cv_policy_parse_error(std::string() + e.error().what())); + } +} + +} // namespace arb diff --git a/arborio/include/arborio/cv_policy_parse.hpp b/arborio/include/arborio/cv_policy_parse.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8a899d468312426b02cfaf1558cf7da4735933cf --- /dev/null +++ b/arborio/include/arborio/cv_policy_parse.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include <any> +#include <string> + +#include <arbor/cv_policy.hpp> +#include <arbor/arbexcept.hpp> +#include <arbor/util/expected.hpp> +#include <arbor/s_expr.hpp> + +namespace arborio { + +struct cv_policy_parse_error: arb::arbor_exception { + explicit cv_policy_parse_error(const std::string& msg, const arb::src_location& loc); + explicit cv_policy_parse_error(const std::string& msg); +}; + +using parse_cv_policy_hopefully = arb::util::expected<arb::cv_policy, cv_policy_parse_error>; + +parse_cv_policy_hopefully parse_cv_policy_expression(const std::string& s); + +namespace literals { + +inline +arb::cv_policy operator "" _cvp(const char* s, std::size_t) { + if (auto r = parse_cv_policy_expression(s)) return *r; + else throw r.error(); +} + +} // namespace literals + +} // namespace arb diff --git a/arborio/include/arborio/label_parse.hpp b/arborio/include/arborio/label_parse.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b51f9361e67e3c87cc9b8ff46b57fd102f30fa2f --- /dev/null +++ b/arborio/include/arborio/label_parse.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include <any> +#include <string> + +#include <arbor/arbexcept.hpp> +#include <arbor/morph/region.hpp> +#include <arbor/morph/locset.hpp> +#include <arbor/util/expected.hpp> + +#include <arbor/s_expr.hpp> + +namespace arborio { + +struct label_parse_error: arb::arbor_exception { + explicit label_parse_error(const std::string& msg, const arb::src_location& loc); + explicit label_parse_error(const std::string& msg): arb::arbor_exception(msg) {} +}; + +template <typename T> +using parse_label_hopefully = arb::util::expected<T, label_parse_error>; + +parse_label_hopefully<std::any> parse_label_expression(const std::string&); +parse_label_hopefully<std::any> parse_label_expression(const arb::s_expr&); + +parse_label_hopefully<arb::region> parse_region_expression(const std::string& s); +parse_label_hopefully<arb::locset> parse_locset_expression(const std::string& s); + +namespace literals { + +struct morph_from_string { + morph_from_string(const std::string& s): str{s} {} + morph_from_string(const char* s): str{s} {} + + std::string str; + + operator arb::locset() const { + if (auto r = parse_locset_expression(str)) return *r; + else throw r.error(); + } + + operator arb::region() const { + if (auto r = parse_region_expression(str)) return *r; + else throw r.error(); + } +}; + +struct morph_from_label { + morph_from_label(const std::string& s): str{s} {} + morph_from_label(const char* s): str{s} {} + + std::string str; + + operator arb::locset() const { return arb::ls::named(str); } + operator arb::region() const { return arb::reg::named(str); } +}; + +inline +arb::locset operator "" _ls(const char* s, std::size_t) { + if (auto r = parse_locset_expression(s)) return *r; + else throw r.error(); +} + +inline +arb::region operator "" _reg(const char* s, std::size_t) { + if (auto r = parse_region_expression(s)) return *r; + else throw r.error(); +} + +inline morph_from_string operator "" _morph(const char* s, std::size_t) { return {s}; } +inline morph_from_label operator "" _lab(const char* s, std::size_t) { return {s}; } +} // namespace literals +} // namespace arborio diff --git a/arbor/morph/label_parse.cpp b/arborio/label_parse.cpp similarity index 58% rename from arbor/morph/label_parse.cpp rename to arborio/label_parse.cpp index 01b11a2080833fa041a5adf278e3ee3d7defec7b..119342d3fa2f89b4a18574e2c78fc2cb52b8e38e 100644 --- a/arbor/morph/label_parse.cpp +++ b/arborio/label_parse.cpp @@ -1,183 +1,24 @@ #include <any> #include <limits> +#include <arborio/label_parse.hpp> + #include <arbor/arbexcept.hpp> -#include <arbor/morph/label_parse.hpp> #include <arbor/morph/locset.hpp> #include <arbor/morph/region.hpp> -#include <arbor/s_expr.hpp> + #include <arbor/util/expected.hpp> -#include "util/strprintf.hpp" +#include "parse_helpers.hpp" -namespace arb { +namespace arborio { -label_parse_error::label_parse_error(const std::string& msg): - arb::arbor_exception(msg) +label_parse_error::label_parse_error(const std::string& msg, const arb::src_location& loc): + arb::arbor_exception(concat("error in label description: ", msg," at :", loc.line, ":", loc.column)) {} -struct nil_tag {}; - -template <typename T> -bool match(const std::type_info& info) { - return info == typeid(T); -} - -template <> -bool match<double>(const std::type_info& info) { - return info == typeid(double) || info == typeid(int); -} - -template <> -bool match<arb::region>(const std::type_info& info) { - return info == typeid(arb::region) || info == typeid(nil_tag); -} - -template <> -bool match<arb::locset>(const std::type_info& info) { - return info == typeid(arb::locset) || info == typeid(nil_tag); -} - -template <typename T> -T eval_cast(std::any arg) { - return std::move(std::any_cast<T&>(arg)); -} - -template <> -double eval_cast<double>(std::any arg) { - if (arg.type()==typeid(int)) return std::any_cast<int>(arg); - return std::any_cast<double>(arg); -} - -template <> -arb::region eval_cast<arb::region>(std::any arg) { - if (arg.type()==typeid(arb::region)) return std::any_cast<arb::region>(arg); - return arb::reg::nil(); -} - -template <> -arb::locset eval_cast<arb::locset>(std::any arg) { - if (arg.type()==typeid(arb::locset)) return std::any_cast<arb::locset>(arg); - return arb::ls::nil(); -} - -template <typename... Args> -struct call_eval { - using ftype = std::function<std::any(Args...)>; - ftype f; - call_eval(ftype f): f(std::move(f)) {} - - template<std::size_t... I> - std::any expand_args_then_eval(std::vector<std::any> args, std::index_sequence<I...>) { - return f(eval_cast<Args>(std::move(args[I]))...); - } - - std::any operator()(std::vector<std::any> args) { - return expand_args_then_eval(std::move(args), std::make_index_sequence<sizeof...(Args)>()); - } -}; - -template <typename... Args> -struct call_match { - template <std::size_t I, typename T, typename Q, typename... Rest> - bool match_args_impl(const std::vector<std::any>& args) const { - return match<T>(args[I].type()) && match_args_impl<I+1, Q, Rest...>(args); - } - - template <std::size_t I, typename T> - bool match_args_impl(const std::vector<std::any>& args) const { - return match<T>(args[I].type()); - } - - template <std::size_t I> - bool match_args_impl(const std::vector<std::any>& args) const { - return true; - } - - bool operator()(const std::vector<std::any>& args) const { - const auto nargs_in = args.size(); - const auto nargs_ex = sizeof...(Args); - return nargs_in==nargs_ex? match_args_impl<0, Args...>(args): false; - } -}; -template <typename T> -struct fold_eval { - using fold_fn = std::function<T(T, T)>; - fold_fn f; - - using anyvec = std::vector<std::any>; - using iterator = anyvec::iterator; - - fold_eval(fold_fn f): f(std::move(f)) {} - - T fold_impl(iterator left, iterator right) { - if (std::distance(left,right)==1u) { - return eval_cast<T>(std::move(*left)); - } - return f(eval_cast<T>(std::move(*left)), fold_impl(left+1, right)); - } - - std::any operator()(anyvec args) { - return fold_impl(args.begin(), args.end()); - } -}; - -template <typename T> -struct fold_match { - using anyvec = std::vector<std::any>; - bool operator()(const anyvec& args) const { - if (args.size()<2u) return false; - for (auto& a: args) { - if (!match<T>(a.type())) return false; - } - return true; - } -}; - -struct evaluator { - using any_vec = std::vector<std::any>; - using eval_fn = std::function<std::any(any_vec)>; - using args_fn = std::function<bool(const any_vec&)>; - - eval_fn eval; - args_fn match_args; - const char* message; - - evaluator(eval_fn f, args_fn a, const char* m): - eval(std::move(f)), - match_args(std::move(a)), - message(m) - {} -}; - -template <typename... Args> -struct make_call { - evaluator state; - - template <typename F> - make_call(F&& f, const char* msg="call"): - state(call_eval<Args...>(std::forward<F>(f)), call_match<Args...>(), msg) - {} - - operator evaluator() const { - return state; - } -}; - -template <typename T> -struct make_fold { - evaluator state; - - template <typename F> - make_fold(F&& f, const char* msg="fold"): - state(fold_eval<T>(std::forward<F>(f)), fold_match<T>(), msg) - {} - - operator evaluator() const { - return state; - } -}; +namespace { std::unordered_multimap<std::string, evaluator> eval_map { // Functions that return regions @@ -268,9 +109,9 @@ std::unordered_multimap<std::string, evaluator> eval_map { "'sum' with at least 2 arguments: (locset locset [...locset])")}, }; -parse_hopefully<std::any> eval(const s_expr& e); +parse_label_hopefully<std::any> eval(const s_expr& e); -parse_hopefully<std::vector<std::any>> eval_args(const s_expr& e) { +parse_label_hopefully<std::vector<std::any>> eval_args(const s_expr& e) { if (!e) return {std::vector<std::any>{}}; // empty argument list std::vector<std::any> args; for (auto& h: e) { @@ -302,15 +143,12 @@ std::string eval_description(const char* name, const std::vector<std::any>& args }; const auto nargs = args.size(); - std::string msg = - util::pprintf("'{}' with {} argument{}", - name, nargs, - nargs==0?"s": nargs==1u?":": "s:"); + std::string msg = concat("'", name, "' with ", nargs, "argument", nargs!=1u?"s:" : ":"); if (nargs) { msg += " ("; bool first = true; for (auto& a: args) { - msg += util::pprintf("{}{}", first?"":" ", type_string(a.type())); + msg += concat(first?"":" ", type_string(a.type())); first = false; } msg += ")"; @@ -318,9 +156,6 @@ std::string eval_description(const char* name, const std::vector<std::any>& args return msg; } -label_parse_error parse_error(std::string const& msg, src_location loc) { - return {util::pprintf("error in label description at {}: {}.", loc, msg)}; -} // Evaluate an s expression. // On success the result is wrapped in std::any, where the result is one of: // int : an integer atom @@ -333,29 +168,9 @@ label_parse_error parse_error(std::string const& msg, src_location loc) { // a label_error_state with an error string and location. // // If there was an unexpected/fatal error, an exception will be thrown. -parse_hopefully<std::any> eval(const s_expr& e) { +parse_label_hopefully<std::any> eval(const s_expr& e) { if (e.is_atom()) { - auto& t = e.atom(); - switch (t.kind) { - case tok::integer: - return {std::stoi(t.spelling)}; - case tok::real: - return {std::stod(t.spelling)}; - case tok::nil: - return {nil_tag()}; - case tok::string: - return std::any{std::string(t.spelling)}; - // An arbitrary symbol in a region/locset expression is an error, and is - // often a result of not quoting a label correctly. - case tok::symbol: - return util::unexpected(parse_error( - util::pprintf("Unexpected symbol '{}' in a region or locset definition. If '{}' is a label, it must be quoted {}{}{}", e, e, '"', e, '"'), - location(e))); - case tok::error: - return util::unexpected(parse_error(e.atom().spelling, location(e))); - default: - return util::unexpected(parse_error(util::pprintf("Unexpected term '{}' in a region or locset definition", e), location(e))); - } + return eval_atom<label_parse_error>(e); } if (e.head().is_atom()) { // This must be a function evaluation, where head is the function name, and @@ -380,28 +195,29 @@ parse_hopefully<std::any> eval(const s_expr& e) { // Unable to find a match: try to return a helpful error message. const auto nc = std::distance(matches.first, matches.second); - auto msg = util::pprintf("No matches for {}", eval_description(name.c_str(), *args)); - msg += util::pprintf("\n There are {} potential candiates{}", nc, nc?":":"."); + auto msg = concat("No matches for ", eval_description(name.c_str(), *args), "\n There are ", nc, " potential candidates", nc?":":"."); int count = 0; for (auto i=matches.first; i!=matches.second; ++i) { - msg += util::pprintf("\n Candidate {} {}", ++count, i->second.message); + msg += concat("\n Candidate ", ++count, " ", i->second.message); } - return util::unexpected(parse_error(msg, location(e))); + return util::unexpected(label_parse_error(msg, location(e))); } - return util::unexpected(parse_error( - util::pprintf("'{}' is not either integer, real expression of the form (op <args>)", e), - location(e))); + return util::unexpected(label_parse_error( + concat("'", e, "' is not either integer, real expression of the form (op <args>)"), + location(e))); } -parse_hopefully<std::any> parse_label_expression(const std::string& e) { +} // namespace + +parse_label_hopefully<std::any> parse_label_expression(const std::string& e) { return eval(parse_s_expr(e)); } -parse_hopefully<std::any> parse_label_expression(const s_expr& s) { +parse_label_hopefully<std::any> parse_label_expression(const s_expr& s) { return eval(s); } -parse_hopefully<arb::region> parse_region_expression(const std::string& s) { +parse_label_hopefully<arb::region> parse_region_expression(const std::string& s) { if (auto e = eval(parse_s_expr(s))) { if (e->type() == typeid(region)) { return {std::move(std::any_cast<region&>(*e))}; @@ -411,14 +227,14 @@ parse_hopefully<arb::region> parse_region_expression(const std::string& s) { } return util::unexpected( label_parse_error( - util::pprintf("Invalid region description: '{}' is neither a valid region expression or region label string.", s))); + concat("Invalid region description: '", s ,"' is neither a valid region expression or region label string."))); } else { return util::unexpected(label_parse_error(std::string()+e.error().what())); } } -parse_hopefully<arb::locset> parse_locset_expression(const std::string& s) { +parse_label_hopefully<arb::locset> parse_locset_expression(const std::string& s) { if (auto e = eval(parse_s_expr(s))) { if (e->type() == typeid(locset)) { return {std::move(std::any_cast<locset&>(*e))}; @@ -427,13 +243,12 @@ parse_hopefully<arb::locset> parse_locset_expression(const std::string& s) { return {ls::named(std::move(std::any_cast<std::string&>(*e)))}; } return util::unexpected( - label_parse_error( - util::pprintf("Invalid locset description: '{}' is neither a valid locset expression or locset label string.", s))); + label_parse_error( + concat("Invalid region description: '", s ,"' is neither a valid locset expression or locset label string."))); } else { return util::unexpected(label_parse_error(std::string()+e.error().what())); } } -} // namespace arb - +} // namespace arborio diff --git a/arborio/parse_helpers.hpp b/arborio/parse_helpers.hpp new file mode 100644 index 0000000000000000000000000000000000000000..655a65df6a9db497dc1479655bfcf0a1064f665a --- /dev/null +++ b/arborio/parse_helpers.hpp @@ -0,0 +1,271 @@ +#pragma once + +#include <any> +#include <string> +#include <sstream> + +#include <arbor/assert.hpp> +#include <arbor/arbexcept.hpp> +#include <arbor/util/expected.hpp> +#include <arbor/morph/locset.hpp> +#include <arbor/morph/region.hpp> + +namespace arborio { +using namespace arb; + +struct nil_tag {}; + +// Check typeinfo against expected types +template <typename T> +bool match(const std::type_info& info) { return info == typeid(T); } +template <> inline +bool match<double>(const std::type_info& info) { return info == typeid(double) || info == typeid(int); } + +// Convert a value wrapped in a std::any to target type. +template <typename T> +T eval_cast(std::any arg) { + return std::move(std::any_cast<T&>(arg)); +} +template <> inline +double eval_cast<double>(std::any arg) { + if (arg.type()==typeid(int)) return std::any_cast<int>(arg); + return std::any_cast<double>(arg); +} + +template <> inline +arb::region eval_cast<arb::region>(std::any arg) { + if (arg.type()==typeid(arb::region)) return std::any_cast<arb::region>(arg); + return arb::reg::nil(); +} + +template <> inline +arb::locset eval_cast<arb::locset>(std::any arg) { + if (arg.type()==typeid(arb::locset)) return std::any_cast<arb::locset>(arg); + return arb::ls::nil(); +} + +// Test whether a list of arguments passed as a std::vector<std::any> can be converted +// to the types in Args. +// +// For example, the following would return true: +// +// call_match<int, int, string>(vector<any(4), any(12), any(string("hello"))>) +template <typename... Args> +struct call_match { + template <std::size_t I, typename T, typename Q, typename... Rest> + bool match_args_impl(const std::vector<std::any>& args) const { + return match<T>(args[I].type()) && match_args_impl<I+1, Q, Rest...>(args); + } + + template <std::size_t I, typename T> + bool match_args_impl(const std::vector<std::any>& args) const { + return match<T>(args[I].type()); + } + + template <std::size_t I> + bool match_args_impl(const std::vector<std::any>& args) const { + return true; + } + + bool operator()(const std::vector<std::any>& args) const { + const auto nargs_in = args.size(); + const auto nargs_ex = sizeof...(Args); + return nargs_in==nargs_ex? match_args_impl<0, Args...>(args): false; + } +}; + +template <typename T> +struct fold_eval { + using fold_fn = std::function<T(T, T)>; + fold_fn f; + + using anyvec = std::vector<std::any>; + using iterator = anyvec::iterator; + + fold_eval(fold_fn f): f(std::move(f)) {} + + T fold_impl(iterator left, iterator right) { + if (std::distance(left,right)==1u) { + return eval_cast<T>(std::move(*left)); + } + return f(eval_cast<T>(std::move(*left)), fold_impl(left+1, right)); + } + + std::any operator()(anyvec args) { + return fold_impl(args.begin(), args.end()); + } +}; + +template <typename T> +struct fold_match { + using anyvec = std::vector<std::any>; + bool operator()(const anyvec& args) const { + if (args.size()<2u) return false; + for (auto& a: args) { + if (!match<T>(a.type())) return false; + } + return true; + } +}; + +// Evaluator: member of make_call, make_arg_vec_call, make_mech_call, make_branch_call, make_unordered_call +struct evaluator { + using any_vec = std::vector<std::any>; + using eval_fn = std::function<std::any(any_vec)>; + using args_fn = std::function<bool(const any_vec&)>; + + eval_fn eval; + args_fn match_args; + const char* message; + + evaluator(eval_fn f, args_fn a, const char* m): + eval(std::move(f)), + match_args(std::move(a)), + message(m) + {} +}; + +// Evaluate a call to a function where the arguments are provided as a std::vector<std::any>. +// The arguments are expanded and converted to the correct types, as specified by Args. +template <typename... Args> +struct call_eval { + using ftype = std::function<std::any(Args...)>; + ftype f; + call_eval(ftype f): f(std::move(f)) {} + + template<std::size_t... I> + std::any expand_args_then_eval(const std::vector<std::any>& args, std::index_sequence<I...>) { + return f(eval_cast<Args>(std::move(args[I]))...); + } + + std::any operator()(const std::vector<std::any>& args) { + return expand_args_then_eval(std::move(args), std::make_index_sequence<sizeof...(Args)>()); + } +}; + +// Wrap call_match and call_eval in an evaluator +template <typename... Args> +struct make_call { + evaluator state; + + template <typename F> + make_call(F&& f, const char* msg="call"): + state(call_eval<Args...>(std::forward<F>(f)), call_match<Args...>(), msg) + {} + + operator evaluator() const { + return state; + } +}; + +template <typename T> +struct make_fold { + evaluator state; + + template <typename F> + make_fold(F&& f, const char* msg="fold"): + state(fold_eval<T>(std::forward<F>(f)), fold_match<T>(), msg) + {} + + operator evaluator() const { + return state; + } +}; + +// Test whether a list of arguments passed as a std::vector<std::any> can be converted +// to a std::vector<std::variant<Args...>>. +// +// For example, the following would return true: +// +// call_match<int, string>(vector<any(4), any(12), any(string("hello"))>) +template <typename... Args> +struct arg_vec_match { + template <typename T, typename Q, typename... Rest> + bool match_args_impl(const std::any& arg) const { + return match<T>(arg.type()) || match_args_impl<Q, Rest...>(arg); + } + + template <typename T> + bool match_args_impl(const std::any& arg) const { + return match<T>(arg.type()); + } + + bool operator()(const std::vector<std::any>& args) const { + for (const auto& a: args) { + if (!match_args_impl<Args...>(a)) return false; + } + return true; + } +}; + +// Convert a value wrapped in a std::any to an optional std::variant type +template <typename T, std::size_t I=0> +std::optional<T> eval_cast_variant(const std::any& a) { + if constexpr (I<std::variant_size_v<T>) { + using var_type = std::variant_alternative_t<I, T>; + return match<var_type>(a.type())? eval_cast<var_type>(a): eval_cast_variant<T, I+1>(a); + } + return std::nullopt; +} + +// Evaluate a call to a function where the arguments are provided as a std::vector<std::any>. +// The arguments are converted to std::variant<Args...> and passed to the function as a std::vector. +template <typename... Args> +struct arg_vec_eval { + using ftype = std::function<std::any(std::vector<std::variant<Args...>>)>; + ftype f; + arg_vec_eval(ftype f): f(std::move(f)) {} + + std::any operator()(const std::vector<std::any>& args) { + std::vector<std::variant<Args...>> vars; + for (const auto& a: args) { + vars.push_back(eval_cast_variant<std::variant<Args...>>(a).value()); + } + return f(vars); + } +}; + +// Wrap arg_vec_match and arg_vec_eval in an evaluator +template <typename... Args> +struct make_arg_vec_call { + evaluator state; + + template <typename F> + make_arg_vec_call(F&& f, const char* msg="argument vector"): + state(arg_vec_eval<Args...>(std::forward<F>(f)), arg_vec_match<Args...>(), msg) + {} + operator evaluator() const { + return state; + } +}; + +template<typename... Ts> +std::string concat(Ts... ts) { + std::stringstream ss; + (ss << ... << ts); + return ss.str(); +} + +// try to parse an atom +template<typename E> +util::expected<std::any, E> eval_atom(const s_expr& e) { + arb_assert(e.is_atom()); + auto& t = e.atom(); + switch (t.kind) { + case tok::integer: + return {std::stoi(t.spelling)}; + case tok::real: + return {std::stod(t.spelling)}; + case tok::nil: + return {nil_tag()}; + case tok::string: + return {std::string(t.spelling)}; + case tok::symbol: + return util::unexpected(E(concat("Unexpected symbol '", e, "' in definition."), location(e))); + case tok::error: + return util::unexpected(E(e.atom().spelling, location(e))); + default: + return util::unexpected(E(concat("Unexpected term '", e, "' in definition"), location(e))); + } +} +} diff --git a/doc/concepts/morphology.rst b/doc/concepts/morphology.rst index 055978e6d94233c378d95e222bb842f8af35c581..13ffcd4bc1134930682aabb69b4d1ae1b679e0fc 100644 --- a/doc/concepts/morphology.rst +++ b/doc/concepts/morphology.rst @@ -693,6 +693,24 @@ and *B*, while *A*Â |Â *B* is a policy which gives all the boundary points from The domain of *A*Â +Â *B* and *A*Â |Â *B* is the union of the domains of *A* and *B*. +Reading CV policies from strings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +CV policies can also be converted to and from strings, using an S-Expression-based +DSL. Constructors are + +* ``(single <optional:region>)`` +* ``(max-extent <double> <optional:region> <optional:flags>)`` +* ``(fixed-per-branch <int> <optional:region> <optional:flags>)`` +* ``(explicit <locset> <optional:region>)`` + +with the obvious correspondences. The composition operators are + +* ``(join <cv-policy> <cv-policy> ...)`` equivalent to ``+`` +* ``(replace <cv-policy> <cv-policy> ...)`` equivalent to ``|`` + +and take arbitrary many policies. + API --- diff --git a/example/dryrun/CMakeLists.txt b/example/dryrun/CMakeLists.txt index 4cd7a797ad7ddf8201b8fa46081342f4d5719fef..e36fe972b931cfd86ae9f62e577d5c1b6cb029ec 100644 --- a/example/dryrun/CMakeLists.txt +++ b/example/dryrun/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable(dryrun EXCLUDE_FROM_ALL dryrun.cpp) add_dependencies(examples dryrun) -target_link_libraries(dryrun PRIVATE arbor arborenv arbor-sup ${json_library_name}) +target_link_libraries(dryrun PRIVATE arbor arborio arborenv arbor-sup ${json_library_name}) diff --git a/example/dryrun/branch_cell.hpp b/example/dryrun/branch_cell.hpp index 77f71c1417f58fd5616a496a148f93597027807d..0cae8cf07f0580c7c15d21569b2e262ff178d172 100644 --- a/example/dryrun/branch_cell.hpp +++ b/example/dryrun/branch_cell.hpp @@ -5,16 +5,17 @@ #include <nlohmann/json.hpp> +#include <arborio/label_parse.hpp> + #include <arbor/cable_cell.hpp> #include <arbor/cable_cell_param.hpp> #include <arbor/common_types.hpp> #include <arbor/morph/segment_tree.hpp> -#include <arbor/string_literals.hpp> #include <string> #include <sup/json_params.hpp> -using namespace arb::literals; +using namespace arborio::literals; // Parameters used to generate the random cell morphologies. struct cell_parameters { diff --git a/example/dryrun/dryrun.cpp b/example/dryrun/dryrun.cpp index bdfa32e7058baf3e0030651ed8a198ab35ff7bbf..81773a97c143f771cb3ee30a428530cbceca35da 100644 --- a/example/dryrun/dryrun.cpp +++ b/example/dryrun/dryrun.cpp @@ -11,6 +11,8 @@ #include <nlohmann/json.hpp> +#include <arborio/label_parse.hpp> + #include <arbor/common_types.hpp> #include <arbor/context.hpp> #include <arbor/cable_cell.hpp> @@ -35,6 +37,8 @@ #include <arborenv/with_mpi.hpp> #endif +using namespace arborio::literals; + struct run_params { std::string name = "default"; bool dry_run = false; diff --git a/example/gap_junctions/CMakeLists.txt b/example/gap_junctions/CMakeLists.txt index 9aee5fbe3528a018c838e280c4059bb5d34fbf99..3e13f79c97d361d9e0b6ddde49768cbf6aef768f 100644 --- a/example/gap_junctions/CMakeLists.txt +++ b/example/gap_junctions/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable(gap_junctions EXCLUDE_FROM_ALL gap_junctions.cpp) add_dependencies(examples gap_junctions) -target_link_libraries(gap_junctions PRIVATE arbor arborenv arbor-sup ${json_library_name}) +target_link_libraries(gap_junctions PRIVATE arbor arborio arborenv arbor-sup ${json_library_name}) diff --git a/example/gap_junctions/gap_junctions.cpp b/example/gap_junctions/gap_junctions.cpp index 97206b037c9974c8ed340b141d05f8791b31b766..08a67b5c79d16179064cd6c8ff6920c1ec7b7166 100644 --- a/example/gap_junctions/gap_junctions.cpp +++ b/example/gap_junctions/gap_junctions.cpp @@ -10,6 +10,8 @@ #include <nlohmann/json.hpp> +#include <arborio/label_parse.hpp> + #include <arbor/assert_macro.hpp> #include <arbor/cable_cell.hpp> #include <arbor/common_types.hpp> @@ -30,6 +32,8 @@ #include <sup/json_meter.hpp> #include <sup/json_params.hpp> +using namespace arborio::literals; + #ifdef ARB_MPI_ENABLED #include <mpi.h> #include <arborenv/with_mpi.hpp> @@ -299,10 +303,10 @@ arb::cable_cell gj_cell(cell_gid_type gid, unsigned ncell, double stim_duration) pas["g"] = 1.0/12000.0; // Paint density channels on all parts of the cell - decor.paint("(all)", nax); - decor.paint("(all)", kdrmt); - decor.paint("(all)", kamt); - decor.paint("(all)", pas); + decor.paint("(all)"_reg, nax); + decor.paint("(all)"_reg, kdrmt); + decor.paint("(all)"_reg, kamt); + decor.paint("(all)"_reg, pas); // Add a spike detector to the soma. decor.place(arb::mlocation{0,0}, arb::threshold_detector{10}, "detector"); diff --git a/example/generators/CMakeLists.txt b/example/generators/CMakeLists.txt index 2cd0c230ce747446e280039c71c38e5fe86bca84..a7422836ea5c53d122be653a9d559e392198006f 100644 --- a/example/generators/CMakeLists.txt +++ b/example/generators/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable(generators EXCLUDE_FROM_ALL generators.cpp) add_dependencies(examples generators) -target_link_libraries(generators PRIVATE arbor arbor-sup ${json_library_name}) +target_link_libraries(generators PRIVATE arbor arborio arbor-sup ${json_library_name}) diff --git a/example/generators/generators.cpp b/example/generators/generators.cpp index 96db4f046836b962574309a1743299f25f15308c..7fe3ea440d71fa17ac31f975c3a439396458aaa0 100644 --- a/example/generators/generators.cpp +++ b/example/generators/generators.cpp @@ -14,6 +14,8 @@ #include <nlohmann/json.hpp> +#include <arborio/label_parse.hpp> + #include <arbor/context.hpp> #include <arbor/common_types.hpp> #include <arbor/domain_decomposition.hpp> @@ -31,6 +33,8 @@ using arb::cell_member_type; using arb::cell_kind; using arb::time_type; +using namespace arborio::literals; + // Writes voltage trace as a json file. void write_trace_json(const arb::trace_data<double>& trace); @@ -56,7 +60,7 @@ public: labels.set("soma", arb::reg::tagged(1)); arb::decor decor; - decor.paint("\"soma\"", "pas"); + decor.paint("soma"_lab, "pas"); // Add one synapse at the soma. // This synapse will be the target for all events, from both diff --git a/example/ring/CMakeLists.txt b/example/ring/CMakeLists.txt index 953ca291846dfe841171f5d7ccf3717c1c769f0b..e72d6bdfebd4a8f35fcd77b41a56bbaeae744d39 100644 --- a/example/ring/CMakeLists.txt +++ b/example/ring/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable(ring EXCLUDE_FROM_ALL ring.cpp) add_dependencies(examples ring) -target_link_libraries(ring PRIVATE arbor arborenv arbor-sup ${json_library_name}) +target_link_libraries(ring PRIVATE arbor arborio arborenv arbor-sup ${json_library_name}) diff --git a/example/ring/branch_cell.hpp b/example/ring/branch_cell.hpp index 1a243a4d1ca4f00af69c25e66da04c81492eb1e8..789684adbd323fdce5a2a6c67d25bb6c52fe86ca 100644 --- a/example/ring/branch_cell.hpp +++ b/example/ring/branch_cell.hpp @@ -5,16 +5,17 @@ #include <nlohmann/json.hpp> +#include <arborio/label_parse.hpp> + #include <arbor/cable_cell.hpp> #include <arbor/cable_cell_param.hpp> #include <arbor/common_types.hpp> #include <arbor/morph/segment_tree.hpp> -#include <arbor/string_literals.hpp> #include <string> #include <sup/json_params.hpp> -using namespace arb::literals; +using namespace arborio::literals; // Parameters used to generate the random cell morphologies. struct cell_parameters { diff --git a/example/ring/ring.cpp b/example/ring/ring.cpp index 3e34d80b01086f188a5e9e0c6321b5127eaedebc..5e9c187e9e87a16225296aaed4069af573589c51 100644 --- a/example/ring/ring.cpp +++ b/example/ring/ring.cpp @@ -11,6 +11,8 @@ #include <nlohmann/json.hpp> +#include <arborio/label_parse.hpp> + #include <arbor/assert_macro.hpp> #include <arbor/common_types.hpp> #include <arbor/cable_cell.hpp> diff --git a/example/single/single.cpp b/example/single/single.cpp index ea6cf74b9a9213abd77e793513f968f944e210e3..b7d3cff22789c673b295f81a55fa463463be9b25 100644 --- a/example/single/single.cpp +++ b/example/single/single.cpp @@ -6,6 +6,8 @@ #include <string> #include <vector> +#include <arborio/label_parse.hpp> + #include <arbor/load_balance.hpp> #include <arbor/cable_cell.hpp> #include <arbor/morph/morphology.hpp> @@ -17,6 +19,8 @@ #include <tinyopt/tinyopt.h> +using namespace arborio::literals; + struct options { std::string swc_file; double t_end = 20; @@ -64,8 +68,8 @@ struct single_recipe: public arb::recipe { arb::decor decor; // Add HH mechanism to soma, passive channels to dendrites. - decor.paint("\"soma\"", "hh"); - decor.paint("\"dend\"", "pas"); + decor.paint("soma"_lab, "hh"); + decor.paint("dend"_lab, "pas"); // Add synapse to last branch. diff --git a/python/cable_probes.cpp b/python/cable_probes.cpp index a8543a07e4ca570bc02792b432a0ebc88ab340b6..4ba583e322fd236b7f4297dc172875c1bce1b98c 100644 --- a/python/cable_probes.cpp +++ b/python/cable_probes.cpp @@ -5,6 +5,8 @@ #include <pybind11/pytypes.h> #include <pybind11/stl.h> +#include <arborio/label_parse.hpp> + #include <arbor/cable_cell.hpp> #include <arbor/morph/locset.hpp> #include <arbor/recipe.hpp> @@ -134,7 +136,7 @@ void register_probe_meta_maps(pyarb_global_ptr g) { // (Probe tag value is implicitly left at zero.) arb::probe_info cable_probe_membrane_voltage(const char* where) { - return arb::cable_probe_membrane_voltage{arb::locset(where)}; + return arb::cable_probe_membrane_voltage{arborio::parse_locset_expression(where).unwrap()}; } arb::probe_info cable_probe_membrane_voltage_cell() { @@ -142,11 +144,11 @@ arb::probe_info cable_probe_membrane_voltage_cell() { } arb::probe_info cable_probe_axial_current(const char* where) { - return arb::cable_probe_axial_current{arb::locset(where)}; + return arb::cable_probe_axial_current{arborio::parse_locset_expression(where).unwrap()}; } arb::probe_info cable_probe_total_ion_current_density(const char* where) { - return arb::cable_probe_total_ion_current_density{arb::locset(where)}; + return arb::cable_probe_total_ion_current_density{arborio::parse_locset_expression(where).unwrap()}; } arb::probe_info cable_probe_total_ion_current_cell() { @@ -162,7 +164,7 @@ arb::probe_info cable_probe_stimulus_current_cell() { } arb::probe_info cable_probe_density_state(const char* where, const char* mechanism, const char* state) { - return arb::cable_probe_density_state{arb::locset(where), mechanism, state}; + return arb::cable_probe_density_state{arborio::parse_locset_expression(where).unwrap(), mechanism, state}; }; arb::probe_info cable_probe_density_state_cell(const char* mechanism, const char* state) { @@ -178,7 +180,7 @@ arb::probe_info cable_probe_point_state_cell(const char* mechanism, const char* } arb::probe_info cable_probe_ion_current_density(const char* where, const char* ion) { - return arb::cable_probe_ion_current_density{arb::locset(where), ion}; + return arb::cable_probe_ion_current_density{arborio::parse_locset_expression(where).unwrap(), ion}; } arb::probe_info cable_probe_ion_current_cell(const char* ion) { @@ -186,7 +188,7 @@ arb::probe_info cable_probe_ion_current_cell(const char* ion) { } arb::probe_info cable_probe_ion_int_concentration(const char* where, const char* ion) { - return arb::cable_probe_ion_int_concentration{arb::locset(where), ion}; + return arb::cable_probe_ion_int_concentration{arborio::parse_locset_expression(where).unwrap(), ion}; } arb::probe_info cable_probe_ion_int_concentration_cell(const char* ion) { @@ -194,7 +196,7 @@ arb::probe_info cable_probe_ion_int_concentration_cell(const char* ion) { } arb::probe_info cable_probe_ion_ext_concentration(const char* where, const char* ion) { - return arb::cable_probe_ion_ext_concentration{arb::locset(where), ion}; + return arb::cable_probe_ion_ext_concentration{arborio::parse_locset_expression(where).unwrap(), ion}; } arb::probe_info cable_probe_ion_ext_concentration_cell(const char* ion) { diff --git a/python/cells.cpp b/python/cells.cpp index d591032bbf5ac7205130e2f981d4f76f74865cc8..9d745cde1246973bf318f1bf885d6a58584a0007 100644 --- a/python/cells.cpp +++ b/python/cells.cpp @@ -11,11 +11,14 @@ #include <pybind11/pybind11.h> #include <pybind11/stl.h> +#include <arborio/cv_policy_parse.hpp> +#include <arborio/label_parse.hpp> + #include <arbor/benchmark_cell.hpp> #include <arbor/cable_cell.hpp> #include <arbor/lif_cell.hpp> +#include <arbor/cv_policy.hpp> #include <arbor/morph/label_dict.hpp> -#include <arbor/morph/label_parse.hpp> #include <arbor/morph/locset.hpp> #include <arbor/morph/region.hpp> #include <arbor/morph/segment_tree.hpp> @@ -75,23 +78,23 @@ std::string to_string(const arb::cable_cell_global_properties& props) { // arb::cv_policy make_cv_policy_single(const std::string& reg) { - return arb::cv_policy_single(reg); + return arb::cv_policy_single(arborio::parse_region_expression(reg).unwrap()); } arb::cv_policy make_cv_policy_explicit(const std::string& locset, const std::string& reg) { - return arb::cv_policy_explicit(locset, reg); + return arb::cv_policy_explicit(arborio::parse_locset_expression(locset).unwrap(), arborio::parse_region_expression(reg).unwrap()); } arb::cv_policy make_cv_policy_every_segment(const std::string& reg) { - return arb::cv_policy_every_segment(reg); + return arb::cv_policy_every_segment(arborio::parse_region_expression(reg).unwrap()); } arb::cv_policy make_cv_policy_fixed_per_branch(unsigned cv_per_branch, const std::string& reg) { - return arb::cv_policy_fixed_per_branch(cv_per_branch, reg); + return arb::cv_policy_fixed_per_branch(cv_per_branch, arborio::parse_region_expression(reg).unwrap()); } arb::cv_policy make_cv_policy_max_extent(double cv_length, const std::string& reg) { - return arb::cv_policy_max_extent(cv_length, reg); + return arb::cv_policy_max_extent(cv_length, arborio::parse_region_expression(reg).unwrap()); } // Helper for finding a mechanism description in a Python object. @@ -264,13 +267,22 @@ void register_cells(pybind11::module& m) { pybind11::class_<arb::cv_policy> cv_policy(m, "cv_policy", "Describes the rules used to discretize (compartmentalise) a cable cell morphology."); cv_policy + .def(pybind11::init([](const std::string& s) { return arborio::parse_cv_policy_expression(s).unwrap(); })) .def_property_readonly("domain", [](const arb::cv_policy& p) {return util::pprintf("{}", p.domain());}, "The domain on which the policy is applied.") .def(pybind11::self + pybind11::self) .def(pybind11::self | pybind11::self) - .def("__repr__", [](const arb::cv_policy& p) {return "(cv-policy)";}) - .def("__str__", [](const arb::cv_policy& p) {return "(cv-policy)";}); + .def("__repr__", [](const arb::cv_policy& p) { + std::stringstream ss; + ss << p; + return ss.str(); + }) + .def("__str__", [](const arb::cv_policy& p) { + std::stringstream ss; + ss << p; + return ss.str(); + }); m.def("cv_policy_explicit", &make_cv_policy_explicit, @@ -488,13 +500,13 @@ void register_cells(pybind11::module& m) { // Paint mechanisms. .def("paint", [](arb::decor& dec, const char* region, const arb::mechanism_desc& d) { - dec.paint(region, d); + dec.paint(arborio::parse_region_expression(region).unwrap(), d); }, "region"_a, "mechanism"_a, "Associate a mechanism with a region.") .def("paint", [](arb::decor& dec, const char* region, const char* mech_name) { - dec.paint(region, arb::mechanism_desc(mech_name)); + dec.paint(arborio::parse_region_expression(region).unwrap(), arb::mechanism_desc(mech_name)); }, "region"_a, "mechanism"_a, "Associate a mechanism with a region.") @@ -505,10 +517,11 @@ void register_cells(pybind11::module& m) { optional<double> Vm, optional<double> cm, optional<double> rL, optional<double> tempK) { - if (Vm) dec.paint(region, arb::init_membrane_potential{*Vm}); - if (cm) dec.paint(region, arb::membrane_capacitance{*cm}); - if (rL) dec.paint(region, arb::axial_resistivity{*rL}); - if (tempK) dec.paint(region, arb::temperature_K{*tempK}); + auto r = arborio::parse_region_expression(region).unwrap(); + if (Vm) dec.paint(r, arb::init_membrane_potential{*Vm}); + if (cm) dec.paint(r, arb::membrane_capacitance{*cm}); + if (rL) dec.paint(r, arb::axial_resistivity{*rL}); + if (tempK) dec.paint(r, arb::temperature_K{*tempK}); }, pybind11::arg_v("region", "the region label or description."), pybind11::arg_v("Vm", pybind11::none(), "initial membrane voltage [mV]."), @@ -520,9 +533,10 @@ void register_cells(pybind11::module& m) { .def("paint", [](arb::decor& dec, const char* region, const char* name, optional<double> int_con, optional<double> ext_con, optional<double> rev_pot) { - if (int_con) dec.paint(region, arb::init_int_concentration{name, *int_con}); - if (ext_con) dec.paint(region, arb::init_ext_concentration{name, *ext_con}); - if (rev_pot) dec.paint(region, arb::init_reversal_potential{name, *rev_pot}); + auto r = arborio::parse_region_expression(region).unwrap(); + if (int_con) dec.paint(r, arb::init_int_concentration{name, *int_con}); + if (ext_con) dec.paint(r, arb::init_ext_concentration{name, *ext_con}); + if (rev_pot) dec.paint(r, arb::init_reversal_potential{name, *rev_pot}); }, "region"_a, "ion_name"_a, pybind11::arg_v("int_con", pybind11::none(), "Initial internal concentration [mM]"), @@ -532,13 +546,13 @@ void register_cells(pybind11::module& m) { // Place synapses .def("place", [](arb::decor& dec, const char* locset, const arb::mechanism_desc& d, const char* label_name) { - return dec.place(locset, d, label_name); }, + return dec.place(arborio::parse_locset_expression(locset).unwrap(), d, label_name); }, "locations"_a, "mechanism"_a, "label"_a, "Place one instance of the synapse described by 'mechanism' on each location in 'locations'. " "The group of synapses has the label 'label', used for forming connections between cells.") .def("place", [](arb::decor& dec, const char* locset, const char* mech_name, const char* label_name) { - return dec.place(locset, mech_name, label_name); + return dec.place(arborio::parse_locset_expression(locset).unwrap(), mech_name, label_name); }, "locations"_a, "mechanism"_a, "label"_a, "Place one instance of the synapse described by 'mechanism' on each location in 'locations'." @@ -546,7 +560,7 @@ void register_cells(pybind11::module& m) { // Place gap junctions. .def("place", [](arb::decor& dec, const char* locset, const arb::gap_junction_site& site, const char* label_name) { - return dec.place(locset, site, label_name); + return dec.place(arborio::parse_locset_expression(locset).unwrap(), site, label_name); }, "locations"_a, "gapjunction"_a, "label"_a, "Place one gap junction site labeled 'label' on each location in 'locations'." @@ -554,7 +568,7 @@ void register_cells(pybind11::module& m) { // Place current clamp stimulus. .def("place", [](arb::decor& dec, const char* locset, const arb::i_clamp& stim, const char* label_name) { - return dec.place(locset, stim, label_name); + return dec.place(arborio::parse_locset_expression(locset).unwrap(), stim, label_name); }, "locations"_a, "iclamp"_a, "label"_a, "Add a current stimulus at each location in locations." @@ -562,7 +576,7 @@ void register_cells(pybind11::module& m) { // Place spike detector. .def("place", [](arb::decor& dec, const char* locset, const arb::threshold_detector& d, const char* label_name) { - return dec.place(locset, d, label_name); + return dec.place(arborio::parse_locset_expression(locset).unwrap(), d, label_name); }, "locations"_a, "detector"_a, "label"_a, "Add a voltage spike detector at each location in locations." @@ -571,7 +585,6 @@ void register_cells(pybind11::module& m) { [](arb::decor& dec, const arb::cv_policy& p) { dec.set_default(p); }, pybind11::arg_v("policy", "A cv_policy used to discretise the cell into compartments for simulation")); - // arb::cable_cell pybind11::class_<arb::cable_cell> cable_cell(m, "cable_cell", @@ -593,11 +606,11 @@ void register_cells(pybind11::module& m) { "The number of unbranched cable sections in the morphology.") // Get locations associated with a locset label. .def("locations", - [](arb::cable_cell& c, const char* label) {return c.concrete_locset(label);}, + [](arb::cable_cell& c, const char* label) {return c.concrete_locset(arb::ls::named(label));}, "label"_a, "The locations of the cell morphology for a locset label.") // Get cables associated with a region label. .def("cables", - [](arb::cable_cell& c, const char* label) {return c.concrete_region(label).cables();}, + [](arb::cable_cell& c, const char* label) {return c.concrete_region(arb::reg::named(label)).cables();}, "label"_a, "The cable segments of the cell morphology for a region label.") // Stringification .def("__repr__", [](const arb::cable_cell&){return "<arbor.cable_cell>";}) diff --git a/python/example/single_cell_swc.py b/python/example/single_cell_swc.py index f229b623a503d3072ea5606a79a6a0d808f528d6..525d90e890c1c992253e1668ed97a117332e88d7 100755 --- a/python/example/single_cell_swc.py +++ b/python/example/single_cell_swc.py @@ -66,7 +66,7 @@ decor.discretization(policy) # Combine morphology with region and locset definitions to make a cable cell. cell = arbor.cable_cell(morpho, labels, decor) -print(cell.locations('"axon_end"')) +print(cell.locations('axon_end')) # Make single cell model. m = arbor.single_cell_model(cell) diff --git a/python/morphology.cpp b/python/morphology.cpp index 7493fc886a1e3342f77ab27560f8b02a19084ce5..5a99baadd449c64c08214888ddd0049082321b53 100644 --- a/python/morphology.cpp +++ b/python/morphology.cpp @@ -12,6 +12,7 @@ #include <arbor/morph/segment_tree.hpp> #include <arbor/version.hpp> +#include <arborio/label_parse.hpp> #include <arborio/swcio.hpp> #include <arborio/neurolucida.hpp> diff --git a/python/proxy.hpp b/python/proxy.hpp index 640db753574443439a6de3909dba343c348fa9af..8f3003f13ee7dbc6dcf767495a27bd3e08b3bffd 100644 --- a/python/proxy.hpp +++ b/python/proxy.hpp @@ -2,8 +2,9 @@ #include <any> +#include <arborio/label_parse.hpp> + #include <arbor/morph/label_dict.hpp> -#include <arbor/morph/label_parse.hpp> #include "strprintf.hpp" @@ -54,7 +55,7 @@ struct label_dict_proxy { // * the description is well-formed, but describes neither a region or locset. try{ // Evaluate the s-expression to build a region/locset. - auto result = arb::parse_label_expression(desc); + auto result = arborio::parse_label_expression(desc); if (!result) { // an error parsing / evaluating description. throw result.error(); } diff --git a/python/single_cell_model.cpp b/python/single_cell_model.cpp index 64b416898a2f4431536e6d35bb8ebbb4b74d54f2..e639067895dcc8617bea81ef0635e5ba97836c0d 100644 --- a/python/single_cell_model.cpp +++ b/python/single_cell_model.cpp @@ -7,6 +7,8 @@ #include <pybind11/pybind11.h> #include <pybind11/stl.h> +#include <arborio/label_parse.hpp> + #include <arbor/cable_cell.hpp> #include <arbor/load_balance.hpp> #include <arbor/recipe.hpp> @@ -235,7 +237,7 @@ void register_single_cell(pybind11::module& m) { "Run model from t=0 to t=tfinal ms.") .def("probe", [](single_cell_model& m, const char* what, const char* where, double frequency) { - m.probe(what, where, frequency);}, + m.probe(what, arborio::parse_locset_expression(where).unwrap(), frequency);}, "what"_a, "where"_a, "frequency"_a, "Sample a variable on the cell.\n" " what: Name of the variable to record (currently only 'voltage').\n" diff --git a/test/common_cells.cpp b/test/common_cells.cpp index 94422df0879c4a70918c3e5577061e8fb7fd69df..49db2965ff2ecff3e0517c0548b547d55dcf9ae7 100644 --- a/test/common_cells.cpp +++ b/test/common_cells.cpp @@ -1,8 +1,9 @@ -#include <arbor/string_literals.hpp> +#include <arborio/label_parse.hpp> #include "arbor/morph/morphology.hpp" #include "common_cells.hpp" namespace arb { +using namespace arborio::literals; // Generate a segment tree from a sequence of points and parent index. arb::segment_tree segments_from_points( @@ -175,7 +176,6 @@ cable_cell_description soma_cell_builder::make_cell() const { */ cable_cell_description make_cell_soma_only(bool with_stim) { - using namespace arb::literals; soma_cell_builder builder(18.8/2.0); auto c = builder.make_cell(); @@ -209,7 +209,6 @@ cable_cell_description make_cell_soma_only(bool with_stim) { */ cable_cell_description make_cell_ball_and_stick(bool with_stim) { - using namespace arb::literals; soma_cell_builder builder(12.6157/2.0); builder.add_branch(0, 200, 1.0/2, 1.0/2, 4, "dend"); @@ -246,7 +245,6 @@ cable_cell_description make_cell_ball_and_stick(bool with_stim) { */ cable_cell_description make_cell_ball_and_3stick(bool with_stim) { - using namespace arb::literals; soma_cell_builder builder(12.6157/2.0); builder.add_branch(0, 100, 0.5, 0.5, 4, "dend"); builder.add_branch(1, 100, 0.5, 0.5, 4, "dend"); diff --git a/test/unit/test_cable_cell.cpp b/test/unit/test_cable_cell.cpp index 882f40824a6fdfbc3258b40eeb054872c7496b8c..9019330c56736c830457990d7626a74075a94d57 100644 --- a/test/unit/test_cable_cell.cpp +++ b/test/unit/test_cable_cell.cpp @@ -3,10 +3,11 @@ #include <arbor/cable_cell.hpp> #include <arbor/cable_cell_param.hpp> -#include <arbor/string_literals.hpp> + +#include <arborio/label_parse.hpp> using namespace arb; -using namespace arb::literals; +using namespace arborio::literals; TEST(cable_cell, lid_ranges) { @@ -21,7 +22,7 @@ TEST(cable_cell, lid_ranges) { arb::morphology morph(tree); label_dict dict; - dict.set("term", locset("(terminal)")); + dict.set("term", "(terminal)"_ls); decor decorations; diff --git a/test/unit/test_fvm_layout.cpp b/test/unit/test_fvm_layout.cpp index ed621761bc989a88e1f020f058706a6f5b0b1cab..1f58d34e7f208cfcdb686b81672310b2f442193b 100644 --- a/test/unit/test_fvm_layout.cpp +++ b/test/unit/test_fvm_layout.cpp @@ -2,10 +2,11 @@ #include <string> #include <vector> +#include <arborio/label_parse.hpp> + #include <arbor/cable_cell.hpp> #include <arbor/math.hpp> #include <arbor/mechcat.hpp> - #include "arbor/cable_cell_param.hpp" #include "arbor/morph/morphology.hpp" #include "arbor/morph/segment_tree.hpp" @@ -22,6 +23,7 @@ using namespace std::string_literals; using namespace arb; +using namespace arborio::literals; using util::make_span; using util::count_along; @@ -54,8 +56,8 @@ namespace { builder.add_branch(0, 200, 1.0/2, 1.0/2, 4, "dend"); auto description = builder.make_cell(); - description.decorations.paint("\"soma\"", "hh"); - description.decorations.paint("\"dend\"", "pas"); + description.decorations.paint("soma"_lab, "hh"); + description.decorations.paint("dend"_lab, "pas"); description.decorations.place(builder.location({1,1}), i_clamp{5, 80, 0.3}, "clamp"); s.builders.push_back(std::move(builder)); @@ -97,8 +99,8 @@ namespace { auto b3 = b.add_branch(1, 180, 0.35, 0.35, 4, "dend"); auto desc = b.make_cell(); - desc.decorations.paint("\"soma\"", "hh"); - desc.decorations.paint("\"dend\"", "pas"); + desc.decorations.paint("soma"_lab, "hh"); + desc.decorations.paint("dend"_lab, "pas"); using ::arb::reg::branch; auto c1 = reg::cable(b1-1, b.location({b1, 0}).pos, 1); @@ -563,10 +565,10 @@ TEST(fvm_layout, density_norm_area) { hh_3["gl"] = seg3_gl; auto desc = builder.make_cell(); - desc.decorations.paint("\"soma\"", std::move(hh_0)); - desc.decorations.paint("\"reg1\"", std::move(hh_1)); - desc.decorations.paint("\"reg2\"", std::move(hh_2)); - desc.decorations.paint("\"reg3\"", std::move(hh_3)); + desc.decorations.paint("soma"_lab, std::move(hh_0)); + desc.decorations.paint("reg1"_lab, std::move(hh_1)); + desc.decorations.paint("reg2"_lab, std::move(hh_2)); + desc.decorations.paint("reg3"_lab, std::move(hh_3)); std::vector<cable_cell> cells{desc}; @@ -712,7 +714,7 @@ TEST(fvm_layout, density_norm_area_partial) { TEST(fvm_layout, valence_verify) { auto desc = soma_cell_builder(6).make_cell(); - desc.decorations.paint("\"soma\"", "test_cl_valence"); + desc.decorations.paint("soma"_lab, "test_cl_valence"); std::vector<cable_cell> cells{desc}; cable_cell_global_properties gprop; @@ -841,9 +843,9 @@ TEST(fvm_layout, revpot) { builder.add_branch(1, 200, 0.5, 0.5, 1, "dend"); builder.add_branch(1, 100, 0.5, 0.5, 1, "dend"); auto desc = builder.make_cell(); - desc.decorations.paint("\"soma\"", "read_eX/c"); - desc.decorations.paint("\"soma\"", "read_eX/a"); - desc.decorations.paint("\"dend\"", "read_eX/a"); + desc.decorations.paint("soma"_lab, "read_eX/c"); + desc.decorations.paint("soma"_lab, "read_eX/a"); + desc.decorations.paint("dend"_lab, "read_eX/a"); std::vector<cable_cell_description> descriptions{desc, desc}; diff --git a/test/unit/test_fvm_lowered.cpp b/test/unit/test_fvm_lowered.cpp index d8f04096b0a9e9e379a38a405bbb52c5c371f71e..e26e6d90435ba2267e8ec4948ba3b1c05cdf7616 100644 --- a/test/unit/test_fvm_lowered.cpp +++ b/test/unit/test_fvm_lowered.cpp @@ -5,6 +5,8 @@ #include "../gtest.h" +#include <arborio/label_parse.hpp> + #include <arbor/common_types.hpp> #include <arbor/domain_decomposition.hpp> #include <arbor/fvm_types.hpp> @@ -15,7 +17,6 @@ #include <arbor/sampling.hpp> #include <arbor/simulation.hpp> #include <arbor/schedule.hpp> -#include <arbor/string_literals.hpp> #include <arbor/util/any_ptr.hpp> #include <arborenv/concurrency.hpp> @@ -37,6 +38,7 @@ #include "../simple_recipes.hpp" using namespace std::string_literals; +using namespace arborio::literals; using backend = arb::multicore::backend; using fvm_cell = arb::fvm_lowered_cell_impl<backend>; @@ -573,7 +575,7 @@ TEST(fvm_lowered, read_valence) { soma_cell_builder builder(6); auto cell = builder.make_cell(); - cell.decorations.paint("\"soma\"", "test_ca_read_valence"); + cell.decorations.paint("soma"_lab, "test_ca_read_valence"); cable1d_recipe rec(cable_cell{cell}); rec.catalogue() = make_unit_test_catalogue(); @@ -596,7 +598,7 @@ TEST(fvm_lowered, read_valence) { // Check ion renaming. soma_cell_builder builder(6); auto cell = builder.make_cell(); - cell.decorations.paint("\"soma\"", "cr_read_valence"); + cell.decorations.paint("soma"_lab, "cr_read_valence"); cable1d_recipe rec(cable_cell{cell}); rec.catalogue() = make_unit_test_catalogue(); rec.catalogue() = make_unit_test_catalogue(); @@ -684,7 +686,6 @@ TEST(fvm_lowered, ionic_concentrations) { } TEST(fvm_lowered, ionic_currents) { - using namespace arb::literals; arb::proc_allocation resources; if (auto nt = arbenv::get_env_num_threads()) { resources.num_threads = nt; diff --git a/test/unit/test_mc_cell_group.cpp b/test/unit/test_mc_cell_group.cpp index e72a3a5360e64699ce1117b4a73aaa6ab0eb289c..035c7d6758b21dda75e779177cb6489a69f8ed14 100644 --- a/test/unit/test_mc_cell_group.cpp +++ b/test/unit/test_mc_cell_group.cpp @@ -1,7 +1,8 @@ #include "../gtest.h" #include <arbor/common_types.hpp> -#include <arbor/string_literals.hpp> + +#include <arborio/label_parse.hpp> #include "epoch.hpp" #include "fvm_lowered_cell.hpp" @@ -13,7 +14,7 @@ #include "../simple_recipes.hpp" using namespace arb; -using namespace arb::literals; +using namespace arborio::literals; namespace { execution_context context; diff --git a/test/unit/test_morph_expr.cpp b/test/unit/test_morph_expr.cpp index 2ff804d23543be61cf74057ba3505fa8bc2f9cdd..2aebcd16e1973b902ec80c96f5dacec053df86d8 100644 --- a/test/unit/test_morph_expr.cpp +++ b/test/unit/test_morph_expr.cpp @@ -2,6 +2,8 @@ #include <vector> +#include <arborio/label_parse.hpp> + #include <arbor/morph/embed_pwlin.hpp> #include <arbor/morph/locset.hpp> #include <arbor/morph/morphexcept.hpp> @@ -9,7 +11,6 @@ #include <arbor/morph/mprovider.hpp> #include <arbor/morph/primitives.hpp> #include <arbor/morph/region.hpp> -#include <arbor/string_literals.hpp> #include "util/span.hpp" #include "util/strprintf.hpp" @@ -19,7 +20,7 @@ #include "morph_pred.hpp" using namespace arb; -using namespace arb::literals; +using namespace arborio::literals; using embedding = embed_pwlin; using testing::region_eq; @@ -124,7 +125,7 @@ TEST(locset, thingify_named) { label_dict dict; dict.set("banana", banana); dict.set("cake", cake); - dict.set("topping", locset("(locset \"fruit\")")); + dict.set("topping", locset("fruit"_lab)); dict.set("fruit", sum(locset("banana"_lab), locset("topping"_lab))); EXPECT_THROW(mprovider(morphology(sm), dict), circular_definition); @@ -157,7 +158,7 @@ TEST(region, thingify_named) { dict.set("banana", banana); dict.set("cake", cake); dict.set("topping", region("fruit"_lab)); - dict.set("fruit", region("(region \"strawberry\")")); + dict.set("fruit", "(region \"strawberry\")"_reg); EXPECT_THROW(mprovider(morphology(sm), dict), unbound_name); } @@ -166,7 +167,7 @@ TEST(region, thingify_named) { dict.set("banana", banana); dict.set("cake", cake); dict.set("topping", region("fruit"_lab)); - dict.set("fruit", join(region("(region \"cake\")"), region("topping"_lab))); + dict.set("fruit", join("(region \"cake\")"_reg, region("topping"_lab))); EXPECT_THROW(mprovider(morphology(sm), dict), circular_definition); } diff --git a/test/unit/test_morph_stitch.cpp b/test/unit/test_morph_stitch.cpp index a26b35ea58ea8e5428e37a3ed21bf312bbf91af2..94d4d049ccb70165fe4a4b7f1392d350c689472c 100644 --- a/test/unit/test_morph_stitch.cpp +++ b/test/unit/test_morph_stitch.cpp @@ -8,13 +8,14 @@ #include <arbor/morph/place_pwlin.hpp> #include <arbor/morph/primitives.hpp> #include <arbor/morph/stitch.hpp> -#include <arbor/string_literals.hpp> + +#include <arborio/label_parse.hpp> #include "../test/gtest.h" #include "morph_pred.hpp" using namespace arb; -using namespace arb::literals; +using namespace arborio::literals; using testing::region_eq; TEST(morph, stitch_none_or_one) { @@ -147,8 +148,8 @@ TEST(morph, stitch_two) { ASSERT_EQ(p2, seg1.dist); EXPECT_TRUE(region_eq(p, "stitch:0"_lab, join(reg::segment(0), reg::segment(1)))); EXPECT_TRUE(region_eq(p, "stitch:1"_lab, reg::segment(2))); - EXPECT_TRUE(region_eq(p, "(segment 2)", reg::segment(2))); - EXPECT_TRUE(region_eq(p, "(region \"stitch:1\")", reg::segment(2))); + EXPECT_TRUE(region_eq(p, "(segment 2)"_reg, reg::segment(2))); + EXPECT_TRUE(region_eq(p, "(region \"stitch:1\")"_reg, reg::segment(2))); } } } diff --git a/test/unit/test_s_expr.cpp b/test/unit/test_s_expr.cpp index 92ddcb1ffaa1539ddc0b9d8b0242dadf0c3898ea..01dd22da1fee2e5a1434f90d24e2b998572ea8e8 100644 --- a/test/unit/test_s_expr.cpp +++ b/test/unit/test_s_expr.cpp @@ -5,15 +5,20 @@ #include <arbor/morph/region.hpp> #include <arbor/morph/locset.hpp> -#include <arbor/morph/label_parse.hpp> +#include <arbor/cv_policy.hpp> + #include <arbor/s_expr.hpp> +#include <arborio/cv_policy_parse.hpp> #include <arborio/cableio.hpp> +#include <arborio/label_parse.hpp> #include "parse_s_expr.hpp" #include "util/strprintf.hpp" using namespace arb; +using namespace arborio; +using namespace arborio::literals; using namespace std::string_literals; TEST(s_expr, atoms) { @@ -174,6 +179,15 @@ std::string round_trip_label(const char* in) { } } +std::string round_trip_cv(const char* in) { + if (auto x = parse_cv_policy_expression(in)) { + return util::pprintf("{}", std::any_cast<cv_policy>(*x)); + } + else { + return x.error().what(); + } +} + std::string round_trip_region(const char* in) { if (auto x = parse_region_expression(in)) { return util::pprintf("{}", std::any_cast<arb::region>(*x)); @@ -192,6 +206,44 @@ std::string round_trip_locset(const char* in) { } } + +TEST(cv_policies, round_tripping) { + auto literals = {"(every-segment (tag 42))", + "(fixed-per-branch 23 (segment 0) 1)", + "(max-extent 23.1 (segment 0) 1)", + "(single (segment 0))", + "(explicit (terminal) (segment 0))", + "(join (every-segment (tag 42)) (single (segment 0)))", + "(replace (every-segment (tag 42)) (single (segment 0)))", + }; + for (const auto& literal: literals) { + EXPECT_EQ(literal, round_trip_cv(literal)); + } +} + +TEST(cv_policies, literals) { + EXPECT_NO_THROW("(every-segment (tag 42))"_cvp); + EXPECT_NO_THROW("(fixed-per-branch 23 (segment 0) 1)"_cvp); + EXPECT_NO_THROW("(max-extent 23.1 (segment 0) 1)"_cvp); + EXPECT_NO_THROW("(single (segment 0))"_cvp); + EXPECT_NO_THROW("(explicit (terminal) (segment 0))"_cvp); + EXPECT_NO_THROW("(join (every-segment (tag 42)) (single (segment 0)))"_cvp); + EXPECT_NO_THROW("(replace (every-segment (tag 42)) (single (segment 0)))"_cvp); +} + +TEST(cv_policies, bad) { + auto check = [](const std::string& s) { + auto cv = parse_cv_policy_expression(s); + if (!cv.has_value()) throw cv.error(); + return cv.value(); + }; + + EXPECT_THROW(check("(every-segment (tag 42) 1)"), cv_policy_parse_error); // extra arg + EXPECT_THROW(check("(every-segment (terminal))"), cv_policy_parse_error); // locset instead of region + EXPECT_THROW(check("(every-segment"), cv_policy_parse_error); // missing paren + EXPECT_THROW(check("(tag 42)"), cv_policy_parse_error); // not a cv_policy +} + TEST(regloc, round_tripping) { EXPECT_EQ("(cable 3 0 1)", round_trip_label<arb::region>("(branch 3)")); EXPECT_EQ("(intersect (tag 1) (intersect (tag 2) (tag 3)))", round_trip_label<arb::region>("(intersect (tag 1) (tag 2) (tag 3))")); @@ -362,9 +414,6 @@ std::ostream& operator<<(std::ostream& o, const mechanism_desc& m) { std::ostream& operator<<(std::ostream& o, const ion_reversal_potential_method& p) { return o << "(ion-reversal-potential-method \"" << p.ion << "\" " << p.method << ')'; } -std::ostream& operator<<(std::ostream& o, const cv_policy&) { - return o; -} std::ostream& operator<<(std::ostream& o, const branch& b) { o << "(branch " << std::to_string(std::get<0>(b)) << " " << std::to_string(std::get<1>(b)); for (auto s: std::get<2>(b)) { diff --git a/test/unit/test_spikes.cpp b/test/unit/test_spikes.cpp index ce47431f7c65abae0c5c55560c82f0f1c286d58f..292c1fa3b1b96ddd653cbed991dbb51b8fc1bcc0 100644 --- a/test/unit/test_spikes.cpp +++ b/test/unit/test_spikes.cpp @@ -1,5 +1,7 @@ #include "../gtest.h" +#include <arborio/label_parse.hpp> + #include <arborenv/concurrency.hpp> #include <arborenv/gpu_env.hpp> @@ -14,6 +16,7 @@ #include <simple_recipes.hpp> using namespace arb; +using namespace arborio::literals; // This source is included in `test_spikes_gpu.cpp`, which defines // USE_BACKEND to override the default `multicore::backend` @@ -221,9 +224,9 @@ TEST(SPIKES_TEST_CLASS, threshold_watcher_interpolation) { for (unsigned i = 0; i < 8; i++) { arb::decor decor; decor.set_default(arb::cv_policy_every_segment()); - decor.place("\"mid\"", arb::threshold_detector{10}, "detector"); - decor.place("\"mid\"", arb::i_clamp::box(0.01+i*dt, duration, 0.5), "clamp"); - decor.place("\"mid\"", arb::mechanism_desc("expsyn"), "synapse"); + decor.place("mid"_lab, arb::threshold_detector{10}, "detector"); + decor.place("mid"_lab, arb::i_clamp::box(0.01+i*dt, duration, 0.5), "clamp"); + decor.place("mid"_lab, arb::mechanism_desc("expsyn"), "synapse"); arb::cable_cell cell(morpho, dict, decor); cable1d_recipe rec({cell});