materials: register plasticity, hardening, damage and state functions - #36
Conversation
Eight material classes were missing from register_default_materials(), so they could not be named in a JSON document at all. Two of them are small_strain_plasticity and linear_isotropic_hardening, which means a document could build elasticity and damage and NOT J2 plasticity -- the library's flagship model -- against a stated goal of being config driven. Registration is a hand-maintained list, and every test that uses these materials constructs them in C++ through ctx.create<T>(), never through the factory. So the tests passed, the materials worked, and the only broken thing was the path the document layer depends on. small_strain_plasticity and rk_plasticity are templates over the yield-function TYPE, so the registrable names are the concrete aliases: j2_plasticity, drucker_prager_plasticity, j2_rk_plasticity. drucker_prager_plasticity is registered but NOT yet fully configurable from a document. Its yield function carries eta, beta and K_bulk and arrives as a C++ object through an undeclared "yield_function" parameter, which the JSON reader cannot convert. A document can name it; it cannot set those three. That is recorded in a test rather than left to be discovered. Three tests: the names resolve; a J2 model built entirely from a document yields (registration alone would pass the name check while still failing here if a parameter were not convertible); and Drucker-Prager's limitation is pinned, so the test fails if yield_function ever becomes expressible and the note goes stale. Closes #33.
…be configured Registering it made a document able to NAME it, and the yield function -- which carries eta, beta and K_bulk -- cannot be expressed in JSON. A document naming it therefore got a DEFAULT-constructed one: eta = beta = k = 0. Probed: it builds without error, runs 20 steps, never yields, and is indistinguishable from elasticity. That is worse than not registering it. Unregistered, a document naming it fails with an unknown material type, which says exactly what is wrong. Registered, it silently returns elastic results from a model the user believes is Drucker-Prager. The test now pins the absence and its reason, so registering it again without making yield_function expressible fails.
petlenz
left a comment
There was a problem hiding this comment.
Critical review of my own PR — probed, not read. One finding, and it made this PR a net negative until fixed. Fixed in the branch; recording it because the failure mode is the one this stack keeps producing.
Registering drucker_prager_plasticity was worse than leaving it out
Its yield function carries eta, beta and K_bulk and arrives as a C++ object the JSON reader cannot convert. Registered, a document could NAME it — and silently got a default-constructed yield function, eta = beta = k = 0. Probed:
built from JSON without error
ran 20 steps: alpha=0.000000
-> a document can build a DP material with eta=beta=k=0 and it runs silently
It builds, runs, never yields, and is indistinguishable from elasticity. A user writing a Drucker-Prager model in JSON gets elastic results and no error anywhere.
Unregistered, the same document fails immediately:
rejected: object_registry::entry(): unknown type 'drucker_prager_plasticity'
That names exactly what is wrong. So it is now deliberately NOT registered, with the reason in the code and a test pinning the absence — registering it again without making yield_function expressible fails the test.
I had originally written a test asserting yield_function was absent from the schema, which recorded the limitation without noticing that the limitation made the registration harmful. Documenting a hazard is not the same as removing it.
What I checked and could not break
- The other eight registrations are reachable and configurable; a J2 model built entirely from a document is driven to yield, which registration alone would not prove.
- No name collisions: 27 registered types, all distinct.
- CI green.
Follow-up worth its own issue: making yield functions expressible in JSON is what unblocks Drucker-Prager, and it is the same shape as zero_blocks on #17 — a typed C++ object reaching a material through an undeclared parameter.
Drucker-Prager was held out of the factory with a stated condition: register it once the yield function is expressible from a document. #43 met that condition by making eta, beta and K_bulk plain required scalars instead of members of a C++ yield_function object the JSON reader could not convert. Registered now, and the test that pinned its absence is replaced by two that pin the reason the absence was needed: - a complete Drucker-Prager document builds and yields - a document missing "eta" throws and leaves no material behind, rather than silently getting eta = beta = k = 0 and running as elasticity local_newton is registered too. Without it #33 would still not be closed: every return map names its solver through "solver_source", so a deck could name j2_plasticity but not the solver it requires, and the model still could not be built from a document. The J2 document in this file also needed updating -- it named backward_euler as the plasticity solver, which is the callback mode #40 removed, and passed elastic_source, which #44 removed when plasticity took ownership of its own elastic tangent. One honest limitation recorded in the test rather than papered over: the exception for a missing parameter carries only a COUNT, "missing 1 required parameter(s)". numsim-core's input_parameter_controller prints the names to stdout and throws the count separately, so a deck typo is loud but not self-explanatory. Fixing that is a numsim-core change; what this test pins is that the cone cannot be built without its parameters. 258/258 tests pass on the merge result, verified locally.
TheOptOutListHasNoStaleEntries did exactly what it exists for: kNotForJson exempted small_strain_plasticity and rk_plasticity, and neither header exists any more -- #43 and #40 split them into j2_plasticity, drucker_prager_plasticity and j2_rk_plasticity, each its own header and, since #36, its own factory entry. The exemption for tensor_component_stepper stays: it is a template over Rank, registered as tensor_component_stepper_rank1 and _rank2. 263/263 tests pass on the merge result, verified locally.
Closes #33.
Eight material classes were missing from
register_default_materials(), so they could not be named in a JSON document. Two aresmall_strain_plasticityandlinear_isotropic_hardening— a document could build elasticity and damage but not J2 plasticity, against a stated goal of being config driven.Nothing detected it: registration is a hand-maintained list, and every test that uses these materials constructs them in C++ via
ctx.create<T>(), never through the factory. The tests passed, the materials worked, and only the document layer was broken.Correcting the issue's guess
#33 speculated the obstacle was a
yield_functionobject parameter. It is not — all 8 declare only JSON-convertible types. The real reason is thatsmall_strain_plasticityandrk_plasticityare templates over the yield-function type, so the registrable names are the concrete aliases:j2_plasticity,drucker_prager_plasticity,j2_rk_plasticity.drucker_prager_plasticityis registered but not yet fully configurable from a document: its yield function carries η, β and K_bulk and arrives as a C++ object through an undeclaredyield_functionparameter the JSON reader cannot convert. A document can name it; it cannot set those three. That is pinned by a test rather than left in a comment, so the test fails if it ever becomes expressible.Tests
Three. The names resolve; a J2 model built entirely from a document is driven to yield (registration alone would pass a name check while still failing if a parameter were unconvertible); and Drucker-Prager's limitation is recorded.