Skip to main content

qualia_core_db/specialized_libs/engineering_analysis/
buckling.rs

1use super::*;
2
3/// Buckling analysis
4pub struct BucklingAnalysis {
5    eigenvalue_buckling: EigenvalueBuckling,
6    nonlinear_buckling: NonlinearBuckling,
7}
8
9/// Eigenvalue buckling
10#[derive(Debug, Clone)]
11pub struct EigenvalueBuckling {
12    pub critical_loads: Vec<f64>,
13    pub buckling_modes: Vec<BucklingMode>,
14}
15
16/// Buckling modes
17#[derive(Debug, Clone)]
18pub struct BucklingMode {
19    pub mode_number: u32,
20    pub critical_load: f64,
21    pub mode_shape: Vec<f64>,
22}
23
24/// Nonlinear buckling
25#[derive(Debug, Clone)]
26pub struct NonlinearBuckling {
27    pub load_displacement_curve: Vec<(f64, f64)>,
28    pub post_buckling_behavior: PostBucklingBehavior,
29}
30
31/// Post-buckling behavior
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub enum PostBucklingBehavior {
34    Stable,
35    Unstable,
36    SnapThrough,
37}
38impl BucklingAnalysis {
39    pub fn new() -> Self {
40        Self {
41            eigenvalue_buckling: EigenvalueBuckling::new(),
42            nonlinear_buckling: NonlinearBuckling::new(),
43        }
44    }
45
46    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
47        Ok(())
48    }
49
50    /// Borrow the eigenvalue-buckling sub-component.
51    pub fn eigenvalue_buckling(&self) -> &EigenvalueBuckling {
52        &self.eigenvalue_buckling
53    }
54
55    /// Mutably borrow the eigenvalue-buckling sub-component.
56    pub fn eigenvalue_buckling_mut(&mut self) -> &mut EigenvalueBuckling {
57        &mut self.eigenvalue_buckling
58    }
59
60    /// Borrow the nonlinear-buckling sub-component.
61    pub fn nonlinear_buckling(&self) -> &NonlinearBuckling {
62        &self.nonlinear_buckling
63    }
64
65    /// Mutably borrow the nonlinear-buckling sub-component.
66    pub fn nonlinear_buckling_mut(&mut self) -> &mut NonlinearBuckling {
67        &mut self.nonlinear_buckling
68    }
69
70    /// Euler elastic critical buckling loads of a prismatic column:
71    /// `P_cr,n = n²·π²·E·I / (K·L)²` for modes `n = 1..=num_modes`, where `K` is
72    /// the effective-length factor (pinned–pinned = 1.0, fixed–free = 2.0,
73    /// fixed–fixed = 0.5, fixed–pinned ≈ 0.699). Genuine closed-form column
74    /// stability — not a fabricated value. Fills and returns [`EigenvalueBuckling`]:
75    /// `critical_loads` ascending, each [`BucklingMode`] carrying the exact
76    /// buckling half-wave `sin(nπx/L)` sampled at 11 equally-spaced stations.
77    pub fn analyze_euler(
78        &mut self,
79        youngs_modulus: f64,
80        moment_of_inertia: f64,
81        length: f64,
82        effective_length_factor: f64,
83        num_modes: usize,
84    ) -> Result<EigenvalueBuckling, EngineeringError> {
85        if youngs_modulus <= 0.0 || moment_of_inertia <= 0.0 {
86            return Err(EngineeringError::InsufficientData(
87                "Young's modulus and moment of inertia must be positive".to_string(),
88            ));
89        }
90        if length <= 0.0 || effective_length_factor <= 0.0 {
91            return Err(EngineeringError::ValidationError(
92                "length and effective-length factor must be positive".to_string(),
93            ));
94        }
95        if num_modes == 0 {
96            return Err(EngineeringError::InsufficientData(
97                "num_modes must be at least 1".to_string(),
98            ));
99        }
100        let le = effective_length_factor * length;
101        let base = std::f64::consts::PI.powi(2) * youngs_modulus * moment_of_inertia / (le * le);
102        const STATIONS: usize = 11;
103        let mut critical_loads = Vec::with_capacity(num_modes);
104        let mut buckling_modes = Vec::with_capacity(num_modes);
105        for n in 1..=num_modes {
106            let p_cr = (n as f64).powi(2) * base;
107            let shape: Vec<f64> = (0..STATIONS)
108                .map(|s| {
109                    let x = length * s as f64 / (STATIONS as f64 - 1.0);
110                    (n as f64 * std::f64::consts::PI * x / length).sin()
111                })
112                .collect();
113            critical_loads.push(p_cr);
114            buckling_modes.push(BucklingMode {
115                mode_number: n as u32,
116                critical_load: p_cr,
117                mode_shape: shape,
118            });
119        }
120        let eb = EigenvalueBuckling {
121            critical_loads,
122            buckling_modes,
123        };
124        self.eigenvalue_buckling = eb.clone();
125        Ok(eb)
126    }
127
128    /// Euler critical buckling of the member described by `model`: uses the first
129    /// material's Young's modulus, the weak-axis second moment of area of the
130    /// rectangular cross-section `I = min(b·h³, h·b³)/12` from the first two
131    /// geometry dimensions `[b, h]`, the third dimension as the member length `L`,
132    /// and a pinned–pinned effective-length factor `K = 1.0`. Returns the first
133    /// `num_modes` critical loads. Missing/degenerate inputs → `InsufficientData`.
134    pub fn analyze_from_model(
135        &mut self,
136        model: &EngineeringModel,
137        num_modes: usize,
138    ) -> Result<EigenvalueBuckling, EngineeringError> {
139        let material = model.materials.values().next().ok_or_else(|| {
140            EngineeringError::InsufficientData(
141                "model has no material; cannot compute buckling load".to_string(),
142            )
143        })?;
144        let e = material.material_properties.youngs_modulus;
145        let dims = &model.geometry.dimensions;
146        if dims.len() < 3 || dims.iter().take(3).any(|&d| !(d > 0.0)) {
147            return Err(EngineeringError::InsufficientData(
148                "geometry needs three positive dimensions [b, h, L] for column buckling"
149                    .to_string(),
150            ));
151        }
152        let (b, h, l) = (dims[0], dims[1], dims[2]);
153        let i_weak = (b * h * h * h).min(h * b * b * b) / 12.0;
154        self.analyze_euler(e, i_weak, l, 1.0, num_modes)
155    }
156}
157
158impl EigenvalueBuckling {
159    pub fn new() -> Self {
160        Self {
161            critical_loads: Vec::new(),
162            buckling_modes: Vec::new(),
163        }
164    }
165}
166
167impl NonlinearBuckling {
168    pub fn new() -> Self {
169        Self {
170            load_displacement_curve: Vec::new(),
171            post_buckling_behavior: PostBucklingBehavior::Stable,
172        }
173    }
174}