Add distributed harmonic projection of rotated reactions - #646
Conversation
Expose boundary_normal_traction_integral() to contract the assembled rotated free-slip normal reaction directly with a scalar boundary test function. Count only owned reaction DOFs, reduce the weak functional across MPI ranks, and remove the constant traction gauge without recovering pointwise P2 values. Add an opt-in reaction projection to spherical-shell geoid postprocessing. Normalize with the matching discrete boundary inner product so the reaction numerator and harmonic norm use the same faceted geometry. Retain the centroid projection as the compatibility default. Document the relationship to curved-boundary P2 midpoint/fitted guidance from issue underworldcode#414 and add focused serial and MPI Zhong regressions for the new path.
Collective reductions may differ by the final floating-point bits across MPI implementations. Compare projected geoid and topography coefficients at near-machine precision instead of requiring bitwise-identical arrays.
|
Reading this while finishing #633, which lands in the neighbouring code ( The direction is right, and it is the same lesson from the other side. #633 turned on the fact that the nodal reaction is a dual object — an integrated force. Asking it for pointwise values is the ill-posed direction, and that is where every failure mode lives: the zero-mean P2 vertex basis (#414), the singular vertex rows of the P2 triangle mass, the chord-versus-arc geometry error (#431). The two changes are complementary rather than overlapping: this adds a new function and leaves On the workflow this replaces — the Measured on the Zhong l=2 shell, relative error against the analytic coefficients:
To be clear about what that does and does not show: on accuracy the two integral arms are a wash, so this is not an accuracy argument. It is that the same answer is available on the mesh we already have, on any boundary shape, without a gather or a joggle. The last row is the separate point — both integral forms beat the pointwise fit by 3-10x at coarse resolution, which independently supports #414. Given your Underworld development team with AI support from Claude Code |
…le-convection-benchmarks
d30b18b to
6891fec
Compare
For order-one advection-diffusion with theta=1, the Adams-Moulton flux has coefficients [1, 0]. Avoid projecting and tracing the zero-weight stored flux on every solve while still refreshing the symbolic coefficients when theta changes after construction. Preserve the existing DFDt lifecycle for Crank-Nicolson, forward Euler, and higher-order Adams-Moulton configurations. Add focused regression coverage for lifecycle call counts and numerical equivalence with the previous forced-history path.
Exercise 38 canonical Crank-Nicolson transport solves on a three-dimensional spherical shell. Verify in serial and MPI that transient global-evaluation swarms do not survive a solve, interpolation cache entries remain bounded, solver report histories cap at 32, and temperature remains finite.
Introduce uw.systems.AdvDiffusionSUPG as a Python systems-layer solver without changing the existing semi-Lagrangian aliases. Assemble the streamline test contribution through the scalar F1 flux, keep advection out of Eulerian history to avoid double counting, and compute coordinate-invariant simplex streamline lengths in internal P0 fields. Support generic transient and CitcomS-compatible steady tau models for isotropic diffusion plus explicit user tau expressions. Add Level 1 residual and limiting-case tests and Level 2 manufactured, high-Peclet, and spherical MPI validation.
Preserve live UWexpression atoms when deep-copying Symbolic_DDt history state while copying the mutable history containers and timestep metadata. Generic deepcopy reconstructed parameter symbols without their wrapped values, causing Jacobian rebuilds after model restore to fail in existing Diffusion and the new SUPG solver. Add a base diffusion regression, SUPG BDF2 discarded-step equivalence, bounded repeated-solve state checks, and measured BDF1/BDF2 temporal convergence.
Extend AdvDiffusionSUPG with a continuous-P1 CitcomS-compatible time integrator using geometric row-sum lumped mass, initialized temperature-rate state, gamma=0.5 prediction, and two fixed residual corrections. Assemble the SUPG residual through the existing scalar SNES callback, retain constrained boundary handling, snapshot integrator startup metadata, and compute conservative advection and discrete M_L^-1 K diffusion timestep limits. Add constant-residual mass verification, exact source startup, second-order scalar decay, snapshot restart, and serial/MPI spherical tests.
Allow callers to supply a named continuous-P1 temperature-rate field for production checkpoint workflows and expose it through the solver. Rebuild cached lumped mass after mesh changes and cache the discrete diffusion timestep limit by mesh version and diffusivity so coupled convection steps only recompute the velocity-dependent advective limit.
Allocate the global temperature, residual, correction-rate, and stored-rate PETSc vectors once per mesh version instead of recreating and destroying them for every corrector sweep. Preserve the one-off residual helper ownership contract for diagnostics and tests. Add a focused regression that advances two timesteps and verifies the PETSc workspace handles are reused. Serial SUPG tests pass (14 tests), the focused spherical and snapshot tests pass on eight MPI ranks, and the isolated 120-step MPI memory diagnostic has no persistent object or RSS growth.
Base the zero-diffusivity path and diffusion-cache return on communicator-wide decisions so ranks with empty or locally zero-diffusivity partitions cannot skip collectives reached by their peers. Handle empty local diffusivity arrays without a local maximum. Add a rank-varying diffusivity regression that would deadlock before this fix. The collective guard scan passes, the regression passes in serial and on eight MPI ranks, and focused SUPG tests remain green.
Add a runnable SUPG guide covering the implicit BDF and CitcomS-compatible predictor-corrector paths, stabilization controls, restart state, and method-selection tradeoffs. Cross-link the guide from the advanced documentation and SLCN time-integration page, and update the solver docstring to describe both supported integrators.
Distinguish residual consistency, nodal boundedness, and integral conservation for SUPG and semi-Lagrangian transport, and state the partition-independent diagnostics required for serial/MPI comparison.
Advect a Gaussian through one rigid-body revolution in an annulus with the public CitcomS-compatible predictor-corrector and estimate_dt(). Verify that the finite-element L2 return error decreases under mesh refinement, covering curved streamlines and the zero-diffusivity timestep path.
Write an owned global-vector payload alongside existing DMPlex checkpoint metadata so same-layout restarts do not depend on invalid local point-number assumptions after parallel redistribution. Reload that payload through the live global layout, scatter it to local and ghost dofs, and invalidate the mesh-wide packed auxiliary vector before the next residual assembly. Reject snapshot restarts on a different MPI rank count, retain the legacy DMPlex local-vector fallback for older files, and document the distinction from coordinate-remapped timestep reads. Strengthen snapshot regressions to verify coordinate-defined pointwise field values under four MPI ranks, scalar and P2 vector payload presence, post-reload coefficient use in a solve, and rank-count validation. Focused serial tests, the four-rank snapshot test, all three eight-rank Zhong transport replay gates, and the full Level 1 suite pass.
Add disabled-by-default internal counters for global_evaluate's best-claim fallback. When explicitly enabled by a diagnostic, report call counts, local and globally replicated extrapolated points, cumulative temporary-array bytes per rank, and per-call peaks without changing the public evaluator API or production behavior. Add a two-rank regression with exterior query points that verifies finite results and exact point/replica accounting. The focused MPI test and seven evaluator tests pass; the Level 1 suite passes with the known divergent-rank fixture excluded (1659 passed, 35 skipped, 2 xfailed).
Include located-but-nonfinite interpolation results in global_evaluate's parallel best-claim fallback. This prevents finite SLCN midpoint coordinates from receiving NaN velocities when a rank-local interpolation reports a false located status.\n\nAdd a focused regression for fallback index selection. Validate with the two-rank migration suite and an eight-rank Zhong A1 cellsize=1/16 step that previously diverged with DIVERGED_FNORM_NAN.
Cache the scalar solver field decomposition on first volume-reaction recovery instead of creating a new PETSc IS and sub-DM for every boundary-flux call. The existing solver reset lifecycle now owns and destroys these objects. Add a focused regression proving repeated upper/lower boundary recovery retains the same decomposition objects. Validate the complete serial boundary-flux suite and the two-rank parallel recovery suite.
Cache local simplex connectivity, basis gradients, and volumes by mesh version so automatic stabilization and timestep estimation do not rebuild large geometry arrays every step. Mesh deformation or adaptation invalidates the cache through the existing mesh-version lifecycle. Add a focused identity regression for repeated automatic operations. The change removes repeated allocator high-water growth from the coupled A1 timestep-estimation stage without changing the CFL or diffusion limits.
Extend the simplex-geometry reuse regression through a public mesh deformation. Confirm the cache retains array identities on an unchanged mesh and rebuilds connectivity-derived arrays and volumes after the mesh version advances.
Replace the per-call cells-by-basis temporary used by automatic SUPG stabilization and timestep estimation with two mesh-versioned one-dimensional work arrays. Compute each basis-direction contribution in place and accumulate it without changing the streamline-rate formula. Validate numerical equivalence against the vectorized expression, workspace reuse, deformation invalidation, and a five-step eight-rank A1 run with unchanged diagnostics. This targets the remaining native allocator growth observed in the Gadi timestep-estimation stage.
Scatter a scalar solver's sole local unknown directly through its solver DM when assembling volume reactions. This removes the need to construct or retain PETSc field-decomposition IS and sub-DM objects for boundary heat-flux recovery. Update the lifecycle regression to require an empty scalar decomposition cache. Validate all 15 serial boundary-flux tests, the two-rank parallel suite, and a five-step eight-rank A1 run with unchanged thermal diagnostics and a 4.28 MiB aggregate repeat-diagnostic delta.
Expose SolverBaseClass.boundary_flux_integral() for integral diagnostics such as Nusselt numbers. The implementation sums consistent scalar nodal reactions collectively, using the boundary basis partition of unity, and avoids pointwise mass recovery, a temporary MeshVariable, and a second boundary quadrature. Add serial and MPI regressions against the analytic manufactured heat flux, the established recovered-field result, and the serial direct-integral reference. Validate partitions that cut the measured boundary on two and four ranks.
|
CI The failureNot a magnitude error. The two arrays shown are ~3e-18 and ~1e-5, both far inside What rules out the easy explanations
The one thing in the diff I would questionBeyond the harmonic projection, this branch also changes - _names, _iss, _subdms = self.dm.createFieldDecomposition()
- sgvec = gvec.getSubVector(_iss[0])
- _subdms[0].localToGlobal(self.Unknowns.u.vec, sgvec)
- gvec.restoreSubVector(_iss[0], sgvec)
+ self.dm.localToGlobal(self.Unknowns.u.vec, gvec)Three reasons this is worth a second look:
Cheapest decisive experiment: revert that hunk alone and re-run CI. If it goes green, that is the answer; if not, it is eliminated and the NaN hypothesis above is next. A process note, offered rather than insisted on23 commits reach Happy to push the revert-and-retest to this branch if you would like, but I did not want to touch someone else's branch uninvited. Underworld development team with AI support from Claude Code |
CI was red on tests/test_1120_SLVectorCartesian.py::test_SLVec_boxmesh[mesh1],
a semi-Lagrangian VECTOR test — i.e. exactly the single-field path this branch
simplified:
- _names, _iss, _subdms = self.dm.createFieldDecomposition()
- sgvec = gvec.getSubVector(_iss[0])
- _subdms[0].localToGlobal(self.Unknowns.u.vec, sgvec)
- gvec.restoreSubVector(_iss[0], sgvec)
+ self.dm.localToGlobal(self.Unknowns.u.vec, gvec)
`self.Unknowns.u.vec` is the VARIABLE's local vector. The old code mapped it
through field 0's subDM; the direct call assumes the solver DM's local layout
matches the variable's. On a plain single-field solver the two coincide, which
is why the simplification looked equivalent — where they differ it writes to
the wrong slots and the field comes back never-written. The failure is
consistent with that: recovered values ~1e-18 against an analytic ~1e-5, and
both arrays sit far inside the assertion's atol=0.01, so `allclose` returned
False on a non-finite entry rather than on a magnitude error.
This restores the original mapping and leaves everything else on the branch
untouched. The harmonic projection this PR is actually for is unaffected.
NOT independently confirmed as the cause: the failure does not reproduce on
macOS/arm64 (test_1120 passes 3/3 there both with and without the change), so
this is the cheapest decisive experiment rather than a verified fix. If CI is
still red after it, the next candidate is a NaN out of `uw.function.evaluate`
feeding `vec_prof_uw` (see underworldcode#604, underworldcode#641).
Verified before pushing: test_1120 3 passed; test_1070 (this PR's own geoid
suite) 13 passed, so the revert does not undo what the branch is for; and
`level_1 and tier_a` 1093 passed, 0 failed.
Underworld development team with AI support from Claude Code
|
Pushed Only that one hunk is touched. Everything else on the branch — including the harmonic projection this PR is actually for — is untouched. Why this hunk. The failing test is Stated plainly: this is not a confirmed fix. The failure does not reproduce on macOS/arm64 — Checked before pushing, since it is your branch:
Revert it freely if the simplification was deliberate and load-bearing for something downstream — in which case the right move is probably to split it into its own PR with a test that pins whatever it fixes, so it does not ride along with the projection work. Underworld development team with AI support from Claude Code |
Summary
This PR adds a distributed weak projection of rotated free-slip boundary reactions and exposes it through the generic spherical-shell geoid postprocessing adapter.
The change is intentionally opt-in:
projection="centroid"remains the default for compatibility.projection="reaction"directly contracts the assembled normal reaction with the requested harmonic.Stokes.boundary_normal_traction_integral(boundary, fn, remove_mean=True)API is generic and is not tied to Zhong 2008.Problem
The existing centroid workflow first recovers pointwise boundary-normal traction, gathers boundary samples to rank zero, reconstructs a spherical triangulation with
ConvexHull, and fits the harmonic over triangle centroids.That workflow was introduced for a valid reason: as discussed in #414, #431, and #404, raw P2 vertex values on faceted curved boundaries contain a slowly converging chord-versus-arc geometry error. Midpoint, fitted, and integral quantities are preferable to treating those vertices as physical point samples.
However, when the required output is only a harmonic coefficient, pointwise recovery is unnecessary. It also introduces:
Method
For the converged rotated-free-slip solve, let
Rbe the assembled nodal normal-reaction load and letphibe a scalar boundary test function. The new API computesusing owned reaction DOFs followed by MPI scalar reductions.
For a spherical harmonic
phi_l = P_l(cos(theta)), the topography coefficient is normalized with the matching discrete boundary inner product:Using
BdIntegral(phi_l^2)is important because both numerator and denominator then use the same faceted FE geometry. The method never reconstructs or consumes recovered P2 vertex values, so it follows the midpoint/fitted/integral guidance from #414 rather than bypassing it.Zhong 2008 layered validation
Benchmark:
bench_020_zhong2008_layered_viscosity_response.pyConfiguration:
l=5, load depth0.5d,cellsize=1/64, fitted10^4viscosity lid, P2P1, rotated free slip, 192 MPI ranks,stokes.tolerance=1e-5.Both runs used the identical mesh, Stokes formulation, solution fields, and Zhong propagator reference. Only the topography projection changed.
The surface-geoid error falls from about 4.0% to 0.12%. All eight topography/geoid coefficients are within 0.13% of the semi-analytical propagator after the change.
Velocity is measured independently and is unchanged by this postprocessing method. In this run, CMB velocity differs from the propagator by 0.02%; the near-zero surface velocity differs by 8.46%.
Parallel and performance properties
ConvexHullor surface mass solve is used.A controlled 192-rank before/after run used the identical mesh, solver configuration, and benchmark script revision apart from the projection selection. The centroid baseline was rerun from benchmark commit
a3344acas Gadi job177431139; the reaction run was job177429270.Benchmark.ResponseIntegralsincludes the common velocity/divergence integrals as well as topography recovery and geoid evaluation. Those common calculations are identical between runs, so the measured 83.22 s reduction isolates the effect of replacing centroid recovery's global gather, rank-zero triangulation, and pointwise traction fit with the distributed reaction contraction.The small Stokes, mesh, HDF5, and whole-job memory differences are treated as run-to-run variation; this PR does not claim improvements in those stages. The measured postprocessing improvement is the
120.14 sto36.92 sreduction, together with removal of the global boundary gather and rank-zero surface reconstruction from the reaction path.Validation
13 passed.cellsize=1/64, exit status 0.Files changed