Skip to content

Yield edge cases: units in sums (#758), a stated residual strength (#759), no silent smoother bump (#782) - #852

Open
lmoresi wants to merge 5 commits into
developmentfrom
bugfix/yield-edge-cases
Open

lmoresi wants to merge 5 commits into
developmentfrom
bugfix/yield-edge-cases

Conversation

@lmoresi

@lmoresi lmoresi commented Oct 18, 2026

Copy link
Copy Markdown
Member

Three yield-law defects, each with the regression test that fails on development.

#758: a dimensional constant in a sum entered unscaled. UWexpression.__add__ (and UWexpression + UWQuantity, __rsub__, UWQuantity.__sub__(UWexpression)) summed operand values eagerly; a quantity's value is its dimensional magnitude, so it entered a non-dimensional sum unscaled. smooth_max(a, b, 0) = (a + b + |a - b|)/2 hit it in a + b: a 0.35 MPa yield-stress floor compiled as 0.175 or 175000 instead of 0.0221 (in the issue's model a floor 83x below the yield stress stopped yielding). The sums are now symbolic unless both operands are quantities, so each term is non-dimensionalised by its own units. The same eager sum also froze an operand's value into the compiled kernel: changing a in a + b after the first solve changed nothing (test_0106).

#759: a tension cut-off at zero gave a singular operator. A yield stress that reads a field (Drucker-Prager through the pressure, a cohesion weakening with damage) floored at the zero default gives eta_pl = tau_y/(2 edot_II) = 0 wherever it is cut off. Per the maintainer ruling (2026-10-18), the VP, VEP and TI-VEP models now refuse it until the residual strength yield_stress_min > 0 is stated; -sympy.oo (bare or wrapped) is the opt-out for a yield stress positive by construction. The message names the model, its material and the fields read. Tests and examples whose yield stress reads a field now state a floor or the opt-out (15 tests, 9 docs/examples; Ex_Shear_Band_Plasticity_SS also had the floor misspelled yield_min, silently ignored).

#782: selecting yield_smoother="powermean" bumped a zero softness to 1.0, the harmonic mean, without a word. A family switch now leaves the stated softness alone; at delta = 0 the power mean runs at s = 1000, close to the exact Min. This is not the issue's preferred option (raise): a zero softness with the power mean is a legitimate, deliberate setting (test_1059_yield_anchor uses it), so the defect was only the silent change.

#776 is already fixed on development (VP and VEP started from rest converge: VP in 31 iterations, VEP in 3); not changed here.

Tests: test_1059_yield_stress_min_units (five spellings of one floor against the closed form eta = max(tau_y, tau_min)/(2R), inert and active; 7 of 10 fail on development), test_0106_a_sum_of_expressions_stays_live (fails on development: error 0.12 against 7e-10), test_1057::test_a_field_dependent_yield_needs_a_residual_strength (refusal, negative floor, closed form in tension, both opt-out spellings), test_1055::test_dp_model_smoother_optin. Tier A/B levels 1-2: 2621 passed; the one failure, test_1079_diffusion_disk_restart, fails on development too (#848).

Internal adversarial review, two rounds (findings in the comment below).

Closes #758
Closes #759
Closes #782

Checklist (UW3 Style Charter — docs/developer/UW3_STYLE_CHARTER.md)

  • I have read the Style Charter and followed it over the surrounding code (S2)
  • No drive-by refactors, renames, or "while I was here" cleanups (S9)
  • Bug fixes ship the regression test, written first, with level_* and tier_* markers (S8)
  • No hedging names (maybe_ / try_ / do_) and no commented-out code (S3, S4)
  • Every exception swallow states its sanctioned failure mode in a comment (S4)
  • New data access uses .array / mesh.X.coords — no with ....access(...), no mesh.data (S7)
  • Parallel safety considered — np2/np4 checked where swarm/mesh/solver dispatch is touched (S11)
  • No pixi.toml / pixi.lock or other dependency changes riding in an unrelated PR
  • AI-assisted work carries the attribution line below

Underworld development team with AI support from Claude Code

…aling (#758)

UWexpression.__add__ summed the two operands' values eagerly whenever the
left one was not a bare quantity, and a quantity's value is its
dimensional magnitude: the quantity entered a non-dimensional sum
unscaled. smooth_max(a, b, 0) = (a + b + |a - b|)/2 hit it in its a + b
term, so a yield-stress floor of 0.35 MPa compiled as 0.175 or 175000
instead of 0.0221 (in the issue's model, a floor 83x below the yield
stress stopped yielding entirely). The same eager arithmetic sat in
UWexpression + UWQuantity, UWexpression.__rsub__ and
UWQuantity.__sub__(UWexpression).

The sums are now symbolic unless both operands are quantities (Pint
adds those exactly), so each term is non-dimensionalised by its own
units; _quantity_as_expression wraps a bare quantity as its own atom
(the block __sub__ already had). evaluate() and the compiled kernels
share the path, so both are fixed.

Test: test_1059_yield_stress_min_units -- five spellings of the same
floor (unset, plain float, quantity in Pa, in MPa, product with a
dimensional cohesion), inert and active, against the closed form
eta = max(tau_y, tau_min)/(2R); and the sum itself, both orders. 7 of 10
fail on development; all pass here.

Underworld development team with AI support from Claude Code
…er bump (#782)

#759: a yield stress that reads a field (Drucker-Prager through the
pressure, a cohesion weakening with damage) floored at the zero default
gives eta_pl = tau_y/(2 edot_II) = 0 wherever it is cut off in tension,
a singular operator. The viscoelastic-plastic, viscoplastic and
transversely isotropic models now refuse it until yield_stress_min, the
residual strength, is stated (> 0, in stress units), or set to -oo for a
yield stress positive by construction (maintainer ruling 2026-10-18).
Tests whose yield stress is positive by construction take the opt-out;
the tension and tension-cutoff tests state a residual strength.

#782: selecting yield_smoother="powermean" bumped a zero yield_softness
to 1.0 -- the harmonic mean, a distinct law -- without a word. A family
switch now leaves the stated softness alone (delta = 0 is s = 1000, near
the exact Min); the tests that pinned the bump now pin its absence.

Regression tests fail on development and pass here:
test_1057::test_a_field_dependent_yield_needs_a_residual_strength,
test_1055::test_dp_model_smoother_optin.

Underworld development team with AI support from Claude Code
… needs (#759)

Nine examples and pages set a yield stress that reads a field with the
floor at the zero default; each now states yield_stress_min -- a residual
strength where the yield can reach zero, -sympy.oo with the reason where it
is positive by construction (fault-network damage_yield / band_yield, the
influence-function fault yields). Ex_Shear_Band_Plasticity_SS misspelled
the floor as yield_min (it could not have run); restored as
yield_stress_min = C. The plasticity guide's opening snippet and floors
section state the rule.

Underworld development team with AI support from Claude Code
- test_1059's no-floor control read a field with the zero default and
  tripped #759 on its own branch: it now states -oo; one level marker.
- The residual-strength check refuses any floor that is not positive
  (a negative floor leaves the same singular corner), and names the model,
  its material and the fields the yield stress reads.
- test_1057: the stated strength is checked against its closed form in
  tension (eta = tau_res / (2 edot_II)); a negative floor is refused.
- test_0106 (new): a sum of two expressions stays live -- changing one
  operand rescales a Poisson solve exactly. The eager sum #758 removes
  also froze the operand's value into the kernel (fails on development).
- fault_network docstring examples and the nonlinear-solver trap list
  state the rule; the split TI model's lagged estimate gets a TODO(BUG)
  (it reads the unfloored yield stress).

Underworld development team with AI support from Claude Code
Second review round: a floor of uw.expression(..., -oo) was refused with a
message asking for -oo, and before this branch evaluated to NaN as a floor.
_floor_is_off reads -oo bare or wrapped, at the check and at every site
that applies the floor (VP, VEP, TI-VEP, the rate-strengthening probe). The
message prints the floor as given and the variables by name; a floor that
is itself a field expression is documented as trusted.

Underworld development team with AI support from Claude Code
Copilot AI balanced review requested due to automatic review settings October 18, 2026 04:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@lmoresi

lmoresi commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

Internal adversarial review — two rounds, probes against a build without the branch.

Round 1:

Round 2 (no blockers): a wrapped -oo was refused with a message asking for -oo (and as a floor it evaluated to NaN before the branch) — _floor_is_off now reads it at the check and at every floor site; message prints the floor as given; a field-valued floor is documented as trusted.

Attacks that failed: 25 arithmetic combinations × units / no-units models all unwrap correctly (a units-model numexpr + qexpr was 302 on development, 2.0003 here); generated C hash identical under three PYTHONHASHSEEDs; no guard false positives (constant, constant expression, coordinate-only yield) in VP/VEP/TI-VEP; caught through nested expressions and swarm variables (proxy degree 0 and 1); zero and negative floors refused in every spelling incl. units; -oo opts out in all three models; the guard fires through MultiMaterialConstitutiveModel; the power mean at delta = 0 stays finite from 1e-30 to 1e25; CI reach (356 files), style gates clean.

Left: in a model without reference scales, mixed-unit sums still add magnitudes (29.1 MPa + 350 kPa unwraps to 379.1 on both builds) — predates the branch, separate from #758.


Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants