Skip to content

C17/C14 round 1: first-step replay, exact level-set hot start, -H -t step rule - #208

Draft
cekees wants to merge 26 commits into
devfrom
c17-r1
Draft

cekees wants to merge 26 commits into
devfrom
c17-r1

Conversation

@cekees

@cekees cekees commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

C17/C14 round 1 (owner of StepControl/SplitOperator/TimeIntegration/ShockCapturing this round). One commit per item, each with a test. macOS arm64, serial, env proteus-bc-261025; "largest relative difference" is over every model's dofs at the final time.

commit what before after
605b1e5 Step schedule on the split operator (so.systemStepSchedule, SO_base.setStepSchedule): a replay takes the first step too. CLSVOF disc_ICs in state() (a retried first step skipped the disc-IC spin-up) step-1 failure vs replay: damBreakLevelSet VOF 5.0e-2; fillingTank CLSVOF 1.03 (3 retries) 0.0; 3.4e-14 (1 retry)
9f55ebe Level-set family hot start bit-identical: coupled quadrature fields, solver unknowns and next system step as archive owners; BackwardEuler(_cfl) state (m_last, dtLast); RANS2P SC/SG lag machines damBreakLevelSet hot start at 0.04 vs uninterrupted, t=0.1: 5.1e-1 (velocity 7.3e-2, forces ~1%); at 0.005: 5.4e-1 (first step 0.01 not 0.0075) 0.0 dofs and force rows, both restarts
ecb148c -H -t: an archived time within 1e-6 of the smallest archived interval counts as at t (hot_start_step) -t 0.06, archive holds 0.06000000000000001: continued from 0.04 continued from 0.06
84e46da options without hotStartMode default to continue (an installed parun is an install-time copy; the env's is from Oct 25) AttributeError in restoreSolverState bit-identical continue
b253fa7 a continue hot start that falls back to a restart (state refused: another partition, rank count or step) says so once, as a RuntimeWarning on rank 0 naming models and reason (C7.1 #213's finding; decision restart-with-message kept) per-model log lines only one warning; test with a perturbed partition fingerprint
1d46cda test: a different-rank-count hot start is a restart from the archived step (cekees 2026-11-05: legitimate, no error) archived 2 ranks, continue on 3 vs restart on 3 equal (0.0 / 9.9e-14 run-to-run); vs serial restart 8.2e-9 (uninterrupted vs restart 3.6e-1)
72f6f80 coverage floor proteus/mprans/NCLS3P.py 76.2 -> 75.6 (#199 moved 15 covered statements to Transport; same hunk as 7c2a5d5 on c0-r6) coverage job: NCLS3P 670 statements, 75.7% < 76.2 at the floor
ef5bac1 test: the CLSVOF hot start (damBreakLevelSet interface=1) is bit-identical; nothing changed for it, the owners above carry it main c09280c: 2.3e-3 (restart at 0.04), 1.2e-2 (at 0.005) 0.0 both, same steps and force rows; restart mode 5.5e-3
c790729 VOF3P and NCLS3P shock capturing take DelayedLagState (rebuilt unlagged by a hot start) rising_bubble_rans3p hot start at 0.02, t=0.05: main 8.1e-1; this branch 2.2e-3 (MCorr3P), VOF3P 2.4e-5 1.9e-4 (MCorr3P), VOF3P 6.1e-8
c0bed9e continue hot start puts back the dofs read from the archive (NS_base.restoreArchivedDofs): RDLS's first residual froze its interface dofs to NCLS3P's corrected ones; the spin-up re-solved pressureInitial at the restart 1.9e-4 (MCorr3P); pressureInitial 1.4e-2 0.0 every model, steps, force and moment rows; restart mode 8.4
ee029de merge cekees/main afcedf6 (#204 C47, #213 C7.1 Phase D, #215); no conflict test set below 113 passed, 1 skipped
3e1d643 solver-state archive writes each model's datasets in one sorted order on every rank: RANS2P's numericalFlux.ebqe key order follows each process's hash seed, the writer's rank-by-rank name check failed and skipped all of rans2p's state (pre-existing, not #213) 2 ranks: 3/6 (pre-merge), 5/6 (post-merge) archives without rans2p state; seeds 5/6: continue 3.7e-3 + warning always written; seeds 5/6: 0.0 both ranks, no warning

Ablation for 9f55ebe (each piece left out alone): coupled fields 6.6e-2, BackwardEuler state 5.0e-1, RANS2P SC 3.2e-3, SG 6.7e-4, unknowns 5.4e-14. The CFL controller's dt_model_last made no difference once dtLast is back, so it is unchanged.

Ablation for c790729 + c0bed9e on rising_bubble_rans3p (each left out alone, the rest applied): VOF3P SC 1.3e-4, NCLS3P SC 1.9e-3, RDLS archived dofs 1.9e-4, pressureInitial archived dofs 1.4e-2 (its own dofs only). RANS3PF's subgrid error lags on this deck too, but a state() for it changed nothing (0.0), so it is not added (inventory stays "rebuilt").

Measured, no change (decision for cekees, in the handoff): an archive hot start shifts the dof history twice before its first step (once before the stepping loop, once at the step), so dof_last_last equals dof_last there: on rising_bubble_rans3p it differs from the uninterrupted run's by up to 0.77 relative (MCorr3P), 0.18 (velocity), 0.14 (pressure increment), while every dof, step and force row is 0.0. Nothing in the tree reads dof_last_last for a computation (only the PUMI adapt transfers it, as *_old_old fields nobody reads); dof_last is exact. The PUMI hot start reloads the history from its checkpoint after the first shift, so it is right there.

Tests run locally: test_step_retry (17), test_hotstart_* incl. the new test_hotstart_levelset (6), test_state_contract, test_mpi_launch_retry, test/TwoPhaseFlow, test/HotStart_3P: 76 + 6 passed. At c0bed9e: the same set plus test/surface_tension/rising_bubble_rans3p, 89 passed (-n 4; test_hotstart_levelset now 12: level set and CLSVOF x two restarts, 3P continue and restart). After the merge (3e1d643), -n 4: test_step_retry, test_hotstart_* (test_hotstart_levelset 13), test_state_contract, test_mpi_launch_retry, test/TwoPhaseFlow, test/HotStart_3P, test/surface_tension/rising_bubble_rans3p, test/mesh_tests/test_subdomain_view_restart (skipped: env's ymf lacks ymf.mesh), test_cython_class_attributes: 114 passed, 1 skipped. #213's per-rank-view restart source: same-rank 2-rank continue reads the views, same fingerprint, exact; the one-warning tests (perturbed fingerprint; 2 ranks archived, 3 continued) pass. CI is the oracle.

Also measured: the damBreakLevelSet hot start at 0.04 on 2 ranks (-mat_partitioning_type current): both ranks' dofs 0.0 at t=0.1 (not in a test). Not done: parun as a console entry point (so it cannot go stale) left for C15.

🤖 Generated with Claude Code

cekees and others added 26 commits November 5, 2026 10:05
…ticmethods; MiscibleDisplacement runs

Cython 3 compiles def functions with binding=True: a compiled function stored
on a class (class-body `from .ctransportCoefficients import f`) and called as
self.f(...) receives self as an extra first argument, like a Python function.
test/MiscibleDisplacement stopped at the first transport evaluate with

  TypeError: groundwaterTransportCoefficientsEvaluate_hetMat() takes exactly
  12 positional arguments (13 given)

Each of the 17 class-stored compiled functions in this file (24 call lines
through self, including the two-phase Darcy classes TwophaseDarcyCoefficients
subclasses) is now wrapped with staticmethod right after its import. The call
sites are unchanged; staticmethod returns the wrapped callable whether or not
it binds, so the same code is correct on Cython 0.29 and 3.x.

test/MiscibleDisplacement had no test. The deck gets context options
(unstructured_mesh, refinement_level, ndays, nDTout; defaults unchanged) so a
test can run it on a regular triangular mesh, which needs material type 0;
test_miscible_displacement.py runs flow (p1-nc) + transport on an 8x8 mesh to
500 d and pins the tracer mass.

Measured (Cython 3.0.11, macOS arm64, env proteus-bc-261025):
  test_miscible_displacement.py   before: TypeError    after: pass, 0.9 s
  parun md_so.py (default deck: triangle, 5000 d, 50 outputs)
                                  before: exit 1, 3.5 s  after: exit 0, 9.4 s
  tracer mass at 500 d, 8x8: 26191.0447689 (identical in 2 runs)
  min c (SUPG + shock capturing undershoot): -0.155 (8x8), -0.29 (16x16),
  -3e-5 (32x32); the test's lower bound is -0.5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and TimeIntegration; invariant test

The same wrap as the previous commit, in every other file outside the mprans
models (C0, after C0 round 5 lands) and TimeIntegration (C17/C14):

  file                                 stored  live self.f( calls
  TwophaseDarcyCoefficients               3      3
  TransportCoefficients                   1      0 (call commented out)
  richards/Richards                       2      1
  m_comp_co2/m_comp_co2                   2      1
  ADR, AuxiliaryVariables                 1+1    0
  PostProcessingTools VPP_P1nc_RT0        9      0 (calls use cpostprocessing.f)
  test/dg/la_gauss_2d_p.py                1      1

PostProcessingTools.VPP_POINT_EVAL_GWVD is left alone: #203
deletes the class.

test/test_cython_class_attributes.py checks the premise (a compiled function
stored on a class binds self; staticmethod returns it unwrapped) and, for each
of the 23 modules that store compiled functions on classes, that the set of
binding class attributes equals a DEFERRED list (TimeIntegration 16, mprans 39,
PostProcessingTools 1). The set is exact in both directions, so fixing a
deferred site fails the test until its entry goes.

Inventory: a runtime scan of every importable proteus module found 89 class
attributes holding a binding compiled function in 23 modules; an AST scan of
test/, scripts/, experimental/ found one more (test/dg). This commit and the
previous one fix 34; 56 remain, listed in DEFERRED.

Measured (Cython 3.0.11, macOS arm64, env proteus-bc-261025):
  test_cython_class_attributes.py   without the wraps: 8 of 24 module checks
                                    fail; with them: 25/25
  parun la_gauss_2d_p.py la_gauss_2d_dgp2_n.py (test/dg)
    main:                     TypeError in TimeIntegration,
                              computeElementNeighborShapeGradients() 5 for 6
    TimeIntegration's 16 wrapped at run time (file untouched), deck unfixed:
                              TypeError: unitSquareRotationEvaluate() takes
                              exactly 6 positional arguments (7 given)
    both:                     exit 0, 66 s
  Existing tests on this branch (richards, griffiths_lane_6, single_phase_gw,
  post_processing, poisson_2d, levelset, LS_with_edgeBased_EV/MCorr,
  test_adr_problem, test_analyticalSolutions, multilevel): 821 passed,
  32 failed with -n 8. None comes from this change: the 8 multilevel failures
  fail identically on unchanged main (ce43b73); 23 were xdist workers sharing
  reference_triangle.* in one cwd and pass serially (93 passed); 1
  (test_ymfrun_knows_which_domains_fix_the_mesh) fails on main too, because the
  env's ymf has no discretizations.mesh_from_domain.

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

TimeIntegration is C17/C14 territory. cekees decided on 2026-11-05 that C47
applies this change on #204, because the change is mechanical and no C17/C14
session is running this week. C17/C14: the only change is one
`f = staticmethod(f)` line after each class-body
`from .ctimeIntegration import f`, as in the two previous commits.

16 class attributes in 14 classes (DGlimiterP1Lagrange1d/2d, P2, PkMonomial,
Durlofsky, the _Sw variants, UnstructuredLimiter_base,
CockburnNotesLimiter2d_base, PsiTCtte, PsiTCtte_new) and 16 live self.f(...)
call lines. Each raised TypeError (N+1 arguments) under Cython 3 when reached.
DEFERRED in test_cython_class_attributes.py loses its TimeIntegration entry.

Measured (Cython 3.0.11, macOS arm64, env proteus-bc-261025):
  parun la_gauss_2d_p.py la_gauss_2d_dgp2_n.py (test/dg, DGlimiterP2Lagrange2d)
    before: TypeError: computeElementNeighborShapeGradients() takes exactly 5
            positional arguments (6 given)
    after:  exit 0, 38.6 s
  test_cython_class_attributes.py: 24/24

test/shallow_water/dambreak_1d (DGlimiterP1Lagrange1d) still does not run, for
an unrelated reason: its flux subclass calls RusanovNumericalFlux_Diagonal
without getPeriodicConditions.

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

After C0 round 5 (#199) and #203 reached cekees/main (c09280c, merged into
this branch), the last sites:

  mprans Dissipation 2, Kappa 2, MCorr 2, MCorr3P 2, NCLS 1, NCLS3P 1,
  RANS2P 4, RANS3PF 4, RANS3PSed 4, RDLS 1, TADR 3, VOF 3, VOF3P 3, VOS3P 3  (35)

Their 12 live self.f(...) call lines are all in Coefficients.evaluate, which
the mprans models do not call (the kernels evaluate the coefficients), so no
mprans result changes. VPP_POINT_EVAL_GWVD went with #203. DEFERRED in
test_cython_class_attributes.py is empty: a runtime scan of every importable
proteus module finds no class attribute holding a binding compiled function
(89 at the start of C47: 88 in proteus/ plus the test/dg deck).

Measured (Cython 3.0.11, macOS arm64, env proteus-bc-261025, serial):
  test_cython_class_attributes.py, MiscibleDisplacement, LS_with_edgeBased_EV,
  levelset, TwoPhaseFlow, test_scatter_dof_array, test_two_stage_newton,
  test_{vof,tadr,twophaseflow}_partition_independence,
  test_numerical_flux_arguments: 103 passed (7 min 20 s). test_damBreak_hotstart
  needed this tree's scripts/parun on PATH (the env's installed parun predates
  --hotStartMode); with the env's parun it fails on that option, not on C47.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A replay (test/step_retry_mpi.py REPLAY=, the C0 retry tests' oracle) made the
system step land on the accepted times of the forced-failure run by wrapping
choose_dt_system, so it could not take the first step: its dt comes from
initialize_dt_system. A failure in step 1 could not be tested.

SO_base.setStepSchedule(times) / so.systemStepSchedule: every system step
that starts before the last scheduled time ends on the next one, the first
step included. initialize_dt_system and choose_dt_system of every adaptive
controller (SO_base, MinModelStep, MinFLCBDF, MinAdaptive, ISO, _SS and the
NotInOrder variants) take scheduled_dt(...) in place of their choice, before
they hand it to the models, so the models' time histories are initialized with
the scheduled step. Past the last time the controller chooses as usual. The
harness now sets so.systemStepSchedule instead of patching two controllers.

With the first step replayable, a CLSVOF failure in step 1 showed a second
defect: CLSVOFNewton's spin-up for a discontinuous initial condition runs in
the first solve and clears coefficients.disc_ICs, which was not in CLSVOF's
state(). The retry skipped the spin-up, started from the raw characteristic
function and diverged (three cuts instead of one). disc_ICs is now in state();
the same flag would have re-run the spin-up on a hot start's smooth field.

Largest relative difference over every model, forced failure vs replay
(test/test_step_retry.py, macOS arm64, serial):
  damBreakLevelSet  VOF fails in step 1     before 5.0e-2   after 0.0
                    (replay's first step was 1e-3, the forced run's 5e-4)
  fillingTank       CLSVOF fails in step 1  before 1.03     after 3.4e-14
                    (schedule alone 3.0e-1; 3 retries before, 1 after)
The test also checks that the replay's accepted times equal the forced run's.
test_step_retry.py 17 passed (54 s, -n 6); test_state_contract.py and the
test_hotstart_* files 24 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
damBreakLevelSet (RANS2P + VOF + NCLS + RDLS + MCorr, he=0.1) hot-started at
0.04 drifted from the uninterrupted run (forces ~1% two steps later). Found by
dumping every array and scalar of every model, its integrator, shock
capturing, subgrid error, step controller and solver at each model's first
solve after the restart, uninterrupted vs hot. What a restart rebuilt
differently, and is now archived in solver_state and put back after the
spin-up:

- the fields other models read at the quadrature points and faces
  (COUPLED_FIELD_NAMES, the set the retry restore keeps): they hold each
  model's last residual, not its dofs, since MCorr and RDLS change VOF's and
  NCLS's dofs after their solves. NumericalSolution owner coupledFields.
- BackwardEuler.m_last, the mass the next step differences against (VOF's is
  its uncorrected mass), and BackwardEuler_cfl.dtLast (caps the next step's
  growth): state()/set_state() on both (inherited by SSP, the RKEVs, PsiTCtte
  and TADR's CompatibleBackwardEuler; inventory updated).
- RANS2P's shock capturing (DelayedLagState: lag machine and numDiff_last)
  and subgrid error (nSteps, v_last alias or copy).
- the solver's unknowns uList[-1], the next Newton's initial guess and the
  source of the dof history shift (owner unknowns).
- the next system step: initialize_dt_system chose afresh from the restart
  state (restarted at 0.005 the first step went to 0.01, not 0.0075). Owner
  systemStep, kept with the first model, via the new SO_base.setNextStep.

Largest relative difference over every model's dofs at the final time, hot
start vs uninterrupted run (macOS arm64, serial; each piece left out alone):
  before (all left out)        5.1e-1 (MCorr; velocity 7.3e-2, VOF 1.4e-2)
  coupled fields left out      6.6e-2
  BackwardEuler state          5.0e-1 (MCorr)
  RANS2P shock capturing       3.2e-3
  RANS2P subgrid error         6.7e-4
  solver unknowns              5.4e-14
  next system step (t=0.005)   5.4e-1
  after                        0.0 (dofs and forceHistory_p), both restarts
The CFL controller's dt_model_last made no difference once dtLast is back
(the same cap), so it is left as it was.

test/test_hotstart_levelset.py: restarts at 0.04 (after the switch to lagged
shock capturing) and 0.005 (capped step) are exact, same accepted times and
force rows; --hotStartMode restart differs (> 1e-3). The harness
(step_retry_mpi.py) gains DTOUT, HOTSTART, HOTSTARTMODE and runs the source
tree's scripts/parun when there is one. 76 passed: test_step_retry,
test_hotstart_*, test_state_contract, test_mpi_launch_retry, test/TwoPhaseFlow,
test/HotStart_3P (-n 8, 78 s).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-H -t 0.06 with archives every 0.02 restarted from 0.04: the archived step is
0.06000000000000001 (a sum of steps), and "the latest archived step at or
before t" compared it with 0.06 exactly. NumericalSolution.hot_start_step()
takes an archived time as at t within a millionth of the smallest interval
between archived times (2e-8 here; a few ulps of accumulated rounding is far
below it, and a real step is far above it), and is the one place the rule
lives.

damBreakLevelSet archived every 0.02 to 0.1, hot-started again with -t 0.06:
  before  continued from 0.04 (first step ended at 0.06000000000000001)
  after   continued from 0.06 (first step ends at 0.08)
test/test_hotstart_levelset.py: the end-to-end case (fails on the parent
commit) and a unit test of hot_start_step (times off by an ulp, between
steps, inf, before the first, a single step). 5 passed.

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

The proteus-bc-261025 env's parun has no --hotStartMode because it is a copy
of scripts/parun made at install time (Oct 25): setup.py installs parun with
scripts=, which copies the file, editable install or not. scripts/parun has
the option; nothing is missing on the proteus side there. What was: NS_base
validated getattr(opts, 'hotStartMode', 'continue') in __init__ but
restoreSolverState read opts.hotStartMode directly, so any options object
older than the option (an installed parun from before it, another driver)
crashed in the hot start with AttributeError. NS_base now resolves it once
(self.hotStartMode) and uses that.

test_a_driver_without_hot_start_mode_continues (harness NOHOTSTARTMODE=1
deletes the attribute): before AttributeError, after a bit-identical
continue (0.0). test_hotstart_levelset.py 6 passed. The harness already runs
the source tree's scripts/parun when there is one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ain with its own numbering

A per-rank view must give the solver back the subdomain partitionMesh built: the partition's maps
number its element boundaries and edges, so those numbers must come back too. generateFromArrays
numbers boundaries and edges its own way; generateFromNumberedArrays takes each element's boundary
numbers and the edge list and calls what partitioning.cpp calls on a subdomain:
constructElementBoundaryElementsArrayWithGivenElementBoundaryAndEdgeNumbers_{triangle,tetrahedron},
then allocate/computeGeometricInfo, then the element boundary flags. mesh.cpp and partitioning.cpp
are not edited. The arrays are allocated with new[] (the Mesh frees them with delete[]); the node and
element fill is shared with generateFromArrays (_fillNodesAndElements).

Measured (macOS arm64, MPICH, env proteus-bc-261025): every rank's subdomain of square4x4_tagged,
square4x4 and cube_3x3x3, owners scattered and reversed, on 2 and 3 ranks (30 rank subdomains):
- generateFromArrays from the same nodes and elements: 7 arrays differ in every case
  (elementBoundariesArray, elementBoundaryNodesArray, elementBoundaryElementsArray,
  elementBoundaryLocalElementBoundariesArray, interior/exteriorElementBoundariesArray, edgeNodesArray);
- generateFromNumberedArrays: all 33 arrays equal (nodeDiametersArray aside, which partitionMesh
  finishes across the ranks afterwards).
test_numbered_arrays (subdomains from partitionNodesLocal in process): 7 passed, serial and 2 ranks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tart that reads them

Decided by cekees 2026-11-05 (decision 3, after C32 round 2): per-rank views written into per-rank
archives (archive-modes layouts B and C) as the same-rank restart source.

- Write: on a per-rank archive's first mesh write (Mesh.writeMeshXdmf, driver per-rank), each rank
  writes subdomain_view/<name> into its own <archive>_p<rank>.h5: the step-H ymf grid (its arrays as
  datasets beside it, the grid in the `ymf` attribute), and what rebuilding the solver's subdomain
  needs: each element's boundary numbers, the edges, every element boundary's flag (-1 included), the
  partition's eight maps, the global mesh's counts and scalars, its fingerprint and whether the
  partition renumbered.
- Read: subdomainViewMesh(archive) -- an open archive or its path -- builds on every rank the mesh that
  stands for the global one (counts, scalars, maps, no arrays; as cartesianSubdomain's) with the
  subdomain rebuilt by cmeshTools.generateFromNumberedArrays, then _finishPartitioning. Collective:
  every rank reads its view or every rank raises SubdomainViewUnavailable with the first reason (no
  per-rank file, no view, another number of ranks, another view format).
- generateMesh(..., restartArchive=None): with an archive whose views fit, a one-level hierarchy
  from them (multilevelMeshFromSubdomainViews), no global mesh and no partitioner; otherwise, or with
  more than one level, the old path, logged with the reason. NumericalSolution does not pass it yet
  (C17/C14's file: the hand-off line is agreed separately); the tests' driver passes the run's open
  archive.
- The input maps and fingerprint carry over a views-built mesh (_localInputNumbering,
  _inputFingerprint), so the restart's archive records them as the global path's does.

Measured (macOS arm64, MPICH, env proteus-bc-261025, ymf c2e646f, PETSC_OPTIONS -mat_partitioning_type
current):
- write then read, square4x4_tagged and cube_3x3x3, 2 and 3 ranks, layouts B and C: on every rank
  all 33 subdomain arrays, the 8 maps, the 17 global scalars and the -1 marking equal partitionMesh's
  (cut faces per rank 6/0, 8/0/8, ...).
- same-rank restart, vortex2D (ls + rdls + vof + mcorr, 4 models), stopped at step 3, hot started to
  step 7 from the same archive with the mesh from the views and with the mesh generated and
  partitioned: on 2 ranks, partitioned (regularGrid global) and per-rank generated meshes, layouts B
  and C, every dataset of both ranks' files equal: 138/138 (fields, lagged solver state, mesh, maps).
  On 3 ranks the run is not reproducible against itself on either path: two global restarts differ
  by up to 1.7e-12 (18 datasets per rank), two view restarts by up to 2.1e-12 once in four, and views
  vs global by at most 1.0e-12, inside that spread. Not this change's (both paths); for C3/C17.
- unpinned (the graph partitioner), the global restart's partition differed from the run it restarted
  (rank 0: 420 elements against 416), so its per-rank lagged state was another partition's; the views
  restart keeps the run's partition by construction.
- test_subdomain_view_restart 8 passed (35 s); test_archive_*, test_archiver_write_field,
  test_hotstart_*, test_gauges*, mesh_tests, ci, poisson_2d, test_adr_problem, test_domain_ymf,
  multilevel, rank-interface, periodic: 1629 passed, 3 xfailed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eed with C17/C14)

The hand-off point agreed with the C17/C14 session (2026-11-05): NumericalSolution's mesh setup
passes restartArchive=self.ar[0] -- the run's open archive, not its path: a hot start opens each
rank's file for append, and HDF5 refuses a second, read-only open in the same process -- unless
opts.hotStartMesh is 'global' (an options attribute for now; a parun flag waits for C15). Nothing
else in NumericalSolution changes. The vortex2D driver now sets opts.hotStartMesh instead of wrapping
generateMesh, and the bit-identity test fails if either hot start logs "no solver state to restore"
(a mesh numbered otherwise than the archive's makes a hot start a silent restart; C17/C14's request).

Measured: test_subdomain_view_restart + test_hotstart_parallel_state + test_hotstart_state: 16 passed.
That log line, counted in the earlier runs: unpinned global restart 4 (one per model: the graph
partitioner gave another partition, so every model restarted without its lagged state); views
restart 0; pinned global restart 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C7.1 Phase D (#213) found that a hot start through the global
mesh path, with the graph partitioner unpinned, gets another partition than
the run it restarts: every model's solver state is refused (its fingerprint
differs) and the run quietly becomes a restart, said only per model at log
level 0. The recorded decision (2026-10-29) is that a refused restore
continues as a restart with a message, so this keeps the behaviour and makes
the message loud: one RuntimeWarning on the first rank (and one WARNING log
line) naming the models and the reason, and saying how to continue exactly
(the archived partition: same ranks, the per-rank mesh views or a pinned
partitioner) or to ask for a restart (--hotStartMode restart). The reason now
tells a different partition (same ranks and mesh, other pieces) apart from
another rank count or no state at all; the existing texts are unchanged.
Whether a refused continue should instead be an error is left to cekees.

test_a_continue_on_another_partition_warns_once (harness OTHERPARTITION=1
changes only the partition hashes of the fingerprint): before, no warning;
after, exactly one, naming rans2p..mcorr and "different partition" (the run
differs from the uninterrupted one by 5.2e-1, as a restart does).
test_hotstart_*, test_state_contract, test_step_retry: 48 passed.

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

cekees decided (2026-11-05) that a hot start on another partition is
legitimate, not an error: with the solution archived through MPI-IO and a
global mesh, it restarts from the archived step's solution without the
solver state (integrator history, lagged values). That is what the continue
fallback does; this pins it.

damBreakLevelSet archived on 2 ranks at 0.04, hot-started on 3 ranks to 0.1
(pinned partition):
  continue (falls back) vs --hotStartMode restart, every rank   0.0 and
    9.9e-14 in two runs (3 ranks is not deterministic run to run; C7.1 saw
    2.1e-12 on vortex2D), same accepted steps, one RuntimeWarning
  3-rank fallback vs a serial restart from 0.04, gathered       8.2e-9
  uninterrupted run vs that serial restart                      3.6e-1
so the fallback restarts from the archived 0.04 solution, read through the
global mesh on the new partition, not from the initial state. The test
asserts < 1e-10. The harness writes u<rank>.csv in parallel (all ranks wrote
u.csv before). test_hotstart_levelset + test_step_retry: 25 passed.

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

- docs/source/models/adr.rst (new, in the Models toctree): what the ADR model
  solves, the embedded boundary (CutFEM: Nitsche or Robin, ghost penalty) and the
  immersed interface (IFEM: two-branch P1/P2 basis, SCIFEM edge terms, interior
  penalty, prescribed jumps), triply-cut cells, the drivers that reproduce the
  paper (paper=True, test/option map, an example command), the two-layer
  overlap needed in parallel, the test/IFEM tests, and the reference.
- proteus/ADR.py: NumPy-style docstring for ADR.Coefficients (rendered by
  napoleon) describing all 42 arguments: defaults, sign conventions, the values
  the paper uses, that aOfX must agree with mua/mub when the immersed interface
  is on, and which arguments are accepted but unused (immersedBoundary_penalty,
  immersedBoundary_u, test). Docstring only; no code change.
- docs/source/index.rst: Running path test/ci (was tests/ci); the ADR
  capability bullet mentions unfitted interfaces and links the new page; the
  paper added to the references.
- docs/source/models/navier_stokes.rst: RANS2P_IB described consistently as
  two-phase flow with immersed solid boundaries (was "three-phase" in one
  place), and distinguished from the immersed finite elements of ADR.

Built locally with Sphinx 8.2.3 and graphviz 14.1.2 (no doxygen): no warnings
from these files; the new page, the Coefficients parameter list, the
cross-references and the inheritance diagrams render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEhZZbGLmd57vYntPKW6am
(cherry picked from commit e8664b4)
…covered lines (same hunk as #209 7c2a5d5, #211 68f5df0)

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

The floor was recorded on main ce43b73 (33f30b1): 522 of 685 statements, 76.2%. #199 (C0 round 5,
76f3e12) replaced NCLS3P's own scatterDOFArray (15 statements, all covered) by the one on OneLevelTransport, so main
c09280c has 507 of 670, 75.67%: the same lines untested, a smaller file. This branch touches no NCLS3P line; its
coverage job (run 42119354796) reports NCLS3P 670 statements, 75.7%, the only module below a floor. Same hunk as
7c2a5d5 on c0-r6; the new floor is the measured value rounded down to a tenth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test/TwoPhaseFlow damBreakLevelSet with interface=1 (RANS2P + CLSVOF, he=0.1), hot start vs the uninterrupted run,
largest relative difference over every model's dofs at the final time (macOS arm64, serial):

  restart at 0.04 (archived every 0.02), run to 0.1     main c09280c 2.3e-3 (CLSVOF), this branch 0.0
  restart at 0.005 (archived every 0.005), run to 0.03  main c09280c 1.2e-2 (CLSVOF), this branch 0.0

Same accepted times and forceHistory_p rows after the restart. --hotStartMode restart at 0.04: 5.5e-3 (so the check can
fail). Nothing changed for it here: the generic owners of 605b1e5 and 9f55ebe (coupled fields, BackwardEuler state,
solver unknowns, next system step, CLSVOF's disc_ICs in state()) carry it. test_hot_start_continues_the_uninterrupted_run
is parametrized over the level-set family and CLSVOF: 4 passed (-n 4, 5 s).

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

VOF3P.ShockCapturing and NCLS3P.ShockCapturing run the same machine as VOF's and NCLS's (start unlagged with
numDiff_last the alias of numDiff, switch to a lagged copy after nStepsToDelay history updates) without
DelayedLagState, so a hot start rebuilt it unlagged: the first step after the restart used the current numDiff where
the uninterrupted run used the lagged one. They now take the mixin (lag, nSteps, alias or numDiff_last copy).

test/surface_tension/rising_bubble_rans3p (VOF3P, NCLS3P, RDLS, MCorr3P, RANS3PF, PresInc, Pres, PresInit),
hot-started at 0.02 and run to 0.05, largest relative difference over each model's dofs vs the uninterrupted run
(macOS arm64, serial; found with the dump-and-transplant tools of this round: dump every array and scalar of every
model at its first solve after the restart, then put the uninterrupted run's values into the hot run by group):

  main c09280c                    8.1e-1 (MCorr3P), VOF3P 2.5e-2
  this branch before this commit   2.2e-3 (MCorr3P), VOF3P 2.4e-5, velocity 1.2e-4; pressureInitial 1.4e-2
  this commit                      1.9e-4 (MCorr3P), VOF3P 6.1e-8, velocity 5.1e-7; pressureInitial 1.4e-2
  each left out, with the next commit (which brings the rest to 0.0):
    VOF3P shock capturing          1.3e-4
    NCLS3P shock capturing         1.9e-3

RANS3PF.SubgridError also switches to lagging on this deck (lag/nSteps differ after the restart), but a state() for
it changed nothing (0.0 with and without, next commit applied), so it stays "rebuilt" in the inventory. Inventory:
SC mprans.NCLS3P and mprans.VOF3P ShockCapturing unmeasured -> state. test_state_contract: 8 passed. The pinning test
comes with the next commit, which closes the remaining gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Between reading the archive and the first step, a hot start evaluates every model's residual once (to compute the
coefficients) and runs the spin-up; either may change a model's dofs, which the uninterrupted run never does at that
instant. Two causes, on test/surface_tension/rising_bubble_rans3p:

- RDLS's residual freezes its interface dofs to the level set's current dofs (freeze_interface_within_elliptic_redist).
  At the restart those are NCLS3P's mass-corrected dofs; the uninterrupted run still holds RDLS's own last solution
  (the archive's phid, exactly), from which RDLS's next preStep builds the normal reconstruction (lumped_qx/qy,
  ELLIPTIC_REDISTANCING=2). Found by tracing RDLS's dofs through the hot start: they left phid by 1.2e-5 in the
  coefficient evaluation (NumericalSolution line 653), nowhere else.
- the spin-up (modelSpinUpList = [pressureInitial]) solves pressureInitial again at the restart time; the
  uninterrupted run solved it at t=0 only. Nothing else read it after the spin-up on this deck (every other model
  0.0 with the RDLS cause alone fixed), but its dofs and archive differed.

NS_base.restoreArchivedDofs keeps each model's dofs as read and puts them back after the solver state, for every
model whose state was restored (a continue hot start). The solver's unknowns are the unknowns owner's, as before. A
restart (--hotStartMode restart, or a continue that falls back to one) is unchanged: it rebuilds from the restart
state, spin-up included.

rising_bubble_rans3p hot-started at 0.02, run to 0.05, largest relative difference over each model's dofs vs the
uninterrupted run (macOS arm64, serial):
  before this commit   1.9e-4 (MCorr3P), VOF3P 6.1e-8, velocity 5.1e-7; pressureInitial 1.4e-2
  after                0.0 on every model, pressureInitial included; same accepted times; forceHistory_p,
                       forceHistory_v and momentHistory rows identical
  each cause left out  RDLS 1.9e-4 (MCorr3P); pressureInitial 1.4e-2 (its own dofs only)
  --hotStartMode restart from the same archive: 8.4 (pressure increment), 8.4e-1 (MCorr3P)
damBreakLevelSet (both restarts, level set and CLSVOF) stays 0.0.

The deck gains a context option T (the automated test's final time, default 0.01 as before) so the harness can run it
further. test_hotstart_levelset.py: test_rans3p_hot_start_continues_the_uninterrupted_run (continue exact; restart
> 1e-3); 12 passed (-n 4, 12 s). Also run (-n 4): test_step_retry, test_hotstart_*, test_state_contract,
test_mpi_launch_retry, test/TwoPhaseFlow, test/HotStart_3P, test/surface_tension/rising_bubble_rans3p: 89 passed
(TwoPhaseFlow with the tree's scripts/parun first on PATH).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns (staticmethod sweep, non-mprans)

From cekees/c47-cython3 at 726eed5 (#204).

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

From cekees/c7-phase-d at ea32584 (#213).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…document ADR.Coefficients (forward port of #197)

From cekees/adr-docs-main at 4da6806 (#215).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main train 2026-11-06: C47 Cython 3 binding fix, C7.1 Phase D per-rank restart views, ADR docs
… on every rank

archiveSolverState compares the ranks' lists of dataset names before its collective creates and writes
nothing for a model whose lists differ. The lists follow the state() dicts' insertion order, and RANS2P's
numericalFlux.ebqe lists ('u', 0)..('u', 3) in an order set by each process's string hash seed, so on two
ranks the coupled-fields owner's names came out in different orders about one run in two: "the ranks have
different solver state to write ... model rans2p" (level-0 log line only), no solver_state/rans2p in the
archive, and the next same-rank continue fell back to a restart with the RuntimeWarning. The earlier
2-rank "0.0" for damBreakLevelSet was a run that happened to hash alike.

Measured (damBreakLevelSet, 2 ranks, -mat_partitioning_type current, archive every 0.02 to 0.04):
  archives missing solver_state/rans2p, random seeds    pre-merge c0bed9e 3/6, after the merge of
                                                       cekees/main 5/6 (not #213's: same before it)
  per-rank seeds equal (1/1, 2/2, 7/7, 5/5)             never; different seeds 5/6: always
  continue at 0.04 to 0.1, seeds 5 and 6, vs the uninterrupted run (same seeds)
    before   3.7e-3 on both ranks, one RuntimeWarning (rans2p)
    after    0.0 on both ranks, same accepted times, no warning
The fingerprint stays first and the step stamp last (written last, as the docstring requires).
The order inside an owner's state does not matter on read (by name).

test_a_two_rank_continue_is_exact_whatever_each_rank_hashes: an MPMD launch with PYTHONHASHSEED 5 and 6;
fails without this change, passes with it. With cekees/main afcedf6 merged (#204, #213, #215), -n 4:
test_step_retry, test_hotstart_* (test_hotstart_levelset 13), test_state_contract, test_mpi_launch_retry,
test/TwoPhaseFlow, test/HotStart_3P, test/surface_tension/rising_bubble_rans3p,
test/mesh_tests/test_subdomain_view_restart (skipped: this env's ymf has no ymf.mesh),
test_cython_class_attributes: 114 passed, 1 skipped. The one-warning tests (perturbed partition
fingerprint; 2 ranks archived, 3 continued) pass with #213's per-rank-view restart source.

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.

2 participants