code
codegen.code
SymPy expression printers that render symbolic model math to backend source code.
Each printer subclasses a SymPy code printer and layers on TVBO’s array-function vocabulary (defined in tvbo.parse.expression), the backend-abstracted array primitives from _ArrayFunctionPrinterMixin (slicing, reductions, broadcasting), and per-backend syntax fixes. get_printer selects a printer by target format (numpy, jax, julia, mtk, fortran, python, lems, sympy), and render_expression is the high-level entry point that parses a string or SymPy expression and prints it for the chosen backend.
Array-manipulation primitives are the ones SymPy cannot represent natively. Each is a handler (printer, expr) -> code string in ARRAY_FUNCTION_PRINTERS, shared by the NumPy and JAX printers so a new primitive is one dict entry rather than one per backend. A handler stays backend-agnostic by calling the printer’s own rendering primitives — _cat, _render_index, _slice_axis, _transpose, _reduce_axis, _shape — which each printer implements for its own conventions: numpy and jax use Python’s 0-based slicing, Julia 1-based and end-relative. Adding an operation is therefore one handler, plus one primitive override wherever a backend’s syntax genuinely differs, and never the same string-building duplicated across printers.
Attributes
| Name | Description |
|---|---|
| ARRAY_FUNCTION_MAPPINGS | |
| logger |
Classes
| Name | Description |
|---|---|
| Brian2Printer | Code printer for Brian2 equation strings. |
| FortranPrinter | Fortran code printer for TVBO symbolic expressions. |
| JaxPrinter | JAX code printer for TVBO symbolic expressions. |
| JuliaPrinter | Julia code printer for TVBO symbolic expressions. |
| LEMSPrinter | Printer for LEMS (Low Entropy Model Specification) math expressions. |
| MTKPrinter | Printer for ModelingToolkit.jl mtkmodel? equations. |
| NumPyPrinter | NumPy code printer for TVBO symbolic expressions. |
| PythonCodePrinter | Plain-Python code printer for TVBO symbolic expressions. |
| TVBEquationPrinter | Print an expression for TVB’s Equation.equation DSL. |
Brian2Printer
codegen.code.Brian2Printer(settings=None)Code printer for Brian2 equation strings.
Brian2’s equation DSL is Python-like but expects unqualified function names (exp, sin, abs …), not the math.-prefixed forms SymPy’s PythonCodePrinter emits — Brian2 resolves them against its own runtime functions so the same equation compiles under any codegen target. Otherwise the plain-Python scalar printing (including the nested-conditional Piecewise, which Brian2 accepts) is exactly what a per-neuron Brian2 equation needs. Units are not printed here — they are carried by the Brian2 namespace and the : dimension annotations the template adds.
FortranPrinter
codegen.code.FortranPrinter(settings=None)Fortran code printer for TVBO symbolic expressions.
Extends SymPy’s FCodePrinter with free-form source, the Fortran 2003 standard, and array contraction disabled. Symbolic constants (pi, E, …) are emitted as plain double-precision literals instead of parameter declarations, which would be invalid in an expression context.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| settings | Printer settings forwarded to the SymPy base printer; source_format, standard, and contract are defaulted. |
None |
JaxPrinter
codegen.code.JaxPrinter(settings=None, module='jnp')JAX code printer for TVBO symbolic expressions.
Extends SymPy’s JaxPrinter with the array-function vocabulary from ARRAY_FUNCTION_MAPPINGS["jax"] and the mixin’s array primitives, routing erf/erfc to jsp.special. When broadcasting inference is enabled it analyzes the index usage of indexed subexpressions to insert explicit axes so that jnp operations broadcast correctly.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| settings | Printer settings forwarded to the SymPy base printer. | None |
|
| module | Module prefix used to qualify JAX names (e.g. jnp). |
'jnp' |
JuliaPrinter
codegen.code.JuliaPrinter(settings=None)Julia code printer for TVBO symbolic expressions.
Extends SymPy’s JuliaCodePrinter with the ARRAY_FUNCTION_MAPPINGS["julia"] vocabulary and Julia-specific overrides of the mixin’s array primitives, which use 1-based, end-relative indexing. Runs non-strict so unknown constructs print partially rather than raising, maps the legacy atan2 name onto Julia’s two-argument atan, and routes domain-restricted powers inside Piecewise branches through NaNMath.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| settings | Printer settings forwarded to the SymPy base printer; strict defaults to False. |
None |
LEMSPrinter
codegen.code.LEMSPrinter(settings=None)Printer for LEMS (Low Entropy Model Specification) math expressions.
Key differences from plain StrPrinter: - Powers use ^ instead of ** - Natural log is log (both SymPy and LEMS log are natural log) - abs instead of Abs - sign(x) → (H(x) - H(-1*x)) (Heaviside decomposition) - Mod(x, y) → (x + y*ceil(-(x/y))) - Relational operators use LEMS dot-notation: .gt., .geq., .lt., .leq., .eq., .neq. - Boolean operators: .and., .or., .not. - Piecewise rendered via Heaviside trick (H(cond)*val)
Parameters
settings : dict, optional Printer settings. Recognised key:
``parameters`` : list of str
Model symbol names. When a SymPy ``Function`` whose name matches
a parameter is encountered, it is printed as implicit multiplication
(``gamma*x``) instead of a function call (``gamma(x)``). This
defends against symbols that were parsed without proper
``parameters=`` overrides.
Methods
| Name | Description |
|---|---|
| parenthesize | Bracket a Piecewise operand as the sum this printer renders it into. |
parenthesize
codegen.code.LEMSPrinter.parenthesize(item, level, strict=False)Bracket a Piecewise operand as the sum this printer renders it into.
SymPy gives Piecewise Func precedence — right for the printers that emit np.where(...) or ifelse(...), which really are atoms, and wrong here, where the output is H(c) * a + (1 - H(c)) * b. Without this an enclosing Mul, Pow or negation binds to the first arm alone.
Declaring the precedence rather than wrapping in _print_Piecewise keeps the brackets to the contexts that need them: parenthesize is only ever called by an enclosing operator, so a top-level equation stays unwrapped.
MTKPrinter
codegen.code.MTKPrinter(settings=None)Printer for ModelingToolkit.jl mtkmodel? equations.
MTK equations are scalar symbolic, so we use plain +, -, *, /, ^ instead of Julia’s element-wise .+, .-, .*, ./, .^.
NumPyPrinter
codegen.code.NumPyPrinter(settings=None, module='np')NumPy code printer for TVBO symbolic expressions.
Extends SymPy’s NumPyPrinter with the array-function vocabulary from ARRAY_FUNCTION_MAPPINGS["numpy"] and the backend-abstracted array primitives supplied by _ArrayFunctionPrinterMixin. Known functions and constants are module-qualified with module, and erf/erfc are routed to scipy.special.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| settings | Printer settings forwarded to the SymPy base printer. | None |
|
| module | Module prefix used to qualify NumPy names (e.g. np). |
'np' |
PythonCodePrinter
codegen.code.PythonCodePrinter(settings=None)Plain-Python code printer for TVBO symbolic expressions.
Extends SymPy’s PythonCodePrinter to run non-strict (partial printing of unknown constructs) and adds ceil, sign, and the ARRAY_FUNCTION_MAPPINGS["python"] vocabulary. Piecewise is rendered as nested conditional expressions and sign(x) as an inline comparison, so the output depends only on math and the standard library.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| settings | Printer settings forwarded to the SymPy base printer; strict defaults to False. |
None |
TVBEquationPrinter
codegen.code.TVBEquationPrinter(settings=None)Print an expression for TVB’s Equation.equation DSL.
TVB evaluates that string with numexpr (falling back to eval against numpy.__dict__), a vocabulary narrower than NumPy’s in three ways: names are unqualified, comparisons are operators rather than numpy.greater calls, and boolean connectives are bitwise. Everything else is NumPy — in particular a Piecewise still lowers to where(...) through the one shared print_Piecewise, which is what makes a conditional stimulus array-safe. Rendering one as a Python a if c else b instead, as TVBO did before, produces a string numexpr refuses outright.
The relational and boolean methods come from StrPrinter, whose operator spelling is already exactly the accepted one, so this printer states only which vocabulary it borrows rather than restating how to print a comparison.
Functions
| Name | Description |
|---|---|
| get_printer | Return a code printer instance for the given target format. |
| inline_functions | Replace every call to a model-defined function with that function’s body. |
| print_Piecewise | Print Piecewise expressions as nested np.where statements. |
| render_equation | Render an equation to a target format. |
| render_equation_cse | Render equation as (setup, final) with common subexpressions hoisted. |
| render_expression | Render a SymPy expression or string to target format code. |
get_printer
codegen.code.get_printer(format, parameters=None, order=None)Return a code printer instance for the given target format.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| format | Target output format. One of numpy, jax, julia, mtk, fortran, python, brian2, tvb, lems, or sympy/symbolic/pyrates. |
required | |
| parameters | Parameter names passed to LEMSPrinter; used only for the lems format. |
None |
|
| order | Term ordering passed to the printer; none preserves source term order. When omitted the printer’s default ordering is used. |
None |
Returns
| Name | Type | Description |
|---|---|---|
A configured printer instance for format. |
Raises
| Name | Type | Description |
|---|---|---|
| ValueError | If format is not a supported output format. |
inline_functions
codegen.code.inline_functions(expr, func_defs)Replace every call to a model-defined function with that function’s body.
The one inliner in TVBO. Backends with no user-function mechanism (LEMS, PyRates) must expand every call before printing, and the generic printers expand on request; all of them arrive here. Build func_defs with function_bodies, which reads each body from the model’s symbolic layer, parsed once against the model’s own scope.
A body may itself call a function — the call graph is a DAG, e.g. Zerlaut’s TF_e calls sigmaV calls muV — so the bodies are first expanded into each other, once, and only then substituted into expr in a single pass.
Reaching the fixed point on expr instead re-probes every body against an expression that grows as it is inlined: Zerlaut’s NeuroML render spent 5.6 s of 10 s here, walking a 12 000-node expression four times over to find nothing on the last pass. Flattening the bodies costs the same work once, over expressions that are small, and the result is memoised because every equation in a model inlines against the same table.
Parameters
expr : sympy.Expr The expression containing function calls to inline. func_defs : dict Maps function name -> (arg_names, body_expr). arg_names are the formal arguments, as strings or as Symbols; body_expr is the parsed body.
Returns:
sympy.Expr Expression with all function calls replaced by their inlined bodies.
Example:
from sympy import symbols, Function, exp A, x, y, e0, r, v0 = symbols(‘A x y e0 r v0’) func_defs = {‘Sigm’: ([‘v’], 2e0/(1 + exp(r(v0 - symbols(‘v’)))))} inline_functions(AFunction(‘Sigm’)(x - y), func_defs) 2Ae0/(1 + exp(r(v0 - x + y)))
print_Piecewise
codegen.code.print_Piecewise(Printer, expr, verbose=False)Print Piecewise expressions as nested np.where statements.
render_equation
codegen.code.render_equation(
equation,
format='jax',
local_dict=None,
user_functions=None,
replace=None,
remove=None,
inline_funcs=None,
preserve_order=False,
**kwargs,
)Render an equation to a target format.
Parameters
equation : Equation The equation to render. format : str Target format: ‘jax’, ‘numpy’, ‘python’, ‘julia’, ‘fortran’, ‘latex’. local_dict : dict Dictionary of local symbols/functions for parsing. user_functions : dict Custom function mappings for the printer. replace : dict Symbol replacements {old_name: new_name}. remove : list Symbols to replace with zero. inline_funcs : dict, optional Dictionary mapping function name -> (arg_names, body_expr) for inlining custom functions. The body_expr should be a sympy expression. Example: {‘Sigm’: ([‘v’], 2e0/(1 + exp(r(v0 - v))))} preserve_order : bool If True, keep the source term order (no SymPy canonicalization). **kwargs Additional arguments passed to parse_eq.
Returns:
str The rendered equation string.
render_equation_cse
codegen.code.render_equation_cse(
equation,
format='numpy',
local_dict=None,
user_functions=None,
replace=None,
remove=None,
inline_funcs=None,
preserve_order=False,
symbol_prefix='_cse',
**kwargs,
)Render equation as (setup, final) with common subexpressions hoisted.
setup is a list of (name, expr_str) assignments (dependency order) and final is the return-expression string. Repeated subexpressions — notably repeated model-function calls such as muV(fe, fi, ...) — are computed once via :func:sympy.cse. Interpreted backends (numpy / TVB) would otherwise re-evaluate every occurrence; the jax path keeps the flat render_equation form and leans on XLA’s JIT-time CSE. setup is empty when nothing is shared.
render_expression
codegen.code.render_expression(
expression,
format='jax',
user_functions=None,
parameters=None,
infer_broadcasting=False,
preserve_order=False,
)Render a SymPy expression or string to target format code.
Uses parse_eq for proper handling of indexed expressions and Sum/Product.
Parameters
expression : str or sympy.Expr The expression to render. format : str Target format (‘jax’, ‘numpy’, ‘julia’, ‘python’, etc.) user_functions : dict Custom function name mappings for the printer. These are also passed to parse_eq so they’re recognized as functions (not implicit multiplication). parameters : list of str, optional Parameter names to define as Symbols. These OVERRIDE SymPy built-in functions (e.g., ‘gamma’ becomes Symbol(‘gamma’), not the gamma function). infer_broadcasting : bool If True, analyze indexed expressions and automatically add broadcasting dimensions (e.g., rmse[i] -> rmse[:, None] when used with a[i,j]). This enables mathematically correct notation to generate correct array code. preserve_order : bool If True, keep the source term order (no SymPy Add/Mul canonicalization) so generated code matches reference code operation-for-operation.