Context
CalculiXExternalTarget (#127) emits a self-contained CalculiX external behaviour for stateless recipes. Its own header already names the sequel:
SCOPE (first cut, linear elasticity): stateless recipes only ... State variables (the STATEV round-trip) are the numsim-materials-backed follow-up.
That follow-up now exists: numsim-materials PR #51 adds umat/calculix_interface.h, a tested adapter for the native call_external_umat_user hook (NUMSIM_MATERIALS_DEFINE_CALCULIX_BEHAVIOUR(TRAITS, FUNC, MODELNAME)) that marshals CalculiX's conventions onto the existing umat_dispatch / umat_registry / material_point_evaluator stack.
This issue is to build the stateful path on that, rather than growing the hand-rolled marshalling.
Why this matters more than ordinary dedup
The emitted entry currently comments out exactly the arguments that only matter once a material has state:
/*iel*/ /*iint*/ /*mi*/ /*NSTATV*/ /*time*/ /*ttime*/ /*iorien*/
That is correct today — stateless, time-independent elasticity needs none of them. But each is a silent-wrong-answer trap the moment state appears. The review of PR #51 found three of them in a freshly written adapter, all invisible under exactly the elastic/one-increment/point-(1,1) configuration this target is scoped to:
- STATEV indexed at point (1,1) for every integration point. ccx passes the whole
xstate(nstate_, mi(1), *) array base (umat_main.f:40,233), not a per-point slice as umat_abaqus.f:295 does, so the callee must index at nstatv*((iint-1) + mi1*(iel-1)). Every Gauss point otherwise shares one slot.
- TIME not rebased. ccx gives step time at the increment END and total time at the STEP start; the Abaqus convention wants both rebased onto the increment start (
umat_abaqus.f:187-188). Passing them through is right only on the first increment of the first step.
- Tangent transposed for major-asymmetric tangents, when reading a column-major buffer row-major.
Anyone extending this target by hand would have to rediscover all three. Reusing #51 inherits them already fixed and regression-tested (each test was verified to fail against the pre-fix code).
Note (3) is already correct here: emit_stiff21_packing in src/targets/calculix_boundary.h symmetrizes (0.5 * (d6[i*6+j] + d6[j*6+i])), which makes the storage order moot. (1) and (2) are simply out of scope today.
Proposed shape: two emission modes, one target
Stateless (unchanged). Keep emitting the standalone plugin. The generated .so has no numsim-materials dependency — a real feature worth preserving, since there is no runtime library to ship, and stateless elasticity exercises none of the machinery below. Do not route this through #51.
Stateful (new). Emit:
- a builder that constructs the material graph from the recipe (state variables -> history properties, evolution equations -> the solver/return-map contract), with
*USER MATERIAL constants bound positionally, as umat_registry::builder already expects;
- a registration for the model name;
NUMSIM_MATERIALS_DEFINE_CALCULIX_BEHAVIOUR(policy, NCG_UMAT, "<MODEL>").
This inherits, already tested: the STATEV offset, the TIME rebasing, the iorien/beta guards, per-thread contexts, the props-invariance check, and the fatal/cutback (PNEWDT = 0.25) semantics.
Mode selection follows the existing scope check: a recipe with state variables takes the materials-backed path; otherwise the standalone one. This pairs naturally with the can_emit() capability query in #137.
Drift risk to close regardless
The CalculiX convention knowledge is now encoded independently in two repos:
src/targets/calculix_boundary.h (emit_stiff21_packing, the abq_std boundary)
numsim-materials umat/calculix_interface.h
They agree today. Nothing keeps them agreeing, and a divergence is silent. At minimum, cross-reference them in both files and point at the same umat_abaqus.f / umat_user.f line numbers as the shared source of truth.
Validation
Neither path has yet been run against a real ccx through the @LIB,FUNC deck. The golden-file harness in #128 is the natural gate: it should cover the stateless path first, then the stateful one, with a plastic material whose STATEV round-trip actually spans increments and integration points.
Depends on
Context
CalculiXExternalTarget(#127) emits a self-contained CalculiX external behaviour for stateless recipes. Its own header already names the sequel:That follow-up now exists: numsim-materials PR #51 adds
umat/calculix_interface.h, a tested adapter for the nativecall_external_umat_userhook (NUMSIM_MATERIALS_DEFINE_CALCULIX_BEHAVIOUR(TRAITS, FUNC, MODELNAME)) that marshals CalculiX's conventions onto the existingumat_dispatch/umat_registry/material_point_evaluatorstack.This issue is to build the stateful path on that, rather than growing the hand-rolled marshalling.
Why this matters more than ordinary dedup
The emitted entry currently comments out exactly the arguments that only matter once a material has state:
That is correct today — stateless, time-independent elasticity needs none of them. But each is a silent-wrong-answer trap the moment state appears. The review of PR #51 found three of them in a freshly written adapter, all invisible under exactly the elastic/one-increment/point-(1,1) configuration this target is scoped to:
xstate(nstate_, mi(1), *)array base (umat_main.f:40,233), not a per-point slice asumat_abaqus.f:295does, so the callee must index atnstatv*((iint-1) + mi1*(iel-1)). Every Gauss point otherwise shares one slot.umat_abaqus.f:187-188). Passing them through is right only on the first increment of the first step.Anyone extending this target by hand would have to rediscover all three. Reusing #51 inherits them already fixed and regression-tested (each test was verified to fail against the pre-fix code).
Note (3) is already correct here:
emit_stiff21_packinginsrc/targets/calculix_boundary.hsymmetrizes (0.5 * (d6[i*6+j] + d6[j*6+i])), which makes the storage order moot. (1) and (2) are simply out of scope today.Proposed shape: two emission modes, one target
Stateless (unchanged). Keep emitting the standalone plugin. The generated
.sohas no numsim-materials dependency — a real feature worth preserving, since there is no runtime library to ship, and stateless elasticity exercises none of the machinery below. Do not route this through #51.Stateful (new). Emit:
*USER MATERIALconstants bound positionally, asumat_registry::builderalready expects;NUMSIM_MATERIALS_DEFINE_CALCULIX_BEHAVIOUR(policy, NCG_UMAT, "<MODEL>").This inherits, already tested: the STATEV offset, the TIME rebasing, the
iorien/betaguards, per-thread contexts, the props-invariance check, and the fatal/cutback (PNEWDT = 0.25) semantics.Mode selection follows the existing scope check: a recipe with state variables takes the materials-backed path; otherwise the standalone one. This pairs naturally with the
can_emit()capability query in #137.Drift risk to close regardless
The CalculiX convention knowledge is now encoded independently in two repos:
src/targets/calculix_boundary.h(emit_stiff21_packing, theabq_stdboundary)numsim-materialsumat/calculix_interface.hThey agree today. Nothing keeps them agreeing, and a divergence is silent. At minimum, cross-reference them in both files and point at the same
umat_abaqus.f/umat_user.fline numbers as the shared source of truth.Validation
Neither path has yet been run against a real
ccxthrough the@LIB,FUNCdeck. The golden-file harness in #128 is the natural gate: it should cover the stateless path first, then the stateful one, with a plastic material whose STATEV round-trip actually spans increments and integration points.Depends on
calculix_interface.h