NMODL SympySolver - sparse
This notebook describes the implementation of the sparse part of the SympySolverVisitor, which solves the systems of ODEs defined in DERIVATIVE blocks when these ODEs are linear and coupled.
For a higher level overview of the approach to solving ODEs in NMODL, please see the nmodl-odes-overview notebook.
For a more general tutorial on using the NMODL python interface, please see the tutorial notebook.
Implementation
The SympySolverVisitor for solver method sparse does the following:
Construct the implicit Euler equations to convert the sysytem of ODEs to a
LINEARblockThis
LINEARblock is then solved as described in nmodl-linear-solver
Implementation Tests
The unit tests may be helpful to understand what these functions are doing - SympySolverVisitor tests are located in test/visitor/sympy_solver.cpp, and tests using the sparse solver method have the tag “[sparse]”
Examples
[1]:
%%capture
! pip install nmodl
[2]:
import nmodl.dsl as nmodl
def run_sympy_solver(mod_string, cse=False):
# parse NMDOL file (supplied as a string) into AST
driver = nmodl.NmodlDriver()
AST = driver.parse_string(mod_string)
# run SymtabVisitor to generate Symbol Table
nmodl.symtab.SymtabVisitor().visit_program(AST)
# constant folding, inlining & local variable renaming passes
nmodl.visitor.ConstantFolderVisitor().visit_program(AST)
nmodl.visitor.InlineVisitor().visit_program(AST)
nmodl.visitor.LocalVarRenameVisitor().visit_program(AST)
# run SympySolver visitor
nmodl.visitor.SympySolverVisitor().visit_program(AST)
# return contents of new DERIVATIVE block as list of strings
return nmodl.to_nmodl(
nmodl.visitor.AstLookupVisitor().lookup(
AST, nmodl.ast.AstNodeType.DERIVATIVE_BLOCK
)[0]
)
Ex. 1
2 coupled linear ODEs
[3]:
ex1 = """
BREAKPOINT {
SOLVE states METHOD sparse
}
STATE {
mc m
}
DERIVATIVE states {
mc' = -a*mc + b*m
m' = a*mc - b*m
}
"""
print(run_sympy_solver(ex1, cse=True))
DERIVATIVE states {
EIGEN_NEWTON_SOLVE[2]{
LOCAL old_mc, old_m
}{
old_mc = mc
old_m = m
}{
nmodl_eigen_x[0] = mc
nmodl_eigen_x[1] = m
}{
nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*a+nmodl_eigen_x[1]*b)+old_mc)/dt
nmodl_eigen_j[0] = -a-1/dt
nmodl_eigen_j[2] = b
nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*a-nmodl_eigen_x[1]*b)+old_m)/dt
nmodl_eigen_j[1] = a
nmodl_eigen_j[3] = -b-1/dt
}{
mc = nmodl_eigen_x[0]
m = nmodl_eigen_x[1]
}{
}
}
Ex. 2
5 coupled linear ODEs
[4]:
ex2 = """
STATE {
c1 o1 o2 p0 p1
}
BREAKPOINT {
SOLVE ihkin METHOD sparse
}
DERIVATIVE ihkin {
LOCAL alpha, beta, k3p, k4, k1ca, k2
evaluate_fct(v, cai)
c1' = (-1*(alpha*c1-beta*o1))
o1' = (1*(alpha*c1-beta*o1))+(-1*(k3p*o1-k4*o2))
o2' = (1*(k3p*o1-k4*o2))
p0' = (-1*(k1ca*p0-k2*p1))
p1' = (1*(k1ca*p0-k2*p1))
}
"""
print(run_sympy_solver(ex2))
DERIVATIVE ihkin {
EIGEN_NEWTON_SOLVE[5]{
LOCAL alpha, beta, k3p, k4, k1ca, k2, old_c1, old_o1, old_o2, old_p0, old_p1
}{
evaluate_fct(v, cai)
old_c1 = c1
old_o1 = o1
old_o2 = o2
old_p0 = p0
old_p1 = p1
}{
nmodl_eigen_x[0] = c1
nmodl_eigen_x[1] = o1
nmodl_eigen_x[2] = o2
nmodl_eigen_x[3] = p0
nmodl_eigen_x[4] = p1
}{
nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*alpha+nmodl_eigen_x[1]*beta)+old_c1)/dt
nmodl_eigen_j[0] = -alpha-1/dt
nmodl_eigen_j[5] = beta
nmodl_eigen_j[10] = 0
nmodl_eigen_j[15] = 0
nmodl_eigen_j[20] = 0
nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*alpha-nmodl_eigen_x[1]*beta-nmodl_eigen_x[1]*k3p+nmodl_eigen_x[2]*k4)+old_o1)/dt
nmodl_eigen_j[1] = alpha
nmodl_eigen_j[6] = -beta-k3p-1/dt
nmodl_eigen_j[11] = k4
nmodl_eigen_j[16] = 0
nmodl_eigen_j[21] = 0
nmodl_eigen_f[2] = (-nmodl_eigen_x[2]+dt*(nmodl_eigen_x[1]*k3p-nmodl_eigen_x[2]*k4)+old_o2)/dt
nmodl_eigen_j[2] = 0
nmodl_eigen_j[7] = k3p
nmodl_eigen_j[12] = -k4-1/dt
nmodl_eigen_j[17] = 0
nmodl_eigen_j[22] = 0
nmodl_eigen_f[3] = (-nmodl_eigen_x[3]+dt*(-nmodl_eigen_x[3]*k1ca+nmodl_eigen_x[4]*k2)+old_p0)/dt
nmodl_eigen_j[3] = 0
nmodl_eigen_j[8] = 0
nmodl_eigen_j[13] = 0
nmodl_eigen_j[18] = -k1ca-1/dt
nmodl_eigen_j[23] = k2
nmodl_eigen_f[4] = (-nmodl_eigen_x[4]+dt*(nmodl_eigen_x[3]*k1ca-nmodl_eigen_x[4]*k2)+old_p1)/dt
nmodl_eigen_j[4] = 0
nmodl_eigen_j[9] = 0
nmodl_eigen_j[14] = 0
nmodl_eigen_j[19] = k1ca
nmodl_eigen_j[24] = -k2-1/dt
}{
c1 = nmodl_eigen_x[0]
o1 = nmodl_eigen_x[1]
o2 = nmodl_eigen_x[2]
p0 = nmodl_eigen_x[3]
p1 = nmodl_eigen_x[4]
}{
}
}
[ ]: