Repository navigation
Conversation
…lement interiors Forward from where the values are known best. A field is known exactly at its own nodes and, since its interpolant is a polynomial inside each element, at any interior point. ForwardNodesSemiLagrangian launches the values at the nodes and at the interior lattice of every element (the discontinuous basis one degree up), carries them one step forward on the shared characteristic trace, fits the arrivals in each cell at the field's own degree (the cell polynomial projector, with its thin-cell and conditioning fallbacks) and reads the fit back at the nodes. The fit never crosses an element boundary, where the interpolant has a kink; a neighbourhood fit across cells was tried first and smoothed (disc error 3.6e-2). As the DuDt of the SLCN advection-diffusion solver on the rotating diffusing Gaussian (P2 temperature, dt 0.02, kappa 0.01): half revolution on the disc 2.00e-2 (backward nodal history 2.52e-2, swarm 2.04e-2, SUPG 1.72e-2), peak 0.1873 against 0.1865 exact; the square box, where the flow crosses all four walls, 6.17e-2 (backward 6.43e-2); a full revolution 7.63e-2 (backward 8.94e-2, SUPG and swarm 8.2e-2). About twice the backward cost per step. First order, serial (the fit near a partition seam needs the other rank's arrivals). Test test_1102: disc and box, hard baselines. Underworld development team with AI support from Claude Code Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
… forward from nodes as an advection-diffusion option ddt.SemiLagrangian(mesh, psi_fn, V_fn, vtype, trace=, launch=) selects one of four schemes: BackwardNodesSemiLagrangian (was SemiLagrangian), BackwardIntegrationPointsSemiLagrangian (was IntegrationPointSemiLagrangian), ForwardIntegrationPointsSemiLagrangian (was ForwardSemiLagrangian) and ForwardNodesSemiLagrangian. A keyword the chosen scheme does not take is a TypeError. The old class names resolve with a FutureWarning. stress_transport takes "backward_nodes" (default), "backward_integration_points", "forward_integration_points", "lagrangian", "eulerian"; the old strings map with a FutureWarning. AdvDiffusionSLCN(transport=...) chooses the value history among the four semi-Lagrangian schemes. ForwardNodesSemiLagrangian carries a field known at its nodes and refuses a flux: a stress is formed at the integration points. A cell nothing reached now keeps the field it launched (no fit carried between steps); inflow is detected by the back-reflected point leaving the domain; a moved mesh is refused. ForwardIntegrationPointsSemiLagrangian takes degree and refuses anything but 1. BackwardNodesSemiLagrangian's continuous now defaults to True. Underworld development team with AI support from Claude Code Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…l; forward from nodes carries a stress Forward from nodes as a stress history: the stress is committed by L2 projection onto the continuous store and launched from that field. It is offered by stress_transport="forward_nodes". The per-cell fits are projected onto the store (a shared node weighs every cell), each cell's fit uses only its own arrivals (a linear fit, their mean, or the launched field when too few), the interior lattice is two degrees up, and an arrival on a shared face is fitted in every cell that contains it. In parallel a seam node is launched by its owner, and arrivals that left a rank or sit on a face are offered to every rank. Partition dependence removed from the existing histories: - a field that is a mesh variable (T.sym as well as T) is recorded into the nodal and integration-point histories by copying its nodal data, in serial as in parallel, instead of evaluating it at nudged nodes; - the nodal trace starts from the node itself: the 0.1% nudge toward the closest cell's centroid depended on which cells a rank holds and gave a no-slip wall node a velocity; - every history projection is solved to 1e-10; - global_evaluate: a point that the migration stranded but that a rank's cell contains is evaluated there with the FE interpolant, not by the nearest-centroid rbf extrapolation (3e-2 at a steep stress). tests/parallel/test_1066: six stress and four advection histories equal serial to 1e-6 at np >= 3 (verified np 3, 4, 6); the particle history is a strict xfail at np >= 4 (1.2e-5, cause open). test_1062 tightened to 1e-6. Underworld development team with AI support from Claude Code Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…tory projections - forward from nodes: an inflow node is one whose back-reflected point the global restore moves (a per-rank "in domain" test called every partition face a boundary); ownership of seam nodes read from the global section (_owned_rows) instead of a write-and-read-back; the tracked field copied, not evaluated, at the nodes; a strictly-inside arrival fitted in its one cell only; containing_cells falls back to the locator for a point whose cell is not among the nearest centroids; quadrilateral centroids corrected. - global_evaluate: the containment round keeps a finite rbf value where the FE value is NaN, and runs only where the cell hint is authoritative (no DMLocatePoints collective inside it); the deadlock note says so. - every history projection is a CG/Jacobi solve (linear_solver moved to the projection mixin) to 1e-12, from zero: the warm start depended on a state a restart does not restore. - Eulerian record uses _tracked_field; _tracked_field refuses another mesh's variable; stress_transport names map through one (trace, launch) table. - tests: test_1066 resets the model per case, asserts the forward exchanges ran, gives the value histories an inflow, marks the particle case #797 (xfail, np >= 4); test_1063 covers forward_nodes; test_1102 held to 1e-4. test_1066 at np 4 is the regression test for the containment round. - TODO(DESIGN) notes: face points offered to every rank; the two forward flavours' arrival rules. np 3/4/6: 10 passed (+1 xfail at 4/6); serial: 388 passed. Underworld development team with AI support from Claude Code Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
Adversarial reviewTwo review passes before the push (on c434d8a and on b20c992). Findings and disposition: Fixed
Deferred (TODO(DESIGN) in the code)
Filed: #797 (particle history, np >= 4), #798 (evalf extrapolation flag), #799 (serial evaluate near nodes). Underworld development team with AI support from Claude Code |
…Stokes velocity history offers every semi-Lagrangian scheme global_evaluate applied monotone="clamp" on the rank that asked for a point, bounding it by that rank's nearest nodes; for a departure point evaluated on another rank those are the wrong neighbourhood. The bound now runs on the evaluating rank (first pass and containment round), on non-dimensional values like the nodal data it compares with. Serial results are unchanged. #682's level-set reproduction (LeVeque swirl, 128^2 quad, P2, SLCN with clamp): volume at np 1/2/4/8 now 0.0706771442/415/438/412 (np 8 was 1.6% off). NavierStokesSLCN(velocity_transport=...) chooses the velocity history among the four semi-Lagrangian schemes, through one _value_history helper shared with AdvDiffusionSLCN(transport=...). Forward schemes need order=1; the forward integration-point fit is linear and refuses a P2 velocity. tests: test_1103 (lid-driven cavity, Re 100, three schemes against recorded values and each other); test_1066 adds the cavity (equal to serial at np 3/4/6). tests/parallel at np 4: 168 passed, 1 xfailed, 1 failed (test_1063_constrained_freeslip_parallel[ti], fails identically on #795's 05d5248). Serial set: 393 passed. Underworld development team with AI support from Claude Code Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
|
Added (28314aa):
Underworld development team with AI support from Claude Code |
…es solver takes any stress history and the log-conformation store uw.systems.NavierStokes: the strong residual SUPG weights now includes the divergence of the stress the history carries (_memory_stress: the model's effective strain rate with the velocity's own derivatives set to zero, times 2 eta, plus the theta rule's stored levels; decoded for a log store). An integration-point store has no derivative; its nodal snapshot stands in, an O(dt) difference. On a developing Oldroyd-B channel (Re 250, Wi 0.6) the SUPG solution moved toward the Galerkin one on the same mesh: velocity 5.0e-5 -> 3.2e-5, stress 3.4e-6 -> 1.6e-6. (Fully developed channel flow is blind to this: the missing residual is constant along streamlines.) NavierStokesSLCN: - stress_transport, the stress-history factory and its prepare/carry/commit steps move from SNES_Stokes into _StressHistoryMixin, shared by both; the solver used to ignore stress_transport (a plain attribute) and always carry a backward nodal history, recorded a step late (#742). - the momentum flux's theta rule reads the carried stress through the model (a log-conformation store decodes) and adds the solvent stress of the carried velocity; the solvent stress was missing from its momentum flux. - its time order is the momentum's (_momentum_order); the stress history's order is the constitutive model's (the solver's order used to be forced on the model). - a supplied DFDt is used (it was overwritten); the viscous-flux history is built only for a model without a stress history. tests: test_1104 (SUPG memory stress: structure, snapshot stand-in, recorded developing channel); test_1059 runs every stress transport and the log store (N1 of upper-convected start-up 1.193 against 1.188 exact; the linear stress store gives 1.108) through NavierStokesSLCN. Serial set 403 passed (+ the test_1060 fix); parallel np 3/6 transport tests pass; tests/parallel at np 4 as before (#801 pre-existing, #797 xfail). Underworld development team with AI support from Claude Code Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…ients in the SUPG memory term, snapshot stand-ins - Both Navier-Stokes solvers form the viscoelastic momentum flux with one helper (_StressHistoryMixin._theta_rule_flux) using the MOMENTUM scheme's weights (DuDt.spatial_weights: BDF puts it all on the new level, the theta rule at first order); NavierStokesSLCN used the stress history's AM weights, a BDF2 derivative against a Crank-Nicolson flux by default. A stored level the stress history does not hold is an error, not a clamp. - _memory_stress is the model's own flux with the velocity's derivatives set to zero (any model: the transversely isotropic one too; the solvent drops out), with expression containers that vary in space expanded first so a varying modulus or viscosity contributes its gradient; constant containers stay runtime parameters. - History stores without a derivative give their stand-ins through one method (_derivative_stand_ins: the integration-point history's nodal snapshots, psi and forcing); used by the SUPG memory term and by the solvent term of an integration-point velocity history, which failed to compile with a viscoelastic model. - Constitutive_Model._carried_stress_sym default (the stored level); the hasattr branches go. - NavierStokesSLCN builds its viscous-flux history when a non-viscoelastic model is assigned (DFDt available before the first solve, as the tutorial expects); the flux_order override applies to that history only. - docs: stress-transport "With inertia" matches the code. - tests: test_1104 adds a numeric memory check, the varying-modulus expansion, NavierStokesSLCN on the developing channel (solvent, log store, momentum order 2 against model order 1) against recorded values, and an integration-point velocity history with a viscoelastic model. Serial set 407 passed; np 3/6 transport tests pass; tests/parallel np 4 as before (#801 pre-existing, #797 xfail). Underworld development team with AI support from Claude Code Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
|
Added (2adf50d, c0704a8): the SUPG residual sees the carried elastic stress, and SUPG.
Adversarial review of 2adf50dFixed in c0704a8
Open
Serial transport/NS/VE set: 407 passed. Transport tests at np 3/6 pass; Underworld development team with AI support from Claude Code |
uw.systems.NavierStokes(..., velocity_transport=...) carries the momentum on
the grid ("eulerian", SUPG, the default) or semi-Lagrangianly
("backward_nodes", "backward_integration_points",
"forward_integration_points", "forward_nodes"); stress_transport chooses the
viscoelastic stress history as before. uw.systems.AdvDiffusion(...,
transport=...) takes the same names. The composed solvers already held any
history manager; the argument builds it through the shared _value_history.
- the extrapolation level after a step is the velocity the step started from
(it copied the history's first level: the departure-point velocity for a
semi-Lagrangian history, and the wrong shape for an integration-point one);
- the stored-level viscous stress reads an integration-point velocity store
through its nodal snapshot (it failed to compile);
- NavierStokesSLCN, NavierStokesSwarm (which carried no swarm) and
AdvDiffusionSLCN resolve with a FutureWarning naming the replacement; the
free-surface solver builds its composition solver by class, without it.
Lid-driven cavity (Re 100): the four velocity histories within 0.6% of the
grid scheme; the semi-Lagrangian path within 0.5% of the former
NavierStokesSLCN (the stored-level stress is now rebuilt from the carried
velocity); the viscoelastic developing channel reproduces it to 2e-8.
test_1103 covers all five, test_1066 checks them against serial at np 3/4/6.
Serial set 409 passed; tests/parallel np 4 as before (#801, #797).
Underworld development team with AI support from Claude Code
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…ter for the forward integration-point fit store_smoothing (alpha = c h^2 in the projection that stores a new flux) moves to a mixin shared by the backward and forward integration-point histories and the forward nodal history. The forward integration-point history keeps flux_smoothing as an absolute override and refuses both at once. The forward integration-point fit gains fit_limiter (Barth-Jespersen: keep the centroid value, scale the slope so no dof leaves the range of what arrived in the cell) and records the unlimited fit's overshoot either way. A cell whose arrivals bunch in a corner passes the conditioning test and extrapolates its slope across the cell; on the viscoelastic cylinder (SUPG velocity, log-conformation store) one such cell in the near wake put tau_II = 4 where nothing above 0.8 arrived, and the next solve diverged. Underworld development team with AI support from Claude Code Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…vals in place of the per-cell fit ParticleL2Projector (utilities/particle_projection.py) projects scattered weighted values onto continuous P1 by one weighted least-squares solve: the finite-element mass matrix with the points as its quadrature rule. A node is set by every point in the patch of cells around it, so it is interpolated rather than extrapolated from one cell's points; a weak pull towards the previous field keeps a node nothing reached; the system is assembled per owning cell and summed across partition seams through the local-to-global ADD, so the answer does not depend on the partition. ForwardIntegrationPointsSemiLagrangian gains reconstruction="cell" (the per-cell fit, the default) or "global" (the projection, read back at the store's interior dofs). On the viscoelastic cylinder (Re 200, Wi 0.5, log-conformation store, SUPG velocity) the per-cell fit needed store_smoothing 0.07 to survive and still failed at t 5.2; the global projection runs to t 8 with no smoothing and no limiter, the field's excursion beyond the carried range steady at 5%, and lands within 8% in drag and 12% in lift of the Eulerian stress history at the same Strouhal number. Underworld development team with AI support from Claude Code Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
… per-cell fit ForwardNodesSemiLagrangian gains reconstruction="global": every arrival, from the nodes and the interior lattice, projected onto the degree-1 store in one L2 solve through ParticleL2Projector, each launch point weighing its share of the cell it launched from and an arrival on a shared face counted once (the multiplicity is summed over the ranks). The excursion of the field beyond the carried range is recorded as for the integration-point flavour, and a shared helper measures it. Underworld development team with AI support from Claude Code Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…y can use the global projection The projector takes degree=2 on triangles: rows over the vertices then the edges (the layout of any continuous P2 variable), the quadratic simplex basis in barycentric coordinates, and element mass and stiffness matrices by Dunavant's degree-4 rule. A quadratic field sampled anywhere in the cells is projected exactly, serially and in parallel. ForwardNodesSemiLagrangian accepts reconstruction="global" at degree 2, so a P2 velocity carried forward from its nodes is reconstructed by one projection instead of a quadratic fit per cell. Underworld development team with AI support from Claude Code Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…field; a bubble penalty for the degree-2 fit Deficit fill (on by default): the share of a cell's measure that the arriving weights do not cover enters as finite-element mass with the previous field as its data, the mechanism the inflow cells already use. A cell the flow has emptied is then held by what it carried rather than by a 1e-8 pull and its neighbours. On the viscoelastic cylinder the P2 velocity history blew up at the end of the inflow ramp without this (reversed flow at the wall from edge dofs set by two arrivals) and passed it with it. Bubble penalty (degree 2, bubble_penalty, default 0): each edge dof's departure from the mean of its two vertices is penalised in units of the bubble's own mass. P1 content is untouched; the quadratic content is kept only where the arrivals support it. The data of a fully covered cell hold a bubble with an effective weight of about 0.04 in these units (the fit trades a cell's bubble against its neighbours' dofs), so 0.01 removes about a fifth of a resolved quadratic and 0.1 about three quarters. The cylinder's fully semi-Lagrangian run (both histories by the global projection) survived developed shedding with the penalty where it failed without it; the forward nodal history exposes it as bubble_penalty. Underworld development team with AI support from Claude Code Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…nges Every stress history sets the smoothing of its commit projection each step, and the setter requested a rewire of the pointwise functions unconditionally, so the C source of the committed flux was regenerated every step (5 s a step for the Oldroyd-B shear box, 22 s for FENE-P, whose expression is larger; the compiled module was then found in the cache and not rebuilt). With the rewire gated on a change of value the shear-box solve goes from 7.5 s to 1.2 s. The baselines of test_1059, 1067, 1102, 1103 and 1104 are unchanged. Underworld development team with AI support from Claude Code Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…ion named as independent choices ViscoElasticPlasticFlowModel(element="maxwell"|"jeffreys", relaxation="linear"|"fene_p") with Parameters.extensibility (L^2). The three choices a viscoelastic model makes are independent and named by their mechanics: the spring-dashpot arrangement (Maxwell; Jeffreys, a dashpot in parallel = the solvent viscosity), the relaxation law (linear = Hookean; FENE-P = finitely extensible with the Peterlin closure) and the objective rate. UCM is maxwell + linear + upper-convected, Oldroyd-B jeffreys + linear + upper-convected, FENE-P jeffreys + fene_p + upper-convected; a Burgers body would add a second history. FENE-P takes its step on the conformation at the rate f(c*)/lambda with f = (L^2 - d)/(L^2 - tr c), f* read from the record before the step (explicit, first order) and carried as nodal fields so the matrix exponential of the record does not enter the compiled flux through the coefficients; the stress is G (f* c - I). The stretching source acts on G (c* - I), which is the carried stress only for a linear spring, and keeps its Oldroyd-B weight: the f* on the relaxation rate cancels against the f* on the stress for every source. Against the closed-form steady simple shear (f^2 (f - 1) = 2 Wi^2 / L^2) the scheme is first order in dt/lambda: errors of +0.0065 / +0.0095 in tau_xy / N1 at dt = 0.1 lambda halving at 0.05. With infinite extensibility it is Oldroyd-B to 1e-8. Underworld development team with AI support from Claude Code Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
… docs for the forward reconstructions and FENE-P AdvDiffusion: the diffusive flux reads a stored level without a derivative through its snapshot stand-in, as the Navier-Stokes solver does, so transport= 'backward_integration_points' runs at the default theta 0.5 (test_0066's 'cn' case is now a comparison, not a refusal); a nodal trace-back marks the field CARRY for a moving mesh, as the old solver did; the SUPG options are refused off the Eulerian path instead of being set on a manager that ignores them; a discontinuous field is refused for every transport; the V_fn setter reaches the characteristic trace; a theta change across 1.0 requests a rewire (the integration-point form drops the stored level at 1); estimate_dt defaults to the cell-crossing time for a semi-Lagrangian transport; `transport` is readable. NavierStokes: the extrapolation state (a_var, u_prev) exists on the Eulerian path only; the SUPG options are refused otherwise; solve(order=) is refused rather than dropped; `velocity_transport` is readable. The deprecation warning on the former names says what differs (defaults, the meaning of order, the form of estimate_dt). Baselines for AdvDiffusion with each transport on the rotating Gaussian (test_1102). Docs: the per-cell fit against the global projection, the bubble penalty, store smoothing on all three histories, the element / relaxation / objective-rate taxonomy and FENE-P. Underworld development team with AI support from Claude Code Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…ctor on an empty rank, and the rest HIGH: the FENE-P fields were named by id(self), which differs across ranks, and variable creation is collective and keyed by name; the first parallel runs deadlocked at their first snapshot. They are named by a counter. The projector's build reshaped an empty cell list with -1 and raised on a rank with no cells; the basis size now comes from the degree. FENE-P with the default (infinite) extensibility was 0/0; it is refused at the first solve, and the spring factor's denominator is floored at one percent of L^2 so a record whose trace passes L^2 by the explicit lag does not turn relaxation into growth. MED: `element` is derived from the solvent viscosity when undeclared and enforced when declared (a Maxwell element with a dashpot is refused). The forward integration-point global path averages the previous field at a seam vertex over the local cells only; it now sums across seams, so the answer is partition-independent as documented. The forward nodal launch weights summed to 1.3 cell measures; each cell now shares its measure among its lattice points and its dofs, so a covered cell reads as covered. The projection's solve checks convergence. The FENE-P refresh reads coords_nd. fit_limiter is a property that refuses the global reconstruction. The per-cell overshoot is reduced over ranks. A user's remesh policy is not overwritten. Tests: an arrival on a shared face is weighed once; the launch weights sum to the domain; the element rules; infinite extensibility refused; the deficit-fill test is serial-only. LOW: stale docstrings on the forward integration-point history, the restart note on the reconstruction options, the smoothing check compares as a number, utilities exports particle_projection, .array writes. Underworld development team with AI support from Claude Code Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
Adversarial review, two rounds (static reading; findings and what we did)Round 1, the one-NavierStokes / one-AdvDiffusion commit (78e62a9). 14 findings; the HIGH ones were real defects: Round 2, bf4e663..59366e9 (store smoothing on every history, ParticleL2Projector and Checked and found correct by the reviewer: the P2 edge-to-vertex mapping and basis gradients, the Dunavant rule and bubble mass, the seam assembly with no-overlap distribution, the deficit fill against the inflow top-up, the multiplicity correction, the FENE-P lag's consistency, the rewire fix's structural compare. Full serial suite after round 2: 2839 passed, 17 skipped, 20 xfailed (one test updated for the clearer Underworld development team with AI support from Claude Code |
… the forward nodal launch weights summed across seams The fill of a cell's uncovered share from the previous field acted on any shortfall. In a flow every cell's received weight fluctuates about its measure, so about half the cells were pulled a few percent towards the previous, un-advected field each step: a lag that acted as diffusion (a quarter turn of the rotating Gaussian lost 18% of its peak with the forward nodal history and 6% with the integration-point one). The fill now starts at half the measure and is complete at zero: an ordinarily covered cell feels nothing, an emptied cell is held by what it carried. The Gaussian's peak is back above the per-cell fit's (0.786 against 0.769). The forward nodal history's launch weights summed each dof's shares over the rank's own cells, so a seam node launched by its owner carried half its share and the parallel field differed from serial; the shares are summed across the seams through the projector's assembly. test_1066 carries both forward histories with the global projection against serial baselines (np 3 equals serial to 1e-6 for the stress box and the rotating Gaussian). Underworld development team with AI support from Claude Code Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
…trace The log-conformation record for FENE-P was log c with c = (sigma/G + I)/f*, f* the lagged nodal spring factor. The conformation is bounded (tr c < L^2) and the nodal projection that commits the record is not: at the cylinder wall, where log c jumps by 4 across one cell, the projection overshot by a factor 1.5 in c and put the record on the saturation floor (tr c 105 of L^2 100 from a stress whose own conformation had 70). The step then alternated between the saturated and the free spring and the velocity multigrid stalled: the FENE-P "hang" at step ~50 on the cylinder at Wi 1 (res 20) and Wi 0.5 (res 40). The record is now log(sigma/G + I) for both relaxation laws: log c for a linear spring, log(f c) for FENE-P. The decode recovers the conformation through the trace, f* = 1 + (tr e^psi - d)/L^2 and c* = e^psi/f*, so every SPD record is an admissible conformation whatever the projection did. The stress reads as G(e^psi - I) for both laws; the relaxation rate keeps its first-order lag in f*. Shear-box error constants are unchanged (+0.0068/+0.0094 at dt 0.1, halving at dt 0.05). Re 100 Wi 1 res 20 on the cylinder now runs through the ramp and on (the previous record hung at step 54); the record's spring factor at the wall sits 60% above the stress's own, which is the projection overshoot made harmless. A FENE-P snapshot written by the previous record decodes wrongly under this one; none of those runs survived to be worth restarting. Underworld development team with AI support from Claude Code Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
From the review of 3170929: the trace decode f = 1 + (tr e^psi - d)/L^2 is positive for every SPD record only when L^2 > d, so the refresh refuses an extensibility at or below the dimension (test); the nodal field the weak form reads, and the carried strain and stress built from it, are now checked against a record written directly (test); the admissibility assertion pins the closed-form value 502/51 instead of a band; _peterlin_np had no caller and is gone, _fene_spring_factor_of_stress is the record formula applied to sigma/G + I with its floor and its claim about the encode removed; the constructor comment no longer describes the log c record; the diagnostic reads L^2 at the points like G; the docs say which of the f* readers keep the lag and that an old FENE-P snapshot decodes short by f under this record. Underworld development team with AI support from Claude Code Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo
|
Review of 3170929 (FENE-P: store log(f c), decode the conformation through the record's trace), fixes in 020b02f. Cause of the FENE-P stall on the cylinder: the record was log c, the nodal projection that commits it overshoots at the wall (tr c 105 with L^2 100 from a stress whose own conformation had 70), the spring factor hit its floor and the velocity multigrid ground to the guard's hang limit. Saturation and the lagged rate were ruled out from the snapshots (f 4.65 at the last one, floor 100). Findings and what we did:
Verified correct: encode/decode algebra; every reader of the record (stress_star, E_eff, DEVSS projection, the solvers' decode paths) reads G(e^psi - I); the step algebra is unchanged; inflow encoding; level handling (FENE-P is order 1); units; parallel structure of the refresh. Shear-box errors against the closed form: +0.0068/+0.0094 at dt 0.1, +0.0034/+0.0051 at 0.05, +0.0018/+0.0027 at 0.025. Cylinder Re 100: Wi 1 res 20 past step 300 (hung at 54 before), Wi 0.5 res 40 past step 100 (stalled at 71 before). All 8 tests in test_1068 pass. |
Why this is red, and what it needsThe two CI failures are not this PR's. They are byte-identical across #785, #789, #795 and #800: Both tests were already fixed on
So this branch is stale, not broken — 58 commits behind The merge is not clean, and the conflicts are the same on all four
The fifth, at the top of Order mattersAll four touch Found in the backlog sweep (#819). No commits pushed to this branch. |
scripts/test.sh and ddt.py resolve as on the other stale transport branches: the broader `tests/test_110*py` glob (#721), and four ddt.py hunks whose development side is empty plus one that takes both (the base-class ETD coefficients from #739 and development's `_note_history_shift`). Verified on the merged tree: one definition of each, no duplicates. The third conflict is this branch's own rename. It replaces `IntegrationPointSemiLagrangian` with the trace-and-launch naming (`BackwardIntegrationPointsSemiLagrangian`), while development added a `@pytest.mark.tier_a` to the same test. Resolved as the new class name plus development's marker, which reproduces development's decorator layout in that file exactly -- including the blank lines between the stacked decorators, so `tier_a` attaches to `test_rotating_gaussian_ip_accuracy` as it does there. Underworld development team with AI support from Claude Code
This branch is the base of a four-PR stack (#785 -> #789 -> #795 -> #800) and was 71 commits behind development, which is why all four showed the same two CI failures: tests development had already renamed (test_1060_nitsche_freeslip's absolute 1e-4 bound and test_1070_free_surface_plume's strong-vs-penalty ratio). The refresh belongs here, at the root, so each PR's diff against its base stays its own work rather than growing development's history. Two conflicts, both this branch's own additions. scripts/test.sh keeps the broader `tests/test_110*py` glob -- development narrowed it to `test_1100*py`, which matches none of the other test_110* files here (#721 recurring); scripts/check_test_coverage.py verifies it. Four of the five ddt.py hunks have an empty development side, and the fifth takes both: this branch's base-class `update_exp_coefficients` / `_exp_alpha` / `_exp_phi` (#739, which replaced three per-flavour copies dating from b6b0e7a in April) and development's `_note_history_shift`. Verified on the merged tree: one definition of each. Underworld development team with AI support from Claude Code
The rename lands in four places the branch had not reached, and one anchor was stale. `SemiLagrangian` is a factory over (trace, launch), not a class, so it carried no class-level description -- and `tests/test_0017_describe_and_render.py` requires every family to answer at that level, while `docs/developer/subsystems/describe-and-view.md` calls `SemiLagrangian.describe_class()` directly. The factory now delegates `describe_class` and `view` to the default scheme, which is what it builds when neither axis is given, and exposes `schemes` for a caller that wants another. Three expectations follow the rename: the describe contract and the step transcript now name `BackwardNodesSemiLagrangian`, and the transport-schemes guide's front matter lists the four trace-and-launch families instead of two names no class carries (`tests/test_0030_capability_guides.py`). `docs/advanced/eulerian-advection-diffusion.md` described three managers; the section now says how the two axes select a scheme, and records that a forward trace's per-cell fit is unstable where the flow empties a cell (#811). The eulerian cavity anchor is re-recorded. It moved 4e-05 relative on merging development, and the new value is development's own: the same cavity with the default EulerianSUPG history gives [-0.129019, -0.05467728] there, before and after #833, unchanged to nine digits from tolerance 1e-8 through 1e-12. The original was recorded 2026-09-29, before this branch last merged development. That leaves something worth knowing: the Eulerian SUPG velocity answer moved on development and nothing there anchors it. This file is the only test that can see it. level_1 tier_a: 1428 passed, and the one failure it had (the capability guide) is fixed here. test_1103: 7 passed. Underworld development team with AI support from Claude Code
Stacked on #795.
What changes
One entry point for the semi-Lagrangian histories.
uw.systems.ddt.SemiLagrangian(mesh, psi_fn, V_fn, vtype, trace=, launch=)selects one of four schemes:tracelaunchBackwardNodesSemiLagrangian(SemiLagrangian)BackwardIntegrationPointsSemiLagrangian(IntegrationPointSemiLagrangian)ForwardIntegrationPointsSemiLagrangian(ForwardSemiLagrangian)ForwardNodesSemiLagrangian(new)A keyword the chosen scheme does not take is a
TypeError. The former class names and the formerstress_transportstrings (semi_lagrangian,integration_point,forward) still work, with aFutureWarning.stress_transporttakesbackward_nodes(default),backward_integration_points,forward_integration_points,forward_nodes,lagrangian,eulerian;AdvDiffusionSLCN(transport=...)chooses its value history among the four semi-Lagrangian schemes.Forward from nodes. The history launches its values from the field's nodes and from a lattice inside every element (the discontinuous basis two degrees up), carries them one step forward, fits the arrivals in each cell at the field's degree, and projects the per-cell fits onto the continuous store. A cell with too few arrivals takes a linear fit to its own arrivals, or their mean; an arrival on a shared face is fitted in every cell that contains it. A stress is committed by L2 projection onto the store and launched from that field. Rotating diffusing Gaussian, P2, SLCN solver, integral error:
The box, where the flow crosses every wall, is slightly worse than the backward scheme. A serial-only version that let thin cells borrow arrivals from their neighbours reached 1.68e-2 and 6.32e-2; that borrowing depends on the partition and was removed. First order; a fixed mesh.
Every history gives the serial answer in parallel.
tests/parallel/test_1066holds the six stress histories (turned-over Maxwell box) and the four semi-Lagrangian value histories (rotating Gaussian with inflow) to 1e-6 of their serial values at np >= 3; verified at np 3, 4 and 6. Five sources of partition dependence were removed:global_evaluate: a point that the swarm migration handed to a rank whose cells do not contain it was given the nearest-centroid rbf extrapolation (3e-2 at a steep stress, np 4). The rank whose cell contains it now evaluates it with the FE interpolant. This runs only where the cell hint is authoritative (noDMLocatePointscollective inside), and a finite rbf value is kept where the FE value is NaN.Open
evalf=True, and serialevaluateoff by up to 4e-5 near nodes for an exactly represented P2 field.TODO(DESIGN): face arrivals are offered to every rank, not only across seams; the two forward schemes use different rules for an arrival exactly on a face.Tests
tests/parallel/test_1066(new),test_1062tightened to 1e-6,test_1102(new, forward from nodes against hard baselines),test_1063coversforward_nodes,test_1059covers selection, renamed strings and the Maxwell box for every history. Serial transport/NS/VE set: 388 passed. Parallel np 3/4/6: 10 passed, 1 xfail at np >= 4.🤖 Generated with Claude Code
https://claude.ai/code/session_017kSkAq7oJ5J3XisuvLBovo