forked from 1b-agent/fieldcraft-upstream
LS-DYNA keyword writer (implicit static and eigenvalue, M3) (#25)
* decks: LS-DYNA keyword writer (long format, implicit static and eigenvalue) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * decks: LS-DYNA golden files, Jacobian-sign node-order tests, re-reader and refusals Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: LS-DYNA writer status and deck notes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * decks: simplify the LS-DYNA writer (plan decides once, cards written in place, shared test tables) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * decks: LS-DYNA joins FORMATS (deck.write lsdyna), refusals reported together, shared concentrated loads Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * decks: LS-DYNA on main's shared deck API (steps parameter, model helpers) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm --------- Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
13 files changed
+2353
-3
No files matched your search
@@ -0,0 +1,676 @@
|
||||
//! LS-DYNA deck writer: golden files of our own small models, node-order mappings checked by the
|
||||
//! sign of the element Jacobian, a re-read of our own output, and what the format cannot take.
|
||||
//!
|
||||
//! Golden files live in `tests/lsdyna/`; `MODELCRAFT_BLESS=1 cargo test -p modelcraft-decks`
|
||||
//! rewrites them after an intended change (review the diff).
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use modelcraft_decks::{Error, lsdyna};
|
||||
use modelcraft_model::*;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Our own models.
|
||||
|
||||
fn steel() -> Material {
|
||||
Material { elastic: Some(Elastic::Isotropic { young: 210000.0, poisson: 0.3 }), density: Some(7.85e-9) }
|
||||
}
|
||||
|
||||
fn step(name: &str, procedure: Procedure, bcs: &[&str], loads: &[&str]) -> Step {
|
||||
let mut s = Step::new(name, procedure);
|
||||
s.boundary_conditions = bcs.iter().map(|b| b.to_string()).collect();
|
||||
s.loads = loads.iter().map(|l| l.to_string()).collect();
|
||||
s
|
||||
}
|
||||
|
||||
/// A 4 × 1 × 1 cantilever of four hex8 elements along x, fixed at x = 0. Nodes are numbered
|
||||
/// x-fastest: label = 1 + i + 5·(j + 2·k).
|
||||
fn cantilever_mesh(m: &mut Model) {
|
||||
m.set_units(UnitSystem::MmTonneS);
|
||||
let label = |i: u32, j: u32, k: u32| 1 + i + 5 * (j + 2 * k);
|
||||
for k in 0..2 {
|
||||
for j in 0..2 {
|
||||
for i in 0..5 {
|
||||
m.set_node(label(i, j, k), [f64::from(i), f64::from(j), f64::from(k)]).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0..4 {
|
||||
let nodes = vec![
|
||||
label(i, 0, 0),
|
||||
label(i + 1, 0, 0),
|
||||
label(i + 1, 1, 0),
|
||||
label(i, 1, 0),
|
||||
label(i, 0, 1),
|
||||
label(i + 1, 0, 1),
|
||||
label(i + 1, 1, 1),
|
||||
label(i, 1, 1),
|
||||
];
|
||||
m.set_element(i + 1, ElementKind::Hex8, nodes).unwrap();
|
||||
}
|
||||
m.add_set("all", Set { elements: vec![1, 2, 3, 4], ..Set::default() }).unwrap();
|
||||
m.add_set("root", Set { nodes: vec![label(0, 0, 0), label(0, 1, 0), label(0, 0, 1), label(0, 1, 1)], ..Set::default() }).unwrap();
|
||||
// Face 4 of a hex8 is the side along edge 2-3: x = 4 for the last element.
|
||||
m.add_set("tip", Set { faces: vec![ElementFace { element: 4, face: 4 }], ..Set::default() }).unwrap();
|
||||
m.add_boundary_condition("clamp", BoundaryCondition::Fixed { target: Target::Set("root".into()) }).unwrap();
|
||||
}
|
||||
|
||||
fn cantilever() -> Model {
|
||||
let mut m = Model::default();
|
||||
cantilever_mesh(&mut m);
|
||||
m.add_material("Steel", steel()).unwrap();
|
||||
m.add_section("beam", Section { set: "all".into(), material: "Steel".into(), kind: SectionKind::Solid {}, formulation: Formulation::default() })
|
||||
.unwrap();
|
||||
m.add_load("tip_load", Load::Force { target: Target::Set("tip".into()), vector: [0.0, 0.0, -100.0], distribution: Distribution::Total }).unwrap();
|
||||
m.add_step(step("Bend", Procedure::Static {}, &["clamp"], &["tip_load"]), None).unwrap();
|
||||
m
|
||||
}
|
||||
|
||||
/// The cantilever in an orthotropic material with incompatible modes, loaded through a coupled
|
||||
/// reference point, then its natural frequencies: two steps, so two cases.
|
||||
fn cantilever_modes() -> Model {
|
||||
let mut m = Model::default();
|
||||
cantilever_mesh(&mut m);
|
||||
let ortho = Elastic::EngineeringConstants { young: [140000.0, 10000.0, 10000.0], poisson: [0.3, 0.3, 0.45], shear: [5000.0, 5000.0, 3500.0] };
|
||||
m.add_material("Ply", Material { elastic: Some(ortho), density: Some(1.6e-9) }).unwrap();
|
||||
let f = Formulation { incompatible_modes: true, ..Formulation::default() };
|
||||
m.add_section("beam", Section { set: "all".into(), material: "Ply".into(), kind: SectionKind::Solid {}, formulation: f }).unwrap();
|
||||
m.add_reference_point("RP", ReferencePoint { xyz: [5.0, 0.5, 0.5], label: 0 }).unwrap();
|
||||
m.add_coupling("pad", Coupling { reference_point: "RP".into(), set: "tip".into(), kind: CouplingKind::Kinematic, dofs: Dofs::all() }).unwrap();
|
||||
m.add_load("twist", Load::Moment { target: Target::ReferencePoint("RP".into()), vector: [250.0, 0.0, 0.0], distribution: Distribution::Total })
|
||||
.unwrap();
|
||||
m.add_load("push", Load::Force { target: Target::ReferencePoint("RP".into()), vector: [0.0, -10.0, 0.0], distribution: Distribution::Total })
|
||||
.unwrap();
|
||||
m.add_step(step("Bend", Procedure::Static {}, &["clamp"], &["push", "twist"]), None).unwrap();
|
||||
m.add_step(step("Modes", Procedure::Frequency { modes: 6, min_hz: Some(1.0), max_hz: Some(5000.0) }, &["clamp"], &[]), None).unwrap();
|
||||
m
|
||||
}
|
||||
|
||||
/// A 2 × 1 plate at z = 0: two quad4 and two tri3 shells, held on the edge x = 0, pressed and
|
||||
/// under gravity.
|
||||
fn plate() -> Model {
|
||||
let mut m = Model::default();
|
||||
m.set_units(UnitSystem::MmTonneS);
|
||||
for (l, x, y) in
|
||||
[(1, 0.0, 0.0), (2, 10.0, 0.0), (3, 20.0, 0.0), (4, 0.0, 10.0), (5, 10.0, 10.0), (6, 20.0, 10.0), (7, 30.0, 0.0), (8, 30.0, 10.0)]
|
||||
{
|
||||
m.set_node(l, [x, y, 0.0]).unwrap();
|
||||
}
|
||||
m.set_element(1, ElementKind::Quad4, vec![1, 2, 5, 4]).unwrap();
|
||||
m.set_element(2, ElementKind::Quad4, vec![2, 3, 6, 5]).unwrap();
|
||||
m.set_element(3, ElementKind::Tri3, vec![3, 7, 8]).unwrap();
|
||||
m.set_element(4, ElementKind::Tri3, vec![3, 8, 6]).unwrap();
|
||||
m.add_set("plate", Set { elements: vec![1, 2, 3, 4], ..Set::default() }).unwrap();
|
||||
m.add_set("edge", Set { nodes: vec![1, 4], ..Set::default() }).unwrap();
|
||||
let top: Vec<ElementFace> = (1..=4).map(|element| ElementFace { element, face: 1 }).collect();
|
||||
m.add_set("top", Set { faces: top, ..Set::default() }).unwrap();
|
||||
m.add_material("Steel", steel()).unwrap();
|
||||
m.add_section(
|
||||
"skin",
|
||||
Section {
|
||||
set: "plate".into(),
|
||||
material: "Steel".into(),
|
||||
kind: SectionKind::Shell { thickness: 0.5, offset: 0.25, points: 5 },
|
||||
formulation: Formulation::default(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
m.add_boundary_condition("hold", BoundaryCondition::Fixed { target: Target::Set("edge".into()) }).unwrap();
|
||||
m.add_load("press", Load::Pressure { set: "top".into(), magnitude: 0.01 }).unwrap();
|
||||
m.add_load("weight", Load::Gravity { acceleration: [0.0, 0.0, -9810.0], set: None }).unwrap();
|
||||
m.add_step(step("Sag", Procedure::Static {}, &["hold"], &["press", "weight"]), None).unwrap();
|
||||
m
|
||||
}
|
||||
|
||||
/// One element of each solid kind besides hex8, side by side (each its own section), with a
|
||||
/// distributing coupling, a prescribed displacement and a traction.
|
||||
fn solids() -> Model {
|
||||
let mut m = Model::default();
|
||||
m.set_units(UnitSystem::MKgS);
|
||||
let mut next = 0u32;
|
||||
let mut node = |m: &mut Model, xyz: [f64; 3]| {
|
||||
next += 1;
|
||||
m.set_node(next, xyz).unwrap();
|
||||
next
|
||||
};
|
||||
// tet4
|
||||
let t: Vec<u32> = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]].iter().map(|p| node(&mut m, *p)).collect();
|
||||
m.set_element(1, ElementKind::Tet4, t.clone()).unwrap();
|
||||
// tet10: corners then mid-sides on 1-2, 2-3, 3-1, 1-4, 2-4, 3-4.
|
||||
let c = [[2.0, 0.0, 0.0], [3.0, 0.0, 0.0], [2.0, 1.0, 0.0], [2.0, 0.0, 1.0]];
|
||||
let mid = |a: usize, b: usize| [(c[a][0] + c[b][0]) / 2.0, (c[a][1] + c[b][1]) / 2.0, (c[a][2] + c[b][2]) / 2.0];
|
||||
let mut t10: Vec<u32> = c.iter().map(|p| node(&mut m, *p)).collect();
|
||||
for (a, b) in [(0, 1), (1, 2), (2, 0), (0, 3), (1, 3), (2, 3)] {
|
||||
t10.push(node(&mut m, mid(a, b)));
|
||||
}
|
||||
m.set_element(2, ElementKind::Tet10, t10).unwrap();
|
||||
// wedge6
|
||||
let w: Vec<u32> = [[4.0, 0.0, 0.0], [5.0, 0.0, 0.0], [4.0, 1.0, 0.0], [4.0, 0.0, 1.0], [5.0, 0.0, 1.0], [4.0, 1.0, 1.0]]
|
||||
.iter()
|
||||
.map(|p| node(&mut m, *p))
|
||||
.collect();
|
||||
m.set_element(3, ElementKind::Wedge6, w).unwrap();
|
||||
// hex20
|
||||
let h = hex20_nodes([6.0, 0.0, 0.0]);
|
||||
let h: Vec<u32> = h.iter().map(|p| node(&mut m, *p)).collect();
|
||||
m.set_element(4, ElementKind::Hex20, h).unwrap();
|
||||
|
||||
m.add_material("Aluminium", Material { elastic: Some(Elastic::Isotropic { young: 70e9, poisson: 0.33 }), density: Some(2700.0) }).unwrap();
|
||||
for (e, name) in [(1, "tet4"), (2, "tet10"), (3, "wedge"), (4, "hex20")] {
|
||||
m.add_set(name, Set { elements: vec![e], ..Set::default() }).unwrap();
|
||||
let f = Formulation { hybrid: e == 1, ..Formulation::default() };
|
||||
m.add_section(name, Section { set: name.into(), material: "Aluminium".into(), kind: SectionKind::Solid {}, formulation: f }).unwrap();
|
||||
}
|
||||
m.add_set("base", Set { nodes: t.clone(), ..Set::default() }).unwrap();
|
||||
// Face 2 of the hex20 is its top (nodes 5-8 and their mid-sides).
|
||||
m.add_set("lid", Set { faces: vec![ElementFace { element: 4, face: 2 }], ..Set::default() }).unwrap();
|
||||
m.add_reference_point("RP", ReferencePoint { xyz: [6.5, 0.5, 2.0], label: 0 }).unwrap();
|
||||
m.add_coupling("spider", Coupling { reference_point: "RP".into(), set: "lid".into(), kind: CouplingKind::Distributing, dofs: Dofs::all() })
|
||||
.unwrap();
|
||||
m.add_boundary_condition("hold", BoundaryCondition::Fixed { target: Target::Set("base".into()) }).unwrap();
|
||||
m.add_boundary_condition(
|
||||
"lift",
|
||||
BoundaryCondition::Displacement { target: Target::Set("tet10".into()), values: [None, None, Some(0.001), None, None, None] },
|
||||
)
|
||||
.unwrap();
|
||||
m.add_load("drag", Load::Traction { set: "lid".into(), vector: [1000.0, 0.0, 0.0] }).unwrap();
|
||||
m.add_load("pull", Load::Force { target: Target::ReferencePoint("RP".into()), vector: [0.0, 0.0, 50.0], distribution: Distribution::Total })
|
||||
.unwrap();
|
||||
m.add_step(step("Load", Procedure::Static {}, &["hold", "lift"], &["drag", "pull"]), None).unwrap();
|
||||
m
|
||||
}
|
||||
|
||||
/// A unit 20-node hexahedron at `o` in model order.
|
||||
fn hex20_nodes(o: [f64; 3]) -> Vec<[f64; 3]> {
|
||||
let corners = CUBE.map(|c| c.map(|v| (v + 1.0) / 2.0));
|
||||
let mut out: Vec<[f64; 3]> = corners.to_vec();
|
||||
out.extend(HEX_EDGES.map(|(a, b)| [0, 1, 2].map(|k| (corners[a][k] + corners[b][k]) / 2.0)));
|
||||
out.iter().map(|p| [p[0] + o[0], p[1] + o[1], p[2] + o[2]]).collect()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Golden files.
|
||||
|
||||
fn golden(name: &str, model: &Model) -> lsdyna::Written {
|
||||
let written = lsdyna::write(model).unwrap();
|
||||
let again = lsdyna::write(model).unwrap();
|
||||
assert_eq!(written, again, "deterministic");
|
||||
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/lsdyna").join(format!("{name}.k"));
|
||||
if std::env::var_os("MODELCRAFT_BLESS").is_some() {
|
||||
std::fs::write(&path, &written.text).unwrap();
|
||||
}
|
||||
let expected = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e} (run with MODELCRAFT_BLESS=1)", path.display()));
|
||||
assert!(written.text == expected, "{name}.k differs from the golden file (MODELCRAFT_BLESS=1 rewrites it)");
|
||||
assert!(written.text.is_ascii());
|
||||
for line in written.text.lines() {
|
||||
assert!(line.len() <= 200 && line == line.trim_end(), "{line:?}");
|
||||
assert!(!line.contains('\t'));
|
||||
}
|
||||
assert_eq!(written.rounded_values, 0);
|
||||
written
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn golden_hex8_cantilever() {
|
||||
let w = golden("cantilever", &cantilever());
|
||||
assert_eq!(w.cases, 0);
|
||||
let deck = read(&w.text);
|
||||
assert_eq!(deck.nodes.len(), 20);
|
||||
// 100 N on the tip face: 25 N on each of its four nodes, written once on the node set.
|
||||
assert!(w.text.contains("*LOAD_NODE_SET"));
|
||||
assert_eq!(deck.node_sets["tip"], vec![5, 10, 15, 20]);
|
||||
assert_eq!(deck.node_sets["root"], vec![1, 6, 11, 16]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn golden_cantilever_modes_in_cases() {
|
||||
let w = golden("cantilever_modes", &cantilever_modes());
|
||||
assert_eq!(w.cases, 2);
|
||||
for k in ["*CASE_BEGIN_1", "*CASE_END_1", "*CASE_BEGIN_2", "*CASE_END_2", "*CONTROL_IMPLICIT_EIGENVALUE", "*MAT_ORTHOTROPIC_ELASTIC_TITLE"] {
|
||||
assert!(w.text.contains(k), "{k}");
|
||||
}
|
||||
// The rigid body's set leads with the reference point.
|
||||
let deck = read(&w.text);
|
||||
let rp = cantilever_modes().reference_point("RP").unwrap().label;
|
||||
assert_eq!(deck.node_sets["pad (rigid body)"].first(), Some(&rp));
|
||||
assert_eq!(deck.nodes[&rp], [5.0, 0.5, 0.5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn golden_plate_with_pressure_and_gravity() {
|
||||
let w = golden("plate", &plate());
|
||||
assert!(w.text.contains("*LOAD_SEGMENT_SET") && w.text.contains("*LOAD_BODY_Z"));
|
||||
let deck = read(&w.text);
|
||||
// Triangles are degenerate quadrilaterals.
|
||||
assert_eq!(deck.elements[&3], vec![3, 7, 8, 8]);
|
||||
assert_eq!(deck.parts.len(), 2, "quad4 and tri3 take different formulations");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn golden_solids_and_distributing_coupling() {
|
||||
let w = golden("solids", &solids());
|
||||
for k in ["*ELEMENT_SOLID_H20", "*CONSTRAINED_INTERPOLATION", "*BOUNDARY_PRESCRIBED_MOTION_SET_ID", "*LOAD_NODE_POINT"] {
|
||||
assert!(w.text.contains(k), "{k}");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// A minimal reader of our own output: nodes, elements and node sets.
|
||||
|
||||
#[derive(Default)]
|
||||
struct Read {
|
||||
nodes: BTreeMap<u32, [f64; 3]>,
|
||||
/// Element → its nodes as written.
|
||||
elements: BTreeMap<u32, Vec<u32>>,
|
||||
/// The part ids elements are in.
|
||||
parts: BTreeSet<u32>,
|
||||
node_sets: BTreeMap<String, Vec<u32>>,
|
||||
}
|
||||
|
||||
fn fields(line: &str) -> Vec<&str> {
|
||||
(0..line.len()).step_by(20).map(|i| line[i..(i + 20).min(line.len())].trim()).collect()
|
||||
}
|
||||
|
||||
fn read(text: &str) -> Read {
|
||||
let mut out = Read::default();
|
||||
let mut keyword = String::new();
|
||||
let mut cards: Vec<&str> = Vec::new();
|
||||
let flush = |keyword: &str, cards: &[&str], out: &mut Read| match keyword {
|
||||
"*NODE" => {
|
||||
for c in cards {
|
||||
let f = fields(c);
|
||||
out.nodes.insert(f[0].parse().unwrap(), [f[1].parse().unwrap(), f[2].parse().unwrap(), f[3].parse().unwrap()]);
|
||||
}
|
||||
}
|
||||
"*ELEMENT_SOLID" | "*ELEMENT_SOLID_H20" => {
|
||||
let per = if keyword == "*ELEMENT_SOLID" { 2 } else { 3 };
|
||||
for e in cards.chunks(per) {
|
||||
let head = fields(e[0]);
|
||||
let nodes: Vec<u32> = e[1..].iter().flat_map(|c| fields(c)).map(|v| v.parse().unwrap()).collect();
|
||||
out.elements.insert(head[0].parse().unwrap(), nodes);
|
||||
out.parts.insert(head[1].parse().unwrap());
|
||||
}
|
||||
}
|
||||
"*ELEMENT_SHELL" | "*ELEMENT_BEAM" => {
|
||||
for c in cards {
|
||||
let f: Vec<u32> = fields(c).iter().map(|v| v.parse().unwrap()).collect();
|
||||
out.elements.insert(f[0], f[2..].to_vec());
|
||||
out.parts.insert(f[1]);
|
||||
}
|
||||
}
|
||||
"*SET_NODE_LIST_TITLE" => {
|
||||
let nodes = cards[2..].iter().flat_map(|c| fields(c)).map(|v| v.parse().unwrap()).collect();
|
||||
out.node_sets.insert(cards[0].to_string(), nodes);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
for line in text.lines() {
|
||||
if line.starts_with('$') {
|
||||
continue;
|
||||
}
|
||||
if line.starts_with('*') {
|
||||
flush(&keyword, &cards, &mut out);
|
||||
keyword = line.to_string();
|
||||
cards.clear();
|
||||
} else {
|
||||
cards.push(line);
|
||||
}
|
||||
}
|
||||
flush(&keyword, &cards, &mut out);
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn re_read_gives_back_nodes_elements_and_sets() {
|
||||
for model in [cantilever(), cantilever_modes(), plate(), solids()] {
|
||||
let w = lsdyna::write(&model).unwrap();
|
||||
let deck = read(&w.text);
|
||||
for (label, n) in model.nodes() {
|
||||
assert_eq!(deck.nodes[label], n.xyz, "node {label} round-trips exactly");
|
||||
}
|
||||
for (label, e) in model.elements() {
|
||||
let written = &deck.elements[label];
|
||||
let unique: BTreeSet<u32> = written.iter().copied().collect();
|
||||
assert_eq!(unique, e.nodes.iter().copied().collect::<BTreeSet<u32>>(), "element {label}");
|
||||
}
|
||||
let r = model.resolve().unwrap();
|
||||
for (name, nodes) in &deck.node_sets {
|
||||
if let Some(s) = r.sets.get(name) {
|
||||
assert_eq!(nodes, &s.nodes, "set {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn awkward_coordinates_round_trip_or_are_counted() {
|
||||
let mut m = cantilever();
|
||||
// Full 17-digit mantissas at every magnitude a model allows.
|
||||
let values = [0.1 + 0.2, -123_456_789.123_456_78, 1e-300, -2.2250738585072014e-308, 0.012_345_678_901_234_567, 999_999_999.999_999_9];
|
||||
for (i, v) in values.iter().enumerate() {
|
||||
m.set_node(100 + i as u32, [*v, -*v, *v / 3.0]).unwrap();
|
||||
}
|
||||
let w = lsdyna::write(&m).unwrap();
|
||||
let deck = read(&w.text);
|
||||
let mut inexact = 0;
|
||||
for (i, _) in values.iter().enumerate() {
|
||||
let label = 100 + i as u32;
|
||||
let want = m.node(label).unwrap().xyz;
|
||||
let got = deck.nodes[&label];
|
||||
for (a, b) in want.iter().zip(got) {
|
||||
if *a != b {
|
||||
inexact += 1;
|
||||
assert!(((a - b) / a).abs() < 5e-13, "{a:e} {b:e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(inexact, w.rounded_values);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Node order: the sign of the Jacobian in LS-DYNA's element conventions.
|
||||
|
||||
/// Hexahedron corners in the reference cube, in LS-DYNA (and model) order.
|
||||
const CUBE: [[f64; 3]; 8] =
|
||||
[[-1., -1., -1.], [1., -1., -1.], [1., 1., -1.], [-1., 1., -1.], [-1., -1., 1.], [1., -1., 1.], [1., 1., 1.], [-1., 1., 1.]];
|
||||
|
||||
/// Hexahedron edges as corner pairs, in mid-side node order.
|
||||
const HEX_EDGES: [(usize, usize); 12] = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)];
|
||||
|
||||
/// det J of the map with shape-function gradients `dn[i]` (reference axes) at nodes `x[i]`.
|
||||
fn jacobian_det(x: &[[f64; 3]], dn: impl Iterator<Item = [f64; 3]>) -> f64 {
|
||||
let mut j = [[0.0; 3]; 3];
|
||||
for (p, d) in x.iter().zip(dn) {
|
||||
for a in 0..3 {
|
||||
for b in 0..3 {
|
||||
j[a][b] += d[a] * p[b];
|
||||
}
|
||||
}
|
||||
}
|
||||
det(j)
|
||||
}
|
||||
|
||||
/// Trilinear hexahedron in LS-DYNA order (1-4 one face, 5-8 over them): det J at a point.
|
||||
fn hex8_det(x: &[[f64; 3]], (r, s, t): (f64, f64, f64)) -> f64 {
|
||||
jacobian_det(
|
||||
x,
|
||||
CUBE.iter().map(|c| {
|
||||
[
|
||||
c[0] * (1.0 + c[1] * s) * (1.0 + c[2] * t) / 8.0,
|
||||
c[1] * (1.0 + c[0] * r) * (1.0 + c[2] * t) / 8.0,
|
||||
c[2] * (1.0 + c[0] * r) * (1.0 + c[1] * s) / 8.0,
|
||||
]
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn det(j: [[f64; 3]; 3]) -> f64 {
|
||||
j[0][0] * (j[1][1] * j[2][2] - j[1][2] * j[2][1]) - j[0][1] * (j[1][0] * j[2][2] - j[1][2] * j[2][0])
|
||||
+ j[0][2] * (j[1][0] * j[2][1] - j[1][1] * j[2][0])
|
||||
}
|
||||
|
||||
const GAUSS: f64 = 0.577_350_269_189_625_8;
|
||||
|
||||
/// Write a one-element model of `kind` with these model-order coordinates and read back the
|
||||
/// written coordinates in LS-DYNA order.
|
||||
fn written_coordinates(kind: ElementKind, xyz: &[[f64; 3]]) -> Vec<[f64; 3]> {
|
||||
let mut m = Model::default();
|
||||
for (i, p) in xyz.iter().enumerate() {
|
||||
m.set_node(i as u32 + 1, *p).unwrap();
|
||||
}
|
||||
m.set_element(1, kind, (1..=xyz.len() as u32).collect()).unwrap();
|
||||
m.add_set("all", Set { elements: vec![1], ..Set::default() }).unwrap();
|
||||
m.add_set("fix", Set { nodes: vec![1], ..Set::default() }).unwrap();
|
||||
m.add_material("M", steel()).unwrap();
|
||||
let kind_of = if kind.dimension() == 3 { SectionKind::Solid {} } else { SectionKind::Shell { thickness: 1.0, offset: 0.0, points: 3 } };
|
||||
m.add_section("s", Section { set: "all".into(), material: "M".into(), kind: kind_of, formulation: Formulation::default() }).unwrap();
|
||||
m.add_boundary_condition("b", BoundaryCondition::Fixed { target: Target::Set("fix".into()) }).unwrap();
|
||||
m.add_step(step("S", Procedure::Frequency { modes: 1, min_hz: None, max_hz: None }, &["b"], &[]), None).unwrap();
|
||||
let deck = read(&lsdyna::write(&m).unwrap().text);
|
||||
deck.elements[&1].iter().map(|n| deck.nodes[n]).collect()
|
||||
}
|
||||
|
||||
/// Slightly distorted coordinates, so a wrong mapping cannot pass by symmetry (short decimals,
|
||||
/// so every coordinate is written exactly).
|
||||
fn jiggle(p: [f64; 3], i: usize) -> [f64; 3] {
|
||||
let k = ((i as f64 * 0.37).sin() * 0.03 * 1e4).round() / 1e4;
|
||||
[p[0] + k, p[1] - k * 0.5, p[2] + k * 0.25].map(|c| (c * 1e6).round() / 1e6)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degenerate_solids_have_positive_jacobians_in_lsdyna_order() {
|
||||
let tet = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
|
||||
let wedge = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]];
|
||||
let hex =
|
||||
[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [1.0, 1.0, 1.0], [0.0, 1.0, 1.0]];
|
||||
for (kind, xyz) in [(ElementKind::Tet4, &tet[..]), (ElementKind::Wedge6, &wedge[..]), (ElementKind::Hex8, &hex[..])] {
|
||||
let xyz: Vec<[f64; 3]> = xyz.iter().enumerate().map(|(i, p)| jiggle(*p, i)).collect();
|
||||
let x = written_coordinates(kind, &xyz);
|
||||
assert_eq!(x.len(), 8, "{}", kind.name());
|
||||
for r in [-GAUSS, GAUSS] {
|
||||
for s in [-GAUSS, GAUSS] {
|
||||
for t in [-GAUSS, GAUSS] {
|
||||
assert!(hex8_det(&x, (r, s, t)) > 0.0, "{} at ({r}, {s}, {t})", kind.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
// The model's own order mirrored is negative (the test can fail).
|
||||
let mut mirrored = xyz.clone();
|
||||
for p in &mut mirrored {
|
||||
p[2] = -p[2];
|
||||
}
|
||||
let x = written_coordinates_unchecked(kind, &mirrored);
|
||||
assert!(hex8_det(&x, (0.1, -0.1, 0.1)) < 0.0, "{}", kind.name());
|
||||
}
|
||||
}
|
||||
|
||||
/// [`written_coordinates`] without writing: the writer's node order applied to coordinates the
|
||||
/// model would reject as inverted.
|
||||
fn written_coordinates_unchecked(kind: ElementKind, xyz: &[[f64; 3]]) -> Vec<[f64; 3]> {
|
||||
lsdyna::node_order(kind).iter().map(|&i| xyz[i]).collect()
|
||||
}
|
||||
|
||||
/// Quadratic tetrahedron in LS-DYNA order (mid-sides 5, 6, 7 on 1-2, 2-3, 3-1 and 8, 9, 10 on
|
||||
/// 2-4, 3-4, 1-4): det J at volume coordinates (l1, l2, l3).
|
||||
fn tet10_det(x: &[[f64; 3]], l: [f64; 3]) -> f64 {
|
||||
let ls = [1.0 - l[0] - l[1] - l[2], l[0], l[1], l[2]];
|
||||
// dL/d(ξ, η, ζ) with L1 = 1 − ξ − η − ζ, L2 = ξ, L3 = η, L4 = ζ.
|
||||
let dl = [[-1.0, -1.0, -1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
|
||||
let edges = [(0, 1), (1, 2), (2, 0), (1, 3), (2, 3), (0, 3)];
|
||||
let corners = (0..4).map(|i| dl[i].map(|d| (4.0 * ls[i] - 1.0) * d));
|
||||
let mids = edges.iter().map(|&(a, b)| [0, 1, 2].map(|c| 4.0 * (dl[a][c] * ls[b] + ls[a] * dl[b][c])));
|
||||
jacobian_det(x, corners.chain(mids))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tet10_mid_side_nodes_sit_on_lsdyna_edges_with_positive_jacobian() {
|
||||
let c = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
|
||||
let mut xyz: Vec<[f64; 3]> = c.iter().enumerate().map(|(i, p)| jiggle(*p, i)).collect();
|
||||
for (a, b) in [(0, 1), (1, 2), (2, 0), (0, 3), (1, 3), (2, 3)] {
|
||||
xyz.push([0, 1, 2].map(|k| (xyz[a][k] + xyz[b][k]) / 2.0));
|
||||
}
|
||||
let x = written_coordinates(ElementKind::Tet10, &xyz);
|
||||
assert_eq!(x.len(), 10);
|
||||
for (m, (a, b)) in [(0, 1), (1, 2), (2, 0), (1, 3), (2, 3), (0, 3)].iter().enumerate() {
|
||||
let want = [0, 1, 2].map(|k| (x[*a][k] + x[*b][k]) / 2.0);
|
||||
assert_eq!(x[4 + m], want, "node {} is on LS-DYNA edge {}-{}", 5 + m, a + 1, b + 1);
|
||||
}
|
||||
for l in [[0.25, 0.25, 0.25], [0.1, 0.1, 0.1], [0.6, 0.1, 0.1], [0.1, 0.6, 0.1], [0.1, 0.1, 0.6]] {
|
||||
assert!(tet10_det(&x, l) > 0.0, "{l:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// 20-node serendipity hexahedron in LS-DYNA order: det J at a point.
|
||||
fn hex20_det(x: &[[f64; 3]], p: [f64; 3]) -> f64 {
|
||||
let mut nodes: Vec<[f64; 3]> = CUBE.to_vec();
|
||||
nodes.extend(HEX_EDGES.map(|(a, b)| [0, 1, 2].map(|k| (CUBE[a][k] + CUBE[b][k]) / 2.0)));
|
||||
let gradient = |n: &[f64; 3]| -> [f64; 3] {
|
||||
let q = [0, 1, 2].map(|k| 1.0 + n[k] * p[k]);
|
||||
match (0..3).find(|k| n[*k] == 0.0) {
|
||||
// Corner: N = q0 q1 q2 (n·p − 2) / 8.
|
||||
None => {
|
||||
let s = n[0] * p[0] + n[1] * p[1] + n[2] * p[2] - 2.0;
|
||||
[0, 1, 2].map(|k| n[k] * q[(k + 1) % 3] * q[(k + 2) % 3] * (s + q[k]) / 8.0)
|
||||
}
|
||||
// Mid-side on an edge along axis z: N = (1 − p_z²) q_a q_b / 4.
|
||||
Some(z) => [0, 1, 2].map(|k| {
|
||||
let others = |skip: usize| (0..3).filter(|m| *m != skip && *m != z).map(|m| q[m]).product::<f64>();
|
||||
if k == z { -2.0 * p[z] * others(z) / 4.0 } else { n[k] * (1.0 - p[z] * p[z]) * others(k) / 4.0 }
|
||||
}),
|
||||
}
|
||||
};
|
||||
jacobian_det(x, nodes.iter().map(gradient))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex20_keeps_the_model_order_with_positive_jacobian() {
|
||||
let xyz: Vec<[f64; 3]> = hex20_nodes([0.0; 3]).into_iter().enumerate().map(|(i, p)| jiggle(p, i)).collect();
|
||||
let x = written_coordinates(ElementKind::Hex20, &xyz);
|
||||
assert_eq!(x, xyz);
|
||||
for r in [-GAUSS, GAUSS] {
|
||||
for s in [-GAUSS, GAUSS] {
|
||||
for t in [-GAUSS, GAUSS] {
|
||||
assert!(hex20_det(&x, [r, s, t]) > 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The unit cube's Jacobian is 1/8 everywhere: the shape functions are right.
|
||||
assert!((hex20_det(&hex20_nodes([0.0; 3]), [0.3, -0.2, 0.7]) - 0.125).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shells_keep_their_normal() {
|
||||
let normal = |x: &[[f64; 3]]| {
|
||||
// Diagonals of the (possibly degenerate) quadrilateral 1-2-3-4.
|
||||
let d1 = [0, 1, 2].map(|k| x[2][k] - x[0][k]);
|
||||
let d2 = [0, 1, 2].map(|k| x[3][k] - x[1][k]);
|
||||
[d1[1] * d2[2] - d1[2] * d2[1], d1[2] * d2[0] - d1[0] * d2[2], d1[0] * d2[1] - d1[1] * d2[0]]
|
||||
};
|
||||
let tri = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
|
||||
let quad = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]];
|
||||
let mid = |p: &[[f64; 3]], a: usize, b: usize| [0, 1, 2].map(|k| (p[a][k] + p[b][k]) / 2.0);
|
||||
let tri6: Vec<[f64; 3]> = tri.iter().copied().chain([mid(&tri, 0, 1), mid(&tri, 1, 2), mid(&tri, 2, 0)]).collect();
|
||||
let quad8: Vec<[f64; 3]> = quad.iter().copied().chain([mid(&quad, 0, 1), mid(&quad, 1, 2), mid(&quad, 2, 3), mid(&quad, 3, 0)]).collect();
|
||||
for (kind, xyz) in
|
||||
[(ElementKind::Tri3, tri.to_vec()), (ElementKind::Quad4, quad.to_vec()), (ElementKind::Tri6, tri6), (ElementKind::Quad8, quad8)]
|
||||
{
|
||||
let x = written_coordinates(kind, &xyz);
|
||||
assert!(normal(&x)[2] > 0.0, "{} keeps +z as its top", kind.name());
|
||||
if kind == ElementKind::Tri6 {
|
||||
assert_eq!(&x[4..7], &xyz[3..6]);
|
||||
}
|
||||
if kind == ElementKind::Quad8 {
|
||||
assert_eq!(&x[4..8], &xyz[4..8]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// What the format cannot take, and hostile models.
|
||||
|
||||
fn unsupported(m: &Model, needle: &str) {
|
||||
match lsdyna::write(m) {
|
||||
Err(Error::Unsupported { format, what }) => {
|
||||
assert_eq!(format, "LS-DYNA");
|
||||
assert!(what.contains(needle), "{what}");
|
||||
}
|
||||
other => panic!("expected unsupported `{needle}`, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_lsdyna_cannot_take_is_named() {
|
||||
let mut m = cantilever();
|
||||
m.add_load("heat", Load::BodyForce { set: "all".into(), vector: [0.0, 0.0, 1.0] }).unwrap();
|
||||
m.replace_step(step("Bend", Procedure::Static {}, &["clamp"], &["tip_load", "heat"])).unwrap();
|
||||
unsupported(&m, "load `heat`");
|
||||
|
||||
let mut m = cantilever();
|
||||
m.replace_section(
|
||||
"beam",
|
||||
Section {
|
||||
set: "all".into(),
|
||||
material: "Steel".into(),
|
||||
kind: SectionKind::Solid {},
|
||||
formulation: Formulation { hybrid: true, ..Formulation::default() },
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
unsupported(&m, "hybrid");
|
||||
|
||||
let mut m = cantilever();
|
||||
m.replace_material("Steel", Material { density: None, ..steel() }).unwrap();
|
||||
unsupported(&m, "density");
|
||||
|
||||
let mut m = cantilever_modes();
|
||||
m.replace_coupling(
|
||||
"pad",
|
||||
Coupling { reference_point: "RP".into(), set: "tip".into(), kind: CouplingKind::Kinematic, dofs: Dofs::try_from(vec![1, 2, 3]).unwrap() },
|
||||
)
|
||||
.unwrap();
|
||||
unsupported(&m, "coupling `pad`");
|
||||
|
||||
let mut m = cantilever_modes();
|
||||
m.add_boundary_condition("hold_rp", BoundaryCondition::Fixed { target: Target::ReferencePoint("RP".into()) }).unwrap();
|
||||
m.replace_step(step("Bend", Procedure::Static {}, &["clamp", "hold_rp"], &["push"])).unwrap();
|
||||
unsupported(&m, "boundary condition `hold_rp`");
|
||||
|
||||
let mut m = plate();
|
||||
m.replace_section(
|
||||
"skin",
|
||||
Section {
|
||||
set: "plate".into(),
|
||||
material: "Steel".into(),
|
||||
kind: SectionKind::Shell { thickness: 0.5, offset: 0.0, points: 11 },
|
||||
formulation: Formulation::default(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
unsupported(&m, "11 integration points");
|
||||
|
||||
let mut m = plate();
|
||||
m.add_set("half", Set { elements: vec![1], ..Set::default() }).unwrap();
|
||||
m.replace_load("weight", Load::Gravity { acceleration: [0.0, 0.0, -9810.0], set: Some("half".into()) }).unwrap();
|
||||
unsupported(&m, "gravity on set `half`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gravity_on_whole_sections_uses_a_part_set() {
|
||||
let mut m = plate();
|
||||
m.replace_load("weight", Load::Gravity { acceleration: [0.0, 0.0, -9810.0], set: Some("plate".into()) }).unwrap();
|
||||
let w = lsdyna::write(&m).unwrap();
|
||||
assert!(w.text.contains("*SET_PART_LIST") && w.text.contains("*LOAD_BODY_PARTS"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn models_that_are_not_ready_are_refused_not_written() {
|
||||
assert!(matches!(lsdyna::write(&Model::default()), Err(Error::Model(_))));
|
||||
let mut m = cantilever();
|
||||
m.remove_step("Bend").unwrap();
|
||||
assert!(matches!(lsdyna::write(&m), Err(Error::Model(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_numbers_stay_within_their_fields() {
|
||||
let mut m = cantilever();
|
||||
m.set_node(1, [1e9, -1e9, 1e-300]).unwrap();
|
||||
m.set_node(2, [f64::MIN_POSITIVE, -f64::MIN_POSITIVE, 5e-324]).unwrap();
|
||||
m.replace_material(
|
||||
"Steel",
|
||||
Material { elastic: Some(Elastic::Isotropic { young: 1e300, poisson: 0.499_999_999_999_999_9 }), density: Some(1e-300) },
|
||||
)
|
||||
.unwrap();
|
||||
m.replace_load(
|
||||
"tip_load",
|
||||
Load::Force { target: Target::Set("tip".into()), vector: [1e-300, -1e300, 1.0 / 3.0], distribution: Distribution::PerNode },
|
||||
)
|
||||
.unwrap();
|
||||
let w = lsdyna::write(&m).unwrap();
|
||||
for line in w.text.lines().filter(|l| !l.starts_with('$') && !l.starts_with('*')) {
|
||||
for f in fields(line) {
|
||||
assert!(f.len() <= 20, "{line}");
|
||||
}
|
||||
}
|
||||
let deck = read(&w.text);
|
||||
assert_eq!(deck.nodes[&1], [1e9, -1e9, 1e-300]);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
*KEYWORD long=y
|
||||
$ Written by ModelCraft: implicit linear analysis, long format (20-column fields).
|
||||
$ Units: mm-t-s-N-MPa (consistent; nothing is converted).
|
||||
*NODE
|
||||
$ nid x y z
|
||||
1 0.0 0.0 0.0
|
||||
2 1.0 0.0 0.0
|
||||
3 2.0 0.0 0.0
|
||||
4 3.0 0.0 0.0
|
||||
5 4.0 0.0 0.0
|
||||
6 0.0 1.0 0.0
|
||||
7 1.0 1.0 0.0
|
||||
8 2.0 1.0 0.0
|
||||
9 3.0 1.0 0.0
|
||||
10 4.0 1.0 0.0
|
||||
11 0.0 0.0 1.0
|
||||
12 1.0 0.0 1.0
|
||||
13 2.0 0.0 1.0
|
||||
14 3.0 0.0 1.0
|
||||
15 4.0 0.0 1.0
|
||||
16 0.0 1.0 1.0
|
||||
17 1.0 1.0 1.0
|
||||
18 2.0 1.0 1.0
|
||||
19 3.0 1.0 1.0
|
||||
20 4.0 1.0 1.0
|
||||
*MAT_ELASTIC_TITLE
|
||||
Steel
|
||||
$ mid ro e pr
|
||||
1 7.85e-9 210000.0 0.3
|
||||
*SECTION_SOLID
|
||||
$ secid elform
|
||||
1 2
|
||||
*PART
|
||||
beam
|
||||
$ pid secid mid eosid hgid
|
||||
1 1 1
|
||||
*ELEMENT_SOLID
|
||||
$ eid pid
|
||||
$ n1 n2 n3 n4 n5 n6 n7 n8 n9 n10
|
||||
1 1
|
||||
1 2 7 6 11 12 17 16
|
||||
2 1
|
||||
2 3 8 7 12 13 18 17
|
||||
3 1
|
||||
3 4 9 8 13 14 19 18
|
||||
4 1
|
||||
4 5 10 9 14 15 20 19
|
||||
*SET_NODE_LIST_TITLE
|
||||
root
|
||||
$ sid
|
||||
2
|
||||
1 6 11 16
|
||||
*SET_NODE_LIST_TITLE
|
||||
tip
|
||||
$ sid
|
||||
3
|
||||
5 10 15 20
|
||||
*TITLE
|
||||
Bend
|
||||
*CONTROL_IMPLICIT_GENERAL
|
||||
$ imflag dt0
|
||||
1 1.0
|
||||
*CONTROL_IMPLICIT_SOLUTION
|
||||
$ nsolvr
|
||||
1
|
||||
*CONTROL_TERMINATION
|
||||
$ endtim
|
||||
1.0
|
||||
*DEFINE_CURVE_TITLE
|
||||
Bend ramp
|
||||
$ lcid
|
||||
1
|
||||
$ a1 o1
|
||||
0.0 0.0
|
||||
1.0 1.0
|
||||
*BOUNDARY_SPC_SET_ID
|
||||
1clamp
|
||||
$ nsid cid dofx dofy dofz dofrx dofry dofrz
|
||||
2 1 1 1 1 1 1
|
||||
$ load `tip_load`
|
||||
*LOAD_NODE_SET
|
||||
$ nsid dof lcid sf
|
||||
3 3 1 -25.0
|
||||
*DATABASE_BINARY_D3PLOT
|
||||
$ dt
|
||||
1.0
|
||||
*DATABASE_SPCFORC
|
||||
$ dt
|
||||
1.0
|
||||
*DATABASE_EXTENT_BINARY
|
||||
$ neiph neips maxint strflg
|
||||
1
|
||||
*END
|
||||
@@ -0,0 +1,124 @@
|
||||
*KEYWORD long=y
|
||||
$ Written by ModelCraft: implicit linear analysis, long format (20-column fields).
|
||||
$ Units: mm-t-s-N-MPa (consistent; nothing is converted).
|
||||
$ 2 steps are written as *CASE analyses: run LS-DYNA with CASE on its command line.
|
||||
*NODE
|
||||
$ nid x y z
|
||||
1 0.0 0.0 0.0
|
||||
2 1.0 0.0 0.0
|
||||
3 2.0 0.0 0.0
|
||||
4 3.0 0.0 0.0
|
||||
5 4.0 0.0 0.0
|
||||
6 0.0 1.0 0.0
|
||||
7 1.0 1.0 0.0
|
||||
8 2.0 1.0 0.0
|
||||
9 3.0 1.0 0.0
|
||||
10 4.0 1.0 0.0
|
||||
11 0.0 0.0 1.0
|
||||
12 1.0 0.0 1.0
|
||||
13 2.0 0.0 1.0
|
||||
14 3.0 0.0 1.0
|
||||
15 4.0 0.0 1.0
|
||||
16 0.0 1.0 1.0
|
||||
17 1.0 1.0 1.0
|
||||
18 2.0 1.0 1.0
|
||||
19 3.0 1.0 1.0
|
||||
20 4.0 1.0 1.0
|
||||
99999999 5.0 0.5 0.5
|
||||
*MAT_ORTHOTROPIC_ELASTIC_TITLE
|
||||
Ply
|
||||
$ mid ro ea eb ec prba prca prcb
|
||||
1 1.6e-9 140000.0 10000.0 10000.0 0.02142857142857143 0.02142857142857143 0.45
|
||||
$ gab gbc gca aopt
|
||||
5000.0 3500.0 5000.0 2.0
|
||||
$ xp yp zp a1 a2 a3
|
||||
1.0 0.0 0.0
|
||||
$ v1 v2 v3 d1 d2 d3
|
||||
0.0 1.0 0.0
|
||||
*SECTION_SOLID
|
||||
$ secid elform
|
||||
1 -18
|
||||
*PART
|
||||
beam
|
||||
$ pid secid mid eosid hgid
|
||||
1 1 1
|
||||
*ELEMENT_SOLID
|
||||
$ eid pid
|
||||
$ n1 n2 n3 n4 n5 n6 n7 n8 n9 n10
|
||||
1 1
|
||||
1 2 7 6 11 12 17 16
|
||||
2 1
|
||||
2 3 8 7 12 13 18 17
|
||||
3 1
|
||||
3 4 9 8 13 14 19 18
|
||||
4 1
|
||||
4 5 10 9 14 15 20 19
|
||||
*SET_NODE_LIST_TITLE
|
||||
root
|
||||
$ sid
|
||||
2
|
||||
1 6 11 16
|
||||
*SET_NODE_LIST_TITLE
|
||||
pad (rigid body)
|
||||
$ sid
|
||||
4
|
||||
99999999 5 10 15 20
|
||||
*CONSTRAINED_NODAL_RIGID_BODY_TITLE
|
||||
pad
|
||||
$ pid cid nsid
|
||||
2 4
|
||||
*CASE_BEGIN_1
|
||||
*TITLE
|
||||
Bend
|
||||
*CONTROL_IMPLICIT_GENERAL
|
||||
$ imflag dt0
|
||||
1 1.0
|
||||
*CONTROL_IMPLICIT_SOLUTION
|
||||
$ nsolvr
|
||||
1
|
||||
*CONTROL_TERMINATION
|
||||
$ endtim
|
||||
1.0
|
||||
*DEFINE_CURVE_TITLE
|
||||
Bend ramp
|
||||
$ lcid
|
||||
1
|
||||
$ a1 o1
|
||||
0.0 0.0
|
||||
1.0 1.0
|
||||
*BOUNDARY_SPC_SET_ID
|
||||
1clamp
|
||||
$ nsid cid dofx dofy dofz dofrx dofry dofrz
|
||||
2 1 1 1 1 1 1
|
||||
*LOAD_NODE_POINT
|
||||
$ nid dof lcid sf
|
||||
99999999 2 1 -10.0
|
||||
99999999 5 1 250.0
|
||||
*DATABASE_BINARY_D3PLOT
|
||||
$ dt
|
||||
1.0
|
||||
*DATABASE_SPCFORC
|
||||
$ dt
|
||||
1.0
|
||||
*DATABASE_EXTENT_BINARY
|
||||
$ neiph neips maxint strflg
|
||||
1
|
||||
*CASE_END_1
|
||||
*CASE_BEGIN_2
|
||||
*TITLE
|
||||
Modes
|
||||
*CONTROL_IMPLICIT_GENERAL
|
||||
$ imflag dt0
|
||||
1 1.0
|
||||
*CONTROL_IMPLICIT_EIGENVALUE
|
||||
$ neig center lflag lftend rflag rhtend
|
||||
6 1 1.0 1 5000.0
|
||||
*CONTROL_TERMINATION
|
||||
$ endtim
|
||||
1.0
|
||||
*BOUNDARY_SPC_SET_ID
|
||||
2clamp
|
||||
$ nsid cid dofx dofy dofz dofrx dofry dofrz
|
||||
2 1 1 1 1 1 1
|
||||
*CASE_END_2
|
||||
*END
|
||||
@@ -0,0 +1,94 @@
|
||||
*KEYWORD long=y
|
||||
$ Written by ModelCraft: implicit linear analysis, long format (20-column fields).
|
||||
$ Units: mm-t-s-N-MPa (consistent; nothing is converted).
|
||||
*NODE
|
||||
$ nid x y z
|
||||
1 0.0 0.0 0.0
|
||||
2 10.0 0.0 0.0
|
||||
3 20.0 0.0 0.0
|
||||
4 0.0 10.0 0.0
|
||||
5 10.0 10.0 0.0
|
||||
6 20.0 10.0 0.0
|
||||
7 30.0 0.0 0.0
|
||||
8 30.0 10.0 0.0
|
||||
*MAT_ELASTIC_TITLE
|
||||
Steel
|
||||
$ mid ro e pr
|
||||
1 7.85e-9 210000.0 0.3
|
||||
*SECTION_SHELL
|
||||
$ secid elform shrf nip
|
||||
1 4 0.8333333333333334 5
|
||||
$ t1 t2 t3 t4 nloc
|
||||
0.5 0.5
|
||||
*SECTION_SHELL
|
||||
$ secid elform shrf nip
|
||||
2 16 0.8333333333333334 5
|
||||
$ t1 t2 t3 t4 nloc
|
||||
0.5 0.5
|
||||
*PART
|
||||
skin tri3
|
||||
$ pid secid mid eosid hgid
|
||||
1 1 1
|
||||
*PART
|
||||
skin quad4
|
||||
$ pid secid mid eosid hgid
|
||||
2 2 1
|
||||
*ELEMENT_SHELL
|
||||
$ eid pid n1 n2 n3 n4 n5 n6 n7 n8
|
||||
1 2 1 2 5 4
|
||||
2 2 2 3 6 5
|
||||
3 1 3 7 8 8
|
||||
4 1 3 8 6 6
|
||||
*SET_NODE_LIST_TITLE
|
||||
edge
|
||||
$ sid
|
||||
1
|
||||
1 4
|
||||
*SET_SEGMENT_TITLE
|
||||
top
|
||||
$ sid
|
||||
3
|
||||
$ n1 n2 n3 n4
|
||||
1 2 5 4
|
||||
2 3 6 5
|
||||
3 7 8 8
|
||||
3 8 6 6
|
||||
*TITLE
|
||||
Sag
|
||||
*CONTROL_IMPLICIT_GENERAL
|
||||
$ imflag dt0
|
||||
1 1.0
|
||||
*CONTROL_IMPLICIT_SOLUTION
|
||||
$ nsolvr
|
||||
1
|
||||
*CONTROL_TERMINATION
|
||||
$ endtim
|
||||
1.0
|
||||
*DEFINE_CURVE_TITLE
|
||||
Sag ramp
|
||||
$ lcid
|
||||
1
|
||||
$ a1 o1
|
||||
0.0 0.0
|
||||
1.0 1.0
|
||||
*BOUNDARY_SPC_SET_ID
|
||||
1hold
|
||||
$ nsid cid dofx dofy dofz dofrx dofry dofrz
|
||||
1 1 1 1 1 1 1
|
||||
$ load `press`
|
||||
*LOAD_SEGMENT_SET
|
||||
$ ssid lcid sf
|
||||
3 1 0.01
|
||||
*LOAD_BODY_Z
|
||||
$ lcid sf
|
||||
1 9810.0
|
||||
*DATABASE_BINARY_D3PLOT
|
||||
$ dt
|
||||
1.0
|
||||
*DATABASE_SPCFORC
|
||||
$ dt
|
||||
1.0
|
||||
*DATABASE_EXTENT_BINARY
|
||||
$ neiph neips maxint strflg
|
||||
1
|
||||
*END
|
||||
@@ -0,0 +1,162 @@
|
||||
*KEYWORD long=y
|
||||
$ Written by ModelCraft: implicit linear analysis, long format (20-column fields).
|
||||
$ Units: m-kg-s-N-Pa (consistent; nothing is converted).
|
||||
*NODE
|
||||
$ nid x y z
|
||||
1 0.0 0.0 0.0
|
||||
2 1.0 0.0 0.0
|
||||
3 0.0 1.0 0.0
|
||||
4 0.0 0.0 1.0
|
||||
5 2.0 0.0 0.0
|
||||
6 3.0 0.0 0.0
|
||||
7 2.0 1.0 0.0
|
||||
8 2.0 0.0 1.0
|
||||
9 2.5 0.0 0.0
|
||||
10 2.5 0.5 0.0
|
||||
11 2.0 0.5 0.0
|
||||
12 2.0 0.0 0.5
|
||||
13 2.5 0.0 0.5
|
||||
14 2.0 0.5 0.5
|
||||
15 4.0 0.0 0.0
|
||||
16 5.0 0.0 0.0
|
||||
17 4.0 1.0 0.0
|
||||
18 4.0 0.0 1.0
|
||||
19 5.0 0.0 1.0
|
||||
20 4.0 1.0 1.0
|
||||
21 6.0 0.0 0.0
|
||||
22 7.0 0.0 0.0
|
||||
23 7.0 1.0 0.0
|
||||
24 6.0 1.0 0.0
|
||||
25 6.0 0.0 1.0
|
||||
26 7.0 0.0 1.0
|
||||
27 7.0 1.0 1.0
|
||||
28 6.0 1.0 1.0
|
||||
29 6.5 0.0 0.0
|
||||
30 7.0 0.5 0.0
|
||||
31 6.5 1.0 0.0
|
||||
32 6.0 0.5 0.0
|
||||
33 6.5 0.0 1.0
|
||||
34 7.0 0.5 1.0
|
||||
35 6.5 1.0 1.0
|
||||
36 6.0 0.5 1.0
|
||||
37 6.0 0.0 0.5
|
||||
38 7.0 0.0 0.5
|
||||
39 7.0 1.0 0.5
|
||||
40 6.0 1.0 0.5
|
||||
99999999 6.5 0.5 2.0
|
||||
*MAT_ELASTIC_TITLE
|
||||
Aluminium
|
||||
$ mid ro e pr
|
||||
1 2700.0 70000000000.0 0.33
|
||||
*SECTION_SOLID
|
||||
$ secid elform
|
||||
1 23
|
||||
*SECTION_SOLID
|
||||
$ secid elform
|
||||
2 16
|
||||
*SECTION_SOLID
|
||||
$ secid elform
|
||||
3 13
|
||||
*SECTION_SOLID
|
||||
$ secid elform
|
||||
4 2
|
||||
*PART
|
||||
hex20
|
||||
$ pid secid mid eosid hgid
|
||||
1 1 1
|
||||
*PART
|
||||
tet10
|
||||
$ pid secid mid eosid hgid
|
||||
2 2 1
|
||||
*PART
|
||||
tet4
|
||||
$ pid secid mid eosid hgid
|
||||
3 3 1
|
||||
*PART
|
||||
wedge
|
||||
$ pid secid mid eosid hgid
|
||||
4 4 1
|
||||
*ELEMENT_SOLID
|
||||
$ eid pid
|
||||
$ n1 n2 n3 n4 n5 n6 n7 n8 n9 n10
|
||||
1 3
|
||||
1 2 3 4 4 4 4 4
|
||||
2 2
|
||||
5 6 7 8 9 10 11 13 14 12
|
||||
3 4
|
||||
16 15 18 19 17 17 20 20
|
||||
*ELEMENT_SOLID_H20
|
||||
$ eid pid
|
||||
$ n1 n2 n3 n4 n5 n6 n7 n8 n9 n10
|
||||
4 1
|
||||
21 22 23 24 25 26 27 28 29 30
|
||||
31 32 33 34 35 36 37 38 39 40
|
||||
*SET_NODE_LIST_TITLE
|
||||
base
|
||||
$ sid
|
||||
1
|
||||
1 2 3 4
|
||||
*SET_NODE_LIST_TITLE
|
||||
lid
|
||||
$ sid
|
||||
3
|
||||
25 26 27 28 33 34 35 36
|
||||
*SET_NODE_LIST_TITLE
|
||||
tet10
|
||||
$ sid
|
||||
4
|
||||
5 6 7 8 9 10 11 12
|
||||
13 14
|
||||
$ coupling `spider`
|
||||
*CONSTRAINED_INTERPOLATION
|
||||
$ icid dnid ddof cidd ityp idnsw fgm
|
||||
1 99999999 123456 1 1
|
||||
$ inid idof
|
||||
3 123
|
||||
*TITLE
|
||||
Load
|
||||
*CONTROL_IMPLICIT_GENERAL
|
||||
$ imflag dt0
|
||||
1 1.0
|
||||
*CONTROL_IMPLICIT_SOLUTION
|
||||
$ nsolvr
|
||||
1
|
||||
*CONTROL_TERMINATION
|
||||
$ endtim
|
||||
1.0
|
||||
*DEFINE_CURVE_TITLE
|
||||
Load ramp
|
||||
$ lcid
|
||||
1
|
||||
$ a1 o1
|
||||
0.0 0.0
|
||||
1.0 1.0
|
||||
*BOUNDARY_SPC_SET_ID
|
||||
1hold
|
||||
$ nsid cid dofx dofy dofz dofrx dofry dofrz
|
||||
1 1 1 1 1 1 1
|
||||
*BOUNDARY_PRESCRIBED_MOTION_SET_ID
|
||||
2lift
|
||||
$ nsid dof vad lcid sf
|
||||
4 3 2 1 0.001
|
||||
*LOAD_NODE_POINT
|
||||
$ nid dof lcid sf
|
||||
25 1 1 -83.33333333333337
|
||||
26 1 1 -83.33333333333336
|
||||
27 1 1 -83.33333333333323
|
||||
28 1 1 -83.3333333333333
|
||||
33 1 1 333.3333333333333
|
||||
34 1 1 333.33333333333337
|
||||
35 1 1 333.3333333333334
|
||||
36 1 1 333.3333333333333
|
||||
99999999 3 1 50.0
|
||||
*DATABASE_BINARY_D3PLOT
|
||||
$ dt
|
||||
1.0
|
||||
*DATABASE_SPCFORC
|
||||
$ dt
|
||||
1.0
|
||||
*DATABASE_EXTENT_BINARY
|
||||
$ neiph neips maxint strflg
|
||||
1
|
||||
*END
|
||||
Reference in new issue
Block a user