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

+10 -3
View File
@@ -64,11 +64,18 @@ pub struct Prepared {
pub contour: Option<Arc<Contour>>,
}
/// A mode's value as the results show it: its frequency, or a buckling mode's load factor.
pub fn mode_value(buckling: bool, eigenvalue: f64, frequency: f64) -> String {
if buckling { format!("λ = {}", number(eigenvalue)) } else { format!("{} Hz", number(frequency)) }
}
impl Plot {
/// A frequency step's mode shown, and its frequency.
pub fn mode(&self) -> Option<(usize, f64)> {
/// A frequency or buckling step's mode shown, and its frequency (Hz) or load factor.
pub fn mode(&self) -> Option<(usize, String)> {
match self.result.kind() {
Kind::Frequency(f) => f.modes.get(self.pick.mode.checked_sub(1)?).map(|m| (self.pick.mode, m.frequency)),
Kind::Frequency(f) => {
f.modes.get(self.pick.mode.checked_sub(1)?).map(|m| (self.pick.mode, mode_value(f.buckling.is_some(), m.eigenvalue, m.frequency)))
}
Kind::Static(_) | Kind::Explicit(_) | Kind::Fluid(_) | Kind::Heat(_) => None,
}
}
+9 -3
View File
@@ -35,8 +35,8 @@ fn legend(plot: &Plot, painter: &egui::Painter, rect: Rect, t: &Tokens) {
(None, None) => "Deformed shape".into(),
};
let mut step = format!("Step {}", plot.view.step);
if let Some((mode, hz)) = plot.mode() {
step.push_str(&format!(", mode {mode}: {} Hz", number(hz)));
if let Some((mode, value)) = plot.mode() {
step.push_str(&format!(", mode {mode}: {value}"));
}
if let Some((k, time, n)) = plot.frame() {
step.push_str(&format!(", frame {k} of {}: t = {}", n.saturating_sub(1), number(time)));
@@ -179,7 +179,13 @@ pub fn controls(app: &mut ModelApp, plot: &Plot, ui: &mut egui::Ui, t: &Tokens)
egui::ScrollArea::vertical().id_salt("fc_results_modes").max_height(150.0).show(ui, |ui| {
for (i, m) in f.modes.iter().enumerate() {
let mode = i + 1;
if ui.selectable_label(plot.view.mode == mode, format!("{mode} {} Hz", number(m.frequency))).clicked() {
if ui
.selectable_label(
plot.view.mode == mode,
format!("{mode} {}", crate::results::mode_value(f.buckling.is_some(), m.eigenvalue, m.frequency)),
)
.clicked()
{
run.push(("results.modes", json!({"mode": mode})));
}
}