Skip to main content

qualia_core_db/specialized_libs/engineering_analysis/
vibration.rs

1use super::*;
2
3/// Vibration analysis
4pub struct VibrationAnalysis {
5    free_vibration: FreeVibration,
6    forced_vibration: ForcedVibration,
7    random_vibration: RandomVibration,
8}
9
10/// Free vibration
11#[derive(Debug, Clone)]
12pub struct FreeVibration {
13    pub natural_frequencies: Vec<f64>,
14    pub mode_shapes: Vec<ModeShape>,
15    pub damping_ratios: Vec<f64>,
16}
17
18/// Forced vibration
19#[derive(Debug, Clone)]
20pub struct ForcedVibration {
21    pub excitation_frequencies: Vec<f64>,
22    pub response_amplitudes: Vec<f64>,
23    pub phase_angles: Vec<f64>,
24}
25
26/// Random vibration
27#[derive(Debug, Clone)]
28pub struct RandomVibration {
29    pub power_spectral_density: Vec<f64>,
30    pub rms_response: f64,
31    pub fatigue_damage: f64,
32}
33impl VibrationAnalysis {
34    pub fn new() -> Self {
35        Self {
36            free_vibration: FreeVibration::new(),
37            forced_vibration: ForcedVibration::new(),
38            random_vibration: RandomVibration::new(),
39        }
40    }
41
42    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
43        Ok(())
44    }
45
46    /// Borrow the free-vibration sub-component.
47    pub fn free_vibration(&self) -> &FreeVibration {
48        &self.free_vibration
49    }
50
51    /// Mutably borrow the free-vibration sub-component.
52    pub fn free_vibration_mut(&mut self) -> &mut FreeVibration {
53        &mut self.free_vibration
54    }
55
56    /// Borrow the forced-vibration sub-component.
57    pub fn forced_vibration(&self) -> &ForcedVibration {
58        &self.forced_vibration
59    }
60
61    /// Mutably borrow the forced-vibration sub-component.
62    pub fn forced_vibration_mut(&mut self) -> &mut ForcedVibration {
63        &mut self.forced_vibration
64    }
65
66    /// Borrow the random-vibration sub-component.
67    pub fn random_vibration(&self) -> &RandomVibration {
68        &self.random_vibration
69    }
70
71    /// Mutably borrow the random-vibration sub-component.
72    pub fn random_vibration_mut(&mut self) -> &mut RandomVibration {
73        &mut self.random_vibration
74    }
75
76    /// Undamped free-vibration analysis of an `num_dofs`-DOF lumped-mass system.
77    /// Delegates to the same generalized eigenproblem as modal analysis
78    /// (`K φ = ω² M φ`, wired to `symmetric_eigen` via [`solve_modal_eigen`]) and
79    /// packs the result into [`FreeVibration`]: `natural_frequencies` are the
80    /// **natural angular frequencies ω (rad/s), ascending**, with their mass-
81    /// normalized mode shapes and zero damping ratios (undamped). Cached into
82    /// `self.free_vibration`.
83    pub fn analyze_free(
84        &mut self,
85        stiffness: &[f64],
86        mass_diag: &[f64],
87        num_dofs: usize,
88    ) -> Result<FreeVibration, EngineeringError> {
89        let modes = solve_modal_eigen(stiffness, mass_diag, num_dofs)?;
90        let mut natural_frequencies = Vec::with_capacity(modes.len());
91        let mut mode_shapes = Vec::with_capacity(modes.len());
92        for (i, (omega, phi)) in modes.into_iter().enumerate() {
93            natural_frequencies.push(omega);
94            mode_shapes.push(ModeShape {
95                mode_number: (i + 1) as u32,
96                natural_frequency: omega,
97                damping_ratio: 0.0,
98                mode_shape_vector: phi,
99            });
100        }
101        let damping_ratios = vec![0.0; natural_frequencies.len()];
102        let fv = FreeVibration {
103            natural_frequencies,
104            mode_shapes,
105            damping_ratios,
106        };
107        self.free_vibration = fv.clone();
108        Ok(fv)
109    }
110
111    /// Single-DOF undamped natural angular frequency `ω = √(k/m)` (rad/s).
112    pub fn natural_frequency_sdof(
113        &self,
114        stiffness: f64,
115        mass: f64,
116    ) -> Result<f64, EngineeringError> {
117        if mass <= 0.0 {
118            return Err(EngineeringError::ValidationError(
119                "mass must be positive".to_string(),
120            ));
121        }
122        if stiffness < 0.0 {
123            return Err(EngineeringError::ValidationError(
124                "stiffness must be non-negative".to_string(),
125            ));
126        }
127        Ok((stiffness / mass).sqrt())
128    }
129
130    /// Steady-state harmonic (forced-vibration) response of a damped single-DOF
131    /// oscillator `m·ẍ + c·ẋ + k·x = F₀·sin(ωt)`. For each excitation angular
132    /// frequency ω (rad/s) in `excitation_freqs`, returns the response amplitude
133    /// `X(ω) = F₀ / √((k − m·ω²)² + (c·ω)²)` and the phase lag
134    /// `φ(ω) = atan2(c·ω, k − m·ω²)` (rad). Genuine closed-form frequency-response
135    /// function; no fabricated values. Fills and returns [`ForcedVibration`].
136    pub fn analyze_harmonic_sdof(
137        &mut self,
138        mass: f64,
139        damping: f64,
140        stiffness: f64,
141        force_amplitude: f64,
142        excitation_freqs: &[f64],
143    ) -> Result<ForcedVibration, EngineeringError> {
144        if mass <= 0.0 {
145            return Err(EngineeringError::ValidationError(
146                "mass must be positive".to_string(),
147            ));
148        }
149        if damping < 0.0 || stiffness < 0.0 {
150            return Err(EngineeringError::ValidationError(
151                "damping and stiffness must be non-negative".to_string(),
152            ));
153        }
154        if excitation_freqs.is_empty() {
155            return Err(EngineeringError::InsufficientData(
156                "no excitation frequencies supplied".to_string(),
157            ));
158        }
159        let mut response_amplitudes = Vec::with_capacity(excitation_freqs.len());
160        let mut phase_angles = Vec::with_capacity(excitation_freqs.len());
161        for &w in excitation_freqs {
162            let re = stiffness - mass * w * w;
163            let im = damping * w;
164            let denom = (re * re + im * im).sqrt();
165            let amp = if denom > 0.0 {
166                force_amplitude / denom
167            } else {
168                f64::INFINITY
169            };
170            response_amplitudes.push(amp);
171            phase_angles.push(im.atan2(re));
172        }
173        let fv = ForcedVibration {
174            excitation_frequencies: excitation_freqs.to_vec(),
175            response_amplitudes,
176            phase_angles,
177        };
178        self.forced_vibration = fv.clone();
179        Ok(fv)
180    }
181}
182
183impl FreeVibration {
184    pub fn new() -> Self {
185        Self {
186            natural_frequencies: Vec::new(),
187            mode_shapes: Vec::new(),
188            damping_ratios: Vec::new(),
189        }
190    }
191}
192
193impl ForcedVibration {
194    pub fn new() -> Self {
195        Self {
196            excitation_frequencies: Vec::new(),
197            response_amplitudes: Vec::new(),
198            phase_angles: Vec::new(),
199        }
200    }
201}
202
203impl RandomVibration {
204    pub fn new() -> Self {
205        Self {
206            power_spectral_density: Vec::new(),
207            rms_response: 0.0,
208            fatigue_damage: 0.0,
209        }
210    }
211}