zoomy_core.model.derivation.closure

zoomy_core.model.derivation.closure#

Concrete-level Galerkin resolve, conservative-form folding, simplification and term-tagging operations of the derivation pipeline. They carry a modal ansatz to the K&T SME system form that system_extract classifies into SystemModel slots.

Module Contents#

Classes#

Split

Fold a self-product flux term c·f·∂_x f into ∂_x(c·f²/2) by splitting it in two halves and applying the inverse product rule to one half. Value-preserving (½·I + ½·ProductRule⁻¹); apply it to the self-product terms only.

InvertMassMatrix

Divide a projected moment row by the coefficient of its ∂_t term, normalising ∂_t Q_k to unit coefficient (the mass-matrix entry is read off the row). Apply it after the stray ∂_t h has been removed so the row carries a single diagonal ∂_t term.

FoldConservative

Tracked op form of fold_to_conservative_form: conservative diffusion atoms ∂_x(D·∂_x q) are left untouched and the hyperbolic remainder is folded. Arguments mirror fold_to_conservative_form (gravity is its gravity_param).

Resolve

Concrete-level Galerkin project-and-resolve: multiply the row by the test weight c(ζ)·φ(l, ζ), substitute the concrete basis polynomials, and integrate over ζ with the ∂_x / ∂_t derivatives kept inside the integral. Apply after the truncation bound is concrete and the Sum ansatz is expanded; modal coefficients and h, b factors are left untouched.

ResolveIntegral

Resolve the opaque ∫…dζ atoms left by an abstract projection, each by a selectable method: “auto” (classified per term by shape), “basis”, “ftc” or “numerical”. classify (Integral -> method or None) overrides the default method per integral.

Consolidate

Op form of consolidate with fold_self and fold_sums enabled.

GaussQuadrature

Replace every definite Integral atom with numeric bounds by an order-point Gauss-Legendre sum Σ_g w_g f(ζ_g), for integrands without a closed form (e.g. non-polynomial material closures). Exact for polynomials up to degree 2·order − 1; apply after the basis is resolved.

DeferQuadrature

Hide every surviving moment-bearing ∫_0^1 f dζ as an opaque NumQuad(f) atom, so non-analytic rational closure integrals ride the rest of the derivation unexpanded. Apply before ResolveBasis; pure-ζ Galerkin brackets are left for analytic closure.

ResolveNumQuad

Resolve every opaque NumQuad(f) to its order-point Gauss-Legendre sum over ζ in [0, 1]; run it as the last derivation step. basis (optional) resolves opaque φ(k, ζ) still present in a bracket body at the nodes.

Simplify

Canonicalisation that keeps fluxes grouped while exposing cancellations: pull_consts followed by consolidate. On an Equation it also runs Sort as a final step (sort=False skips it).

AutoTag

Tag each untagged additive term with its physics category, leaving existing tags alone. Pairs with SortByTag; Sort runs both.

SortByTag

Reorder an equation’s terms into TAG_ORDER, stable within each group.

Sort

AutoTag then SortByTag; apply it last, because a later expression-level op rebuilds the term list and drops the ordering.

Functions#

fold_to_conservative_form

Fold the spatial part of a momentum/continuity residual into conservative flux and hydrostatic-pressure bundles, leaving bed and cross-mode couplings as explicit nonconservative terms. Reproduces the production K&T decomposition, e.g. g·h·∂_x b + ∂_x(g·h²/2) + ∂_x(q_0²/h + q_1²/3h) + ∂_t q_0.

is_conservative_diffusion

True iff term is coeff·∂_x(F) with a single first-order ∂_x factor whose flux F itself contains a ∂_x, the ∂_x(D·∂_x q) shape that system_extract types as diffusion_matrix. Advective fluxes ∂_x(q²/h) and first-order couplings coeff·∂_x q do not match.

project_conservative_diffusion

Galerkin-project a viscous second-order momentum block so the diffusive flux stays in conservative ∂_x(F^d) form (typed as diffusion_matrix), with the σ-metric residual integrated in place. Built from Multiply(test weight), the inverse ProductRule, abstract Integrate and ResolveIntegral (basis); the sum equals the in-place Resolve.

pull_consts

∂_v(c·f) -> c·∂_v f for every factor c independent of the derivative variables, to a fixpoint and through nested derivatives. Gentle: it never applies the product rule to a v-dependent product, so ∂_x(g h²/2) -> (g/2)∂_x(h²) keeps h² grouped.

