solver, model, engine: linear buckling steps

Procedure::Buckle {modes, shift}: the step's loads as a linear preload, K_σ from its stresses,
and (K + λ K_σ) φ = 0 by block Lanczos in the K inner product: K⁻¹(−K_σ) on Cholesky without a
shift, the buckling transformation (K + σ K_σ)⁻¹ K on LDLᵀ with one, whose inertia counts the
load factors below σ (Grimes–Lewis–Simon 1994). Lanczos gains an operand and an eigenvalue map
(defaults keep frequency steps bit for bit) and reports converged negative eigenvalues. Results
are frequency results with a `buckling` part; modes display with their load factor.
solve.buckle (in the Static drop-down); Abaqus *BUCKLE, Nastran SOL 105 and LS-DYNA refuse it by
name. Euler columns of hex20 within 0.18 % of Engesser's load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 13:30:59 -04:00
1 parent 9a8ae50bca
commit 1b57e2e672
20 files changed
+511 -27

No files matched your search

+28
View File
@@ -1216,6 +1216,34 @@ impl Deck {
base_bcs.clone_from(&step.boundary_conditions);
base_loads.clone_from(&step.loads);
}
Procedure::Buckle { modes, shift } => {
// Abaqus's buckling base state is the end of the previous general step;
// FieldCraft's steps all start from the undeformed, unloaded model.
if !base_loads.is_empty()
|| steps
.iter()
.take_while(|s| !std::ptr::eq(*s, step))
.any(|s| !matches!(s.procedure, Procedure::Frequency { .. } | Procedure::Buckle { .. }))
{
return Err(Error::Unsupported {
format: FORMAT,
what: format!(
"step `{}`: a buckling step after a general step (Abaqus would buckle from that step's loaded state)",
step.name
),
});
}
if shift.is_some() {
return Err(Error::Unsupported { format: FORMAT, what: format!("step `{}`: a buckling shift", step.name) });
}
self.line(&format!("*STEP, NAME={}", step.name));
self.line("*BUCKLE, EIGENSOLVER=LANCZOS");
self.line(&format!("{modes}"));
self.step_boundaries(model, r, &base_bcs, &step.boundary_conditions)?;
let loads = StepLoads::new(model, r, step, &everything);
self.step_loads(&loads, written)?;
written = loads.written();
}
Procedure::Frequency { modes, min_hz, max_hz } => {
self.line(&format!("*STEP, NAME={}", step.name));
self.line("*FREQUENCY, EIGENSOLVER=LANCZOS");
+1 -1
View File
@@ -899,7 +899,7 @@ fn write_step(d: &mut Deck, model: &Model, r: &Resolved, plan: &Plan, step: &Ste
d.card([Field::Blank, Field::Blank, Field::Blank, Field::Blank, Field::from(1)]);
}
}
Procedure::ExplicitDynamic(_) | Procedure::HeatTransfer { .. } | Procedure::CoupledTemperatureDisplacement {} => {
Procedure::ExplicitDynamic(_) | Procedure::HeatTransfer { .. } | Procedure::CoupledTemperatureDisplacement {} | Procedure::Buckle { .. } => {
return Err(Error::Unsupported { format: FORMAT, what: format!("step `{}`: {} steps", step.name, step.procedure.name()) });
}
}
+3
View File
@@ -280,6 +280,9 @@ fn solution(steps: &[&Step]) -> std::result::Result<u32, String> {
if let Some(s) = steps.iter().find(|s| matches!(s.procedure, Procedure::ExplicitDynamic(_))) {
return Err(format!("step `{}`: explicit dynamic steps (Nastran's linear solutions have none)", s.name));
}
if let Some(s) = steps.iter().find(|s| matches!(s.procedure, Procedure::Buckle { .. })) {
return Err(format!("step `{}`: buckling steps (Nastran's SOL 105; this writer writes SOL 101 and 103 so far)", s.name));
}
if let Some(s) = steps.iter().find(|s| matches!(s.procedure, Procedure::NonlinearStatic(_))) {
return Err(format!("step `{}`: nonlinear static steps (Nastran's SOL 400; this writer writes SOL 101 and 103 so far)", s.name));
}