Skip to main content

qualia_core_db/specialized_libs/engineering_analysis/
dynamics.rs

1use super::*;
2
3/// Structural dynamics
4pub struct StructuralDynamics {
5    modal_analysis: ModalAnalysis,
6    transient_analysis: TransientAnalysis,
7    harmonic_analysis: HarmonicAnalysis,
8}
9
10/// Modal analysis
11pub struct ModalAnalysis {
12    eigenvalue_solver: EigenvalueSolver,
13    mode_shapes: Vec<ModeShape>,
14    modal_parameters: ModalParameters,
15}
16
17/// Eigenvalue solver
18#[derive(Debug, Clone)]
19pub struct EigenvalueSolver {
20    pub solver_type: EigenvalueSolverType,
21    pub num_modes: u32,
22    pub frequency_range: (f64, f64),
23}
24
25/// Eigenvalue solver types
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27pub enum EigenvalueSolverType {
28    Lanczos,
29    Subspace,
30    Power,
31    QR,
32}
33
34/// Mode shapes
35#[derive(Debug, Clone)]
36pub struct ModeShape {
37    pub mode_number: u32,
38    pub natural_frequency: f64,
39    pub damping_ratio: f64,
40    pub mode_shape_vector: Vec<f64>,
41}
42
43/// Modal parameters
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ModalParameters {
46    pub mass_normalization: bool,
47    pub participation_factors: Vec<f64>,
48    pub effective_mass: Vec<f64>,
49}
50
51/// Transient analysis
52pub struct TransientAnalysis {
53    time_integration: TimeIntegration,
54    loading_history: LoadingHistory,
55    response_calculation: ResponseCalculation,
56}
57
58/// Time integration
59#[derive(Debug, Clone)]
60pub struct TimeIntegration {
61    pub integration_method: IntegrationMethod,
62    pub time_step: f64,
63    pub total_time: f64,
64}
65
66/// Integration methods
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68pub enum IntegrationMethod {
69    CentralDifference,
70    Newmark,
71    WilsonTheta,
72    HilberHughesTaylor,
73}
74
75/// Loading history
76#[derive(Debug, Clone)]
77pub struct LoadingHistory {
78    pub time_points: Vec<f64>,
79    pub load_values: Vec<f64>,
80    pub load_type: LoadType,
81}
82
83/// Load types
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub enum LoadType {
86    Force,
87    Displacement,
88    Acceleration,
89    Pressure,
90    Point,
91}
92
93/// Response calculation
94#[derive(Debug, Clone)]
95pub struct ResponseCalculation {
96    pub response_types: Vec<ResponseType>,
97    pub calculation_method: CalculationMethod,
98}
99
100/// Response types
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
102pub enum ResponseType {
103    Displacement,
104    Velocity,
105    Acceleration,
106    Stress,
107    Strain,
108}
109
110/// Calculation methods
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
112pub enum CalculationMethod {
113    Direct,
114    Modal,
115    FrequencyDomain,
116}
117
118/// Harmonic analysis
119pub struct HarmonicAnalysis {
120    frequency_response: FrequencyResponse,
121    resonance_detection: ResonanceDetection,
122}
123
124/// Frequency response
125#[derive(Debug, Clone)]
126pub struct FrequencyResponse {
127    pub frequencies: Vec<f64>,
128    pub response_amplitudes: Vec<f64>,
129    pub response_phases: Vec<f64>,
130}
131
132/// Resonance detection
133#[derive(Debug, Clone)]
134pub struct ResonanceDetection {
135    pub resonance_frequencies: Vec<f64>,
136    pub resonance_amplitudes: Vec<f64>,
137    pub quality_factors: Vec<f64>,
138}
139impl StructuralDynamics {
140    pub fn new() -> Self {
141        Self {
142            modal_analysis: ModalAnalysis::new(),
143            transient_analysis: TransientAnalysis::new(),
144            harmonic_analysis: HarmonicAnalysis::new(),
145        }
146    }
147
148    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
149        Ok(())
150    }
151
152    /// Borrow the modal-analysis sub-component.
153    pub fn modal_analysis(&self) -> &ModalAnalysis {
154        &self.modal_analysis
155    }
156
157    /// Mutably borrow the modal-analysis sub-component.
158    pub fn modal_analysis_mut(&mut self) -> &mut ModalAnalysis {
159        &mut self.modal_analysis
160    }
161
162    /// Borrow the transient-analysis sub-component.
163    pub fn transient_analysis(&self) -> &TransientAnalysis {
164        &self.transient_analysis
165    }
166
167    /// Mutably borrow the transient-analysis sub-component.
168    pub fn transient_analysis_mut(&mut self) -> &mut TransientAnalysis {
169        &mut self.transient_analysis
170    }
171
172    /// Borrow the harmonic-analysis sub-component.
173    pub fn harmonic_analysis(&self) -> &HarmonicAnalysis {
174        &self.harmonic_analysis
175    }
176
177    /// Mutably borrow the harmonic-analysis sub-component.
178    pub fn harmonic_analysis_mut(&mut self) -> &mut HarmonicAnalysis {
179        &mut self.harmonic_analysis
180    }
181
182    /// Genuine transient time-history analysis for a 1-DOF system (m, c, k) driven
183    /// by the configured `loading_history`. Uses explicit integration with `time_step`
184    /// up to `total_time` from the `time_integration` configuration.
185    pub fn analyze_transient(
186        &self,
187        mass: f64,
188        stiffness: f64,
189        damping: f64,
190    ) -> Result<DynamicsResults, EngineeringError> {
191        if mass <= 0.0 {
192            return Err(EngineeringError::ValidationError(
193                "mass must be positive".to_string(),
194            ));
195        }
196        let ti = &self.transient_analysis.time_integration;
197        let lh = &self.transient_analysis.loading_history;
198        if ti.time_step <= 0.0 || ti.total_time <= 0.0 {
199            return Err(EngineeringError::ValidationError(
200                "time_step and total_time must be positive".to_string(),
201            ));
202        }
203
204        let num_steps = (ti.total_time / ti.time_step).ceil() as usize;
205        let mut positions = Vec::with_capacity(num_steps + 1);
206        let mut velocities = Vec::with_capacity(num_steps + 1);
207        let mut accelerations = Vec::with_capacity(num_steps + 1);
208
209        let mut pos = 0.0;
210        let mut vel = 0.0;
211        let dt = ti.time_step;
212
213        for i in 0..=num_steps {
214            let t = i as f64 * dt;
215
216            // Interpolate force from loading history
217            let mut force = 0.0;
218            if !lh.time_points.is_empty() && lh.time_points.len() == lh.load_values.len() {
219                if t <= lh.time_points[0] {
220                    force = lh.load_values[0];
221                } else if t >= *lh.time_points.last().unwrap() {
222                    force = *lh.load_values.last().unwrap();
223                } else {
224                    for j in 0..lh.time_points.len() - 1 {
225                        if t >= lh.time_points[j] && t <= lh.time_points[j + 1] {
226                            let dt_int = lh.time_points[j + 1] - lh.time_points[j];
227                            let df = lh.load_values[j + 1] - lh.load_values[j];
228                            let frac = (t - lh.time_points[j]) / dt_int;
229                            force = lh.load_values[j] + df * frac;
230                            break;
231                        }
232                    }
233                }
234            }
235
236            let acc = (force - damping * vel - stiffness * pos) / mass;
237
238            positions.push(pos);
239            velocities.push(vel);
240            accelerations.push(acc);
241
242            // Symplectic Euler step
243            vel += acc * dt;
244            pos += vel * dt;
245        }
246
247        let final_pos = positions.last().copied().unwrap_or(0.0);
248        let final_vel = velocities.last().copied().unwrap_or(0.0);
249        let ke = 0.5 * mass * final_vel * final_vel;
250        let pe = 0.5 * stiffness * final_pos * final_pos;
251        Ok(DynamicsResults {
252            positions,
253            velocities,
254            accelerations,
255            kinetic_energy: ke,
256            potential_energy: pe,
257            total_energy: ke + pe,
258            time_steps: (0..=num_steps).map(|i| i as f64 * dt).collect(),
259        })
260    }
261}
262
263impl ModalAnalysis {
264    pub fn new() -> Self {
265        Self {
266            eigenvalue_solver: EigenvalueSolver::new(),
267            mode_shapes: Vec::new(),
268            modal_parameters: ModalParameters::new(),
269        }
270    }
271
272    /// Borrow the eigenvalue solver configuration.
273    pub fn eigenvalue_solver(&self) -> &EigenvalueSolver {
274        &self.eigenvalue_solver
275    }
276
277    /// Mutably borrow the eigenvalue solver configuration.
278    pub fn eigenvalue_solver_mut(&mut self) -> &mut EigenvalueSolver {
279        &mut self.eigenvalue_solver
280    }
281
282    /// Append a computed mode shape to the results.
283    pub fn add_mode_shape(&mut self, mode: ModeShape) {
284        self.mode_shapes.push(mode);
285    }
286
287    /// Borrow the computed mode shapes.
288    pub fn mode_shapes(&self) -> &[ModeShape] {
289        &self.mode_shapes
290    }
291
292    /// Borrow the modal parameters.
293    pub fn modal_parameters(&self) -> &ModalParameters {
294        &self.modal_parameters
295    }
296
297    /// Mutably borrow the modal parameters.
298    pub fn modal_parameters_mut(&mut self) -> &mut ModalParameters {
299        &mut self.modal_parameters
300    }
301
302    /// Undamped modal analysis: solves the generalized eigenproblem
303    /// `K φ = ω² M φ` for a symmetric stiffness matrix `stiffness` (row-major
304    /// `num_dofs × num_dofs`) and a lumped (diagonal) mass matrix `mass_diag`
305    /// (`num_dofs` positive entries), wired to the crate's symmetric Jacobi
306    /// eigensolver via [`solve_modal_eigen`]. Returns one [`ModeShape`] per DOF,
307    /// ordered by ascending **natural angular frequency ω (rad/s)** (stored in
308    /// `ModeShape::natural_frequency`), with zero damping (undamped) and the
309    /// mass-normalized mode-shape vector (unit maximum component). The result is
310    /// also cached in `self.mode_shapes`.
311    pub fn analyze_modal(
312        &mut self,
313        stiffness: &[f64],
314        mass_diag: &[f64],
315        num_dofs: usize,
316    ) -> Result<Vec<ModeShape>, EngineeringError> {
317        let modes = solve_modal_eigen(stiffness, mass_diag, num_dofs)?;
318        let shapes: Vec<ModeShape> = modes
319            .into_iter()
320            .enumerate()
321            .map(|(i, (omega, phi))| ModeShape {
322                mode_number: (i + 1) as u32,
323                natural_frequency: omega,
324                damping_ratio: 0.0,
325                mode_shape_vector: phi,
326            })
327            .collect();
328        self.mode_shapes = shapes.clone();
329        Ok(shapes)
330    }
331}
332
333impl EigenvalueSolver {
334    pub fn new() -> Self {
335        Self {
336            solver_type: EigenvalueSolverType::Lanczos,
337            num_modes: 10,
338            frequency_range: (0.0, 1000.0),
339        }
340    }
341}
342
343impl ModalParameters {
344    pub fn new() -> Self {
345        Self {
346            mass_normalization: true,
347            participation_factors: Vec::new(),
348            effective_mass: Vec::new(),
349        }
350    }
351}
352
353impl TransientAnalysis {
354    pub fn new() -> Self {
355        Self {
356            time_integration: TimeIntegration::new(),
357            loading_history: LoadingHistory::new(),
358            response_calculation: ResponseCalculation::new(),
359        }
360    }
361
362    /// Borrow the time-integration configuration.
363    pub fn time_integration(&self) -> &TimeIntegration {
364        &self.time_integration
365    }
366
367    /// Mutably borrow the time-integration configuration.
368    pub fn time_integration_mut(&mut self) -> &mut TimeIntegration {
369        &mut self.time_integration
370    }
371
372    /// Borrow the loading history.
373    pub fn loading_history(&self) -> &LoadingHistory {
374        &self.loading_history
375    }
376
377    /// Mutably borrow the loading history.
378    pub fn loading_history_mut(&mut self) -> &mut LoadingHistory {
379        &mut self.loading_history
380    }
381
382    /// Borrow the response-calculation configuration.
383    pub fn response_calculation(&self) -> &ResponseCalculation {
384        &self.response_calculation
385    }
386
387    /// Mutably borrow the response-calculation configuration.
388    pub fn response_calculation_mut(&mut self) -> &mut ResponseCalculation {
389        &mut self.response_calculation
390    }
391}
392
393impl TimeIntegration {
394    pub fn new() -> Self {
395        Self {
396            integration_method: IntegrationMethod::Newmark,
397            time_step: 0.01,
398            total_time: 10.0,
399        }
400    }
401}
402
403impl LoadingHistory {
404    pub fn new() -> Self {
405        Self {
406            time_points: Vec::new(),
407            load_values: Vec::new(),
408            load_type: LoadType::Force,
409        }
410    }
411}
412
413impl ResponseCalculation {
414    pub fn new() -> Self {
415        Self {
416            response_types: vec![ResponseType::Displacement, ResponseType::Stress],
417            calculation_method: CalculationMethod::Modal,
418        }
419    }
420}
421
422impl HarmonicAnalysis {
423    pub fn new() -> Self {
424        Self {
425            frequency_response: FrequencyResponse::new(),
426            resonance_detection: ResonanceDetection::new(),
427        }
428    }
429
430    /// Borrow the frequency-response data.
431    pub fn frequency_response(&self) -> &FrequencyResponse {
432        &self.frequency_response
433    }
434
435    /// Mutably borrow the frequency-response data.
436    pub fn frequency_response_mut(&mut self) -> &mut FrequencyResponse {
437        &mut self.frequency_response
438    }
439
440    /// Borrow the resonance-detection data.
441    pub fn resonance_detection(&self) -> &ResonanceDetection {
442        &self.resonance_detection
443    }
444
445    /// Mutably borrow the resonance-detection data.
446    pub fn resonance_detection_mut(&mut self) -> &mut ResonanceDetection {
447        &mut self.resonance_detection
448    }
449}
450
451impl FrequencyResponse {
452    pub fn new() -> Self {
453        Self {
454            frequencies: Vec::new(),
455            response_amplitudes: Vec::new(),
456            response_phases: Vec::new(),
457        }
458    }
459}
460
461impl ResonanceDetection {
462    pub fn new() -> Self {
463        Self {
464            resonance_frequencies: Vec::new(),
465            resonance_amplitudes: Vec::new(),
466            quality_factors: Vec::new(),
467        }
468    }
469}