Skip to content

C5 step 1: the dispatch table, (formulation × discretization) → mprans module + flags - #211

Draft
cekees wants to merge 7 commits into
devfrom
c5-dispatch
Draft

cekees wants to merge 7 commits into
devfrom
c5-dispatch

Conversation

@cekees

@cekees cekees commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

C5 steps 1 and 2 (claim A1, PARALLEL_SESSIONS §10), following YMF_MODEL_FAMILIES_C42 §6 D1 (dispatch) and D8 (hints are checked, and dispatch wins).

A ymf spec states the model, the formulation (a weak_forms entry named by from_weak_form), and the discretization: spaces, time integration, stabilization, sub-problems, bound symbols and regularization. proteus.Dispatch reads one discretization of a closed spec. For each sub-problem, in solve order, it returns the mprans module, the Coefficients flags, and the physics- and numerics-module attributes. Each value records where it came from: cell, spec, default or hint. The table, proteus/DispatchTable.py, is data only and can be reviewed on its own.

commit what measured
1bf365b DispatchTable.py (13 cells: RANS2P ×2 forms, RANS3PF + PresInc/Pres/PresInit, VOF, NCLS, RDLS, MCorr, CLSVOF, SW2DCV, GN_SW2DCV), Dispatch.py, test/dispatch 20 tests pass. Object-by-object match with the decks' built p/n modules: level_set_five = damBreakLevelSet (5 models, 100 attributes), level_set_clsvof = damBreakLevelSet interface=1 (2, 49), clsvof_projection = damBreak (5, 75), SW2DCV = dam3Bumps (23), GN_SW2DCV = dSWEs_steady_state (16). 10 of 10 single-value mutations are caught.
267de2b ymf_run refuses split discretizations before adr_problem and prints what each one dispatches to before: a SymbolicError traceback on the dam break and the lake at rest; after: exit 2 with the dispatch printed. test_pro_cli + test_adr_problem + test_domain_ymf + test/dispatch: 213 passed, 1 skipped
68f5df0 .github/coverage/floors.json: NCLS3P.py 76.2 → 75.6 Not this PR's code. 76f3e12 on main removed 15 covered statements from NCLS3P (685 → 670) and the same 163 lines are still missed, so 76.2% → 75.67%. The floor is set to floor(10·now)/10. Every branch off main c09280c hits this check.
6f0d07c Step 2, two-phase: ymf_run runs a split discretization from its dispatch. proteus/DispatchRun.py builds a TwoPhaseFlowProblem from the dispatch and from ymf's split_problem: Tank2D + BC_RANS setters matched on the closed conditions, ICs (Proteus's smoothedHeaviside for heaviside(φ)), and ρ/ν/g from the family's ρ, μ, g. It checks the built p/n against the dispatch before solving; the archive records the dispatch and the realization, and --check works dambreak_short.ymf (level_set_five, 3 outputs, h = 0.2576) vs damBreakLevelSet.py -C "final_time=0.03 dt_output=0.01 he=0.08 genMesh=True": 103/103 datasets bitwise equal on 1 rank, 141/141 on 2 (-mat_partitioning_type current). --check reproduces bitwise. Negative control ε = 1.6 h: vof_t0 differs by 1.7e-2. Needs ymf's hypot (below)
f3f0078 Step 2, shallow water: an SWFlowProblem with SWFlow's own sw_p/sw_n loaded through Context; walls → reflectingBCs. A cells-meshed split discretization is refused up front dam3bumps.ymf (t ∈ (0, 0.5], 5 outputs, h = 2, 969 nodes) vs dam3Bumps.py -C "final_time=0.5 dt_output=0.1 structured=False he=2.0": 70/70 numeric datasets bitwise equal on 1 rank, 77/77 on 2. For scale, the deck on 1 vs 2 ranks differs by up to 4.3e-6. Runs on ymf main
1cbe30c ymf_run refuses, before anything runs, a split discretization the realizer would refuse on the data alone (DispatchRun.precheck: family, box, boundary setters, two-phase values) level_set_clsvof: before, a traceback mid-solve (exit 1); after, exit 2 with nothing written. The reason: no sub-problem carries χ, so the closed atmosphere has no χ inflow, and no BC_RANS setter matches {p = 0, zero velocity flux}. 27 passed (hypot) / 24 + 3 skipped (ymf main)
bed1cef test/dispatch: no skip without ymf's hypot; the dam-break oracle needs cekees/ymf#12 ymf notation-hypot (6feb177): 27 passed. ymf main (c2e646f): 24 passed, 3 failed (the dam-break runs on 1 and 2 ranks, and the ymf_run test)