fold_conservatives

Recover conservative groupings g·∂_v f + f·∂_v g -> ∂_v(f·g) by a pairwise fold applied only when it is exact. Terms with Integral atoms and genuine nonconservative couplings are left untouched.

leibniz_fold

Pairwise Leibniz fold with a shared passive factor M: M·a·∂_v b + M·b·∂_v a -> M·∂_v(a·b). Accepted only when exact and M carries no derivative or integral.

fold_self_products

Fold a self-product M·f·∂_v f into M·∂_v(f²/2) (M moved inside when v-independent), e.g. g·h·∂_x h -> ∂_x(g h²/2). Fires only when M does not reintroduce f, which makes the fold idempotent.

push_consts_into_flux

Push every coordinate-free factor back into a bare derivative (inverse of pull_consts), e.g. (g/2)·∂_x(h²)·⟨c,φ_k⟩ -> ∂_x((g/2)·h²·⟨c,φ_k⟩). Fires only on a term with one derivative factor whose other factors are all coordinate-free; also applied inside every Sum.

merge_sums_over_add

Merge Sum(A, lims) + Sum(B, lims) -> Sum(A + B, lims) for identical limits, pulling index-independent coefficients inside first. The Sum analogue of merge_integrals_over_add.

consolidate

Unify terms under a common outer derivative: leibniz_fold on the top-level terms and, after merging same-limit integrals, recursively on every integrand and Sum summand.

Data#

API#

class zoomy_core.model.derivation.closure.Split(variables=None, name='split')#

Bases: zoomy_core.model.operations.Operation

Fold a self-product flux term c·f·∂_x f into ∂_x(c·f²/2) by splitting it in two halves and applying the inverse product rule to one half. Value-preserving (½·I + ½·ProductRule⁻¹); apply it to the self-product terms only.

Initialization

variables restricts the fold to these fields (None: all).

whole_leaf_op#

True

class zoomy_core.model.derivation.closure.InvertMassMatrix(time=None, name='invert_mass_matrix')#

Bases: zoomy_core.model.operations.Operation

Divide a projected moment row by the coefficient of its ∂_t term, normalising ∂_t Q_k to unit coefficient (the mass-matrix entry is read off the row). Apply it after the stray ∂_t h has been removed so the row carries a single diagonal ∂_t term.

Initialization

time is the time coordinate (default: the global t).

whole_leaf_op#

True

zoomy_core.model.derivation.closure.fold_to_conservative_form(expr, flux_fields, *, h, b, x, gravity_param)#

Fold the spatial part of a momentum/continuity residual into conservative flux and hydrostatic-pressure bundles, leaving bed and cross-mode couplings as explicit nonconservative terms. Reproduces the production K&T decomposition, e.g. g·h·∂_x b + ∂_x(g·h²/2) + ∂_x(q_0²/h + q_1²/3h) + ∂_t q_0.

Args

  • expr: residual of one row, in Function form.

  • flux_fields: conserved modal fields q(k, t, x).

  • h, b: depth and bed (applied Functions).

  • x: spatial coordinate.

Returns

The re-bundled residual.

References

  • Kowalski, Torrilhon 2019, https://doi.org/10.4208/cicp.OA-2017-0263

class zoomy_core.model.derivation.closure.FoldConservative(flux_fields, *, h, b, x, gravity=None, name='fold_conservative')#

Bases: zoomy_core.model.operations.Operation

Tracked op form of fold_to_conservative_form: conservative diffusion atoms ∂_x(D·∂_x q) are left untouched and the hyperbolic remainder is folded. Arguments mirror fold_to_conservative_form (gravity is its gravity_param).

Initialization

Store the fold arguments.

whole_leaf_op#

True

class zoomy_core.model.derivation.closure.Resolve(test_function, basis_cls, level, *, var=None, bounds=(0, 1), name='resolve')#

Bases: zoomy_core.model.operations.Operation

Concrete-level Galerkin project-and-resolve: multiply the row by the test weight c(ζ)·φ(l, ζ), substitute the concrete basis polynomials, and integrate over ζ with the ∂_x / ∂_t derivatives kept inside the integral. Apply after the truncation bound is concrete and the Sum ansatz is expanded; modal coefficients and h, b factors are left untouched.

