Skip to content

ymf: closure records the split; mesh fingerprint; typed inline values (cekees/ymf#10, #11) - #27

Merged
cekees merged 8 commits into
proteustoolkit:mainfrom
cekees:main
Nov 5, 2026
Merged

cekees merged 8 commits into
proteustoolkit:mainfrom
cekees:main

Conversation

@cekees

@cekees cekees commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

Two reviewed PRs from cekees/ymf, merged there in order. cekees/ymf main is a fast-forward of this repository's main.

proteus CI installs ymf from this repository's main, so this must merge before the next proteus main train: cekees/proteus#200 records and checks the fingerprint, and its tests skip until then.

Merge with "Create a merge commit", so that the fork's main stays a fast-forward of this one.

🤖 Generated with Claude Code

cekees and others added 8 commits November 5, 2026 09:26
The alias expansion (#9) made a closed spec say what was solved for its
boundary conditions. Two more things were still left to a reader's
defaults: a split discretization's sub-problems (a lag written
`φ: previous_step`, a coefficient read through the sub-problem that carries
it, a sub-problem's solver refining the entry's, its regularization
replacing the entry's function by function, its `for` defaulting to what it
introduces), and which coefficients are functions of the state. ymf.explicit
writes them out, and closure.write and closure.input_slice use it in place
of the bare expansion:

- each coefficient whose formula names an unknown, directly or through
  another coefficient, in the map form with `depends_on` (ρ, μ, χ: [φ] in
  the dam break; τb, Fd, Fr, β: [h, q] in Torino);
- each sub-problem with its `for`, merged `solver` and `regularize`, and
  every lag as {of, via, at, carried_by} (flow's χ: {via: vof, at:
  previous_step, carried_by: χh}).

The names are read without sympy: the notation's tokenizer moves to
ymf.names (ymf.symbolic.notation re-exports it) with the parser's rule for
a run of letters (a declared name, else the product of its letters; a
function call is not a name), so the core closes a spec as the front end
does. It agrees with ymf.symbolic.problem.state_coefficients on every
coefficient that resolves (14 in examples and fixtures) and also names the
abstract model's ρ and μ, which the symbolic reader leaves out for lack of
values. A test checks the name reader's function list is the parser's.
`depends_on` and `carried_by` are checked on load (a false one is refused)
and `depends_on` does not count towards a key.

Measured, closing every example and fixture with closure.write on cekees/ymf
main b77262a and on this commit: 9 specs without coefficients of the state
or sub-problems close to byte-identical documents with the same keys;
examples/nonlinear_diffusion gains `depends_on: [u]` and keeps its 6 keys;
the dam break, lake at rest and Torino re-key (the split is written out;
no solver has run a split spec, Proteus refuses one), and each closed spec
reloads with its spec's keys. split_problem gives equal results for the
dam break spec and its closed form. ymf suite 845 passed, 2 skipped (was
827; 16 new in tests/test_explicit.py, 2 core-import cases), macOS arm64,
petsc-dev env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
§ 7 of the C42 model-family note left this for the next PR: adr_problem
and split_problem substitute ρ = ρw (1 − χ) + ρa χ into every slot, which
is all a runner needs, but a kernel generator (C19) and the dispatcher
want ρ once, as an inline function of the state, and the slots over the
name. Beside the substituted form, each coefficient of the state now has

- `inline`: its formula over the state, constants substituted and other
  coefficients of the state kept by name (`uses`);

and the problem (or sub-problem) has `named`: the `order` to evaluate those
coefficients in, each naming only earlier ones, and the ADR slot values
(m, f, a, r, H) over the names. The equations are read a second time with
each coefficient of the state a known field rather than a constant, so a
divergence of ρ v stays a flux and a gradient of ρ is kept (grad_ρ_0). The
derivatives and depends_on stay the substituted form's, which are exact; in
`named` a name is held fixed. Both are absent without coefficients of the
state.

The dam break's flow sub-problem now reads: order χ, ρ, μ; χ := χh (VOF's
carrier), ρ := 998.2 - 996.995*χ, μ := 0.0010021928 - 0.0009841178*χ;
momentum m = v_0*ρ, f = [p + v_0**2*ρ, v_0*v_1*ρ], a = [2*μ, μ], the
vertical's r = 9.8*ρ. Torino's drag law keeps Fr, β and Fd (a vector) by
name; nonlinear_diffusion's a = [k, k] with k := u**2 + 1.

Measured: evaluating each named slot after its inline coefficients equals
the substituted slot at 7 random points per name to 1e-13 (dam-break flow
15 slot values, Torino swe, nonlinear_diffusion; tests/test_named_coefficients.py).
ymf suite 850 passed, 2 skipped (was 845; 5 new, one test updated for the
new key), macOS arm64, petsc-dev env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_hReg)

