forked from 1b-agent/fieldcraft-upstream
model: nonlinear static steps and neo-Hookean hyperelasticity, mapped by the deck writers
A nonlinear_static procedure (nonlinear geometry, initial/min/max increments, increment and iteration caps, force/displacement/energy tolerances, line search), validated with caps against hostile values. Materials gain an explicit hyperelastic option (decoupled compressible neo-Hookean on the isotropic constants). Abaqus writes *STEP, NLGEOM, INC / *STATIC with the increments and *HYPERELASTIC, NEO HOOKE; LS-DYNA writes the ramped implicit run with full Newton and *CONTROL_IMPLICIT_AUTO; Nastran refuses SOL 400 by name, as LS-DYNA does hyperelastic materials in nonlinear steps. Explicit dynamics refuses hyperelastic materials by name; solve.run names the solver still to come. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
1 parent
d2fe8a8303
commit
bc9fe2191a
13 files changed
+408
-19
No files matched your search
@@ -753,3 +753,45 @@ fn plasticity_is_written_and_failure_reported() {
|
||||
let Err(Error::Unsupported { what, .. }) = abaqus::write(&failing) else { panic!("damage is not written") };
|
||||
assert!(what.contains("material `Steel`: damage and erosion"), "{what}");
|
||||
}
|
||||
|
||||
/// A nonlinear static step is a general step with NLGEOM, its increments and its line search; a
|
||||
/// neo-Hookean material is then `*HYPERELASTIC` and a linear static step beside it is refused.
|
||||
#[test]
|
||||
fn nonlinear_steps_and_hyperelastic_materials_are_written() {
|
||||
use fieldcraft_model::{Hyperelastic, NonlinearStatic};
|
||||
let mut m = cantilever();
|
||||
let controls = NonlinearStatic {
|
||||
initial_increment: 0.05,
|
||||
min_increment: 1e-4,
|
||||
max_increment: 0.5,
|
||||
max_increments: 40,
|
||||
line_search: true,
|
||||
..NonlinearStatic::default()
|
||||
};
|
||||
m.replace_step(step("Bend", Procedure::NonlinearStatic(controls.clone()), &["clamp"], &["end"])).unwrap();
|
||||
let deck = abaqus::write(&m).unwrap();
|
||||
let bend = block(&deck, "Bend");
|
||||
assert_eq!(bend.first(), Some(&"*STEP, NAME=Bend, NLGEOM=YES, INC=40"), "{bend:?}");
|
||||
let at = bend.iter().position(|l| *l == "*STATIC").unwrap();
|
||||
assert_eq!(bend.get(at + 1..at + 4), Some(&["0.05, 1.0, 0.0001, 0.5", "*CONTROLS, PARAMETERS=LINE SEARCH", "5,"][..]), "{bend:?}");
|
||||
assert!(deck.contains("*ELASTIC, TYPE=ISOTROPIC\n210000.0, 0.3\n"), "St Venant–Kirchhoff is the elastic material");
|
||||
|
||||
let small = NonlinearStatic { nonlinear_geometry: false, line_search: false, ..controls };
|
||||
m.replace_step(step("Bend", Procedure::NonlinearStatic(small), &["clamp"], &["end"])).unwrap();
|
||||
m.replace_material("Steel", Material { hyperelastic: Some(Hyperelastic::NeoHookean {}), ..steel() }).unwrap();
|
||||
let deck = abaqus::write(&m).unwrap();
|
||||
let bend = block(&deck, "Bend");
|
||||
assert_eq!(bend.first(), Some(&"*STEP, NAME=Bend, NLGEOM=NO, INC=40"), "{bend:?}");
|
||||
assert!(!bend.iter().any(|l| l.starts_with("*CONTROLS")), "{bend:?}");
|
||||
// μ = E/(2(1 + ν)) = 80769.23…, κ = E/(3(1 − 2ν)) = 175000: C10 = μ/2, D1 = 2/κ.
|
||||
let (c10, d1) = (210_000.0 / 2.6 / 2.0, 2.0 / 175_000.0);
|
||||
let line = deck.split("*HYPERELASTIC, NEO HOOKE\n").nth(1).and_then(|s| s.lines().next()).unwrap();
|
||||
let values: Vec<f64> = line.split(", ").map(|v| v.parse().unwrap()).collect();
|
||||
assert!((values[0] - c10).abs() < 1e-9 * c10 && (values[1] - d1).abs() < 1e-9 * d1, "{line}");
|
||||
assert!(!deck.contains("*ELASTIC"), "{deck}");
|
||||
|
||||
m.add_step(step("Linear", Procedure::Static {}, &["clamp"], &["end"]), None).unwrap();
|
||||
assert!(unsupported(&m).contains("step `Linear`: a linear static step beside nonlinear steps of hyperelastic material `Steel`"));
|
||||
m.remove_step("Bend").unwrap();
|
||||
assert!(abaqus::write(&m).unwrap().contains("*ELASTIC, TYPE=ISOTROPIC\n"), "with linear steps only, the elastic constants are the whole of it");
|
||||
}
|
||||
@@ -737,3 +737,37 @@ fn plasticity_is_written_as_plastic_materials() {
|
||||
let Err(Error::Unsupported { what, .. }) = lsdyna::write(&with(jc, None)) else { panic!() };
|
||||
assert!(what.contains("Johnson–Cook"), "{what}");
|
||||
}
|
||||
|
||||
/// A nonlinear static step: the ramped implicit run with full Newton and automatic increments;
|
||||
/// what LS-DYNA cannot take is named.
|
||||
#[test]
|
||||
fn nonlinear_static_steps() {
|
||||
let mut m = cantilever();
|
||||
let controls =
|
||||
NonlinearStatic { initial_increment: 0.05, min_increment: 1e-4, max_increment: 0.5, max_iterations: 12, ..NonlinearStatic::default() };
|
||||
m.replace_step(step("Bend", Procedure::NonlinearStatic(controls.clone()), &["clamp"], &["tip_load"])).unwrap();
|
||||
let deck = lsdyna::write(&m).unwrap().text;
|
||||
let after = |keyword: &str| -> Vec<String> {
|
||||
let mut lines = deck.lines().skip_while(|l| *l != keyword).skip(1).filter(|l| !l.starts_with('$'));
|
||||
lines.next().unwrap().split_whitespace().map(str::to_string).collect()
|
||||
};
|
||||
let num = |s: &String| s.parse::<f64>().unwrap();
|
||||
let general = after("*CONTROL_IMPLICIT_GENERAL");
|
||||
assert_eq!((num(&general[0]), num(&general[1])), (1.0, 0.05), "{general:?}");
|
||||
let solution = after("*CONTROL_IMPLICIT_SOLUTION");
|
||||
assert_eq!(solution.iter().map(num).collect::<Vec<_>>(), [2.0, 1.0, 12.0], "{solution:?}");
|
||||
let auto = after("*CONTROL_IMPLICIT_AUTO");
|
||||
assert_eq!(auto.iter().map(num).collect::<Vec<_>>(), [1.0, 1e-4, 0.5], "blank fields are skipped: {auto:?}");
|
||||
assert!(deck.contains("*DEFINE_CURVE_TITLE\nBend ramp\n"), "the loads follow the ramp");
|
||||
assert!(deck.contains("*LOAD_NODE"), "{deck}");
|
||||
|
||||
m.replace_step(step("Bend", Procedure::NonlinearStatic(NonlinearStatic { nonlinear_geometry: false, ..controls }), &["clamp"], &["tip_load"]))
|
||||
.unwrap();
|
||||
unsupported(&m, "a nonlinear step without nonlinear geometry");
|
||||
m.replace_step(step("Bend", Procedure::NonlinearStatic(NonlinearStatic::default()), &["clamp"], &["tip_load"])).unwrap();
|
||||
m.replace_material("Steel", Material { hyperelastic: Some(Hyperelastic::NeoHookean {}), ..steel() }).unwrap();
|
||||
unsupported(&m, "hyperelastic material `Steel` in a nonlinear step");
|
||||
// In a linear step the elastic constants are the whole of a hyperelastic material.
|
||||
m.replace_step(step("Bend", Procedure::Static {}, &["clamp"], &["tip_load"])).unwrap();
|
||||
assert!(lsdyna::write(&m).unwrap().text.contains("*MAT_ELASTIC_TITLE"));
|
||||
}
|
||||
@@ -782,3 +782,13 @@ fn plasticity_is_written_as_mats1() {
|
||||
let Err(Error::Unsupported { what, .. }) = nastran::write(&m) else { panic!() };
|
||||
assert!(what.contains("Johnson–Cook plasticity") && what.contains("damage and erosion"), "{what}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonlinear_static_steps_are_refused_by_name() {
|
||||
let mut m = cantilever();
|
||||
let name = m.steps()[0].name.clone();
|
||||
let mut s = m.steps()[0].clone();
|
||||
s.set_procedure(Procedure::NonlinearStatic(fieldcraft_model::NonlinearStatic::default()));
|
||||
m.replace_step(s).unwrap();
|
||||
assert!(unsupported(&m).contains(&format!("step `{name}`: nonlinear static steps (Nastran's SOL 400")), "{}", unsupported(&m));
|
||||
}
|
||||
Reference in new issue
Block a user