zoomy_core.symbolic package

Contents

zoomy_core.symbolic package#

Principled symbolic-derivation primitive layer for zoomy_core.

This package replaces the semi-automatic, partially sympy-driven pipeline in zoomy_core.model.models.ins_generator with a closed set of well-defined mathematical primitives. Every primitive is ONE textbook calculus rule that fires only when explicitly invoked. Sympy is used for arithmetic on Add/Mul/Pow and for purely-structural xreplace; sympy is never used to fire chain rule, Leibniz, IBP, common-denominator, factor, or .doit().

Public surface (Phase 0):

  • D(), Int(), Sub() — held-form sympy constructors (always evaluate=False).

  • PrimitiveDoesNotMatch — exception raised when a primitive’s preconditions fail.

  • AutoEvalGuard — context manager that bans sympy auto-evaluation calls inside its block (used in tests + the slim walkthrough verifier).

Primitives are added incrementally during Phase 0:

  • primitives_canonical — purely-structural rewriters (AlphaRename, SplitIntegralOverAdd, MergeIntegralsOverAdd, DistributeMulOverAdd, ZeroDerivativeOfFreeSymbol, UnSubs).

  • primitives_calculus — calculus rules (ProductRuleForward, ProductRuleInverse, ChainRule, Leibniz, FundamentalTheorem, IntegrationByParts, PolynomialIntegrate, DistributeDerivativeOverAdd, PullScalarOutOfDerivative, PushScalarIntoDerivative).

  • primitives_change_of_variableAffineChangeOfVariable.

  • primitives_basisCanonicalizePhiDerivativeSubs, ProjectBasisIntegrand.

  • primitives_substitutionSubst, FunctionExpand, SubsAtPoint, SolveFor.

  • canonicalise — the fixed, finite-step canonicalise pass that replaces the legacy _simplify_preserve_integrals 6-iteration fixpoint loop.

zoomy_core.symbolic.D(expr, *vars)#

Held Derivative(expr, *vars).

Parameters:
  • expr (sympy.Expr) –

  • *vars – Each entry is either a single Symbol (∂_v applied once) or a (symbol, n) tuple (∂_v^n). Same shape sympy’s Derivative accepts.

Returns:

Always held — never auto-evaluated. The primitive layer decides when to apply chain/product/Leibniz rules.

Return type:

sympy.Derivative

zoomy_core.symbolic.Int(integrand, *limits)#

Held Integral(integrand, *limits).

Each limit is either a bare Symbol (indefinite integral, single arg) or a (symbol, lower, upper) triple (definite integral).

sp.Integral is held by default (it never auto-evaluates on construction), so this is a thin documenting wrapper. Only .doit() or sp.integrate(...) would evaluate it — both are forbidden inside the symbolic package.

zoomy_core.symbolic.Sub(expr, var, value)#

Held Subs(expr, var, value).

sp.Subs is held by default, so this is a thin alias that documents intent: any “evaluate expr at var = value” should go through Sub and produce a held atom rendered as expr|_{var=value} by the latex printer.

The primitive UnSubs is the only thing that may unwrap this.

zoomy_core.symbolic.held_function(name, *args, **kwargs)#

Construct a held UndefinedFunction call f(*args).

A passthrough wrapper for completeness — sympy’s default behaviour on UndefinedFunction calls is already non-evaluating, so this is just sp.Function(name)(*args).

exception zoomy_core.symbolic.PrimitiveDoesNotMatch(primitive, atom, reason='')#

Bases: Exception

The primitive’s structural pattern did not match the input.

Carries the primitive name and the (sympy) atom that failed the match so the caller can localise the error.

Parameters:
  • primitive (str) –

  • reason (str) –

class zoomy_core.symbolic.AutoEvalGuard#

Bases: object

Monkeypatch sympy.Basic.doit and the eval-style sympy helpers to raise. Restored on exit.

exception zoomy_core.symbolic.AutoEvalForbidden#

