Skip to content

materials: J2 with Armstrong-Frederick kinematic hardening on vector_newton (#162) - #166

Open
petlenz wants to merge 2 commits into
mainfrom
feature/af-kinematic-hardening
Open

petlenz wants to merge 2 commits into
mainfrom
feature/af-kinematic-hardening

Conversation

@petlenz

@petlenz petlenz commented Oct 21, 2026

Copy link
Copy Markdown
Member

Closes #162. Kinematic half of #20.

What

materials/j2_kinematic_plasticity.h — J2 with Armstrong-Frederick kinematic hardening on top of the existing isotropic hardening_source, returned as one coupled system over {Δλ, Δβ} by vector_newton. The first shipped material on the coupled solver.

With s_tr = 2G dev(ε − ε_p,n), η = s_tr − dev(β), q = σ_eq(η), N = (3/2) η/q:

R_λ = q − 3G Δλ − σ_0 − H(κ_n + Δλ)          yield on the returned state
R_β = (1 + γ Δλ) β − β_n − (2/3) C Δλ N       Armstrong-Frederick, β = β_n + Δβ

Jacobian and the partial strain derivatives are derived (see the header). The consistent tangent comes through the solver's strain_derivative opt-in (#19) and the chain rule dσ/dε = C_e − 2G [N ⊗ dΔλ/dε + Δλ dN/dε].

γ = 0 is Prager: radial in the shifted space, closed form Δλ = (q_tr − σ_0 − H_n)/(3G + C + H'), one Newton step from the trial seed. γ > 0 rotates the flow direction during the step and the solve iterates. The tensor unknown is the increment Δβ, so the solver's zero seed is the trial state; with β itself as the unknown even Prager needed several steps once β_n ≠ 0.

vector_newton — two small additions the material needs:

  • graph_driven (bool, default true). false leaves the unknowns without an update callback, so the solver runs only when a material calls solve() through a material_ref. This is the design plasticity.md §9 settled on for local_newton, applied here: a graph-driven solver would also run a cold-start solve at every point, elastic ones included, where the yield residual has a negative root. The material checks the flag and throws.
  • reset(): zero the unknowns, mark not converged. The material's residual callback is still a property the engine fires once per update, at whatever the solver's slots hold, and it writes κ_n + Δλ into the history the hardening source reads. On an elastic step the previous point's converged Δλ would otherwise be added to this point's κ. Found by the Bauschinger test below; the elastic test now pins it.

Registered as j2_kinematic_plasticity; the conformance tests pick it up. Documented in plasticity.md (§2 interface, §9 the solver decision) and the README.

Tests (tests/test_j2_kinematic_plasticity.cpp, 8)

test what it pins
PragerStepMatchesTheClosedForm Δλ, β, κ and the shifted yield condition against the closed form, on a second step whose committed β_n is not parallel to the trial deviator
OnlyRecoveryNeedsMoreThanOneNewtonStep Prager converges with max_iter = 1 on that step; Armstrong-Frederick throws
RecoveryReturnsOnTheShiftedSurfaceAndRotatesTheFlow γ > 0: shifted surface, evolution law met by the final N, flow direction rotated away from η_tr
AnElasticStepDoesNotRunTheSolver elastic stress and tangent, no solve, and after a committed plastic step an elastic one leaves κ, β and the unknowns clean
RejectsAGraphDrivenSolver the graph_driven = true configuration throws
JacobianMatchesDirectionalFiniteDifferences all four blocks as directional derivatives along symmetric directions, off the root
TangentMatchesDirectionalFiniteDifferencesOfTheStress consistent tangent vs central differences of the stress, γ = 0 and γ = 40, non-proportional committed history
ReverseYieldIsShiftedByTheBackStress Bauschinger: the reverse elastic limit sits at β_12 − Y/√3, probed from both sides

Directional finite differences rather than component loops: the Mandel space the solver works in sees IIsym as the identity, so perturbing one off-diagonal component alone compares against the wrong object.

Mutations, each caught:

mutation caught by
dR_β/dε sign tangent
J_βλ drops γ β Jacobian, tangent
J_ββ coupling 1.5 → 2 Jacobian, tangent
tangent drops Δλ dN/dε tangent
tangent ignores dβ/dε tangent
residual evaluated at β_n instead of β closed form, one-step, recovery, Jacobian, tangent
no reset() on the elastic branch elastic, Bauschinger

Not in this PR

A return map that reaches the solver through a material_ref and solves only
when the trial state yields (docs/plasticity.md, section 9) needs two things
the solver did not offer:

- graph_driven (default true). false leaves the unknowns without an update
  callback, so ctx.update() no longer runs a cold-start solve at every point,
  elastic ones included, where the yield residual has a negative root.
- reset(): zero the unknowns and mark not converged. The function material's
  residual callback is still a property the engine fires once per update, at
  whatever the slots hold; on an elastic step zero is the local solution and a
  stale dl from the previous point must not be read as this point's.

Existing behaviour is unchanged: all 29 vector_newton tests pass as before.
…newton (#162)

The first shipped material on the coupled solver. Unknowns {dlambda, dbeta}
with dbeta = beta - beta_n, so the zero seed is the trial state:

  R_l = q - 3G dl - sigma_0 - H(kappa_n + dl)
  R_b = (1 + gamma dl) beta - beta_n - (2/3) C dl N

gamma = 0 is Prager: radial in the shifted space, closed form, one Newton step.
gamma > 0 rotates the flow direction during the step and the solve iterates.
The consistent tangent comes through the solver's strain_derivative opt-in.

The solver is held by material_ref and run only when the trial state yields;
it must be created with graph_driven = false (checked) and the elastic branch
calls reset(), because the engine fires the residual callback once per update
and it writes kappa_n + dl into the history the hardening source reads.

Tests: Prager closed form on a step with a committed, non-parallel beta_n;
Prager converges with max_iter = 1 while Armstrong-Frederick throws; shifted
surface and evolution law for gamma > 0; elastic steps leave history and
unknowns clean; graph-driven solver rejected; Jacobian blocks and consistent
tangent against directional central differences; Bauschinger reverse limit.
Seven mutations of the material each fail at least one of them.

Registered as j2_kinematic_plasticity; documented in plasticity.md and README.
The back stress stays a plain tensor2 history until #91 lands.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

J2 with Armstrong-Frederick kinematic hardening on vector_newton

1 participant