expression
parse.expression
Parse TVBO equation strings into SymPy expressions.
Provides parse_eq for turning an Equation (or a raw string) into a SymPy expression, along with the custom aggregation symbols and the ARRAY_FUNCTIONS registry of array reduction/manipulation functions (sum, mean, slice_axis, mode_dot, …) that the code printers in tvbo.codegen.code lower to backend-specific calls.
parse_eq is the only parser in TVBO. An Equation states its right-hand side either directly or as a list of conditional branches, and parse_eq resolves both, so callers never have to ask which form they were given — the branch that used to be written out at each call site now lives here once. The namespace to parse against is supplied by the caller, normally as a SymbolContext.
ARRAY_FUNCTIONS is the single source of truth for parsing. Each entry is an undefined SymPy Function — that is what makes mean(x) parse as a call rather than being split by implicit multiplication into m*e*a*n*(x). The names are lowercase to keep them distinct from SymPy’s own symbolic Sum and Product, which need explicit index variables where these reduce over whole arrays, numpy-style. Printer mappings live in tvbo.codegen.code.
Every argument is positional. SymPy’s parser forwards f(x, axis=0) into Basic’s options and raises ValueError: Unknown options, so a primitive can never carry a keyword. Anything that would be one — a metric, an axis, a target range, a distribution, a seed — is a schema field on the DAG step and is lowered into a positional argument, which is why the Procedural graph-generator DAG is typed rather than free-form.
The registry covers array manipulation with Python-specific semantics (window_mean, subsample), structural slice and shape ops so a pipeline that selects a variable of interest, trims a transient or downsamples is authored as declarative equations rather than source_code, general ops for per-timestep detectors and permutation-significance tests (take, sum_axis, pearson), the graph-construction primitives a Procedural GraphGenerator lowers to, and the distribution samplers.
A sampler takes its PRNG state as the first argument, because JAX is functionally pure and a key cannot be threaded implicitly through a rendered expression; the trailing arguments are the sample shape. Draws are not bit-identical across backends — numpy’s PCG64 is not jax’s Threefry.
Symbolic summation uses SymPy’s own Sum, which needs explicit index variables: Sum(x[i]*y[i], (i, 0, n-1)). Index variables are detected from the Sum and Product limits, and the code printers handle the translation.
Attributes
| Name | Description |
|---|---|
| ARRAY_FUNCTIONS |
Classes
| Name | Description |
|---|---|
| Mean | Mean over indexed expression: Mean(f(x[i]), (i, 0, N-1)). |
Mean
parse.expression.Mean()Mean over indexed expression: Mean(f(x[i]), (i, 0, N-1)).
Mathematical notation for averaging over a dimension. Translates to jnp.mean(jax.vmap(…)) or jnp.mean(…) depending on the inner function.
Example
Mean(1 - correlation(x[i], y[i]), (i, 0, N-1)) -> jnp.mean(jax.vmap(lambda x, y: 1 - correlation(x, y))(x, y))
Methods
| Name | Description |
|---|---|
| eval | Suppress automatic simplification so the symbol survives to codegen. |
eval
parse.expression.Mean.eval(*args)Suppress automatic simplification so the symbol survives to codegen.
SymPy calls this classmethod when a Mean(...) is constructed. Returning None signals that no closed-form evaluation should be performed, keeping the expression as an unevaluated Mean node that the code printers in tvbo.codegen.code translate into the backend’s mean/reduction call.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| *args | The positional arguments the Mean was called with (the inner expression and index limit tuple). Left unused. |
() |
Returns
| Name | Type | Description |
|---|---|---|
Always None, leaving the Mean application unevaluated. |
Functions
| Name | Description |
|---|---|
| function_bodies | A model’s function definitions as {name: (arg_names, body)}. |
| parse_eq | Parse the right-hand side of an equation or a raw expression string. |
| states_an_expression | Whether equation states anything to parse — a right-hand side, or branches. |
function_bodies
parse.expression.function_bodies(model)A model’s function definitions as {name: (arg_names, body)}.
The table inline_functions consumes, for the backends that have no user-function mechanism and must expand every call before printing.
Read from the model’s symbolic layer, so a body is parsed once however many backends inline it. A free function rather than a Dynamics method because both flavours of model need it: the runtime Dynamics in tvbo.classes and the generated one an edge’s resolved_dyn is. Both carry the layer, so both answer from the same parse, and the caller no longer supplies a namespace of its own, which is what let a body be parsed against names the model does not declare.
The layer’s scope registers every function name as a function, so a call to one inside another’s body parses as an application rather than a product — Zerlaut’s sigmaV calls muV. Functions with no arguments or no equation are skipped: there is nothing to substitute into, and a call to one is left for the printer to emit verbatim.
parse_eq
parse.expression.parse_eq(equation, parameters=None, **kwargs)Parse the right-hand side of an equation or a raw expression string.
Extends parsing with the ability to pass parameters, functions, symbols, and arbitrary SymPy objects commonly used in nonlinear systems dynamics.
A user-defined parameter always overrides a SymPy built-in of the same name, so a model free to call something gamma or lambda gets its own symbol rather than the special function. Indexed variables are detected in the source text and bound as IndexedBase, which likewise overrides any Symbol of that name — x[i] cannot be parsed against a plain Symbol. Index variables are picked out of Sum and Product limits and bound as plain Symbols where they are not already defined.
Parameters
equation : Equation | str An Equation from tvbo’s datamodel or a raw expression string. If an Equation with latex=True is provided, LaTeX parsing is used. parameters : Iterable[str] | Mapping[str, object] | None Names or a mapping of parameter names to SymPy objects or numbers. If an iterable of strings is provided, they are created as SymPy Symbols and injected into the parsing context. If a mapping is provided, the values are injected as-is (Symbols, Functions, Expressions, numbers, etc.).
Keyword-only enhancements (optional)
local_dict : dict Additional local names to inject into the parser (merged on top of defaults). functions : Iterable[str] | Mapping[str, object] Names or mapping for functions. String names are created as undefined SymPy functions, e.g., Function(‘f’). Mapping values are used as-is. symbols : Iterable[str] | Mapping[str, Symbol] Extra symbol names or mapping for state variables, etc. String names are created as SymPy Symbols. Mapping values are used as-is. objects : Mapping[str, object] Arbitrary additional objects (e.g., Heaviside, MatrixSymbol, IndexedBase, Derivative alias, etc.) to inject into the local namespace. extra_transformations : Iterable[callable] Extra SymPy parser transformations to augment the defaults. transformations : Iterable[callable] Full control over the transformation pipeline (overrides defaults if provided).
Returns:
sympy.Expr Parsed SymPy expression.
states_an_expression
parse.expression.states_an_expression(equation)Whether equation states anything to parse — a right-hand side, or branches.
An Equation carries its expression in either slot, so a caller that guards with if eq.rhs: silently skips every equation written purely as conditional branches. Guard with this instead and hand the whole Equation to parse_eq, which resolves both spellings.