Bases: RuntimeError

Raised when a forbidden sympy auto-evaluation call fires.

The redesign’s invariant is: every math rule is invoked by an explicit primitive. Sympy’s auto-evaluation calls (.doit(), sp.simplify, etc.) bypass that surface and cause subtle order-of-operations bugs. The guard makes any such call a hard failure.

zoomy_core.symbolic.canonicalise(expr)#

Normalise expr via the fixed structural pipeline.

Pure: canonicalise(canonicalise(x)) == canonicalise(x). Equality-preserving: canonicalise(x - y) == 0 iff x and y are structurally equal up to the canonicalisation rules (alpha-renaming of bound variables, Mul-over-Add distribution, Integral linearity, zero-collapse).

zoomy_core.symbolic.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.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.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.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.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.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.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.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).

zoomy_core.symbolic.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.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.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.chain_rule(term, outer_predicate, var)#

∂_v f(g(v)) f'(g)·∂_v g where outer_predicate(f) bool selects the function families to expand.

Walks the expression and applies the chain rule wherever the pattern matches. outer_predicate is a callable taking a Function instance (the call site, e.g. phi_1((z-b)/h)) and returning True if 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 Derivative atoms whose inner is a matching function call; replaces with the explicit chain-rule expansion using held Derivative atoms. No .doit() — other Derivative atoms are not touched.

zoomy_core.symbolic.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 Mul whose as_coeff_Mul separates a numeric scalar → pull it out (separate primitive pull_scalar_out_of_derivative() does that; this one only does Add-distribution).

  • Trivially zero (inner == 0 or inner doesn’t reference any diff-var) → returns 0.

No .doit() — distributed Derivative atoms are held.

zoomy_core.symbolic.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 g and higher-order-in-var (one var peels off). Mixed-order ∂_var ∂_other g strips one var from the diff-tuple.

Returns None if the integrand isn’t a Derivative whose diff-vars include var (caller convention preserved from ins_generator’s rule-registry: None = “rule does not apply”, not an exception).

zoomy_core.symbolic.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_integrand already carries the - sign and is wrapped in a Integral(..., (var, lower, upper)).

Lifted from integrate_by_parts (ins_generator.py:4757), with the IBPResult wrapper unpacked since it lives in the legacy layer.

zoomy_core.symbolic.leibniz(integrand, var, lower, upper)#

∫_lo^hi ∂_y f dvar = ∂_y(∫_lo^hi f dvar) f(hi)·∂_y hi + f(lo)·∂_y lo when y var and f is polynomial in var.

Lifted from _rule_derivative_of_polynomial (ins_generator.py:1976) with the inner _doit_only_derivatives walk removed. If the caller wants var-factors to surface from Derivative(var·g, y) etc. they must run distribute_derivative_over_add() first.

Returns None when:

  • integrand is not a Derivative,

  • diff-tuple has more than one entry,

  • differentiation is w.r.t. var itself (fundamental_theorem handles that),

  • the integrand is independent of var,

  • sp.Poly cannot build a polynomial in var.

zoomy_core.symbolic.leibniz_general(integrand, var, lower, upper)#

Conservative-form Leibniz that does NOT require f to be polynomial in var.

For ∫_lo^hi ∂_y f dvar with y var:

= ∂_y[ ∫_lo^hi f dvar ]   −  f|_hi · ∂_y hi   +   f|_lo · ∂_y lo

The volume ∫f dvar stays as a held sympy.Integral atom — no attempt to evaluate it. Boundary terms are produced as held sympy.Subs atoms when the bounds depend on y.

Use this when the integrand’s antiderivative is unknown or opaque; use leibniz() (poly-specialised) when f is polynomial in var and you want the closed-form volume.

Returns None if:

  • integrand is not a Derivative,

  • diff-tuple has more than one entry,

  • differentiation is w.r.t. var itself (fundamental_theorem handles that).

zoomy_core.symbolic.polynomial_integrate(integrand, var, lower, upper)#