test_ymf_run_says_what_a_split_discretization_dispatches_to is in commit 1's test file and needs commit 2. Since 6f0d07c it describes the run: what the split dispatched to and what the archive records. The refusal has its own tests: an uncovered cell, a cells-meshed split, and a boundary no setter imposes.

Needs cekees/ymf#12 merged into proteustoolkit/ymf main first. It adds hypot(a, b) to ymf's notation. damBreakLevelSet.py's phi_init uses hypot; the spec's sqrt-of-squares form differs from it by 1 ulp (4.4e-16) at 2 of the 109 stored-mesh nodes, and by t = 0.03 that grows to 1182 values, up to 1.5e-12 in p. With hypot, 0 values differ. proteus CI installs ymf from proteustoolkit/ymf@main, so until ymf#12 merges upstream the dam-break runs and the in-process ymf_run test cannot pass on CI. They are not skipped: until then they fail on CI (unknown name 'hypot'), which is the intended order. The dam3Bumps runs do not need it.

Finding: both in-tree dam breaks solve the non-conservative inertia form. RANS2P.Coefficients defaults to NONCONSERVATIVE_FORM=1.0, and RANS3PF.h hardwires that form. The C42 note's from_weak_form: conservative_inertia ("to confirm") is therefore wrong for both. (conservative, P2 projection) is a cell no module covers, and dispatch refuses it by name.

How a sub-problem finds its cell: the family comes from physical_model. The role comes from the closed spec's structure: which unknowns its equations are for, and what it enforces or introduces. The cell is then chosen by formulation and by each option the spec states. Values the dispatcher infers from typed keys:

  • epsFact and the other widths, from regularize widths in element sizes
  • useVF, from lagging χ = heaviside(φ)
  • shock capturing, from stabilization
  • maxNonlinearIts, from solver
  • quadrature, from the highest space order

Refusals tested:

  • the conservative projection
  • a P2 RANS2P velocity
  • SSP_RK3 on VOF
  • a model with no family, a sub-problem with no role, an unknown formulation
  • RANS3PF without PresInit
  • an unregularized heaviside
  • an unread SWE scheme option
  • hints that disagree with the spec (useVF, RDLS epsFact) or with the cell (NONCONSERVATIVE_FORM), name another module, or are unknown to the cell

Not in this PR: the CLSVOF and projection discretizations as runs. They dispatch, and the realizer should build them, but no oracle has been run for them yet. Also not here: the dispersive SWE (refused, since nothing realizes it), gauges, open SWE boundaries, 3-D tanks, and stored meshes as a spec domain (C7.1).

Run locally: env proteus-bc-261025, macOS arm64; ymf main c2e646f, and ymf notation-hypot d51ca2d for the dam-break runs.

🤖 Generated with Claude Code

cekees and others added 3 commits November 5, 2026 22:06
…odule + flags

YMF_MODEL_FAMILIES_C42 section 6, D1 and D8. A ymf spec states the model, the
formulation (a weak_forms entry, named by from_weak_form) and the discretization
(spaces, time integration, stabilization, the split into sub-problems, bound
symbols, regularization). proteus.Dispatch reads one discretization of a closed
spec and returns, per sub-problem in solve order, the mprans module, its
Coefficients flags, the physics-module attributes and the numerics-module
attributes, each with its source (cell, spec, default, hint).

- proteus/DispatchTable.py: data only. Families (by physical_model), roles,
  and 13 cells: RANS2P conservative and non-conservative (one module, two
  cells: NONCONSERVATIVE_FORM 0/1), RANS3PF projection + PresInc/Pres/PresInit,
  VOF, NCLS, RDLS, MCorr, CLSVOF; SW2DCV; GN_SW2DCV.
- proteus/Dispatch.py: role from the closed spec's structure (which unknowns a
  sub-problem's equations are for, what it enforces/introduces), cell by
  formulation and each stated option; epsFact from regularize widths in
  element sizes, useVF from lagging the heaviside coefficient, shock
  capturing from stabilization, maxNonlinearIts from solver, quadrature from
  the highest space order. An uncovered cell is refused by name with each
  candidate's mismatch. A proteus: hint is checked: disagreeing with the spec
  or the cell is refused, a hint on a defaulted flag replaces the default, an
  unknown hint is refused.
- test/dispatch: the C42 dam-break family + case with the two inertia forms
  as weak forms and three discretizations; ymf's shallow-water fixtures
  (copied from ymf tests/fixtures/shallow_water at c2e646f). Force-added:
  .gitignore ignores *.ymf (archives), as for test/poisson_2d/square4x4.ymf.

