solver: J2 plasticity in nonlinear static steps, finite and small strain

Elements of a plastic material take the J2 material point: total Lagrangian
with nonlinear geometry, the small-strain loops without (elastic elements keep
their linear stiffness, so elastic results do not move). The plastic history is
committed per converged increment. Increments follow the step's load path,
ending at its points, the extrapolated predictor scaled by the change of the
load factor. Steps with path-dependent materials start an increment without a
predictor from the tangent predictor (the prescribed motion spread through the
converged tangent), and a first tangent that is not positive definite is a
cutback when the undeformed model's is. Refused by name: the Johnson-Cook rate
term (a static step has no time) and kinematic hardening at finite strain;
damage, erosion and adiabatic heating are left out with a warning. Results
carry the equivalent plastic strain at the integration points (solve.probe,
the step summary). LS-DYNA writes a load path as the step's curve; Abaqus
refuses it and a plastic deck of several general steps (Abaqus carries the
plastic state between them).

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 569b69c061
commit 17b2c837f2
11 files changed
+588 -64

No files matched your search

+25 -1
View File
@@ -37,7 +37,10 @@
//! and tolerances are left to Abaqus's controls, whose residual norms are defined differently.
//! Abaqus starts each general step from the end of the previous one, while every model step
//! starts from the undeformed state; for elastic materials the end state depends on the final
//! loads alone, so the two agree.
//! loads alone, so the two agree. A plastic material's state depends on the path, so a deck
//! with nonlinear steps, a plastic material and more than one general step is refused, as is
//! a nonlinear step's load path (an amplitude on every load and boundary condition, not
//! written yet).
//!
//! # Loads
//!
@@ -313,6 +316,27 @@ fn check_expressible(model: &Model) -> Result<()> {
));
}
}
for step in model.steps() {
if let Procedure::NonlinearStatic(n) = &step.procedure
&& n.load_path.is_some()
{
bad.insert(format!(
"step `{}`: a load path (it would be an amplitude on every load and boundary condition of the step; not written yet)",
step.name
));
}
}
let general = model.steps().iter().filter(|s| matches!(s.procedure, Procedure::Static {} | Procedure::NonlinearStatic(_))).count();
let plastic_in_use = model.sections().values().find(|s| model.material(&s.material).is_some_and(|m| m.plastic.is_some()));
if has_nonlinear_step(model)
&& general > 1
&& let Some(section) = plastic_in_use
{
bad.insert(format!(
"plastic material `{}` in a deck of {general} general steps (Abaqus carries the plastic state from one general step to the next, while every model step starts from the undeformed state; write each step to its own deck)",
section.material
));
}
for (name, m) in model.materials() {
if m.damage.is_some() || m.erosion.is_some() {
bad.insert(format!(
+9 -3
View File
@@ -25,7 +25,8 @@
//! - per step: `*CONTROL_IMPLICIT_GENERAL`, `*CONTROL_IMPLICIT_SOLUTION` (linear) or
//! `*CONTROL_IMPLICIT_EIGENVALUE`, `*CONTROL_TERMINATION` (a static step is one implicit step
//! from time 0 to 1 with every load ramped by one curve, `*DEFINE_CURVE`); a nonlinear static
//! step runs over the same time with the same ramp, its first increment as `dt0`, the nonlinear
//! step runs over the same time with the same ramp (or its load path as the curve), its first
//! increment as `dt0`, the nonlinear
//! solver with a stiffness reformation every iteration (`ilimit` 1, full Newton) and its
//! iteration cap as `maxref`, and `*CONTROL_IMPLICIT_AUTO` with its minimum and maximum
//! increments (its increment cap and tolerances have no LS-DYNA counterpart and are left to
@@ -872,8 +873,13 @@ fn write_step(d: &mut Deck, model: &Model, r: &Resolved, plan: &Plan, step: &Ste
d.header(&["lcid"]);
d.card([Field::from(n)]);
d.header(&["a1", "o1"]);
d.card([Field::from(0.0), Field::from(0.0)]);
d.card([Field::from(1.0), Field::from(1.0)]);
let path = match &step.procedure {
Procedure::NonlinearStatic(s) => s.load_path.clone(),
_ => None,
};
for [t, f] in path.unwrap_or_else(|| vec![[0.0, 0.0], [1.0, 1.0]]) {
d.card([Field::from(t), Field::from(f)]);
}
}
for b in &step.boundary_conditions {