∫_lo^hi p(var) dvar via sp.Poly.integrate for polynomial p (with arbitrary opaque coefficients).

Lifted from _rule_polynomial_integrand (ins_generator.py:2061) with the inner _doit_derivs walk removed. If the caller wants var-factors to surface from Derivative(var·g, y) etc. they must run distribute_derivative_over_add() first.

Returns (hi lo) · integrand for var-free integrands (constant integrand rule). Returns None if sp.Poly cannot build a polynomial.

zoomy_core.symbolic.product_rule_forward(term, var)#

∂_v(Π fᵢ) Σᵢ (Π_{j≠i} fⱼ)·∂_v fᵢ and ∂_v(f^n) n·f^(n-1)·∂_v f for integer n 2.

Acts on a single Derivative atom. If term is not a Derivative, returns term unchanged (no-op). If the derivative has multiple variables or its inner is neither a Mul nor an integer-power Pow, returns term unchanged.

Lifted from the forward branch of ProductRule._one_term (ins_generator.py:3358). No .doit() involved — every output factor is a held Derivative.

zoomy_core.symbolic.product_rule_inverse(term, var)#

coeff · ∂_v f ∂_v(coeff · f) ∂_v(coeff) · f.

The legacy version (ProductRule._one_term inverse branch, ins_generator.py:3402) called Derivative(coeff, var).doit() on the residual. That .doit() was the exact bug-3 source: when coeff is 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 when coeff is genuinely var-free, otherwise the user calls chain_rule() or product_rule_forward() explicitly to expand it.

zoomy_core.symbolic.pull_scalar_out_of_derivative(expr)#

∂_v(c·f) c·∂_v f when c is a sympy Number (Integer/Rational/Float).

Hoisted from the Mul.as_coeff_Mul branch of _evaluate_linear_derivatives (ins_generator.py:4644). Numeric-only — free symbols stay inside.

zoomy_core.symbolic.push_scalar_into_derivative(expr)#

c · ∂_v f ∂_v(c · f) when c is a sympy Number.

Inverse of pull_scalar_out_of_derivative(). Lifted verbatim from _fold_numeric_coeffs_into_derivative (ins_generator.py:4674).

zoomy_core.symbolic.affine_change_of_variable(expr, var, lower, upper, ref_var, ref_lo=0, ref_hi=1)#
Apply ``∫_{lower}^{upper} f(var) dvar
= ((upper-lower)/(ref_hi-ref_lo)) ·

∫_{ref_lo}^{ref_hi} f(φ(ref_var)) d(ref_var)``

where φ(s) = lower + (upper-lower)·(s-ref_lo)/(ref_hi-ref_lo).

Walks expr and rewrites every Integral(f, (var, lower, upper)) whose limits structurally match the given tuple. Other Integral atoms are left untouched — call the primitive again with their limits if you want them transformed.

Bottom-up walk: nested integrals’ integrands are processed first so an inner running integral is in the new variable form before the outer transform runs.

Examples

>>> from zoomy_core.symbolic import Int
>>> from zoomy_core.symbolic.primitives_change_of_variable import affine_change_of_variable
>>> import sympy as sp
>>> z, h = sp.symbols('z h', real=True)
>>> b = sp.Function('b')(sp.Symbol('t'), sp.Symbol('x'))
>>> zeta = sp.Symbol(r'\hat{\zeta}', real=True)
>>> e = Int(sp.Function('f')(z), (z, b, b + h))
>>> affine_change_of_variable(e, z, b, b + h, zeta)
zoomy_core.symbolic.canonicalize_phi_derivative_subs(expr)#

Subs(Derivative(phi_k(ξ), ξ), ξ, val) Derivative(phi_k(val), val).

The chain-rule artefact sympy creates when a phi_k(arg) is substituted into a Derivative context. Recanonicalising as a plain Derivative(phi_k(val), val) makes the pattern visible to downstream basis-cache lookups.

