Skip to content

multiplier_schur_pc: verify or remove — it is read, but no observable responds to it #486

Description

@lmoresi

Stokes_Constrained.multiplier_schur_pc is a documented public property. It is read — at one site it swaps the multiplier block's Pmat term to the 1/μ Schur mass:

hh_pc = self._pp_G0 if self._multiplier_schur_pc else self._hh_G0[k]
PetscDSSetJacobianPreconditioner(ds.ds, fid, fid, ext.fns_jacobian[i_jac[hh_pc]], NULL, NULL, NULL)

So it is not dead code. But we could not construct a case where it changes anything.

Measured

Annulus, Stokes_Constrained with add_constraint_bc on both arcs (2 multipliers present, so the loop above is reached), snes_type=ksponly:

configuration flag=Falseflag=True
constant viscosity |u|, |p|, outer KSP its all identical
η contrast 1e4 identical — including the solve failing identically both ways (DIVERGED_LINEAR_SOLVE, 100 KSP its)
η contrast 1e6 identical, 4 KSP its both
assembled |Pmat| (η contrast 1e3) identical to 12 digits

The flag reads back correctly (True/False round-trip), so it is recorded.

Also notable in the same runs: |Pmat| equalled |J| exactly in both settings, despite Pmat being a distinct matrix.

Why this is filed as verify-or-remove rather than as a bug

The negative could be honest — two good Schur preconditioners can both converge in four iterations on a problem this easy, and we may simply not have found the discriminating case. The comment at the call site says the point is to reuse pressure's already-compiled 1/μ term "so there is NO extra JIT term", which suggests the motivation was compile cost rather than convergence.

But per the "identical cells are a common defect, never 'this variable does not matter'" rule that found #477, four identical rows across two decades of viscosity contrast is not evidence the flag works.

So: either produce the case where it earns its keep — and add it as a test, so the next audit does not re-open this — or remove the property. A public flag nobody can demonstrate an effect for is the same class as #477 and solver.strategy: accepted, recorded, and invisible if it is doing nothing.

Context worth knowing for whoever picks this up: Stokes_Constrained deliberately rejects saddle_preconditioner ("the Schur preconditioner is built automatically (selfp + the 1/viscosity mass from constitutive_model.K)"), so that class owns its Schur preconditioner by design. multiplier_schur_pc is the one hook left into it.

Found by the property-inertness pass of the solver configuration audit — docs/reviews/2026-07/solver-configuration-reachability-audit.md, Phase 3 (#485). Repro: ~/+Simulations/solver_reachability_audit/property_inertness_fair.py.

Underworld development team with AI support from Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions