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)
+13 -9
View File
@@ -11,6 +11,7 @@ use fieldcraft_fem::beam::{Beam, BeamSection};
use fieldcraft_fem::constitutive::MaterialModel;
use fieldcraft_fem::dense::Matrix;
use fieldcraft_fem::element::{Formulation, Solid, ThermalStrain};
use fieldcraft_fem::finite::Kinematics;
use fieldcraft_fem::plane::{Plane, Theory};
use fieldcraft_fem::shell::{Shell, ShellSection};
use fieldcraft_fem::truss::{Truss, young_modulus};
@@ -113,27 +114,30 @@ impl Kernel<'_> {
}
}
/// Finite-strain internal forces and tangent ([`fieldcraft_fem::finite`]) for the element's
/// displacements `u` (its degrees of freedom in order: 3 per node, or 2 for 2D elements).
pub fn finite_strain(
/// Nonlinear internal forces and tangent ([`fieldcraft_fem::finite`]) for the element's
/// displacements `u` (its degrees of freedom in order: 3 per node, or 2 for 2D elements),
/// total Lagrangian or with small strain.
pub fn nonlinear(
&self,
m: &MaterialModel,
u: &[f64],
kin: Kinematics,
committed: &[f64],
updated: &mut [f64],
tangent: bool,
) -> fieldcraft_fem::Result<fieldcraft_fem::finite::Finite> {
match self {
Kernel::Solid(s) => s.finite_strain(m, u.as_chunks::<3>().0, committed, updated, tangent),
Kernel::Plane(p) => p.finite_strain(m, u.as_chunks::<2>().0, committed, updated, tangent),
Kernel::Solid(s) => s.nonlinear(m, u.as_chunks::<3>().0, kin, committed, updated, tangent),
Kernel::Plane(p) => p.nonlinear(m, u.as_chunks::<2>().0, kin, committed, updated, tangent),
}
}
/// Cauchy stress and logarithmic strain at the integration points, as [`Self::finite_strain`].
pub fn finite_strain_stresses(&self, m: &MaterialModel, u: &[f64], committed: &[f64]) -> fieldcraft_fem::Result<Vec<PointStress>> {
/// Cauchy stress and strain (logarithmic at finite strain) at the integration points, as
/// [`Self::nonlinear`].
pub fn nonlinear_stresses(&self, m: &MaterialModel, u: &[f64], kin: Kinematics, committed: &[f64]) -> fieldcraft_fem::Result<Vec<PointStress>> {
match self {
Kernel::Solid(s) => s.finite_strain_stresses(m, u.as_chunks::<3>().0, committed),
Kernel::Plane(p) => p.finite_strain_stresses(m, u.as_chunks::<2>().0, committed),
Kernel::Solid(s) => s.nonlinear_stresses(m, u.as_chunks::<3>().0, kin, committed),
Kernel::Plane(p) => p.nonlinear_stresses(m, u.as_chunks::<2>().0, kin, committed),
}
}