Select Git revision
symdiff.hpp
Sam Yates authored and
Ben Cumming
committed
Incorporate symbolic GE code from prototype (with some simplifications) in msparse.hpp, symge.hpp and symge.cpp, together with unit tests. Add two kinetic scheme test cases for validation: test_kin1 (simple exponential scheme) and test_kinlva (combination of exponential gate and a three-species kinetic scheme, modelling a low voltage-activated Calcium channel from Wang, X. J. et al., J. Neurophys. 1991). Adapt numeric HH validation data generation to LVA Ca channel, with explicit stopping at stimulus discontinuities. Add two new validation tests based on above: kinetic.kin1_numeric_ref and kinetic.kinlva_numeric_ref (multicore backend only). Introduce a BlockRewriterBase visitor base class, as an aid for visitors that transform/rewrite procedure bodies; refactor KineticRewriter over this class. Introduce common error_stack mixin class for common functionality across Module and the various procedure rewriters. Implement visitors and public-facing convenience wrappers in symdiff.hpp and symdiff.cpp: involves_identifer for testing if an expression contains given identifiers. constant_simplify for constant folding with removal of trivial terms arising from a NumberExpression of zero or one. expr_value to extract the numerical value of a NumberExpression, or NaN othereise. is_zero to test if an expression is numerically zero. symbolic_pdiff to perform symbolic partial differentiation; this adds a new (not parseable) expression subclass to represent opaque partial differential terms. substitute to substitute identifiers for other expressions within an expression. linear_test for linearity, diagonality and homogeneity testing (this is probably redundant, given ExpressionClassifier already exists). Simplify unnecessary uses of make_unique with Vistor subclasses. Make SOLVE statement rewriting more generic, through the use of solve-rewriter visitors CnexpSolverVisitor, SparseSolverVisitor, and DirectSolverVisitor; implementations in solvers.hpp and solvers.cpp. Supports multiple SOLVE statements for independent subsets of state variables with the BREAKPOINT block. Add block rewriter for the removal of unused local variables, with convenience wrapper remove_unused_locals. Generalize is_in utility in modccutil.hpp. Simplify expression comparison in modcc unit tests with EXPECT_EXPR_EQ macro added to tests/modcc/test.hpp, that operates by comparing expression text representations. Simplify and consolidate verbose printing in modcc unit tests with verbose_print function that tests the global verbose flag and handles expression_ptr and similar which have to_string methods.
symdiff.hpp 3.76 KiB
#pragma once
// Perform naive symbolic differenation on a (rhs) expression;
// treat all identifiers as independent, and function calls
// with the variable in argument as opaque.
//
// This is just for linearity and possibly polynomiality testing, so
// don't try too hard.
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include "expression.hpp"
// True if `id` matches the spelling of any identifier in the expression.
bool involves_identifier(Expression* e, const std::string& id);
using identifier_set = std::vector<std::string>;
bool involves_identifier(Expression* e, const identifier_set& ids);
// Return new expression formed by folding constants and removing trivial terms.
expression_ptr constant_simplify(Expression* e);
// Extract value of expression that is a NumberExpression, or else return NAN.
long double expr_value(Expression* e);
// Test if expression is a NumberExpression with value zero.
inline bool is_zero(Expression* e) {
return expr_value(e)==0;
}
// Return new expression of symbolic partial differentiation of argument wrt `id`.
expression_ptr symbolic_pdiff(Expression* e, const std::string& id);
// Substitute all occurances of identifier `id` within expression by a clone of `sub`.
// (Only applicable to unary, binary, call and number expressions.)
expression_ptr substitute(Expression* e, const std::string& id, Expression* sub);
using substitute_map = std::map<std::string, expression_ptr>;
expression_ptr substitute(Expression* e, const substitute_map& sub);
// Convenience interfaces for the above functions work with `expression_ptr` as
// well as with `Expression*` values.
inline bool involves_identifier(const expression_ptr& e, const std::string& id) {
return involves_identifier(e.get(), id);
}
inline bool involves_identifier(const expression_ptr& e, const identifier_set& ids) {
return involves_identifier(e.get(), ids);
}
inline expression_ptr constant_simplify(const expression_ptr& e) {
return constant_simplify(e.get());
}
inline long double expr_value(const expression_ptr& e) {
return expr_value(e.get());
}
inline long double is_zero(const expression_ptr& e) {
return is_zero(e.get());
}
inline expression_ptr symbolic_pdiff(const expression_ptr& e, const std::string& id) {
return symbolic_pdiff(e.get(), id);
}
inline expression_ptr substitute(const expression_ptr& e, const std::string& id, const expression_ptr& sub) {
return substitute(e.get(), id, sub.get());
}
inline expression_ptr substitute(const expression_ptr& e, const substitute_map& sub) {
return substitute(e.get(), sub);
}
// Linearity testing
struct linear_test_result {
bool is_linear = false;
bool is_homogeneous = false;
expression_ptr constant;
std::map<std::string, expression_ptr> coef;
bool monolinear() const {
unsigned nlinear = 0;
for (auto& entry: coef) {
if (!is_zero(entry.second) && ++nlinear>1) return false;
}
return true;
}
bool monolinear(const std::string& var) const {
for (auto& entry: coef) {
if (!is_zero(entry.second) && var!=entry.first) return false;
}
return true;
}
friend std::ostream& operator<<(std::ostream& o, const linear_test_result& r) {
o << "{linear: " << r.is_linear << "; homogeneous: " << r.is_homogeneous << "\n";
o << " constant term: " << r.constant->to_string();
for (const auto& p: r.coef) {
o << "\n coef " << p.first << ": " << p.second->to_string();
}
o << "}";
return o;
}
};
linear_test_result linear_test(Expression* e, const std::vector<std::string>& vars);
inline linear_test_result linear_test(const expression_ptr& e, const std::vector<std::string>& vars) {
return linear_test(e.get(), vars);
}