function name collision between sympy and nmodl
Consider a sample MOD file (provided by @alkino):
NEURON {
SUFFIX glia
}
STATE {
C1
C2
}
BREAKPOINT {
SOLVE kstates METHOD sparse
}
FUNCTION alfa(v(mV)) {
alfa = exp(v)
}
FUNCTION beta(v(mV)) {
beta = exp(-v)
}
KINETIC kstates {
~ C1 <-> C2 (alfa(v),beta(v))
}
Running this with NMODL and Sympy == 1.5.1 gives:
$ ./bin/nmodl test2.mod
[NMODL] [info] :: Processing test2.mod
[NMODL] [info] :: Running symtab visitor
[NMODL] [info] :: Running semantic analysis visitor
[NMODL] [info] :: Running CVode to cnexp visitor
[NMODL] [info] :: Running code compatibility checker
[NMODL] [info] :: Running verbatim rename visitor
[NMODL] [info] :: Running KINETIC block visitor
[NMODL] [info] :: Running STEADYSTATE visitor
[NMODL] [info] :: Parsing Units
[NMODL] [info] :: Running local variable rename visitor
[NMODL] [info] :: Automatically enable sympy_analytic because it exists solver of type sparse
[NMODL] [info] :: Running sympy solve visitor
[NMODL] [warning] :: SympySolverVisitor :: solve_non_lin_system python exception: beta takes exactly 2 arguments (1 given)
...
Renaming the function name from beta to something else like betaX works. So this seems like the function name collision mentioned here: https://github.com/sympy/sympy/issues/9253.
By the way, this doesn't happen with newer sympy versions like >= 1.9.
captain here:
gamma and beta are reserved functions in sympy. Docs in: https://docs.sympy.org/latest/modules/functions/special.html
we should forbid or the conflict of the following special functions:
gamma, lowergamma, uppergamma, polygamma, loggamma, digamma, trigamma, beta
I just tested with 1.5.1 and it gives the same error:
[cattabia@bbpv1 tmp]$ nmodl test.mod
[NMODL] [info] :: Processing test.mod
[NMODL] [info] :: Running symtab visitor
[NMODL] [info] :: Running semantic analysis visitor
[NMODL] [info] :: Running CVode to cnexp visitor
[NMODL] [info] :: Running code compatibility checker
[NMODL] [info] :: Running verbatim rename visitor
[NMODL] [info] :: Running KINETIC block visitor
[NMODL] [info] :: Running STEADYSTATE visitor
[NMODL] [info] :: Parsing Units
[NMODL] [info] :: Running local variable rename visitor
[NMODL] [info] :: Automatically enable sympy_analytic because it exists solver of type sparse
[NMODL] [info] :: Running sympy solve visitor
[NMODL] [warning] :: SympySolverVisitor :: solve_non_lin_system python exception: beta takes exactly 2 arguments (1 given)
[...]
>>> import sympy
>>> sympy.__version__
'1.5.1'
By the way, this doesn't happen with newer sympy versions like >= 1.9.
@cattabiani : that's correct, right? I mentioned that this does not happen with the newer version but version like 1.5.1. (bypass the way, with version 1.9 I see NMODL Parser Error at later stage but don't know the source of that)
ok, I finally tracked down the problem. Let's focus on sympy and on a simpler version of the same problem. Consider this minimal python file:
import sympy as sp
print(sp.sympify('C1 = beta(v)'.split("=", 1)[1], locals={}))
- sympy 1.5.1: throws the error: beta requires 2 inputs
- sympy 1.9: it just "sympifies" it too much and returns
beta(v, v)effectively duplicating the input to fit
the previous version (1.5.1) behavior was much better for us because with 1.9 beta is still considered like the special function and its input is modified to fit its purposes. It is totally misshandled by sympy 1.9 IMHO. This happens in _sympify_eqs where the custom functions do not even enter.
In other words, in 1.9 sympy treats beta as the special function that is and just modifies its input more aggressively without throwing errors. This could generate unforseen problems. Atm it just does not wok because at the end the beta function in the cpp code gets only 1 input. However, imagine if it could get 1 + 1 optional. We pass 1 to sympy and we get a call with 2!
I tried to rename in python beta but it does not work. Sympy simply has 2 versions of beta, the base one is imported when importing sympy. Even reducing the initial imported stuff does not work: beta is implicitly imported with sympify (which we use directly).
The only solution that I see is to do a name change for the forbidden functions in solve_non_lin_system (and maybe in other places where the custom functions enter) or before in the sympy_solver_visitor. I will provide a version of the first option (change names in the ode.py). However, it may be better to do it in the cpp part before. I dunno.
example:
eq_strings = ['beta(v) = a', 'beta(v)+gamma(v)=c']
function_calls = {'beta', 'gamma'}
for w in function_calls:
eq_strings = [i.replace(w, f"custom_{w}") for i in eq_strings]
print(eq_strings)
# sympy does its job the output is code. Here I replace this part with the following line
code = eq_strings
for w in function_calls:
code = [i.replace(f"custom_{w}", w) for i in code]
print(code)
This seems to have been implemented and there's newer tickets for other types of name clashes.