Args

  • test_function: Galerkin test weight, typically c(ζ)·φ(l, ζ) with l bound to the row’s mode.

  • basis_cls: concrete basis class providing eval(k, ζ) and weight.

  • level: concrete basis level.

  • var: ζ integration variable (default zeta).

  • bounds: integration interval (default (0, 1)).

References

  • Kowalski, Torrilhon 2019, https://doi.org/10.4208/cicp.OA-2017-0263

Initialization

Build the test-weight multiplier and the concrete basis.

whole_leaf_op#

True

class zoomy_core.model.derivation.closure.ResolveIntegral(basis_cls=None, *, var=None, method='auto', level=None, weight=None, classify=None, bounds=(0, 1), name='resolve_integral')#

Bases: zoomy_core.model.operations.Operation

Resolve the opaque ∫…dζ atoms left by an abstract projection, each by a selectable method: “auto” (classified per term by shape), “basis”, “ftc” or “numerical”. classify (Integral -> method or None) overrides the default method per integral.

Args

  • basis_cls: concrete basis used by the “basis” and “auto” methods.

  • method: default resolution method.

  • weight: basis weight when no basis_cls is given.

  • classify: optional per-integral method override.

Initialization

Configure the default method, basis, and bounds.

whole_leaf_op#

True

zoomy_core.model.derivation.closure.is_conservative_diffusion(term, x)#

True iff term is coeff·∂_x(F) with a single first-order ∂_x factor whose flux F itself contains a ∂_x, the ∂_x(D·∂_x q) shape that system_extract types as diffusion_matrix. Advective fluxes ∂_x(q²/h) and first-order couplings coeff·∂_x q do not match.

zoomy_core.model.derivation.closure.project_conservative_diffusion(visc_expr, test_weight, *, basis_cls, level, var, x)#

Galerkin-project a viscous second-order momentum block so the diffusive flux stays in conservative ∂_x(F^d) form (typed as diffusion_matrix), with the σ-metric residual integrated in place. Built from Multiply(test weight), the inverse ProductRule, abstract Integrate and ResolveIntegral (basis); the sum equals the in-place Resolve.

Args

  • visc_expr: viscous term of the momentum row.

  • test_weight: Galerkin test weight c·φ_k.

  • basis_cls, level: concrete basis and its level.

  • var: ζ integration variable.

  • x: horizontal coordinate.

Returns

The resolved viscous moment (conservative flux plus metric residual).

zoomy_core.model.derivation.closure.pull_consts(expr)#

∂_v(c·f) -> c·∂_v f for every factor c independent of the derivative variables, to a fixpoint and through nested derivatives. Gentle: it never applies the product rule to a v-dependent product, so ∂_x(g h²/2) -> (g/2)∂_x(h²) keeps h² grouped.

zoomy_core.model.derivation.closure.fold_conservatives(expr)#

Recover conservative groupings g·∂_v f + f·∂_v g -> ∂_v(f·g) by a pairwise fold applied only when it is exact. Terms with Integral atoms and genuine nonconservative couplings are left untouched.

zoomy_core.model.derivation.closure.leibniz_fold(expr)#

Pairwise Leibniz fold with a shared passive factor M: M·a·∂_v b + M·b·∂_v a -> M·∂_v(a·b). Accepted only when exact and M carries no derivative or integral.

zoomy_core.model.derivation.closure.fold_self_products(expr)#

Fold a self-product M·f·∂_v f into M·∂_v(f²/2) (M moved inside when v-independent), e.g. g·h·∂_x h -> ∂_x(g h²/2). Fires only when M does not reintroduce f, which makes the fold idempotent.

zoomy_core.model.derivation.closure.push_consts_into_flux(expr, coords=None)#

Push every coordinate-free factor back into a bare derivative (inverse of pull_consts), e.g. (g/2)·∂_x(h²)·⟨c,φ_k⟩ -> ∂_x((g/2)·h²·⟨c,φ_k⟩). Fires only on a term with one derivative factor whose other factors are all coordinate-free; also applied inside every Sum.

zoomy_core.model.derivation.closure.merge_sums_over_add(expr)#

Merge Sum(A, lims) + Sum(B, lims) -> Sum(A + B, lims) for identical limits, pulling index-independent coefficients inside first. The Sum analogue of merge_integrals_over_add.

zoomy_core.model.derivation.closure.consolidate(expr, fold_self=False, fold_sums=False)#