The model-family note's regularization list (§ 3.1 item 5) had three
entries: heaviside and sign, done in round 1, and u = q/h at h = 0, which
the shallow-water fixture left to a comment. A split discretization (or one
sub-problem) may now give

    regularize:
      inverse: {by: wet_dry_cutoff, of: h, width: hε}

and every negative power of the scalar unknown h it solves for (q⊗q/h,
Manning's h^(-7/3), Fr's h^(-3/2)) is read through Proteus SW2DCV.h's
one_over_hReg, 2h/(h² + max(h, hε)²): 1/h wherever h ≥ hε, bounded below,
0 at h = 0. The rewrite happens after parsing and before differentiation,
so the derivatives are the regularized expression's, and it reaches the
named form's inline coefficients too. `inverse` is the one regularization
that is not a function of the notation, so it names its unknown (`of`);
checked on load (`of` required for inverse, an unknown, and refused on
heaviside or sign). The shallow-water numerics bind hε (meaning:
dry_depth, the solver's 1e-5 max(h0)) and use it; split_problem records
it under `regularized` with its `of`.

Measured: the lake-at-rest flux q_0²/h + 4.905 h² is the unregularized
value to 1e-12 at h = 1, 2hε and hε, equals 4.905 h² + 0.09·2h/(h² + hε²)
at h = hε/4, and is 0 at h = 0 (hε = 1e-5); Torino's Froude drag still
matches the deck to 1e-12 at h = 0.1. ymf suite 854 passed, 2 skipped (was
850; 3 refusal cases, 1 dry-node test; 3 shallow-water tests updated for
the regularized code), macOS arm64, petsc-dev env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…spec file

Decided by cekees 2026-11-05 (C7.1 round 2, decision 2): store the mesh's fingerprint in the
archive now, in the mesh's provenance, so a restart or `pro gauge` can verify the mesh before
reading. ymf carries the definition so every writer computes the same digest; proteus writes and
checks it on cekees/proteus#200.

- archive.provenance(..., fingerprint=) records {method: ymf-mesh-1, sha256}; the canonicalizer
  takes the key (anything but {method, sha256} is refused), validation checks the digest's form.
- ymf.mesh.fingerprint(nodes, cells): SHA-256 over a tag, the shapes (int64), the nodes (float64,
  three columns, 2D padded with z = 0) and the cells (int64), little-endian, in the order given.
  grid_fingerprint(grid) of a stored grid; check_fingerprint(grid, base_dir, digest=None) returns
  the recorded digest, None if none, raises on another mesh or another method; `digest` is the
  input-order digest for a writer whose rows are permuted.

Two gaps proteus found posing a `discrete` domain from a spec file (proteus#200's end-to-end test
worked around both):
- the strict spec schema had no `attributes` (or `provenance`) on a discrete domain, though
  ymf.domain allows them; added.
- strictyaml reads the mesh's keys as Any, so inline values came back as text ("'0' is not a
  number" from the canonicalizer). ymf.domain.type_discrete_block, run on load in
  compose._check_document, types each data item's values by its data_type (and dimensions,
  precision, topology counts as integers), refusing text that is not a number. The archive's own
  refusal of text values (test_inline_values_are_checked) is kept.

Measured: tests 764 passed, 11 skipped (was 751 + 13 new: 7 fingerprint, 1 spec-file round trip
with attributes and typed values, and the parametrized refusals). The unit square of test_mesh
digests to 4631cb7c6e4c040d...; the digest is pinned so a change of definition must change the
method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… `dimension`

Found by proteus once the previous commit let a spec carry an inline mesh: proteus writes 2D
meshes with three coordinates (XYZ, z = 0), and adr_problem took the space dimension from the
geometry's columns, so `dimension: 2` was ignored: dim 3, upper z = 0, and proteus solved a 3D
problem on a 2D mesh (L2 error nan for square4x4_tagged).

_inline_nodes now keeps the first `dimension` coordinates when the block gives one, refuses
coordinates beyond it that vary (a tilted mesh is not planar), and refuses a dimension above the
geometry's width.

Measured: ymf tests 765 passed, 11 skipped (+1). proteus (cekees/proteus#200,
test_a_discrete_domain_inline_with_its_attributes_solves_as_its_file): square4x4_tagged inline
with its node and element tags, before L2 nan at levels 1 and 2, after < 1e-12 and the archive
bit-equal to the `kind: file` run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C42 round 2: closure records the split and state coefficients; names kept in the plain data; wet/dry cutoff
docs/changes.md conflicted (both added 2026-11-05 entries): both kept, this branch's first; its
entry now also names the inline XYZ planar mesh fix (7ae230c). schema.py and symbolic/problem.py
merged cleanly. ymf tests after the merge: 792 passed, 11 skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C7.1: a mesh records its fingerprint; a discrete domain loads from a spec file
@cekees
cekees merged commit c2e646f into proteustoolkit:main Nov 5, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant