Skip to content

umat: compare the deck's constants by value, not just by count - #27

Open
petlenz wants to merge 10 commits into
feature/vector-solverfrom
fix/umat-props-value-check
Open

umat: compare the deck's constants by value, not just by count#27
petlenz wants to merge 10 commits into
feature/vector-solverfrom
fix/umat-props-value-check

Conversation

@petlenz

@petlenz petlenz commented Aug 16, 2026

Copy link
Copy Markdown
Member

The per-thread cache keyed a built context on the material name and then checked only NPROPS on later calls. Two calls for one name with the same count but different numbers pass that check and are served the first call's graph — whose constants are baked into its parameters.

The analysis converges and reports nothing. The moduli are simply wrong from the second call on.

- if (it->second.nprops != props.size())
+ if (!std::equal(it->second.props.begin(), it->second.props.end(),
+                 props.begin(), props.end()))

NPROPS doubles is nothing against an evaluation — rebuilding a graph costs 22x an update, so the comparison is not the cost worth saving here.

Test

UmatInterface.ChangingPropsValuesForTheSameNameIsFatal, against a material name no other test warms — the check only fires on a cached entry, so a shared name would let test order decide the outcome. Verified to fail without the fix and pass with it.

Found while writing tests for the JSON model layer (#26), but the defect is in the registry and independent of it. Split out so it can land on its own.

The per-thread cache keyed a built context on the material name and checked
only NPROPS on later calls. Two calls for one name with the same count but
different numbers therefore passed the check and were served the FIRST call's
graph, whose constants are baked into its parameters. The analysis converges
and reports nothing; the moduli are simply wrong from the second call on.

Store the constants and compare them. NPROPS doubles is nothing against an
evaluation -- 22x an update just to rebuild a graph, so the comparison is not
the cost worth saving here.

Found while writing tests for the JSON model layer, but the defect is in the
registry and independent of it.
petlenz added a commit that referenced this pull request Aug 17, 2026
…nd model

Two fixes from reviewing the props_scalar PR, both reproduced before fixing.

1. The guard disabled the consistency check for the whole model as soon as ANY
   constant was live. A document mixing the two binding times therefore let a
   changed BAKED constant through: through the real umat_ entry point, with
   K baked and G live and both changed from {100,40} to {300,140}, the tangent
   came back 286.667 = 100 + 4(140)/3. G tracked the deck, K silently kept its
   first value, no diagnostic. That is the defect #27 exists to catch,
   reintroduced for mixed documents.

   The check is now per slot: the count still has to match, and each value is
   compared unless a props_scalar owns that slot. Mixing is a reasonable thing
   to want -- a temperature-dependent modulus beside a fixed yield stress -- so
   rejecting mixed documents outright was the worse fix.

2. Nothing distinguished "no live constants" from "live constants, never
   bound". An unbound model published zero for every modulus, giving an
   all-zero DDSDDE and a host that fails to converge with nothing naming the
   cause. evaluate_canonical now throws if the model has readers and
   bind_props has never run.

   It catches never-bound, not stale: the plane-stress solve runs the graph
   repeatedly for one host call and must not re-bind per iterate, so
   "deliberately the same" and "forgot to re-bind" are indistinguishable. Under
   the registry the gap does not exist -- it binds on every call.

Four tests, each verified to fail against the code it guards: a mixed document
rejecting a changed baked constant, the same document honouring a changed live
one, evaluating before binding, and the plane-stress bind_props forward -- which
had no coverage at all and is the one place binding meets an iterative
evaluator.

@petlenz petlenz left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Critical review. The fix itself is correctstd::equal's four-iterator overload handles unequal lengths, empty-vs-empty compares equal so a zero-constant material is fine, and exact comparison is right here because the deck values are bit-identical between calls by construction.

One diagnostic regression, inline.

Also flagging a downstream interaction that is not a defect in this PR but changes what merges on top of it: #31 needs this check answered per slot rather than per model, because a document may mix constants baked into the graph with constants read per call. I found and fixed that in #31, and it re-splits this message in the process — so if this PR merges first, expect that follow-up rather than a conflict.

" constants but this call supplies " +
std::to_string(props.size()) +
" — PROPS must be constant for a given material name");
"' was built from a different set of " +

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The message lost information it used to carry.

Before this PR the count case read:

was built from 2 constants but this call supplies 3

Now every case, including a pure count mismatch, reads:

was built from a different set of 2 constants than this call supplies

The caller's count is gone. For the value case that is fine — the counts are equal by then, so stating one covers both. For the count case it drops the more useful half: a user reading NPROPS=3 against a message that only says 2 has to work out which number is theirs.

The check now covers two genuinely different faults with one message, and they want different text — a wrong CONSTANTS= count is a deck error, whereas equal counts with different numbers is a dispatch error. Splitting them also lets the value case name the offending slot, which is the thing a user actually needs:

constant 1 was baked into the graph as 100.000000 but this call supplies 300.000000

This is what #31 ends up doing, for the per-slot reason in the review body — so it can either land here or arrive with that PR. Mentioning it because diagnostics are the entire value of this check: it never changes a result, it only explains one.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed here rather than deferred to #31, since it is this PR's regression.

Two messages for two faults:

material 'STIFF' was built from 2 constants but this call supplies 3
  — NPROPS cannot vary for a given material name

material 'STIFF' constant 1 was baked into the graph as 100.000000
  but this call supplies 300.000000

The tests now assert the message contents — both counts in the first, the slot number and both values in the second — rather than that the word "constant" appears somewhere. This check never changes a result, so the message is the entire feature and deserves to be pinned.

#31 has been re-merged on top and keeps its per-slot skip for live constants.

…agrees

Folding both faults into one message lost information the old count check
carried. It read "was built from a different set of 2 constants than this call
supplies" -- the caller's count gone, so a user reading NPROPS=3 has to work out
which number is theirs.

They are different faults and want different text: a wrong CONSTANTS= count is a
deck error, while equal counts with different numbers is a dispatch error. The
value case now names the offending slot and both values:

  material 'STIFF' constant 1 was baked into the graph as 100.000000 but this
  call supplies 300.000000

This check never changes a result -- it only ever explains one -- so the message
is the entire feature. The tests now assert the contents rather than that the
word "constant" appears somewhere.
@petlenz

petlenz commented Aug 17, 2026

Copy link
Copy Markdown
Member Author

Second review: no findings. Re-probed the earlier ones and both are closed — the split messages now carry both counts and the offending slot, and the weighted_sum/isotropic_damage notes are documentation-only so there was nothing behavioural to re-verify beyond the suite (194/194).

Findings this round are on #30 (one low) and #31 (one medium).

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.

1 participant