Finding: both in-tree dam breaks solve the NON-conservative inertia form.
RANS2P.Coefficients defaults NONCONSERVATIVE_FORM=1.0 and RANS3PF.h hardwires
it, so the C42 note's "from_weak_form: conservative_inertia" (marked "to
confirm") is wrong for both; (conservative_inertia, P2 projection) is a cell
no module covers and is refused.

Measured (env proteus-bc-261025, macOS arm64, serial; ymf main c2e646f):
test/dispatch 20 passed. Object by object against the decks' built p/n
modules (TwoPhaseFlowProblem.initializeAll, SWFlow sw_p/sw_n, nothing solved):

  spec discretization   deck                           models  attributes compared
  level_set_five        damBreakLevelSet.py                 5   100 (cell 33, spec 51, default 16, hint 1)
  level_set_clsvof      damBreakLevelSet.py interface=1     2    49
  clsvof_projection     damBreak.py                         5    75
  convex_limited        dam3Bumps.py (SW2DCV)               1    23
  (relaxed, from test)  dSWEs_steady_state.py (GN)          1    16

10 of 10 single-value mutations of a dispatch result (a flag, a space, the
quadrature order, a step controller, a level model, a lag) fail the
comparison. Refusals tested: conservative projection, a P2 RANS2P velocity,
SSP_RK3 on VOF, no family, no role, an unknown formulation, RANS3PF without
PresInit, an unregularized heaviside, an unread scheme option; hints
disagreeing with the spec (useVF, RDLS epsFact), with the cell
(NONCONSERVATIVE_FORM), naming another module, or unknown to the cell.
The test restores PETSc's options database and the global Context after
building each deck (checked: no option leaks).

Not here: building pnList from the dispatch and running the solve (C5 step 2,
through ymf_run).

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

Before: `pro run` on a spec whose model only a split can solve (the dam break,
the lake at rest) died in adr_problem with a SymbolicError traceback
("DiracDelta(φ) has no value at a point", "a dirichlet condition on the normal
component ... runs in a split discretization's solver"), before blockers()
could say anything. After: split discretizations are caught before the model
is classified as one ADR system; each prints what proteus.Dispatch maps it
onto (or the dispatch's refusal), exit 2, nothing written. --ignore drops the
split ones when others are selected. blockers() uses the same message
(split_reason), so the split check there keeps "split into sub-problems (...)".

  pro run test/dispatch/dambreak.ymf -> exit 2:
    level_set_five: ... It dispatches to flow RANS2P, vof VOF, ncls NCLS,
      rdls RDLS, mcorr MCorr; running a split discretization from the
      dispatch is not wired up yet
    clsvof_projection: ... clsvof CLSVOF, flow RANS3PF, pressureInc PresInc,
      pressure Pres, pressureInit PresInit; ...
  pro run test/dispatch/lake_at_rest.ymf -> exit 2: ... swe SW2DCV; ...

test_pro_cli.py, test_adr_problem.py, test_domain_ymf.py, test/dispatch:
213 passed, 1 skipped (was 191 passed, 1 skipped without test/dispatch).

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

The coverage check failed on this PR for proteus/mprans/NCLS3P.py: floor 76.2%,
measured 75.7%. This PR does not touch NCLS3P. 76f3e12 (on main, "One
scatterDOFArray, on OneLevelTransport, replacing the 11 copies in mprans")
removed 15 covered statements from the module. The same 163 statements are
missed before and after, compared line by line between the c47-cython3 run
42031161865 and this PR's run 42109633045. So the percentage falls with the
denominator:

  before  522/685 = 76.2%
  after   507/670 = 75.67%

The floor is set the way ratchet.py writes one: floor(10 * now) / 10 = 75.6.
Every branch off current main hits this check. If another PR lowers the same
floor, the change is identical and merges cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cekees added a commit that referenced this pull request Nov 5, 2026
…covered lines (same hunk as #209 7c2a5d5, #211 68f5df0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cekees and others added 3 commits November 6, 2026 09:01
…patch, bitwise the deck

ymf_run used to refuse every split discretization (exit 2). A split discretization the dispatch covers now runs:
proteus.DispatchRun builds it through the deck machinery, not a parallel path. It builds a TwoPhaseFlowProblem from
the dispatch result (models in dispatched order, each with its TwoPhaseFlow Parameters class; coefficient flags and
numerics knobs set before initializeAll) and from ymf's plain data of the split (ymf.symbolic.split.split_problem).
The domain becomes a SpatialTools Tank2D, and each side gets the BC_RANS setter whose signature matches the closed
(expanded) conditions of its region. Initial data come from the model's unknowns; heaviside(φ) under
smoothed_heaviside is evaluated with Proteus's own smoothedHeaviside. rho/nu/g come from the family's ρ, μ, g at
phase indicator 0 and 1, simplified symbolically, so nu_0 is νw itself. After initializeAll, every attribute the
dispatch gives is compared with the built p and n modules (DispatchRun.differences, the comparison the step-1
tests made); a difference is refused before anything solves. The archive records the dispatch and what the
realization took from where; --check reruns and compares bitwise.

Kept: a cell the table does not cover is refused before anything runs (DispatchError, exit 2); so is a family
with no realizer yet (the shallow-water families, next commit).

Oracle: test/dispatch/dambreak_short.ymf (level_set_five, t in (0, 0.03], 3 outputs, h = 0.2576) against
damBreakLevelSet.py -C "final_time=0.03 dt_output=0.01 he=0.08 genMesh=True" (h = 3.22 * 0.08 = 0.2576 exactly).
triangle meshes the same PSLG to 161 nodes and 273 triangles. macOS arm64, env proteus-bc-261025:

  run                                        datasets compared   bitwise equal   max |velocity| at t = 0.03
  parun deck vs pro run spec, 1 rank         103                 103             0.522
  parun deck vs pro run spec, 2 ranks        141                 141             (same run, partition pinned:
                                                                                  -mat_partitioning_type current)
  pro run --check of its own archive         1 output            reproduced, bitwise
  negative control: ε = 1.6 h for 1.5 h      103                 differ: vof_t0 by 1.7e-2 (39 of 161 nodes),
                                                                 v_t2 by 2.8e-2

The mesh records (Mesh_Spatial_Domain_*) differ only in the HDF5 file's name; the mesh fingerprint is the same.
Wall time: deck 1.2 s, spec 1.9 s (serial).

Found on the way:
- useSuperlu is a direct solve on any number of ranks (SuperLU serially, SuperLU_DIST through PETSc in
  parallel), so linear_solver: superlu maps onto it on 2 ranks. Mapping petsc there gives GMRES+ASM: p differs
  from the deck by 3.2e-2.
- phi_init uses hypot. The spec's sqrt(max(..)² + max(..)²) form differs from it by 1 ulp at 2 of the 109 nodes
  of the stored mesh. dambreak_short.ymf therefore writes hypot, which needs ymf's notation to have it (cekees/ymf
  branch notation-hypot); without it the two run tests skip, saying so. dambreak.ymf keeps the sqrt form, so the
  step-1 tests run on ymf main.
- The archive writer rewrites the free-form hint value 1.0e6 as '1000000.0'. Reading the archive back then keys
  the output differently, so --check found no record. dambreak.ymf writes 1000000.0; the root cause is ymf's.

Tests: test/dispatch 23 passed (with notation-hypot), 20 passed + 2 skipped (ymf main); with test_pro_cli,
test_adr_problem and test_domain_ymf: 216 passed, 1 skipped (-n 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…patch, bitwise dam3Bumps

The second family through the deck machinery. proteus.DispatchRun.shallow_water builds an SWFlowProblem from the
dispatch and from ymf's split plain data, then loads SWFlow's own sw_p/sw_n through Context's mySWFlowProblem, as
parun --SWEs loads them:

- the domain is a RectangularDomain's PSLG meshed by triangle at h (SWFlow's unstructured path);
- the bed Z and the initial depth are the spec's formulas;
- g, n (Manning), θ, α, β become physical_parameters, in copies (SWFlowProblem hands out its module's default
  dicts, which decks edit in place);
- the dispatched flags become swe_parameters, and SSPOrder comes from the dispatched time order;
- walls on the whole boundary (the closed expansion of `wall`: q·n = 0, zero tangential flux) become
  reflectingBCs. Any other boundary is refused until SWFlow's open boundaries are wired up.

The built p and n go through the same check as the two-phase models before anything solves.

Also refused before anything runs: a split discretization meshed by cells or with levels > 1 (the lake at rest's
cells study). The deck machinery meshes by element size. The dispersive family (GN_SW2DCV) still dispatches
and is refused: nothing realizes it, and ymf cannot state its auxiliary unknowns' data.
Dispatch: `outputs` in the free-form scheme block is the runner's (as on ymf_run's ADR path), not an unread
scheme option.

Oracle: test/dispatch/dam3bumps.ymf (dam break over three cones, 75 m x 30 m, t in (0, 0.5], 5 outputs,
h = 2) against dam3Bumps.py -C "final_time=0.5 dt_output=0.1 structured=False he=2.0". triangle meshes 969
nodes. macOS arm64, env proteus-bc-261025:

  run                                        datasets compared   bitwise equal
  parun deck vs pro run spec, 1 rank         70 numeric          70
  parun deck vs pro run spec, 2 ranks        77 numeric          77   (-mat_partitioning_type current)
  (for scale: the deck on 1 vs 2 ranks       differs by up to 4.3e-6 in the momentum)

The bed is written with 47.5 as 95/2. With the decimal, sympy factors it out of the square under the sqrt
(3 - 14.25*sqrt((0.0210526*x - 1)**2 + ...)): the same number, other arithmetic. SWFlowProblem's he is not read
by SW2DCV: the deck's 0.9375 and the spec's 2.0 give the same bits.

This case needs nothing new from ymf, so it runs on CI with ymf main. The dam break needs the notation's hypot,
and its two runs skip there, saying so.

Tests: test/dispatch 26 passed (ymf notation-hypot); with test_pro_cli, test_adr_problem and test_domain_ymf on
ymf main: 216 passed, 4 skipped (-n 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re anything runs

What realize() refuses on the data alone is now checked in split_blockers, with every other blocker, before
anything is solved or written (DispatchRun.precheck). That covers the family, the box, the boundary (each tank
side's region and the BC_RANS setter its closed conditions match, or SWFlow's treatment of the whole boundary)
and the two-phase physical values. Before this, such a refusal surfaced mid-run as a RealizeError traceback,
after earlier outputs of the same run had been solved.

Found with level_set_clsvof (dambreak.ymf, RANS2P + CLSVOF). No sub-problem carries χ, so ymf's split gives the
closed atmosphere no χ inflow. What is left (p = 0, zero velocity flux) matches no setter in the table, so it is
refused:

  before: ymf_run ... --discretization level_set_clsvof  ->  traceback in the solve, exit 1
  after:  exit 2, nothing written: "level_set_clsvof: split into sub-problems (flow, clsvof). It dispatches to
          flow RANS2P, clsvof CLSVOF, and cannot run: region top (side y+): no BC_RANS setter imposes its closed
          conditions ..."

The deck (damBreakLevelSet.py interface=1) calls setAtmosphere there. Whether the table should map that
two-condition signature onto setAtmosphere for CLSVOF is a question for the handoff, not decided here.

Tests: test/dispatch 27 passed (ymf notation-hypot), 24 passed + 3 skipped (ymf main), -n 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cekees added a commit to cekees/ymf that referenced this pull request Nov 6, 2026
…k's phi_init

tests/fixtures/two_phase/dambreak.ymf states the case of proteus's test/TwoPhaseFlow/damBreakLevelSet.py. Its
initial signed distance is now written with hypot (previous commit), as the deck's phi_init computes it. The sqrt of
a sum of squares rounds twice.

Measured with proteus cekees/proteus#211 (C5 step 2), macOS arm64, numpy 2. The deck is damBreakLevelSet.py
-C "final_time=0.03 dt_output=0.01 he=0.08 genMesh=True"; the spec is the same case run through ymf_run, with
161 nodes:

                                               sqrt form                          hypot form
  phi0 at the deck's stored mesh (109 nodes)   2 nodes differ, max 4.4e-16        0 differ
                                               (1 ulp)
  phi0 at the run's mesh (161 nodes)           2 nodes differ, max 4.4e-16        0 differ
  deck vs spec archive at t = 0                4 values differ (phi, phid),       0 differ (103/103 datasets
                                               max 4.4e-16                        bitwise)
  deck vs spec archive at t = 0.03             1182 values differ, max 1.5e-12    0 differ
                                               (p)

So without hypot no run from a spec can reproduce the deck bitwise. test_notation_functions keeps the sqrt form's
three-branch check at abs_tol 1e-15, and adds the hypot form's bitwise one.

Tests: 795 passed, 11 skipped (tests/, -n 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cekees/ymf#12

The two dam-break runs and the in-process ymf_run test skipped when the installed ymf lacked hypot. CI would then
have been green without the oracle running. They now run, and with ymf main (c2e646f) they fail: dambreak_short.ymf
does not parse there. proteus CI installs ymf from proteustoolkit/ymf@main, so they stay red until cekees/ymf#12
(hypot in the notation) is merged upstream. That is the order: ymf#12 first, then this PR.

  ymf notation-hypot (6feb177): test/dispatch 27 passed
  ymf main (c2e646f):           24 passed, 3 failed (the dam-break runs on 1 and 2 ranks, and the ymf_run test)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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