Unify terms under a common outer derivative: leibniz_fold on the top-level terms and, after merging same-limit integrals, recursively on every integrand and Sum summand.

Args

  • expr: expression to fold.

  • fold_self: also fold self-products (fold_self_products) and push constants back into fluxes.

  • fold_sums: hide Galerkin brackets and merge same-index Sums first.

class zoomy_core.model.derivation.closure.Consolidate(name='consolidate')#

Bases: zoomy_core.model.operations.Operation

Op form of consolidate with fold_self and fold_sums enabled.

Initialization

Create the op.

whole_leaf_op#

True

log_level#

5

class zoomy_core.model.derivation.closure.GaussQuadrature(var=None, order=8, name=None)#

Bases: zoomy_core.model.operations.Operation

Replace every definite Integral atom with numeric bounds by an order-point Gauss-Legendre sum Σ_g w_g f(ζ_g), for integrands without a closed form (e.g. non-polynomial material closures). Exact for polynomials up to degree 2·order − 1; apply after the basis is resolved.

Args

  • var: integration variable (default zeta).

  • order: number of Gauss nodes (>= 1).

References

  • Golub, Welsch 1969, https://doi.org/10.1090/s0025-5718-69-99647-1

Initialization

Store the node count and validate it.

whole_leaf_op#

True

log_level#

1

zoomy_core.model.derivation.closure.NumQuad#

‘type(…)’

class zoomy_core.model.derivation.closure.DeferQuadrature(var=None, name='defer_quadrature')#

Bases: zoomy_core.model.operations.Operation

Hide every surviving moment-bearing ∫_0^1 f dζ as an opaque NumQuad(f) atom, so non-analytic rational closure integrals ride the rest of the derivation unexpanded. Apply before ResolveBasis; pure-ζ Galerkin brackets are left for analytic closure.

Initialization

Set the integration variable (default zeta).

whole_leaf_op#

True

log_level#

1

class zoomy_core.model.derivation.closure.ResolveNumQuad(var=None, order=8, basis=None, name=None)#

Bases: zoomy_core.model.operations.Operation

Resolve every opaque NumQuad(f) to its order-point Gauss-Legendre sum over ζ in [0, 1]; run it as the last derivation step. basis (optional) resolves opaque φ(k, ζ) still present in a bracket body at the nodes.

References

  • Golub, Welsch 1969, https://doi.org/10.1090/s0025-5718-69-99647-1

Initialization

Store the node count and the optional basis.

whole_leaf_op#

True

log_level#

1

class zoomy_core.model.derivation.closure.Simplify(name='simplify', sort=True)#

Bases: zoomy_core.model.operations.Operation

Canonicalisation that keeps fluxes grouped while exposing cancellations: pull_consts followed by consolidate. On an Equation it also runs Sort as a final step (sort=False skips it).

Initialization

sort toggles the final Sort pass on Equations.

whole_leaf_op#

True

log_level#

5

apply_to_equation(eq)#

Canonicalise the residual, then tag and sort its terms when sort is on.

zoomy_core.model.derivation.closure.TAG_ORDER#

[‘time_derivative’, ‘flux’, ‘pressure_flux’, ‘diffusion’, ‘nonconservative_flux’, ‘source’, ‘untagge…

class zoomy_core.model.derivation.closure.AutoTag(name='auto_tag', detect_ncp=True)#

Bases: zoomy_core.model.operations.Operation

Tag each untagged additive term with its physics category, leaving existing tags alone. Pairs with SortByTag; Sort runs both.

Initialization

detect_ncp toggles detection of nonconservative products (off: they become source).

log_level#

5

apply_to_equation(eq)#

Tag the untagged terms of eq.

class zoomy_core.model.derivation.closure.SortByTag(name='sort_by_tag')#

Bases: zoomy_core.model.operations.Operation

Reorder an equation’s terms into TAG_ORDER, stable within each group.

Initialization

Create the op.

log_level#

5

apply_to_equation(eq)#

Sort eq’s terms by tag rank.

class zoomy_core.model.derivation.closure.Sort(name='sort', detect_ncp=True)#

Bases: zoomy_core.model.operations.Operation

AutoTag then SortByTag; apply it last, because a later expression-level op rebuilds the term list and drops the ordering.

Initialization

detect_ncp is passed to AutoTag.

log_level#

5

apply_to_equation(eq)#

Auto-tag, then sort eq’s terms.