Skip to content

C0 round 6 - #209

Draft
cekees wants to merge 8 commits into
devfrom
c0-r6
Draft

cekees wants to merge 8 commits into
devfrom
c0-r6

Conversation

@cekees

@cekees cekees commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

C0 round 6 (2.0-only). One commit per item, each with a test that fails without it. macOS arm64, MPICH,
FI_PROVIDER=tcp, -mat_partitioning_type current. Merge after or with #204 (C47): the dambreak_1d deck test skips,
saying why, until #204's staticmethod limiter attributes are on the branch; nothing here touches TimeIntegration.py.

commit item main after
7d95b91 RusanovNumericalFlux (and _Diagonal, ConvexOneSonicPoint) take the ordinary 4-argument call; sweep of all 50 NF_base subclasses 2 classes refuse the ordinary call, 1 base __init__ call does not bind; dambreak_1d TypeError at NumericalFlux.py:4726 (since 2014) 0 / 0; the 23 classes without a periodic argument pinned
bb12679 ctimeIntegration wrappers take TimeIntegration.py's arguments again (broken by the 2019 Cython rewrite, proteustoolkit#879) 11 of 16 calls do not bind (every 1D DG limiter, Durlofsky 2D/3D, PsiTCtte) 0; limiter and psiTC values checked directly
f2938dc RANS2P_IB deck (test/rans2p_ib/beam_channel, rigid or flexible beam) and four faults: Jacobian elf NameError; Coefficients dropped every argument; beam loads integrated over ghost elements too; history rows to stdout / not deferred deck cannot run; with the first two fixed, 2 ranks: drag +40% (rigid), +42% and 70% more bending (flexible) serial = 2 = 3 ranks to 4e-16; forced retry = failure-free replay 0.0 (rigid, flexible)
6412404 dambreak_1d deck test, serial and 2 ranks deck fails mass 29/6 at all 101 outputs to 1.2e-14; serial = 2, 3, 4 ranks bitwise (element partition, 1 layer)
7c2a5d5 coverage floor NCLS3P.py 76.2 -> 75.6 main is below its floor: #199 moved 15 covered statements out of NCLS3P (522/685 -> 507/670); #212 fails on the same the measured value
83ac62c Step retry: a model that finished in the failed attempt steps again (saveSystemStepState/restoreSystemStepState keep each model's t_model_last; SO_base.modelStepTaken, i.e. Sequential_MinModelStep, the default) beam channel + dye (beam_channel_so.py), dye fails: RANS2P_IB skipped in the retry, one step behind after, never reaches T; 9.2e-2 against the failure-free replay 6.4e-6 (RANS2P_IB's lagged drag, next row)
07f4d6a RANS2P_IB Coefficients.state(): the lagged beam drag (q/ebqe_dragBeam*, loads q1-q3) and the beams (xv..zq, each solver's Phi, x, y, z, file counter) 6.4e-6 (rigid and flexible); drag only restored: 6.6e-7 and the deflection 1.8e-3 off 0.0, deflection history equal, no row from the failed attempt
8f919e2 Diffusion_IIPG/SIPG_exterior, Stokes_*_IIPG/SIPG_exterior refuse periodic conditions (ValueError naming StrongDirichlet); StrongDirichlet, DoNothing, NoFlux measured unaffected and keep them C0 P1 periodic Poisson, 8x8: L2 3.5e-1 / 4.4e-1, Newton never converges (3.2e-2 / 4.3e-2 in 1 iteration without the periodic pair) refused; no deck or test builds them with periodic conditions

Measured, not changed: RANS2P_IB's linear Newton rate is RANS2P's (its Jacobian freezes the stabilization tau; with
subgridError.lag=True RANS2P_IB converges quadratically), not the beam drag, which is a lagged constant source.
Advection_DiagonalUpwind_Diffusion_IIPG_exterior has the same periodic-seam defect as the refused fluxes (1.06e-1 vs
3.2e-2) but pairs faces and is the base of the mprans VOF/NCLS fluxes that run periodic decks; fix in a later round.

Not in this PR (in the round handoff): the RANS3PSed deck (none has ever existed in this repository; its 6-10-model
sediment chain is missing), the moving-mesh retry (Chrono state), and a measured proposal for node flags without
vertex markers (in-memory and -F differ, and -F is partition-dependent; the fix belongs to C32/C7.1 files after a
decision).

🤖 Generated with Claude Code

cekees and others added 5 commits November 5, 2026 21:58
…gument call

OneLevelTransport builds a flux as numericalFluxType(vt, dirichlet, advective, diffusive) and appends the periodic
conditions only when the p module sets them. RusanovNumericalFlux_Diagonal and ConvexOneSonicPointNumericalFlux
required the periodic argument, so the ordinary call raised TypeError, and RusanovNumericalFlux called
RusanovNumericalFlux_Diagonal.__init__ with four arguments, so it failed even when given five. Every deck using
RusanovNumericalFlux stopped in its constructor (test/shallow_water/dambreak_1d at NumericalFlux.py:4726), since
b8912a0 (2014) added the periodic argument. The periodic argument now defaults to None in all three and
RusanovNumericalFlux forwards it; the Rusanov exterior flux already pairs periodic faces.

Sweep of the 50 NF_base subclasses (test/test_numerical_flux_constructors.py, which imports the module and binds
every constructor and every Base.__init__(self, ...) call):
  ordinary call refused:       main 2 (ConvexOneSonicPoint, Rusanov_Diagonal)  -> 0
  base __init__ call unbound:  main 1 (RusanovNumericalFlux:4726)             -> 0
  periodic argument dropped by a subclass that takes one: 0 on main
23 classes take no periodic argument at all; a periodic deck with one of them stops with TypeError. The test pins
that set: most of them have no periodic face pairing, so accepting the argument would hide the gap, not close it.
Seven more accept it and pair no faces (Diffusion_IIPG/SIPG_exterior, Stokes_*_IIPG/SIPG_exterior, NoFlux,
StrongDirichlet, DoNothing): noted in the round handoff, not changed.

dambreak_1d after this commit gets past the flux and stops in the DG limiter (ctimeIntegration, next commit).
Tests: test_numerical_flux_constructors (3, all 3 fail on main), test_numerical_flux_arguments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 2019 Cython rewrite of the ctimeIntegration C module (proteustoolkit#879, f277124) gave seven wrappers new signatures: the
sizes as leading arguments (nElements_global, nNodes_element, ...), the 1D limiter's flag first instead of last, and
psiTCtteDT writing dt into a seventh argument instead of returning it. TimeIntegration.py kept the old calls, so 11 of
its 16 calls into the module raised TypeError on first use: every 1D DG limiter (DGlimiterP1Lagrange1d and the
classes built on it, the _withVacuumTol ones), the Durlofsky 2D/3D limiters and PsiTCtte's dt choice. The three
Cockburn/shape-gradient wrappers already took the old arguments and are unchanged. The wrappers again infer the sizes
from the array shapes, exactly as the C module did (nSpace from grad_v's last axis, nDOF from l2g), take the limiter
flag last with its old default 1 (MUSCL), and psiTCtteDT returns dt. Nothing else calls these wrappers.

  TimeIntegration.py calls that do not bind:   main 11 of 16 -> 0
  P1 1D limiter, 4 elements: linear data kept, an extremum cell and its falling neighbour flattened to their means
  (flags 0/1/2; vacuum-tolerance variant the same); psiTCtteDT reads all 6 points of a (2,3) array and returns
  sqrt(tau/(max dy + 1e-8))*0.9 exactly.
  test/test_time_integration_wrappers.py: 3 tests, all 3 fail on main.

test/shallow_water/dambreak_1d (RKDG P1, SSPRK2, Rusanov, P1 limiter) then runs to T=20 in 2.9 s, given the Cython 3
staticmethod wrapping of the class-stored limiter functions in TimeIntegration.py (#204, C47; without
it Cython 3 passes self as an extra argument). Measured with #204's TimeIntegration.py hunks applied locally: mass
29/6 at all 101 outputs to 1.2e-14 (the P1 interpolation of the step), min h -5.0e-8 (3 outputs
below 0, at the wall cell as the front arrives), and with an element partition
(1 layer) serial = 2, 3 and 4 ranks to the last bit in h and hu at all 101 outputs. The deck test lands after #204.

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

RANS2P_IB (RANS2P plus immersed Euler-Bernoulli beams, beamFEM) had no deck and could not have run one:

1. getJacobian read `elf.coefficients` (NameError on the first Newton iteration).
2. Coefficients passed RANS2P its default arguments instead of its own, so rho, nu, g, nd, LS_model, useMetrics, ...
   were dropped: every RANS2P_IB run was 3D single-phase water (nu_0=1e-3, nd=2, LS_model=2 came out as 1.004e-6,
   3, None). It now forwards them; the defaults it had (dragAlpha 0.01, eb_penalty_constant 10) are unchanged.
3. calculateBeams integrated the beam loads q1-q3, vel_avg and netBeamDrag over ghost elements too, then summed over
   ranks, so elements shared by two ranks counted twice. They are now integrated over owned elements; every local
   element still gets q_dragBeam*, the fluid's source. (C0's kernel territory, mprans/*.h.)
4. postStep printed the forces to stdout, so the history files RANS2P opens stayed empty, and wrote the deflection
   row directly. It now queues RANS2P's rows (time, wetted area, forces, moments), the beam drag and the deflection
   with deferWrite, so a retried step leaves no row. (RANS2P's postStep itself does not fit: this LevelModel is an
   older copy without the lagged dV/active-element arrays, which its kernel does not read.)

Deck test/rans2p_ib/beam_channel: 0.4 x 0.69 x 0.2 channel from rest, inflow 0.1 m/s, no slip on the bottom and side
walls, one beam (radius 5 mm, height 0.1) at (0.2, 0.345); rigid (default) or flexible (beamRigid=False, EI=GJ=1e-4,
bends ~1 cm by t=0.04). P1, Backward Euler, dt 0.01 to 0.04, direct solves, Newton atol 1e-10.

Serial vs N ranks (node partition, no overlap, sums over owned nodes of p, u, v, w and squares, beam drag, q1-q3,
beam vertex coordinates), worst relative difference:
                          before 3. (1, 2 fixed)                     after
  rigid,    2 ranks       drag 1.12e-2 vs 7.99e-3 (+40%), q1 +41%    4e-16 (drag1, 4e-6 itself, 4e-14)
  flexible, 2 ranks       drag +42%, beam x-sum 1.0331 vs 1.0195,    4e-16
                          fluid u-sum 3.7e-8
  rigid/flexible, 3 ranks                                            4e-16
  (the fluid matched to 1e-15 before 3. for a rigid beam: the loads feed back only through a flexible beam's shape)
Retry (test_step_retry, two new cases): RANS2P_IB failing its 3rd solve, retried at half the step, against a
failure-free run on the same times: 0.0 in p, u, v, w, rigid and flexible; the deflection and time histories hold one
row per accepted step (before 4.: timeHistory.txt empty). NORESTORE gives 0.0 too on this single-model deck: postStep
runs only after an accepted step, so nothing of the beam's is ahead of the retry. In a multi-model deck a later
model's failure would leave a flexible beam moved (Beam_Solver Phi, xv/yv/zv, xq/yq/zq): RANS2P_IB needs a state()
for that, noted in the round handoff.

Tests: test/rans2p_ib/test_rans2p_ib.py (3; on main the deck stops at 1., and the coefficient test fails),
test_step_retry.py beam_channel cases (2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deck (RKDG P1 shallow water, Rusanov flux, P1 limiter, SSPRK2, a dry-bed dam break between two walls, 30
elements to T=20) stopped in RusanovNumericalFlux's constructor (7d95b91) and then in the 1D limiter wrappers
(bb12679). Measured (macOS arm64, with #204's TimeIntegration.py, which Cython 3 needs for the limiter
class attributes):
  mass 29/6 (the P1 interpolation of the step) at all 101 outputs to 1.2e-14;
  h below zero at 3 of 101 outputs, by at most 5.0e-8 (t=0.6, the wall cell as the front arrives: the P1 limiter
  does not preserve positivity); h in [0.3645, 0.4985] at T=20, settling to the rest depth 29/60;
  element partition, 1 layer, bjacobi/LU mass solve: serial = 2, 3 and 4 ranks to the last bit in h and hu at every
  output (2 ranks in the test).
The test skips, saying why, until #204's staticmethod attributes are on the branch, and runs from then on. The p
module's docstring is now raw (it held "\in", a SyntaxWarning).

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. Every pull request on c09280c without #204
(whose staticmethod lines add covered statements to the file) is below the floor: this one's coverage job and #212's
both report NCLS3P 75.7%. Line by line, the NCLS3P lines hit in this branch's run and in #204's run (mapped through
#204's edits) are the same set. The new floor is the measured value rounded down to a tenth.

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 08:51
SO_base.modelStepTaken (Sequential_MinModelStep, default_so's default, 11 so files under test/; and
Sequential_tnList) moves a model's t_model_last to the end of the system step as soon as that model's step
is taken. When a later model of the same system step failed, restoreSystemStepState put the dofs and the
lag state back but not t_model_last, so the retry, stepping to an earlier time, found the model already
past it and skipped it; from then on it ran one step behind the others and never reached T. The
controllers that override modelStepTaken (Sequential_MinAdaptiveModelStep, the TwoPhaseFlow decks, and
the FLCBDF ones) do not move it there, which is why test_step_retry's cases never saw it.

saveSystemStepState now keeps each model's t_model_last and restoreSystemStepState puts it back.

The case: test/rans2p_ib/beam_channel_so.py, the beam channel with a dye (VOF, carried by RANS2P_IB's
velocity) after RANS2P_IB; the dye's third solve is reported failed (step_retry_mpi.py FAILMODEL=dye
FAILAT=3), the step 0.02 -> 0.03 is cut to 0.025, against a failure-free run on the same steps (REPLAY).
Largest relative difference over each model's dofs, macOS arm64, serial, SuperLU:

  | | RANS2P_IB p, u, v, w | dye |
  |---|---|---|
  | main c09280c (rigid and flexible beam) | 4.8e-2, 8.0e-2, 5.2e-2, 9.2e-2 | 3.3e-4 |
  | this commit | 1.2e-6, 2.8e-6, 6.4e-6, 3.3e-6 | 2.7e-8 |

Model solves of the forced run on main: beam_channel at 0.01, 0.02, 0.03 | retry: dye 0.025, beam_channel
0.02 (dt 0.005), dye 0.03, beam_channel 0.025, ... beam_channel last at 0.035. With the fix both models solve
at 0.025, 0.03, 0.035, 0.04. What remains is RANS2P_IB's lagged beam drag, which the retry does not restore
yet (next commit); the new test bounds it at 1e-5 until then.

test_step_retry.py (all cases) passes unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RANS2P_IB's beam drag is lagged: after each model step its LevelModel's beamStep spreads the drag from the
beams onto the mesh (Coefficients.q_dragBeam1-3, ebqe_dragBeam1-3, which the next step's residual reads)
and integrates the beam loads (q1-q3); postStep then moves a flexible beam (xv..zq, each beamFEM solver's
Phi and x, y, z) and numbers the beam file (tStep). Both run before the later models of the system step.
Its Coefficients had no state() (RANS2P.Coefficients.state returns None for this older LevelModel copy),
so when a later model failed, the retry restored the dofs and kept the failed attempt's drag and beam.

Coefficients.state()/set_state() now carry them (vel_avg is set in attachModels so state() has it before
the first beamStep). The same state is archived for a hot start (solver_state/<model>/coefficients).

test/rans2p_ib: the dye after RANS2P_IB fails its third solve (beam_channel_so.py, previous commit), the
retry against a failure-free run on the same steps. Largest relative difference over each model's dofs,
macOS arm64, serial, SuperLU:

  | | RANS2P_IB p, u, v, w | dye | flexible beam deflection at the retried step |
  |---|---|---|---|
  | previous commit, rigid | 1.2e-6, 2.8e-6, 6.4e-6, 3.3e-6 | 2.7e-8 | |
  | previous commit, flexible | 1.3e-6, 3.2e-6, 6.4e-6, 3.8e-6 | 2.7e-8 | |
  | drag restored, beam not (flexible) | 8.8e-8, 1.3e-7, 6.6e-7, 1.8e-7 | 7.7e-10 | 8.2941e-3 vs 8.2956e-3 |
  | this commit, rigid and flexible | 0.0 | 0.0 | equal, no row from the failed attempt |

The test's bound goes from 1e-5 to 1e-12, and the flexible case checks the deflection history.
Ran: test/rans2p_ib (5, incl. serial = 2 ranks), test_step_retry beam_channel cases (2).
Not checked: a hot start of the deck, which stops before reading any state (the restart reads
regular_9x14x5_grid.node/.ele, which an in-memory box mesh never writes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 6's sweep found seven fluxes that accept the periodic argument and pair no periodic faces
(Diffusion_IIPG/SIPG_exterior, Stokes_Advection_DiagonalUpwind_Diffusion_IIPG/SIPG_exterior, NoFlux,
StrongDirichlet, DoNothing). All seven are boundary-only, so they serve C0 spaces, where the periodic DOF
map already joins the seam; whether skipping the pairing matters was measured, not assumed.

NF_base marks the face points of a periodic pair as DOF boundary with no value. The diffusion fluxes then
apply their weak-Dirichlet flux there with the trace equal to the interior value: the residual keeps a
one-sided -a grad(u).n on each side of the seam (it does not cancel, grad(u) jumps across) and the
Jacobian a penalty the residual lacks. The other three put nothing at the seam that depends on it.

C0 P1, -Laplace u = 5 pi^2 sin(2 pi x) sin(pi y) on the unit square, u = 0 at y = 0, 1, 8x8 cells, L2 error
and Newton iterations; "without" makes x = 0, 1 Dirichlet (the exact solution vanishes there too):

  | flux | without the periodic pair | periodic in x |
  |---|---|---|
  | Diffusion_IIPG_exterior | 3.2e-2, 1 | 3.5e-1, not converged in 30 (10 retries) |
  | Diffusion_SIPG_exterior | 4.3e-2, 1 | 4.4e-1, not converged |
  | StrongDirichlet | 4.6e-2, 1 | 4.7e-2, 1 |
  | NoFlux | 4.1 (imposes no Dirichlet) | 4.1, 1 |
  | DoNothing | 1.5 (imposes no Dirichlet) | 1.5, 1 |

So the four diffusion fluxes (Stokes: the same diffusive flux at the same points, not run, no periodic
Stokes deck exists) now raise ValueError when built with periodic conditions, naming StrongDirichlet.
NF_base checks the exact class name (PERIODIC_SEAM_REFUSED); subclasses with their own exterior flux
(the HamiltonJacobi Lesaint-Raviart SIPG pair) are not covered. StrongDirichlet, DoNothing and NoFlux keep
accepting periodic conditions.

Who builds them with periodic conditions (Transport hook over test/periodic, TADR, LS_with_edgeBased_EV,
POD, test_adr_problem, test_pro_cli; plus a grep of every p/n module that sets periodicDirichletConditions):
  StrongDirichlet: ADRProblem's StrongDirichletFactory (test_adr_problem's periodic Poisson, periodic
    channel, periodic on 2-4 ranks);
  DoNothing: test/periodic ls_consrv_n, redist_n; la_periodicGauss_n, hotstart_measure/rkev_gauss_n
    (MCorr, RDLS);
  the four refused classes: nobody. The Stokes ones are used without periodic conditions only
    (solver_tests stokesDrivenCavity_2d_n, mesh_tests stokes_2d_n), unaffected.

Not changed, for the next round: Advection_DiagonalUpwind_Diffusion_IIPG_exterior pairs the periodic faces
and has the same seam defect on the same problem (1.06e-1 vs 3.2e-2, Newton not converged). The mprans
fluxes built on it (VOF, NCLS, CLSVOF, Kappa, Dissipation) run periodic decks in test/periodic; VOF and
NCLS have no diffusion there. Refusing it would break them; the fix is to drop the exterior diffusive flux
at C0 periodic face points.

Ran: test_numerical_flux_constructors (5), test_numerical_flux_arguments, test/periodic, test/TADR,
test/LS_with_edgeBased_EV, test/POD, test_adr_problem, solver_tests/test_stokesDC_TH_2D,
mesh_tests/test_quads: 127 passed, 6 skipped, 1 failed (test_ymfrun_knows_which_domains_fix_the_mesh:
the env's ymf has no discretizations.mesh_from_domain; fails the same without this commit).

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