Skip to content

set_pressure_gauge crashes under units (computes the gauge in user space, not inside the SNES non-dim cordon) #271

Description

@lmoresi

Summary

SNES_Stokes.set_pressure_gauge(boundary) crashes when a uw.Model / units scaling is active, and is architecturally mis-placed across the non-dimensionalisation boundary.

Bug

The gauge callback runs inside the SNES non-dimensionalisation cordon (it is an add_update_callback fired by SNESSetUpdate), where p.data is non-dimensional. But it computes the surface mean with BdIntegral.evaluate(), which returns a dimensional Pint Quantity:

def _pressure_gauge(solver, iteration):
    mean = p_surface_integral.evaluate() / area   # Pint Quantity (dimensional)
    p.data[...] -= (mean - reference)             # p.data is non-dimensional

With units active this raises:

numpy.core._exceptions._UFuncOutputCastingError: Cannot cast ufunc 'subtract'
output from dtype('O') to dtype('float64') with casting rule 'same_kind'

Stripping the Pint wrapper (.magnitude) avoids the crash but is still wrong: the magnitude is in dimensional (Pa) units while p.data is non-dimensional, so the correction is off by the pressure scaling factor. Reproduced with the Spiegelman benchmark (docs/examples/WIP/Benchmark/Ex_VP_Spiegelman_Benchmark.py) under its uw.Model scaling.

Fix (design)

The gauge is a solver-internal operation and must be computed in non-dimensional space — no evaluate() round-trip. Make it a fixed linear functional of p.data:

mean = (w · p.data) / Σw            # w_i = ∫_Γ φ_i dS  (boundary-mass weights, built once)
p.data[...] -= (mean - reference)

w precomputed once → the gauge is identical every iteration (no flapping) and stays in-cordon. Two requirements:

  1. Area-weighting (w_i = ∫_Γ φ_i dS) to match the documented (1/|Γ|)∫_Γ p dS and keep test_1016_snes_update_callbacks::test_pressure_gauge_zero_mean_on_boundary exact (a uniform nodal mean fails it on a non-uniform boundary). For P1 on straight facets the lumped edge-length weights are exact.
  2. Discontinuous pressure: the boundary label carries no DOFs for a DG pressure, so the DOF set must come from the facet support (adjacent cells), not just its closure. (A working _boundary_dof_mask using closure-then-support was prototyped — selects strict top vertices for P1, top-cell DOFs for DG0/DG1.)

Related finding (not a bug)

For pressure-dependent plasticity (Drucker–Prager, σ_y = C + sinφ·p) a pressure gauge is counter-productive: gauging the pressure shifts the yield surface every iteration, so the solve oscillates (~50 iters / DIVERGED_MAX_IT vs ~5 clean without it). The pressure there is physical, not a free gauge — worth a note in the set_pressure_gauge docstring.


Found via Claude Code while testing the gauge on the Spiegelman benchmark (yield-homotopy / FMG work).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions