zoomy_core.symbolic.primitives_calculus module#
Calculus-rule primitives.
Each function in this module implements ONE textbook calculus rule
that fires only when its structural pattern matches. The rules
never call .doit(), sp.simplify, sp.cancel,
sp.together, or sp.factor. Sympy is used only for:
sp.expand(Mul-over-Add only, viazoomy_core.symbolic.primitives_canonical.distribute_mul_over_add()),sp.Poly(...).integrate()for finite polynomial antiderivatives,Add/Mul/Powconstructors (commutative-ring arithmetic),xreplacefor purely-structural substitution.
Refusal semantics: when the input contains no atom matching the
primitive’s pattern, the function returns the input unchanged
(silent no-op — preserving the equation but doing nothing). Use
Expression.apply_to_term(i, primitive)() for explicit
single-term targeting.
Functions are lifted from
zoomy_core.model.models.ins_generator with two surgical
changes:
ProductRuleInverseno longer callsDerivative(coeff, var).doit()on the residual (legacy bug source —ins_generator.py:3402); the residual is left as a heldDerivativeatom which Canonicalise reduces structurally.Leibnizno longer runs the inner_doit_only_derivativeswalk (legacy bug source —ins_generator.py:2026); the caller must applyDistributeDerivativeOverAddfirst if they wantvar-factor surfacing.PolynomialIntegrateno longer runs_doit_derivs(ins_generator.py:2078). Same reason.
- zoomy_core.symbolic.primitives_calculus.fundamental_theorem(integrand, var, lower, upper)#
∫_lo^hi ∂_var g dvar = g|_hi − g|_lo.Lifted verbatim from
_rule_fundamental_theorem(ins_generator.py:1934). Handles single-order∂_var gand higher-order-in-var(onevarpeels off). Mixed-order∂_var ∂_other gstrips onevarfrom the diff-tuple.Returns
Noneif the integrand isn’t aDerivativewhose diff-vars includevar(caller convention preserved from ins_generator’s rule-registry:None= “rule does not apply”, not an exception).
- zoomy_core.symbolic.primitives_calculus.leibniz(integrand, var, lower, upper)#
∫_lo^hi ∂_y f dvar = ∂_y(∫_lo^hi f dvar) − f(hi)·∂_y hi + f(lo)·∂_y lowheny ≠ varandfis polynomial invar.Lifted from
_rule_derivative_of_polynomial(ins_generator.py:1976) with the inner_doit_only_derivativeswalk removed. If the caller wantsvar-factors to surface fromDerivative(var·g, y)etc. they must rundistribute_derivative_over_add()first.Returns
Nonewhen:integrand is not a
Derivative,diff-tuple has more than one entry,
differentiation is w.r.t.
varitself (fundamental_theoremhandles that),the integrand is independent of
var,sp.Polycannot build a polynomial invar.
- zoomy_core.symbolic.primitives_calculus.leibniz_general(integrand, var, lower, upper)#
Conservative-form Leibniz that does NOT require
fto be polynomial invar.For
∫_lo^hi ∂_y f dvarwithy ≠ var:= ∂_y[ ∫_lo^hi f dvar ] − f|_hi · ∂_y hi + f|_lo · ∂_y lo
The volume
∫f dvarstays as a heldsympy.Integralatom — no attempt to evaluate it. Boundary terms are produced as heldsympy.Subsatoms when the bounds depend ony.Use this when the integrand’s antiderivative is unknown or opaque; use
leibniz()(poly-specialised) whenfis polynomial invarand you want the closed-form volume.Returns
Noneif:integrand is not a
Derivative,diff-tuple has more than one entry,
differentiation is w.r.t.
varitself (fundamental_theoremhandles that).
- zoomy_core.symbolic.primitives_calculus.polynomial_integrate(integrand, var, lower, upper)#
∫_lo^hi p(var) dvarviasp.Poly.integratefor polynomialp(with arbitrary opaque coefficients).Lifted from
_rule_polynomial_integrand(ins_generator.py:2061) with the inner_doit_derivswalk removed. If the caller wantsvar-factors to surface fromDerivative(var·g, y)etc. they must rundistribute_derivative_over_add()first.Returns
(hi − lo) · integrandforvar-free integrands (constant integrand rule). ReturnsNoneifsp.Polycannot build a polynomial.
- zoomy_core.symbolic.primitives_calculus.integration_by_parts(f, g, var, lower, upper)#
∫_lo^hi (∂_var f)·g dvar = [f·g]_lo^hi − ∫_lo^hi f·∂_var g dvar.Returns a 3-tuple
(volume_integrand, boundary_upper, boundary_lower)so the caller can place each piece in the appropriate term tag.volume_integrandalready carries the-sign and is wrapped in aIntegral(..., (var, lower, upper)).Lifted from
integrate_by_parts(ins_generator.py:4757), with theIBPResultwrapper unpacked since it lives in the legacy layer.
- zoomy_core.symbolic.primitives_calculus.product_rule_forward(term, var)#
∂_v(Π fᵢ) → Σᵢ (Π_{j≠i} fⱼ)·∂_v fᵢand∂_v(f^n) → n·f^(n-1)·∂_v ffor integern ≥ 2.Acts on a single
Derivativeatom. Iftermis not aDerivative, returnstermunchanged (no-op). If the derivative has multiple variables or its inner is neither a Mul nor an integer-power Pow, returnstermunchanged.Lifted from the forward branch of
ProductRule._one_term(ins_generator.py:3358). No.doit()involved — every output factor is a heldDerivative.
- zoomy_core.symbolic.primitives_calculus.product_rule_inverse(term, var)#
coeff · ∂_v f → ∂_v(coeff · f) − ∂_v(coeff) · f.The legacy version (
ProductRule._one_terminverse branch, ins_generator.py:3402) calledDerivative(coeff, var).doit()on the residual. That.doit()was the exact bug-3 source: whencoeffis e.g.φ_1((z-b)/h),.doit()fires the chain rule against the moving frame at exactly the wrong pipeline position.The redesigned primitive leaves the residual as a held
Derivative(coeff, var)atom. Canonicalise will reduce it to zero whencoeffis genuinelyvar-free, otherwise the user callschain_rule()orproduct_rule_forward()explicitly to expand it.
- zoomy_core.symbolic.primitives_calculus.chain_rule(term, outer_predicate, var)#
∂_v f(g(v)) → f'(g)·∂_v gwhereouter_predicate(f) → boolselects the function families to expand.Walks the expression and applies the chain rule wherever the pattern matches.
outer_predicateis a callable taking aFunctioninstance (the call site, e.g.phi_1((z-b)/h)) and returningTrueif this primitive should chain-rule it.For example, to chain-rule every
phi_*family call:def is_basis(call): name = getattr(call.func, '__name__', '') return name.startswith('phi_') chain_rule(expr, is_basis, var=x)
Implementation: walks
Derivativeatoms whose inner is a matching function call; replaces with the explicit chain-rule expansion using heldDerivativeatoms. No.doit()— otherDerivativeatoms are not touched.
- zoomy_core.symbolic.primitives_calculus.distribute_derivative_over_add(expr)#
∂_v Add(t1, t2) → Add(∂_v t1, ∂_v t2)— linearity.Hoisted from the Add branch of
_evaluate_linear_derivatives(ins_generator.py:4651). Distinguishes:Inner is structurally an
Add→ distribute outer Derivative.Inner is a
Mulwhoseas_coeff_Mulseparates a numeric scalar → pull it out (separate primitivepull_scalar_out_of_derivative()does that; this one only does Add-distribution).Trivially zero (
inner == 0or inner doesn’t reference any diff-var) → returns 0.
No
.doit()— distributed Derivative atoms are held.
- zoomy_core.symbolic.primitives_calculus.pull_scalar_out_of_derivative(expr)#
∂_v(c·f) → c·∂_v fwhencis a sympyNumber(Integer/Rational/Float).Hoisted from the
Mul.as_coeff_Mulbranch of_evaluate_linear_derivatives(ins_generator.py:4644). Numeric-only — free symbols stay inside.
- zoomy_core.symbolic.primitives_calculus.push_scalar_into_derivative(expr)#
c · ∂_v f → ∂_v(c · f)whencis a sympyNumber.Inverse of
pull_scalar_out_of_derivative(). Lifted verbatim from_fold_numeric_coeffs_into_derivative(ins_generator.py:4674).