Summary
linear_elasticity owns its tangent and builds it once in the constructor. That is
optimal while K and G are fixed, but it leaves no way to recompute it — and,
more awkwardly, no way to recompute it correctly even if one were added.
The ordering problem
A material reading its own property creates no edge in the property graph. The
engine builds edges from input_dependencies and from material-level input wiring,
so elastic::stress and elastic::tangent both hang off the same single edge from
the strain source, and their relative order falls out of constructor registration
order. Measured:
=== Property execution order ===
strain_in::strain
elastic::stress <- reads m_C
elastic::tangent <- writes m_C, too late
Attaching an update callback to tangent — the obvious, idiomatic fix — therefore
produces a stress lagging the constants by one call, silently. Verified: the
order is unchanged with the callback attached.
This is a latent trap beyond this material. Anyone who binds a callback to a
material's second output that its first output reads gets the same staleness, with
no diagnostic.
Proposal
Split the stiffness into its own material. Consuming another material's property
creates a real Global edge, which the topological sort honours:
stiffness::tangent <- produced first
elastic::stress
Same physics, different graph shape, ordering guaranteed by construction rather than
by registration order.
isotropic_tangent — params K, G, recompute; output tangent
linear_stress — inputs tangent + strain; output stress
recompute
The update callback is bound only when recompute is true. add_output ignores
a null callback, so with recompute=false the property carries no callback at all
and the engine skips it outright — per-call cost is zero, not merely small, matching
linear_elasticity. The tangent is built once in the constructor either way, so it
is valid before the first update() regardless.
|
|
recompute = false |
constants fixed (Abaqus: PROPS cannot vary per name) |
recompute = true |
constants vary per call (CalculiX temperature interpolation) |
Defaults to false, matching linear_elasticity. The failure mode of the wrong
setting is worth naming: recompute=false while the constants move leaves a stale
tangent with the stress still correct, so it costs convergence rate rather than
accuracy and produces no diagnostic. A debug assertion catches it, and
RecomputeFalseIgnoresALaterParameterWrite pins the behaviour so it is recorded
rather than discovered.
Why this is additive
Nothing existing changes. linear_elasticity is untouched and remains the better
choice whenever the moduli are fixed — one fewer material in the graph, tangent
computed once. j2_plasticity needs no change either: its elastic_source only
requires some material producing tangent.
Beyond the immediate problem, the generators become interchangeable — anisotropic,
temperature-dependent, damage-degraded stiffness all producing the same property,
with consumers indifferent to which. This is the decomposition isotropic_damage
already uses (state function + yield + damage law + assembler).
Open question
Whether recompute should default to false (matches linear_elasticity; a
CalculiX user who forgets gets a stale tangent) or true (safe by default; everyone
else pays a rank-4 rebuild per integration point until they opt out).
Branch
feature/tangent-generator (89b640b), stacked on feature/material-set-parameter
(#23) — the write-and-retrigger test needs set_parameter, and recompute=true has
no purpose without it. 191/191 tests pass.
Summary
linear_elasticityowns its tangent and builds it once in the constructor. That isoptimal while
KandGare fixed, but it leaves no way to recompute it — and,more awkwardly, no way to recompute it correctly even if one were added.
The ordering problem
A material reading its own property creates no edge in the property graph. The
engine builds edges from
input_dependenciesand from material-level input wiring,so
elastic::stressandelastic::tangentboth hang off the same single edge fromthe strain source, and their relative order falls out of constructor registration
order. Measured:
Attaching an update callback to
tangent— the obvious, idiomatic fix — thereforeproduces a stress lagging the constants by one call, silently. Verified: the
order is unchanged with the callback attached.
This is a latent trap beyond this material. Anyone who binds a callback to a
material's second output that its first output reads gets the same staleness, with
no diagnostic.
Proposal
Split the stiffness into its own material. Consuming another material's property
creates a real Global edge, which the topological sort honours:
Same physics, different graph shape, ordering guaranteed by construction rather than
by registration order.
isotropic_tangent— paramsK,G,recompute; outputtangentlinear_stress— inputstangent+strain; outputstressrecomputeThe update callback is bound only when
recomputeis true.add_outputignoresa null callback, so with
recompute=falsethe property carries no callback at alland the engine skips it outright — per-call cost is zero, not merely small, matching
linear_elasticity. The tangent is built once in the constructor either way, so itis valid before the first
update()regardless.recompute = falsePROPScannot vary per name)recompute = trueDefaults to
false, matchinglinear_elasticity. The failure mode of the wrongsetting is worth naming:
recompute=falsewhile the constants move leaves a staletangent with the stress still correct, so it costs convergence rate rather than
accuracy and produces no diagnostic. A debug assertion catches it, and
RecomputeFalseIgnoresALaterParameterWritepins the behaviour so it is recordedrather than discovered.
Why this is additive
Nothing existing changes.
linear_elasticityis untouched and remains the betterchoice whenever the moduli are fixed — one fewer material in the graph, tangent
computed once.
j2_plasticityneeds no change either: itselastic_sourceonlyrequires some material producing
tangent.Beyond the immediate problem, the generators become interchangeable — anisotropic,
temperature-dependent, damage-degraded stiffness all producing the same property,
with consumers indifferent to which. This is the decomposition
isotropic_damagealready uses (state function + yield + damage law + assembler).
Open question
Whether
recomputeshould default tofalse(matcheslinear_elasticity; aCalculiX user who forgets gets a stale tangent) or
true(safe by default; everyoneelse pays a rank-4 rebuild per integration point until they opt out).
Branch
feature/tangent-generator(89b640b), stacked onfeature/material-set-parameter(#23) — the write-and-retrigger test needs
set_parameter, andrecompute=truehasno purpose without it. 191/191 tests pass.