Lifted verbatim from ProjectBasisIntegrals._canon (ins_generator.py:2236). Pure xreplace — no math change.

zoomy_core.symbolic.has_phi_call(e)#

True iff e contains any phi_* function call.

zoomy_core.symbolic.project_basis_integrand(expr, basis_cache)#

Look up every Integral(kernel, (ζ̂, 0, 1)) whose kernel contains a phi_* call in the given basis_cache and substitute the polynomial result.

Single-pass: walks the expression once, replacing each matching Integral in place. Outer factors are pulled out structurally: integrand factors that don’t reference the integration variable become an outer coefficient; the rest is the kernel handed to the cache.

Lifted from the Pass-3 _map of ProjectBasisIntegrals (ins_generator.py:2282), with the surrounding fixpoint loop (lines 2339-2344) removed — the caller is expected to compose this primitive with canonicalize_phi_derivative_subs() and zoomy_core.symbolic.primitives_canonical.un_subs() for any boundary-Subs resolution.

zoomy_core.symbolic.function_expand(expr, field_fn, rhs_callable)#

Rewrite every call of field_fn everywhere in expr.

rhs_callable(*args) is invoked with the call’s positional arguments and must return the replacement expression. Used to insert a velocity ansatz like

u(t, x, arg) → α_0 + α_1·φ_1((arg − b)/h)

The walk goes through Derivative, Subs, Integral integrands — anywhere a field_fn call lives.

Lifted verbatim from _FieldExpansion.apply_to (ins_generator.py:3785) — already a clean primitive.

zoomy_core.symbolic.solve_for(eq_expr, variable)#

Solve eq_expr == 0 for variable; return a relation dict {variable: solution}.

Derivative(Integral(...)) atoms in eq_expr are protected as Dummies before sp.solve sees 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 h and 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 dict so callers can pass it directly to subst(). Use PrimitiveDoesNotMatch semantics: if no solution exists, raise.

Lifted verbatim from _NodeProxy.solve_for (derived_system.py:326).

zoomy_core.symbolic.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}. Only un_subs() (in zoomy_core.symbolic.primitives_canonical) may unwrap it, and only when the safety guards pass.

zoomy_core.symbolic.subst(expr, rule)#

Apply rule (dict or Relation) via structural xreplace.

Pure xreplace — no sympy .subs() mass-substitution behaviour, no Subs wrapping when the substitution lands inside a Derivative whose differentiation variable matches a key. Sympy’s xreplace is pure structural matching — what you ask for is what you get.

Accepted rule shapes:

  • dict of {lhs: rhs}.

  • Any object with an _as_relation attribute (a dict), typically produced by solve_for().

  • Any object with an apply_to(expr) method, in which case the method is delegated to (matches the legacy Relation / Material protocol).

zoomy_core.symbolic.BoundaryIntegral(integrand, domain)#

Construct a symbolic ∫_∂D f ds atom.

domain should be a boundary domain (e.g. K.boundary()). The result is an opaque sympy.Function call whose class carries _domain back to domain — operations like DivergenceTheorem and MapToReferenceElement walk for instances of this class to find boundary integrals.

Parameters:
  • integrand (Expr) –

  • domain (Domain) –

Return type:

Expr

class zoomy_core.symbolic.Box(intervals, name=None)#

Bases: Domain

Tensor product of Interval factors.

Parameters:
  • intervals (Sequence[Interval]) –

  • name (str) –

boundary()#
Return type:

BoxBoundary

reference()#
Return type:

Box

affine_map()#

Return (B, V0) such that x = V0 + B · ξ maps the reference element to self. Defined only on volume domains.

Return type:

tuple[sympy.matrices.dense.MutableDenseMatrix, sympy.matrices.dense.MutableDenseMatrix]

class zoomy_core.symbolic.BoxBoundary(parent, name=None)#

Bases: _BoundaryDomain

Opaque boundary of a Box — same convention as SimplexBoundary.

