Skip to content

materials: decompose the elastic stiffness into a material the graph can order - #28

Open
petlenz wants to merge 10 commits into
feature/vector-solverfrom
feature/elastic-stiffness-material
Open

materials: decompose the elastic stiffness into a material the graph can order#28
petlenz wants to merge 10 commits into
feature/vector-solverfrom
feature/elastic-stiffness-material

Conversation

@petlenz

@petlenz petlenz commented Aug 16, 2026

Copy link
Copy Markdown
Member

linear_elasticity computes stress and tangent in one material, so its tangent is invisible to the property graph: reading a property inside the material that owns it creates no edge, and elastic::stress happens to sort before elastic::tangent. Anything that wanted the stiffness to follow a changing input would silently lag by one call.

Split it in three, so every dependency is an edge the engine can see:

material publishes inputs
constant_scalar value (plain property)
isotropic_tangent tangent = C(K, G) K, G as Global
linear_stress stress = C : ε tangent, strain as Global

The moduli being graph inputs is the point. A constant is one node today and a host-bound or temperature-dependent source tomorrow, with no change to the two materials downstream.

isotropic_tangent recomputes on every update rather than memoising. It has one job; the memo measured 28.1 ns against 27.0 ns for the monolithic material; and a validity flag is state that can disagree with its inputs.

material_point_evaluator::config::tangent_source

With the stiffness in its own material, stress and tangent no longer share an owner. The field is std::optional<std::string> so unset and "" stay distinct, and it is appended, not inserted — this header is embedded in third-party UMATs, and a new field in the middle would silently re-bind the trailing arguments of an existing aggregate initialiser.

Tests

10, including that the decomposed pair reproduces linear_elasticity through the evaluator exactly — the decomposition has to be a refactor of the physics, not a new model.

Split out of #26.

…can order

linear_elasticity computes stress and tangent in one material, so its tangent
is invisible to the property graph: reading a property inside the material that
owns it creates no edge, and elastic::stress happens to sort before
elastic::tangent. Anything that wanted the stiffness to follow a changing input
would silently lag by one call.

Split it in three, so every dependency is an edge the engine can see:

  constant_scalar    publishes a scalar as a plain property
  isotropic_tangent  C(K, G), with K and G as Global inputs
  linear_stress      sigma = C : eps, with C as a Global input

The moduli being graph inputs is the point. A constant is one node today and a
host-bound or temperature-dependent source tomorrow, with no change to the two
materials downstream.

isotropic_tangent recomputes on every update rather than memoising. It has one
job, the memo was 28.1 ns against 27.0 ns for the monolithic material, and a
validity flag is state that can disagree with its inputs.

material_point_evaluator gains config::tangent_source for the same reason:
with the stiffness in its own material, the stress and the tangent no longer
share an owner. It is optional and appended, not inserted -- this header is
embedded in third-party UMATs, and a field in the middle would re-bind the
trailing arguments of an existing aggregate initialiser.

10 tests, including that the decomposed pair reproduces linear_elasticity
through the evaluator exactly.

@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 — probed rather than read. Nothing found. Recording what I tried to break so the absence of findings means something.

Silent-default holes: none. isotropic_tangent marks K_source and G_source is_required, and linear_stress marks tangent_source and strain_source is_required. There is no path where a missing wire degrades into a plausible default — the failure is at construction, by name.

The ABI claim about config::tangent_source holds. I checked the struct rather than trusting the comment: it really is the last member, so an existing aggregate initialiser cannot have its trailing arguments re-bound.

std::vector<statev_exclusion> extra_exclusions{};
std::optional<std::string> tangent_source{};   // last

A missing tangent owner fails loudly. linear_stress publishes only "stress", so using it as a stress source with tangent_source unset makes the evaluator resolve elastic::tangent, which does not exist — fatal_error naming the property, not a zero tangent. That is the right side of the line for a setup fault.

Dropping the memo is safe under repeated evaluation. The concern with recompute-every-update was a material inside a solver's inner loop recomputing needlessly; K and G do not change across those iterations, so the result is identical and the cost is the 28.1 vs 27.0 ns already measured.

One thing I deliberately did not treat as a finding: linear_stress computes sigma = C : eps from TOTAL strain, which is only the constitutive law for linear elasticity. That is what the name says, and the tests pin it against linear_elasticity exactly, so it is a correct narrow material rather than an incomplete general one.

Findings on the rest of the stack: two on #30 (one high), two notes on #29, one diagnostic regression on #27.

@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.

Second review. No correctness findings — this PR came back clean last time too. One documentation accuracy point inline.

/// K_property/G_property if it does not publish under "value". Which one you
/// wire IS the choice, so no flag can disagree with it.
///
/// Rebuilds on every update: no memo, so no cached state to go stale. Costs

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.

This number compares against code that no longer exists.

"~309 ns against ~28 ns memoised" — the memoised variant was removed in this PR, so a reader cannot check the comparison or reproduce it. It is a claim about a counterfactual.

Re-measured what is actually here, 300k iterations each:

isotropic_tangent alone, per update :   286.8 ns
decomposed pair, per update         :   315.0 ns
linear_elasticity, per update       :    26.1 ns

So 309 was in the right area for the rebuild itself (286.8 here), but the useful comparison is the last two lines: 315 ns against 26 ns, 12x on the whole graph, against a material that exists and that the same sentence already points at. That is the number someone deciding between the two would want.

The sentence's conclusion does not change — where the moduli are fixed, linear_elasticity is the cheaper choice — only the evidence offered for it. Worth swapping since a comparison against deleted code cannot be re-checked when either side drifts.

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. The header now quotes the comparison against a material that exists:

isotropic_tangent alone :  286.8 ns
decomposed pair         :  315.0 ns
linear_elasticity       :   26.1 ns

~315 ns against ~26 ns — 12x on the whole graph. Conclusion unchanged; the evidence can now be re-run when either side drifts, which a comparison against deleted code could not.

The header compared against a memoised variant this PR deletes, so the number
could not be checked or re-run. Replaced with the comparison someone choosing
between the two would actually make, against a material that exists:

  isotropic_tangent alone :  286.8 ns
  decomposed pair         :  315.0 ns
  linear_elasticity       :   26.1 ns

The conclusion is unchanged; only the evidence is now reproducible.
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