materials: decompose the elastic stiffness into a material the graph can order - #28
materials: decompose the elastic stiffness into a material the graph can order#28petlenz wants to merge 10 commits into
Conversation
…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
left a comment
There was a problem hiding this comment.
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{}; // lastA 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
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
linear_elasticitycomputes 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, andelastic::stresshappens to sort beforeelastic::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_scalarvalue(plain property)isotropic_tangenttangent= C(K, G)K,Gas Globallinear_stressstress= C : εtangent,strainas GlobalThe 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_tangentrecomputes 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_sourceWith 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_elasticitythrough the evaluator exactly — the decomposition has to be a refactor of the physics, not a new model.Split out of #26.