zoomy_core.symbolic.primitives_canonical module

zoomy_core.symbolic.primitives_canonical module#

Purely-structural primitive operations.

These primitives implement structural rewrites that preserve mathematical equality without firing any calculus rule. They use only sympy.Basic.xreplace() and explicit pattern matching on the expression tree — never .doit(), sp.simplify, or sp.expand outside of carefully-scoped Mul-over-Add distribution.

Each primitive is a callable primitive(expr) -> expr. Composition with zoomy_core.model.models.ins_generator.Operation-style wrappers happens elsewhere; here we keep the pure-function implementations so they can be unit-tested in isolation.

Functions in this module are lifted verbatim from zoomy_core.model.models.ins_generator (see file header for the specific source-line range each one came from). No logic changes — just relocation behind a documented primitive interface.

zoomy_core.symbolic.primitives_canonical.alpha_rename(expr)#

Rename every Integral ‘s bound variable to a depth-keyed canonical Dummy.

Lifted verbatim from _canonicalize_integral_dummies (ins_generator.py:4154). Pure xreplace; no math change — alpha-equivalence of bound variables is a definitional property of the integral.

zoomy_core.symbolic.primitives_canonical.split_integral_over_add(expr)#

∫(f+g) dv ∫f dv + ∫g dv — linearity of the integral.

Distributes through Derivative(Integral(...), v) by linearity of the derivative. Outer multiplicative factors stay attached.

Lifted from _split_integrals_expr (ins_generator.py:4217).

zoomy_core.symbolic.primitives_canonical.merge_integrals_over_add(expr)#

Combine sibling Integrals with matching (limits, deriv-wrapper) signature into a single Integral(Σ c_i · f_i, lim).

Inverse of split_integral_over_add(). An outer factor moves inside the integral only if it doesn’t depend on the integration variable — otherwise the factor stays out (passthrough); moving it inside would change the math.

Lifted from _merge_integrals_expr (ins_generator.py:4263).

zoomy_core.symbolic.primitives_canonical.distribute_mul_over_add(expr)#

c·(a+b) c·a + c·b — ring-axiom distribution.

Uses sp.expand with all non-Mul branches disabled so the only rewrite that fires is Mul-over-Add distribution. Integrals are protected during the expansion so their integrands stay intact (sympy’s expand would otherwise fragment Integral(f+g, lim) into Integral(f, lim) + Integral(g, lim) — that’s a separate primitive, split_integral_over_add).

Lifted from _expand_preserve_integrals (ins_generator.py:4124).

zoomy_core.symbolic.primitives_canonical.kill_zero_length_integral(expr)#

Integral(_, (var, a, a)) 0 — structural equality only.

This is a tightened version of the legacy _kill_zero_length_integrals (ins_generator.py:4366) which fell back to sp.simplify(lo - hi) == 0. sp.simplify can fire arbitrary rules on the bounds — for the redesign we accept only structural equality (lo == hi or lo - hi reduces to S.Zero via sympy’s Add canonicalisation alone, no further-simplification pass).

Callers who want a deeper bound match must apply a Subst rule that makes the bounds structurally equal first.

zoomy_core.symbolic.primitives_canonical.zero_derivative_of_free_symbol(expr)#

∂_v sym 0 when sym is a free Symbol distinct from every differentiation variable.

These appear as Leibniz boundary residuals (∂_x z when z is a coordinate, not a function). Sympy doesn’t auto-reduce.

Lifted verbatim from _kill_free_derivatives (ins_generator.py:4387).

zoomy_core.symbolic.primitives_canonical.un_subs(expr)#

Unwrap every Subs(f, var, val) whose inner is safe to xreplace(var val).

Safety = none of:

  • var appears as a Derivative differentiation variable in f (would commit to chain-rule-through-val ambiguity).

  • var is the integration variable of a nested Integral in f (would shadow the binder and produce nonsense limits).

  • var is the binding variable of a nested Subs in f (same shadowing concern).

Lifted verbatim from _resolve_subs_safe (ins_generator.py:4539). The redesign promotes this to a primitive — the previously implicit calls (inside Expression.apply, EvaluateIntegrals, ProjectBasisIntegrals) all become explicit user steps.

zoomy_core.symbolic.primitives_canonical.drop_zero_integrand(expr)#

Integral(0, …) 0 — structural.

Lifted from the ∫0 branch of SimplifyIntegrals._leaf_sp (ins_generator.py:2371) and IsolateBasisIntegrand (ins_generator.py:2698).

zoomy_core.symbolic.primitives_canonical.drop_zero_derivative_inner(expr)#

Derivative(0, …) 0 — structural.

Lifted from the ∂(0) branch of _evaluate_linear_derivatives (ins_generator.py:4621).

zoomy_core.symbolic.primitives_canonical.constant_integrand(expr)#

∫_a^b c dvar c·(b a) when integrand has no var.

Lifted from the constant-integrand branch of SimplifyIntegrals._leaf_sp (ins_generator.py:2373) and IsolateBasisIntegrand (ins_generator.py:2698).

zoomy_core.symbolic.primitives_canonical.protect_integrals(expr, *, also_derivative_of_integral=False)#

Replace every Integral (and optionally Derivative(Integral(...), ...)) atom with a fresh Dummy and return (protected_expr, restore_map).

Restore via protected_expr.xreplace(restore_map). Used to run arithmetic (sp.expand Mul-over-Add) on the expression’s outer structure without sympy fragmenting the integrand.

Lifted from _expand_preserve_integrals (ins_generator.py:4124) and the protect-helper inside _simplify_preserve_integrals (ins_generator.py:4434).