NMODL SympySolver - derivimplicit
This notebook describes the implementation of the derivimplicit
part of the SympySolverVisitor
, which solves the systems of ODEs defined in DERIVATIVE
blocks when these ODEs are nonlinear.
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 derivimplicit
does the following:
Construct the implicit Euler equations to convert the sysytem of ODEs to a
NONLINEAR
blockThis
NONLINEAR
block is then solved as described in nmodl-nonlinear-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 involving the derivimplicit
solver method have the tag “[derivimplicit]
”
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 new DERIVATIVE block
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]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc
nmodl_eigen_j[0] = -a*dt-1.0
nmodl_eigen_j[2] = b*dt
nmodl_eigen_f[1] = nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[1]*b*dt-nmodl_eigen_x[1]+old_m
nmodl_eigen_j[1] = a*dt
nmodl_eigen_j[3] = -b*dt-1.0
}{
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]*alpha*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*beta*dt+old_c1
nmodl_eigen_j[0] = -alpha*dt-1.0
nmodl_eigen_j[5] = beta*dt
nmodl_eigen_j[10] = 0
nmodl_eigen_j[15] = 0
nmodl_eigen_j[20] = 0
nmodl_eigen_f[1] = nmodl_eigen_x[0]*alpha*dt-nmodl_eigen_x[1]*beta*dt-nmodl_eigen_x[1]*dt*k3p-nmodl_eigen_x[1]+nmodl_eigen_x[2]*dt*k4+old_o1
nmodl_eigen_j[1] = alpha*dt
nmodl_eigen_j[6] = -beta*dt-dt*k3p-1.0
nmodl_eigen_j[11] = dt*k4
nmodl_eigen_j[16] = 0
nmodl_eigen_j[21] = 0
nmodl_eigen_f[2] = nmodl_eigen_x[1]*dt*k3p-nmodl_eigen_x[2]*dt*k4-nmodl_eigen_x[2]+old_o2
nmodl_eigen_j[2] = 0
nmodl_eigen_j[7] = dt*k3p
nmodl_eigen_j[12] = -dt*k4-1.0
nmodl_eigen_j[17] = 0
nmodl_eigen_j[22] = 0
nmodl_eigen_f[3] = -nmodl_eigen_x[3]*dt*k1ca-nmodl_eigen_x[3]+nmodl_eigen_x[4]*dt*k2+old_p0
nmodl_eigen_j[3] = 0
nmodl_eigen_j[8] = 0
nmodl_eigen_j[13] = 0
nmodl_eigen_j[18] = -dt*k1ca-1.0
nmodl_eigen_j[23] = dt*k2
nmodl_eigen_f[4] = nmodl_eigen_x[3]*dt*k1ca-nmodl_eigen_x[4]*dt*k2-nmodl_eigen_x[4]+old_p1
nmodl_eigen_j[4] = 0
nmodl_eigen_j[9] = 0
nmodl_eigen_j[14] = 0
nmodl_eigen_j[19] = dt*k1ca
nmodl_eigen_j[24] = -dt*k2-1.0
}{
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]
}{
}
}
[ ]: