fem: J2 plasticity at the implicit material point, finite and small strain

The radial return of the explicit update is now J2::radial_return, called by
both solvers, with the consistent tangent J2::tangent (Simo & Hughes 1998,
Box 3.2). The new elastoplastic module gives the implicit material point:
Simo's multiplicative plasticity with the exponential return map in the
principal logarithmic strains (CMAME 99, 1992) and its spatial tangent through
the Daleckii-Krein divided differences of the logarithm, and the additive
radial return of small strain with kinematic hardening. MaterialModel::Plastic
carries it, decoupled (deviatoric return, Hencky volume) so hybrid elements
take it. The element loops of finite.rs gain small-strain kinematics for
inelastic materials and pass history through the mixed path. Every tangent is
checked against central differences, element by element, from a plastic
history.

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 3ecb7c6883
commit 72940fe78f
8 files changed
+908 -137

No files matched your search

+3 -2
View File
@@ -62,6 +62,7 @@
use fieldcraft_fem::constitutive::MaterialModel;
use fieldcraft_fem::dense::Matrix;
use fieldcraft_fem::element::{Extrapolation, extrapolation};
use fieldcraft_fem::finite::Kinematics;
use fieldcraft_fem::quadrature::Rule;
use fieldcraft_fem::{DofKind, DofSet, ElementType, PointStress, face, von_mises};
use fieldcraft_model::{Extent, Load, Model, NonlinearStatic, Procedure, Step};
@@ -250,7 +251,7 @@ impl Problem<'_> {
}
let material = self.materials.get(e).ok_or_else(|| Error::Internal("element material".into()))?;
let mut updated = committed.to_vec();
let r = kernel.finite_strain(material, &ue, committed, &mut updated, tangent).map_err(at)?;
let r = kernel.nonlinear(material, &ue, Kinematics::Finite, committed, &mut updated, tangent).map_err(at)?;
Ok((r.force, r.tangent, updated))
}
@@ -833,7 +834,7 @@ fn finish(
let kernel = el.kernel()?;
let points = if problem.finite {
let material = problem.materials.get(i).ok_or_else(|| Error::Internal("element material".into()))?;
kernel.finite_strain_stresses(material, &ue, committed.get(i).map(Vec::as_slice).unwrap_or_default())
kernel.nonlinear_stresses(material, &ue, Kinematics::Finite, committed.get(i).map(Vec::as_slice).unwrap_or_default())
} else {
let per_node: Vec<[f64; 3]> = el.nodes.iter().map(|n| displacement.get(*n).copied().unwrap_or_default()).collect();
kernel.stresses_with(&el.d, &per_node, None)