zoomy_core.symbolic.primitives_substitution module#
Substitution + solver primitives.
Four primitives, all mathematically transparent:
subst()— purely-structuralxreplace-based substitution. Accepts a dict{lhs: rhs}or any object exposing_as_relation(e.g. a Relation produced bysolve_for()).function_expand()— function-level rewritef(args) → rhs_callable(*args)everywherefappears as a function call. Lifted from_FieldExpansion(ins_generator.py:3764).subs_at_point()— produce a heldSubs(expr, var, value)rendered asexpr|_{var=value}by the latex printer.solve_for()— solve an equation for a single variable, withDerivative(Integral(...))atoms protected against sympy’s Leibniz expansion (the bug-1 fix lifted verbatim from_NodeProxy.solve_for, derived_system.py:326).
- zoomy_core.symbolic.primitives_substitution.subst(expr, rule)#
Apply
rule(dict or Relation) via structuralxreplace.Pure
xreplace— no sympy.subs()mass-substitution behaviour, no Subs wrapping when the substitution lands inside aDerivativewhose differentiation variable matches a key. Sympy’sxreplaceis pure structural matching — what you ask for is what you get.Accepted
ruleshapes:dictof{lhs: rhs}.Any object with an
_as_relationattribute (a dict), typically produced bysolve_for().Any object with an
apply_to(expr)method, in which case the method is delegated to (matches the legacyRelation/Materialprotocol).
- zoomy_core.symbolic.primitives_substitution.function_expand(expr, field_fn, rhs_callable)#
Rewrite every call of
field_fneverywhere inexpr.rhs_callable(*args)is invoked with the call’s positional arguments and must return the replacement expression. Used to insert a velocity ansatz likeu(t, x, arg) → α_0 + α_1·φ_1((arg − b)/h)
The walk goes through
Derivative,Subs,Integralintegrands — anywhere afield_fncall lives.Lifted verbatim from
_FieldExpansion.apply_to(ins_generator.py:3785) — already a clean primitive.
- zoomy_core.symbolic.primitives_substitution.subs_at_point(expr, var, value)#
Produce a held
Subs(expr, (var,), (value,)).The held form means the latex printer renders it as
expr|_{var=value}. Onlyun_subs()(inzoomy_core.symbolic.primitives_canonical) may unwrap it, and only when the safety guards pass.
- zoomy_core.symbolic.primitives_substitution.solve_for(eq_expr, variable)#
Solve
eq_expr == 0forvariable; return a relation dict{variable: solution}.Derivative(Integral(...))atoms ineq_exprare protected as Dummies beforesp.solvesees them. Without this protection sympy eagerly Leibniz-expands such atoms — the legacy bug-1 source in_NodeProxy.solve_for(derived_system.py:326), which produced a substitution rule like∂_t h = ∫∂_x u dẑ − u(b+h)·∂_x(b+h) + u(b)·∂_x b
instead of the conservative form
∂_t h = −∂_x[Integral(u, (ẑ, b, b+h))]
that pen-and-paper uses. When applied to an equation containing
α·∂_t hand then composed with_FieldExpansion, the Leibniz-expanded form double-counts the moving-frame contribution. The Dummy-protect/unprotect bracket avoids the issue entirely.Returns a plain
dictso callers can pass it directly tosubst(). UsePrimitiveDoesNotMatchsemantics: if no solution exists, raise.Lifted verbatim from
_NodeProxy.solve_for(derived_system.py:326).