Parameters:
  • parent (Domain) –

  • name (str) –

class zoomy_core.symbolic.Domain(coords, name)#

Bases: object

Continuous integration region in symbolic form.

Subclasses fill in geometry (vertices, intervals) and override boundary, reference, affine_map, normal as appropriate for their shape.

Parameters:
  • coords (tuple[sympy.core.symbol.Symbol, ...]) –

  • name (str) –

coords: tuple[sympy.core.symbol.Symbol, ...]#
name: str#
property dim: int#
boundary()#
Return type:

Domain

reference()#
Return type:

Domain

affine_map()#

Return (B, V0) such that x = V0 + B · ξ maps the reference element to self. Defined only on volume domains.

Return type:

tuple[sympy.matrices.dense.MutableDenseMatrix, sympy.matrices.dense.MutableDenseMatrix]

normal()#

Return the sympy.Function class for the outward unit normal on self. Defined only on boundary domains.

Return type:

type

property boundary_integral_fn: type#

sympy.Function subclass for ∫_∂(self) f ds atoms.

One class per Domain instance. The back-reference cls._domain lets operations recover the surrounding domain. Renders in LaTeX as ∮_{<name>} f \, dS via the _latex method.

property normal_fn: type#

sympy.Function subclass for outward unit normal components.

Calls n(i) carry the back-reference cls._domain so downstream operations can verify “this normal belongs to that boundary”. Renders in LaTeX as n_<name>^{(i)}.

class zoomy_core.symbolic.Interval(var, a, b, name=None)#

Bases: Domain

1D interval [a, b] parametrised by a single var.

Parameters:
  • var (sp.Symbol) –

  • name (str) –

boundary()#
Return type:

PointSet

reference()#
Return type:

Interval

affine_map()#

Return (B, V0) such that x = V0 + B · ξ maps the reference element to self. Defined only on volume domains.

Return type:

tuple[sympy.matrices.dense.MutableDenseMatrix, sympy.matrices.dense.MutableDenseMatrix]

zoomy_core.symbolic.NormalVector(domain)#

Return the sympy.Function class for outward unit normal components on domain.

Usage:

n = NormalVector(K.boundary())
flux = sp.diff(u, x) * n(0) + sp.diff(u, y) * n(1)
Parameters:

domain (Domain) –

Return type:

type

class zoomy_core.symbolic.PointSet(var, points, name=None)#

Bases: _BoundaryDomain

Degenerate 0-dim boundary — the boundary of an Interval.

Bridge to the existing Subs(f, var, bound) plumbing in ins_generator: a BoundaryIntegral over a PointSet is semantically f|_{var=b} f|_{var=a} and resolves through that convention rather than producing a new symbolic kind.

Parameters:
  • var (sp.Symbol) –

  • points (Sequence) –

  • name (str) –

property dim: int#
class zoomy_core.symbolic.Simplex(vertices, coords, name=None)#

Bases: Domain

n-dim simplex from a vertex tuple.

vertices is a sequence of n+1 points, each a tuple/Matrix of length n (matching coords). B = [V₁−V₀ | V₂−V₀ | …], V₀ is the offset, and the affine map is x = V₀ + B · ξ.

Parameters:
  • vertices (Sequence) –

  • coords (tuple[sympy.core.symbol.Symbol, ...]) –

  • name (str) –

boundary()#
Return type:

SimplexBoundary

reference()#
Return type:

Simplex

affine_map()#

Return (B, V0) such that x = V0 + B · ξ maps the reference element to self. Defined only on volume domains.

Return type:

tuple[sympy.matrices.dense.MutableDenseMatrix, sympy.matrices.dense.MutableDenseMatrix]

class zoomy_core.symbolic.SimplexBoundary(parent, name=None)#

Bases: _BoundaryDomain

Opaque boundary of a Simplex — one symbolic atom covers ∂K (no per-face decomposition in this layer).

Parameters:
  • parent (Domain) –

  • name (str) –

Submodules#