verification: implicit plasticity oracle cases and documentation

Four cases, each against its closed form: uniaxial tension of a J2 cube to a
logarithmic strain of 0.2 (Kirchhoff stress exact; 0.13 % from the explicit
solver's hypoelastic Cauchy stress, the elastic volume change between the two
laws), the Bauschinger effect of linear kinematic hardening on a load path
back to zero strain (exact), Hill's elastic-perfectly plastic thick cylinder
(bore displacement at a plastic radius of 1.5a within 0.009 %, collapse
pressure within 2e-8; the pending case is now active) and a cantilever's
plastic collapse, converged 3.1 % above beam theory's hinge, the bound a
continuum exceeds. docs/fem.md, the literature, ROADMAP (M15), parity and the
oracle page follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 06:03:36 -04:00
1 parent 17b2c837f2
commit 7a69fe5e2f
21 files changed
+414 -25

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
# Bauschinger effect of linear kinematic hardening
One hex8 cube (E = 200000, ν = 0.3, σ_y = 250, H = 2000, Prager's linear kinematic hardening)
held by symmetry on three faces, its fourth face pulled to a strain of 0.01 and brought back to
zero within one nonlinear static step without nonlinear geometry, by the load path
[[0, 0], [0.5, 1], [1, 0]] in increments of 0.05.
At ε_a the stress is σ_a = σ_y + E_t (ε_a − σ_y/E) = 267.327. The yield surface keeps its size
and moves with the back stress, so unloading is elastic only until σ_a − 2σ_y and then hardens
with E_t: back at zero strain σ = σ_a − 2σ_y − E_t (ε_a − 2σ_y/E) = −247.525. Isotropic hardening
would yield again only at −σ_a and end at −281.835. The equivalent plastic strain accumulates
(σ_a − σ_y)/H forward and E/(E + H)(ε_a − 2σ_y/E) in reverse, 0.0160891.
+45
View File
@@ -0,0 +1,45 @@
{
"title": "Bauschinger effect of linear kinematic hardening: tension, then back to zero strain",
"kind": "theory",
"source": "J2 plasticity with Prager's linear kinematic hardening in uniaxial stress (W. Prager, Ing.-Arch. 1955; Simo and Hughes, Computational Inelasticity, Springer 1998, §2.3): from σ_a = σ_y + E_t (ε_a − σ_y/E) the material unloads elastically by 2σ_y and then hardens with E_t, so at zero strain σ = σ_a − 2σ_y − E_t (ε_a − 2σ_y/E)",
"quantities": [
{
"id": "residual_stress",
"quantity": "axial stress back at zero strain after a pull to ε_a = 0.01",
"measure": {
"command": "solve.probe",
"params": {
"step": "Pull",
"elements": [
1
]
},
"pointer": "/elements/0/stress/0/0"
},
"value": -247.52475247524754,
"tolerance": {
"relative": 1e-06
},
"reason": "a homogeneous small-strain state the element represents exactly, with increments that end at the load path's turn; isotropic hardening would give −281.835 (it yields again only at −σ_a)"
},
{
"id": "plastic_strain",
"quantity": "accumulated equivalent plastic strain",
"measure": {
"command": "solve.probe",
"params": {
"step": "Pull",
"elements": [
1
]
},
"pointer": "/elements/0/plastic_strain/0"
},
"value": 0.016089108910891083,
"tolerance": {
"relative": 1e-06
},
"reason": "(σ_a − σ_y)/H forward plus E/(E + H) (ε_a − 2σ_y/E) in reverse"
}
]
}
+13
View File
@@ -0,0 +1,13 @@
{
"note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.",
"values": {
"plastic_strain": {
"band": 1.6089108910891082e-11,
"value": 0.01608910891089074
},
"residual_stress": {
"band": 2.4752475247524754e-7,
"value": -247.52475247484173
}
}
}
+16
View File
@@ -0,0 +1,16 @@
[
{"command": "mesh.block", "params": {"body": "Cube", "kind": "hex8", "corners": [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]], "divisions": [1, 1, 1], "faces": {"6": "x0", "3": "y0", "1": "z0", "4": "x1"}}},
{"command": "model.set.create", "params": {"name": "all", "geometry": [{"body": "Cube"}]}},
{"command": "model.set.create", "params": {"name": "x0", "geometry": [{"body": "Cube", "face": "x0"}]}},
{"command": "model.set.create", "params": {"name": "y0", "geometry": [{"body": "Cube", "face": "y0"}]}},
{"command": "model.set.create", "params": {"name": "z0", "geometry": [{"body": "Cube", "face": "z0"}]}},
{"command": "model.set.create", "params": {"name": "x1", "geometry": [{"body": "Cube", "face": "x1"}]}},
{"command": "model.material.create", "params": {"name": "Steel", "elastic": {"type": "isotropic", "young": 200000.0, "poisson": 0.3}, "plastic": {"type": "bilinear", "yield_stress": 250.0, "hardening_modulus": 2000.0, "hardening": "kinematic"}}},
{"command": "model.section.create", "params": {"name": "cube", "set": "all", "material": "Steel", "kind": {"type": "solid"}}},
{"command": "model.boundary_condition.create", "params": {"name": "sx", "type": "symmetry", "target": {"set": "x0"}, "plane": "x"}},
{"command": "model.boundary_condition.create", "params": {"name": "sy", "type": "symmetry", "target": {"set": "y0"}, "plane": "y"}},
{"command": "model.boundary_condition.create", "params": {"name": "sz", "type": "symmetry", "target": {"set": "z0"}, "plane": "z"}},
{"command": "model.boundary_condition.create", "params": {"name": "pull", "type": "displacement", "target": {"set": "x1"}, "values": [0.01, null, null, null, null, null]}},
{"command": "model.step.create", "params": {"name": "Pull", "procedure": {"type": "nonlinear_static", "nonlinear_geometry": false, "initial_increment": 0.05, "max_increment": 0.05, "load_path": [[0, 0], [0.5, 1], [1, 0]]}, "boundary_conditions": ["sx", "sy", "sz", "pull"], "loads": []}},
{"command": "solve.nonlinear", "params": {"step": "Pull"}, "id": "solve"}
]
+16
View File
@@ -0,0 +1,16 @@
# Uniaxial tension of a J2 bilinear-hardening cube to large strain (implicit)
The cube of `j2-uniaxial-explicit` solved by the implicit nonlinear solver: one fully integrated
hex8, E = 200000, ν = 0.3, σ_y = 250, H = 2000 (isotropic), held by symmetry on three faces and
stretched on the fourth to λ = e^0.2 in a nonlinear static step, lateral faces free.
Simo's exponential return map integrates this proportional path exactly in the logarithmic
strain, so the Kirchhoff stress is the small-strain curve τ = σ_y + E_t (ε − σ_y/E) = 643.564 at
ε = 0.2 and the equivalent plastic strain (τ − σ_y)/H = 0.196782. The Cauchy stress is
σ = τ/J with Hencky's volume law ln J = τ/(3K): 642.737.
Cross-check with the explicit solver: `j2-uniaxial-explicit` reaches 643.57 for the Cauchy stress
with the same plastic strain. Its hypoelastic update takes the logarithmic strain rate against
the Cauchy stress, while the implicit model is hyperelastic in the Kirchhoff stress: the two
laws differ by the factor J = 1.0013 (the elastic volume change), which is the 0.13 % between
them; both meet the closed form of their own law.
+45
View File
@@ -0,0 +1,45 @@
{
"title": "Uniaxial tension of a J2 bilinear-hardening cube to large strain (implicit, finite strain)",
"kind": "theory",
"source": "J2 plasticity with linear isotropic hardening in uniaxial stress (Simo and Hughes, Computational Inelasticity, Springer 1998, §2.3) in the logarithmic strain, which Simo's exponential return map (CMAME 99:61–112, 1992) integrates exactly on a proportional path: Kirchhoff stress τ = σ_y + E_t (ε − σ_y/E), Cauchy stress σ = τ/J with ln J = τ/(3K) (Hencky's volume law)",
"quantities": [
{
"id": "sigma",
"quantity": "axial Cauchy stress at a logarithmic strain of 0.2",
"measure": {
"command": "solve.probe",
"params": {
"step": "Pull",
"elements": [
1
]
},
"pointer": "/elements/0/stress/0/0"
},
"value": 642.7365391418857,
"tolerance": {
"relative": 1e-06
},
"reason": "a homogeneous deformation the element represents exactly, reached on a proportional path: the tolerance only admits the Newton tolerance and rounding"
},
{
"id": "plastic_strain",
"quantity": "equivalent plastic strain",
"measure": {
"command": "solve.probe",
"params": {
"step": "Pull",
"elements": [
1
]
},
"pointer": "/elements/0/plastic_strain/0"
},
"value": 0.19678217821782182,
"tolerance": {
"relative": 1e-06
},
"reason": "ε_p = (τ − σ_y)/H, as for the stress"
}
]
}
+13
View File
@@ -0,0 +1,13 @@
{
"note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.",
"values": {
"plastic_strain": {
"band": 1.9678217821795454e-10,
"value": 0.19678217821795452
},
"sigma": {
"band": 6.427365391418857e-7,
"value": 642.7365390754569
}
}
}
+16
View File
@@ -0,0 +1,16 @@
[
{"command": "mesh.block", "params": {"body": "Cube", "kind": "hex8", "corners": [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]], "divisions": [1, 1, 1], "faces": {"6": "x0", "3": "y0", "1": "z0", "4": "x1"}}},
{"command": "model.set.create", "params": {"name": "all", "geometry": [{"body": "Cube"}]}},
{"command": "model.set.create", "params": {"name": "x0", "geometry": [{"body": "Cube", "face": "x0"}]}},
{"command": "model.set.create", "params": {"name": "y0", "geometry": [{"body": "Cube", "face": "y0"}]}},
{"command": "model.set.create", "params": {"name": "z0", "geometry": [{"body": "Cube", "face": "z0"}]}},
{"command": "model.set.create", "params": {"name": "x1", "geometry": [{"body": "Cube", "face": "x1"}]}},
{"command": "model.material.create", "params": {"name": "Steel", "elastic": {"type": "isotropic", "young": 200000.0, "poisson": 0.3}, "plastic": {"type": "bilinear", "yield_stress": 250.0, "hardening_modulus": 2000.0, "hardening": "isotropic"}}},
{"command": "model.section.create", "params": {"name": "cube", "set": "all", "material": "Steel", "kind": {"type": "solid"}}},
{"command": "model.boundary_condition.create", "params": {"name": "sx", "type": "symmetry", "target": {"set": "x0"}, "plane": "x"}},
{"command": "model.boundary_condition.create", "params": {"name": "sy", "type": "symmetry", "target": {"set": "y0"}, "plane": "y"}},
{"command": "model.boundary_condition.create", "params": {"name": "sz", "type": "symmetry", "target": {"set": "z0"}, "plane": "z"}},
{"command": "model.boundary_condition.create", "params": {"name": "pull", "type": "displacement", "target": {"set": "x1"}, "values": [0.22140275816016985, null, null, null, null, null]}},
{"command": "model.step.create", "params": {"name": "Pull", "procedure": {"type": "nonlinear_static"}, "boundary_conditions": ["sx", "sy", "sz", "pull"], "loads": []}},
{"command": "solve.nonlinear", "params": {"step": "Pull"}, "id": "solve"}
]
@@ -0,0 +1,13 @@
# Plastic collapse of a cantilever
A steel bar 200 long with a 10 × 10 section (E = 200000, ν = 0.3, elastic–perfectly plastic at
σ_y = 250), reduced hex20 20 × 2 × 4, in a nonlinear static step without nonlinear geometry. The
root x = 0 is a plane of symmetry (no rotation, the section free to contract laterally), held in
Y, Z and against twist on average through a distributing coupling; a coupling at the tip pushes
the end down by 15, 4.5 times the deflection at first yield.
Beam theory's collapse is a plastic hinge at the root: M_p = σ_y b h²/4 = 62500 and
P_c = M_p/L = 312.5. The solid's collapse lies above it, since a continuum cannot rotate about a
hinge (that needs a jump of the normal velocity across the section): refined to 8 elements
through the depth or 160 along the length, the force settles at 322.3, 3.1 % above. The case
checks that the implicit plasticity reaches this plateau and holds it.
@@ -0,0 +1,20 @@
{
"title": "Plastic collapse of a cantilever: the fully plastic moment at the root",
"kind": "theory",
"source": "Limit analysis (B. G. Neal, The Plastic Methods of Structural Analysis, 3rd ed., Chapman & Hall 1977, ch. 1–2): beam theory's plastic hinge at the root, the fully plastic moment M_p = σ_y b h²/4 of a rectangular section, collapses a cantilever of length L at P_c = M_p/L = 312.5 (b = h = 10, L = 200, σ_y = 250); a bound for the solid, whose collapse lies above it (W. Prager, P. G. Hodge, Theory of Perfectly Plastic Solids, Wiley 1951: a continuum mechanism allows no normal velocity jump, which the beam's hinge rotation is)",
"quantities": [
{
"id": "collapse_force",
"quantity": "tip force at a tip deflection of 15 (4.5 times the first yield's)",
"measure": {
"from": "solve",
"pointer": "/reference_points/1/reaction/2"
},
"value": -312.5,
"tolerance": {
"relative": 0.05
},
"reason": "the solid's collapse lies above beam theory's hinge: refined through the depth (4 to 8 elements) and along the length (40 to 160) the force settles 3.1 % above M_p/L (322.3), so the band is that bound's gap; the root is a symmetry plane (no rotation, free lateral contraction) held on average through a coupling, the tip pushed through another; reduced hex20, 20 × 2 × 4"
}
]
}
@@ -0,0 +1,9 @@
{
"note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.",
"values": {
"collapse_force": {
"band": 3.2260685847425373e-7,
"value": -322.6068584742537
}
}
}
@@ -0,0 +1,17 @@
[
{"command": "mesh.block", "params": {"body": "Beam", "kind": "hex20", "corners": [[0, 0, -5.0], [200.0, 0, -5.0], [200.0, 10.0, -5.0], [0, 10.0, -5.0], [0, 0, 5.0], [200.0, 0, 5.0], [200.0, 10.0, 5.0], [0, 10.0, 5.0]], "divisions": [20, 2, 4], "faces": {"6": "root", "4": "tip"}}},
{"command": "model.set.create", "params": {"name": "all", "geometry": [{"body": "Beam"}]}},
{"command": "model.set.create", "params": {"name": "root", "geometry": [{"body": "Beam", "face": "root"}]}},
{"command": "model.set.create", "params": {"name": "tip", "geometry": [{"body": "Beam", "face": "tip"}]}},
{"command": "model.material.create", "params": {"name": "Steel", "elastic": {"type": "isotropic", "young": 200000.0, "poisson": 0.3}, "plastic": {"type": "bilinear", "yield_stress": 250.0}}},
{"command": "model.section.create", "params": {"name": "beam", "set": "all", "material": "Steel", "kind": {"type": "solid"}, "formulation": {"integration": "reduced"}}},
{"command": "model.boundary_condition.create", "params": {"name": "plane", "type": "symmetry", "target": {"set": "root"}, "plane": "x"}},
{"command": "model.reference_point.create", "params": {"name": "Root", "xyz": [0, 5.0, 0]}},
{"command": "model.coupling.create", "params": {"name": "support", "reference_point": "Root", "set": "root", "kind": "distributing"}},
{"command": "model.boundary_condition.create", "params": {"name": "hold", "type": "displacement", "target": {"reference_point": "Root"}, "values": [null, 0, 0, 0, null, null]}},
{"command": "model.reference_point.create", "params": {"name": "Tip", "xyz": [200.0, 5.0, 0]}},
{"command": "model.coupling.create", "params": {"name": "end", "reference_point": "Tip", "set": "tip", "kind": "distributing"}},
{"command": "model.boundary_condition.create", "params": {"name": "push", "type": "displacement", "target": {"reference_point": "Tip"}, "values": [null, null, -15.0, null, null, null]}},
{"command": "model.step.create", "params": {"name": "Collapse", "procedure": {"type": "nonlinear_static", "nonlinear_geometry": false}, "boundary_conditions": ["plane", "hold", "push"], "loads": []}},
{"command": "solve.nonlinear", "params": {"step": "Collapse"}, "id": "solve"}
]
+13 -2
View File
@@ -1,3 +1,14 @@
# Elastic–perfectly plastic thick cylinder: collapse pressure
# Elastic–perfectly plastic thick cylinder: plastic zone and collapse pressure
The Lamé cylinder of `lame-cylinder`, elastic–perfectly plastic, loaded to collapse.
The Lamé cylinder of `lame-cylinder-axisymmetric` (a = 10, b = 20, an axisymmetric slice held
axially on both faces, so plane strain), now elastic–perfectly plastic: E = 200000,
ν = 0.4999 (Hill's solution takes the elasticity incompressible), σ_y = 250, one row of 20
reduced quad8 elements through the wall, in two nonlinear static steps without nonlinear
geometry:
- **Pressure**: the bore pressure p = k (2 ln(c/a) + 1 − c²/b²) = 180.195 (k = σ_y/√3) that
takes the plastic zone to c = 15. The bore then moves u_a = k c²/(2 G a) = 0.024355: the plastic
zone moves incompressibly (u r constant) and the elastic ring outside yields at its inner edge.
- **Collapse**: the bore pushed out by 0.3 (24 times its first-yield displacement), past the
pressure at which the wall is plastic through; the radial force on the bore stays at the
collapse pressure 2k ln(b/a) = 200.094 times 2πa per unit height.
@@ -1,14 +1,43 @@
{
"title": "Elastic–perfectly plastic thick cylinder: collapse pressure",
"title": "Elastic–perfectly plastic thick cylinder: plastic zone and collapse pressure",
"kind": "theory",
"source": "Hill, The Mathematical Theory of Plasticity, Oxford 1950, ch. V §2: plane strain von Mises collapse pressure p = (2/√3)σ_y ln(b/a)",
"status": "pending",
"requires": "implicit (static) J2 plasticity: explicit J2 is in, a collapse load needs the static solver",
"source": "Hill, The Mathematical Theory of Plasticity, Oxford 1950, ch. V §2: a von Mises cylinder in plane strain, elastically incompressible, with the plastic zone a ≤ r ≤ c under the pressure p = k (2 ln(c/a) + 1 − c²/b²), k = σ_y/√3, and the bore displacement u_a = k c²/(2 G a); collapse when c = b at p = 2k ln(b/a)",
"quantities": [
{
"id": "p_collapse",
"quantity": "collapse pressure for a = 10, b = 20, σ_y = 250",
"value": 200.0944
"id": "u_bore",
"quantity": "bore displacement at the pressure that takes the plastic zone to c = 15 (a = 10, b = 20, σ_y = 250)",
"measure": {
"command": "solve.probe",
"params": {
"step": "Pressure",
"at": [
[
10.0,
0,
0
]
]
},
"pointer": "/nodes/0/displacement/0"
},
"value": 0.02435534068380524,
"tolerance": {
"relative": 0.002
},
"reason": "20 reduced quad8 elements through the wall resolve the logarithmic plastic field and ν = 0.4999 stands in for Hill's incompressible elasticity (0.009 % apart); u_a ∝ c², so 0.2 % on u_a is 0.1 % on the plastic radius"
},
{
"id": "collapse_force",
"quantity": "radial force on the bore (per radian times 2π, height 1) at a bore displacement of 0.3, 24 times the first yield's: the collapse pressure 2k ln 2 = 200.094 times 2πa",
"measure": {
"from": "collapse",
"pointer": "/reaction_total/0"
},
"value": 12572.299154203694,
"tolerance": {
"relative": 0.001
},
"reason": "once the wall is plastic through, perfect plasticity holds the force at the collapse value (σ_θ − σ_r = 2k everywhere, σ_z the mean of the others as plastic flow dominates); the band admits the quadrature of the logarithmic field"
}
]
}
}
@@ -0,0 +1,13 @@
{
"note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.",
"values": {
"collapse_force": {
"band": 0.000012572299377396248,
"value": 12572.299377396248
},
"u_bore": {
"band": 2.4357507851220506e-11,
"value": 0.024357507851220504
}
}
}
@@ -0,0 +1,15 @@
[
{"command": "mesh.block", "params": {"body": "Pipe", "kind": "quad8", "corners": [[10.0, 0, 0], [20.0, 0, 0], [20.0, 1, 0], [10.0, 1, 0]], "divisions": [20, 1], "bias": [1, 1], "edges": [{"corners": [1, 4], "name": "bore"}, {"corners": [1, 2], "name": "bottom"}, {"corners": [3, 4], "name": "top"}]}},
{"command": "model.set.create", "params": {"name": "all", "geometry": [{"body": "Pipe", "face": "area"}]}},
{"command": "model.set.create", "params": {"name": "bore", "geometry": [{"body": "Pipe", "edge": "bore"}]}},
{"command": "model.set.create", "params": {"name": "ends", "geometry": [{"body": "Pipe", "edge": "bottom"}, {"body": "Pipe", "edge": "top"}]}},
{"command": "model.material.create", "params": {"name": "Steel", "elastic": {"type": "isotropic", "young": 200000.0, "poisson": 0.4999}, "plastic": {"type": "bilinear", "yield_stress": 250.0}}},
{"command": "model.section.create", "params": {"name": "pipe", "set": "all", "material": "Steel", "kind": {"type": "axisymmetric"}, "formulation": {"integration": "reduced"}}},
{"command": "model.boundary_condition.create", "params": {"name": "ends", "type": "symmetry", "target": {"set": "ends"}, "plane": "y"}},
{"command": "model.load.create", "params": {"name": "p", "type": "pressure", "set": "bore", "magnitude": 180.19538034924005}},
{"command": "model.boundary_condition.create", "params": {"name": "expand", "type": "displacement", "target": {"set": "bore"}, "values": [0.3, null, null, null, null, null]}},
{"command": "model.step.create", "params": {"name": "Pressure", "procedure": {"type": "nonlinear_static", "nonlinear_geometry": false}, "boundary_conditions": ["ends"], "loads": ["p"]}},
{"command": "model.step.create", "params": {"name": "Collapse", "procedure": {"type": "nonlinear_static", "nonlinear_geometry": false}, "boundary_conditions": ["ends", "expand"], "loads": []}},
{"command": "solve.nonlinear", "params": {"step": "Pressure"}, "id": "pressure"},
{"command": "solve.nonlinear", "params": {"step": "Collapse"}, "id": "collapse"}
]