Uh oh!
There was an error while loading. Please reload this page.
TI-VEP viscosity reports the yield-limited weak-plane value; Nitsche scales on K (#463) - #535
Conversation
…ity now reports the yield-limited eta_1 the stress uses (#463) TransverseIsotropicVEPFlowModel.viscosity computed the yield-limited fault-plane viscosity and then returned the un-yielded bulk eta_0 — the computed value was a dead local. The flux was always correct (the rank-4 tensor applies the yield via _eta_for_tensor), but renders, projections and anything else reading .viscosity saw no yielding at all. The property now delegates to _eta_for_tensor with the ACTIVE integrator mode, so it returns exactly the coefficient the stress tensor is built from: under yield, the persistent _eta1_yield_eff container from #493 (a wrapped atom, so the Picard freezing contract holds for anything that bakes the property). plastic_fraction had the same drift squared (bulk-vs-fault comparison that Max()ed to zero, plus a misplaced ternary) and now reports 1 - eta_1_eff/eta_1_ve on the weak-plane channel. K stays shear_viscosity_0, deliberately. The two Nitsche boundary kernels used .viscosity as their PENALTY scale; that consumer wants the stiffness scale, so they now read .K — which is identical to their old behaviour for every model (isotropic: K == viscosity; TI: K == eta_0 == the old .viscosity return) and immune to the weak-plane value collapsing at yield. New test (validated fail-before on the pre-fix build): test_ti_vep_viscosity_property_reports_yield_limited_eta1 — property is the container _eta_for_tensor bakes, frozen under d/dL with a Newton- unwrap positive control, and reports the unlimited VE fault-plane value when yield is disabled (negative control). Underworld development team with AI support from Claude Code
…ke every other yielding model (the tidy deferred on #493) ViscoElasticPlasticFlowModel._build_c_tensor baked the UNWRAPPED .sym contents of the effective viscosity into the c-tensor. The tangent happened to be frozen anyway (the strain-rate content sat inside wrapped atoms one level down), but the class was the one yielding model not following the wrapped-atom freezing contract from #457/#493. The viscosity property now stores the combined (yield-limited, floored) coefficient inside a persistent _vep_eff_viscosity container and returns it — the ViscoPlasticFlowModel._plastic_eff_viscosity pattern — and the _build_c_tensor override is deleted so the base ViscousFlowModel build bakes the wrapped atom (taking two stray debug prints with it). The property now always returns a UWexpression, so _object_viewer no longer breaks on a yielding VEP model. Proof nothing changes: the new frozenness pin (test_vep_c_tensor_coefficients_are_frozen, with a Newton-unwrap positive control) passes on both sides of the change, and a 6-step shear-box loading sequence through yield reruns identically to 3.2e-10 in v, p and the tau_xy trace (solver tolerance 1e-6; the TI-VEP twin of the same check is bitwise identical). Underworld development team with AI support from Claude Code
lmoresi
commented
Aug 12, 2026
ReviewWe verified the branch independently of the implementing agent:
Clean. Merging on CI green. Underworld development team with AI support from Claude Code |
There was a problem hiding this comment.
Pull request overview
This PR fixes a reporting/diagnostics bug in TI-VEP rheology where TransverseIsotropicVEPFlowModel.viscosity did not reflect the yield-limited weak-plane coefficient actually used in the stress tensor, and adjusts Nitsche penalty scaling to use the intended stiffness scale (K) rather than the (now yield-sensitive) .viscosity.
Changes:
- Update
TransverseIsotropicVEPFlowModel.viscosityto return the yield-limited weak-plane coefficient produced by_eta_for_tensor(...), and fixplastic_fractionto report a meaningful weak-plane plastic fraction. - Refactor isotropic
ViscoElasticPlasticFlowModel.viscosityto return a persistent wrapped container (frozen Picard tangent contract) and drop its custom_build_c_tensoroverride in favor of the base implementation. - Switch Nitsche BC penalty scaling in the generic SNES kernels from
.viscosityto.Kto preserve stiffness scaling for TI models.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/underworld3/constitutive_models.py | Fixes TI-VEP viscosity/plastic_fraction reporting drift; introduces persistent wrapped VEP effective-viscosity container and removes the unwrapped c-tensor override. |
src/underworld3/cython/petsc_generic_snes_solvers.pyx | Uses constitutive-model stiffness scale K for Nitsche penalties to avoid under-scaling when TI .viscosity yields on the weak plane. |
tests/test_1066_stokes_jacobian_layout.py | Adds regression tests for TI-VEP viscosity reporting and isotropic VEP c-tensor coefficient freezing contract. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
TI-VEP
.viscositynow reports the yield-limited fault-plane viscosity the stress actually usesFixes#463
The bug
TransverseIsotropicVEPFlowModel.viscositycomputed the yield-limitedfault-plane viscosity
eta_1_effand then returned the un-yielded bulkshear_viscosity_0— the computed value was a dead local. The residual wasnever affected (the flux path builds the rank-4 tensor through
_eta_for_tensor, which applies the yield correctly), but every reportingsurface that reads
.viscosity— diagnostics, viscosity renders, projections —saw no yielding at all.
Measured on a yielding shear state (tau_y = 0.5, fault-plane shear rate 50,
eta_0 = 1, eta_1 = 0.01): projecting
.viscositygave a constant 1.0(bulk) while the tensor coefficient the stress uses projects to 0.005
(the yield limit tau_y / (2 |gamma_dot|)).
The fix
viscositynow delegates to_eta_for_tensorwith the ACTIVE integratormode, so the property returns exactly the coefficient the stress tensor is
built from. Under yield that is the persistent
_eta1_yield_effcontainerintroduced in Stokes uu_G3 transposed tangent + TI un-frozen Picard: fix issue #457 at source #493 — a wrapped UWexpression atom, so the Picard freezing
contract holds for anything that bakes the property. For the hybrid
integrator it reports the BDF (yield-clipped) branch, matching the default
self._c.TransverseIsotropicVEPFlowModel.plastic_fractionhad the same driftsquared: it compared the bulk
eta_0againsteta_1_ve(identically zeroafter the
Max(0, ...)), and a misplaced ternary made thehasattrguarda no-op. It now reports
1 - eta_1_eff / eta_1_veon the weak-planechannel (the bulk is structurally non-yieldable).
K(the Schur/stiffness scale) staysshear_viscosity_0— deliberately,per the issue.
SNES_Vector.add_nitsche_bc,SNES_Stokes_SaddlePt.add_nitsche_bc) used.viscosityas their PENALTYscale. That consumer wants the stiffness scale, not the (now weak-plane,
possibly yield-collapsed) effective viscosity — they now read
.K, whichis bit-identical to the old behaviour for every model (isotropic models:
K == viscosity; TI models:K == eta_0 ==the old.viscosityreturn).The deferred
.symtidy from #493 (isotropic VEP)PR #493 noted that
ViscoElasticPlasticFlowModel._build_c_tensorstill bakedthe UNWRAPPED
.symcontents of the effective viscosity into the c-tensor(tangents were symmetric either way because the VEP solve flux goes through
stress()). That tidy is done here for contract uniformity:ViscoElasticPlasticFlowModel.viscositynow stores the combined(yield-limited, floored) coefficient inside a persistent
_vep_eff_viscositycontainer and returns it — theViscoPlasticFlowModel._plastic_eff_viscositypattern. (Bonus: theproperty now always returns a UWexpression, so
_object_viewerno longerbreaks on a yielding VEP model.)
_build_c_tensoroverride is deleted; the baseViscousFlowModelbuild bakes the wrapped container atom element-wise (and the stray debug
prints in the override go with it).Proof nothing changes:
strain-rate content sat inside wrapped atoms one level down) — the new
frozenness test
test_vep_c_tensor_coefficients_are_frozenpasses on bothsides of the change, with a Newton-unwrap positive control.
(
_yield_mode="min", tau_y = 0.5, the test_1052 configuration) was run onthe pre-change and post-change builds for BOTH the isotropic VEP and the
TI-VEP models. TI-VEP: v, p and the tau_xy trace are bitwise identical
(its flux path is untouched). Isotropic VEP: identical to 3.2e-10
max-abs (v: 9.4e-11, p: 2.3e-10, tau_xy: 3.2e-10) against a 1e-6 solver
tolerance — not bitwise, because routing the coefficient through the
container changes the compiled kernel's expression form, not its value.
Tests
tests/test_1066_stokes_jacobian_layout.py(alongside the #457/#493freezing-contract tests):
test_ti_vep_viscosity_property_reports_yield_limited_eta1— the propertymust return the same container
_eta_for_tensorbakes; frozen underd/dL, Newton unwrap reveals the yield law (positive control), and withthe yield limit disabled it reports the unlimited VE fault-plane viscosity
(negative control). Validated fail-before: on pre-fix code it fails at
assert visc is not cm.Parameters.shear_viscosity_0.test_vep_c_tensor_coefficients_are_frozen— isotropic VEP pin for thetidy (passes pre- and post-change, as intended for a no-behaviour-change
refactor).
Stakeholder suites: test_1066 (7 passed), test_0104 / test_0610 / test_1055 /
test_1057 / test_1059 / test_1060 / test_1065 / test_1067 (69 passed),
test_1052 VEP stability regression, level_2 (5 passed).
Gate:
pytest tests -m "level_1 and tier_a" -q --ignore=tests/test_0050_utils.py→ 592 passed, 0 failed, 17 skipped (MPI opt-in), 1 xfailed, in 7:09.
Underworld development team with AI support from Claude Code