Skip to main content

qualia_core_db/specialized_libs/engineering_analysis/
structural.rs

1use super::*;
2
3/// Structural analyzer for structural engineering analysis
4pub struct StructuralAnalyzer {
5    pub(super) finite_element_solver: FiniteElementSolver,
6    structural_dynamics: StructuralDynamics,
7    buckling_analysis: BucklingAnalysis,
8    vibration_analysis: VibrationAnalysis,
9    model_store: HashMap<String, EngineeringModel>,
10    /// Phase 2 linear-algebra library used for FEA matrix assembly / solves.
11    linear_algebra: Option<Arc<Mutex<LinearAlgebraLibrary>>>,
12}
13
14/// Finite element solver
15pub struct FiniteElementSolver {
16    mesh_generator: MeshGenerator,
17    element_library: ElementLibrary,
18    solver_engine: SolverEngine,
19    post_processor: PostProcessor,
20    /// ZNS zone manager for zero-copy mesh / element storage.
21    zns_manager: Option<Arc<Mutex<ZnsZoneManager>>>,
22}
23
24/// Mesh generator
25pub struct MeshGenerator {
26    mesh_types: HashMap<String, MeshType>,
27    mesh_algorithms: HashMap<String, MeshAlgorithm>,
28    mesh_quality: MeshQuality,
29}
30
31/// Mesh types
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub enum MeshType {
34    /// Triangular mesh
35    Triangular,
36    /// Quadrilateral mesh
37    Quadrilateral,
38    /// Tetrahedral mesh
39    Tetrahedral,
40    /// Hexahedral mesh
41    Hexahedral,
42    /// Mixed mesh
43    Mixed,
44    /// Structured mesh
45    Structured,
46    /// Unstructured mesh
47    Unstructured,
48}
49
50/// Mesh algorithms
51#[derive(Debug, Clone)]
52pub struct MeshAlgorithm {
53    pub algorithm_id: String,
54    pub algorithm_name: String,
55    pub algorithm_type: MeshAlgorithmType,
56    pub parameters: MeshAlgorithmParameters,
57}
58
59/// Mesh algorithm types
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61pub enum MeshAlgorithmType {
62    Delaunay,
63    AdvancingFront,
64    Octree,
65    Cartesian,
66    Custom(String),
67}
68
69/// Mesh algorithm parameters
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct MeshAlgorithmParameters {
72    pub element_size: f64,
73    pub refinement_level: u32,
74    pub quality_criteria: Vec<QualityCriterion>,
75}
76
77/// Quality criteria
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct QualityCriterion {
80    pub criterion_name: String,
81    pub minimum_value: f64,
82    pub maximum_value: f64,
83}
84
85/// Mesh quality
86pub struct MeshQuality {
87    pub quality_metrics: HashMap<String, QualityMetric>,
88    pub quality_assessment: QualityAssessment,
89}
90
91/// Quality metrics
92#[derive(Debug, Clone)]
93pub struct QualityMetric {
94    pub metric_name: String,
95    pub metric_value: f64,
96    pub metric_type: MetricType,
97}
98
99/// Metric types
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101pub enum MetricType {
102    AspectRatio,
103    Skewness,
104    Orthogonality,
105    Jacobian,
106}
107
108/// Quality assessment
109#[derive(Debug, Clone)]
110pub struct QualityAssessment {
111    pub overall_quality: f64,
112    pub quality_grade: QualityGrade,
113    pub recommendations: Vec<String>,
114}
115
116/// Quality grades
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118pub enum QualityGrade {
119    Excellent,
120    Good,
121    Fair,
122    Poor,
123}
124
125/// Element library
126pub struct ElementLibrary {
127    elements: HashMap<String, Element>,
128    element_properties: HashMap<String, ElementProperties>,
129}
130
131/// Elements
132#[derive(Debug, Clone)]
133pub struct Element {
134    pub element_id: String,
135    pub element_name: String,
136    pub element_type: ElementType,
137    pub nodes: Vec<Node>,
138    pub properties: ElementProperties,
139}
140
141/// Element types
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143pub enum ElementType {
144    /// 1D elements
145    Truss,
146    Beam,
147    Frame,
148    /// 2D elements
149    Shell,
150    Plate,
151    Membrane,
152    /// 3D elements
153    Solid,
154    Tetrahedron,
155    Hexahedron,
156    /// Special elements
157    Mass,
158    Spring,
159    Damper,
160}
161
162/// Nodes
163#[derive(Debug, Clone)]
164pub struct Node {
165    pub node_id: String,
166    pub coordinates: Vec<f64>,
167    pub degrees_of_freedom: Vec<DOF>,
168    pub constraints: Vec<Constraint>,
169}
170
171/// Degrees of freedom
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173pub enum DOF {
174    UX,
175    UY,
176    UZ,
177    ROTX,
178    ROTY,
179    ROTZ,
180    Temperature,
181    Pressure,
182}
183
184/// Constraints
185#[derive(Debug, Clone)]
186pub struct Constraint {
187    pub constraint_id: String,
188    pub constraint_type: ConstraintType,
189    pub constraint_value: f64,
190}
191
192/// Constraint types
193#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
194pub enum ConstraintType {
195    Fixed,
196    Pinned,
197    Roller,
198    Displacement,
199    Rotation,
200    Temperature,
201}
202
203/// Element properties
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct ElementProperties {
206    pub material_properties: MaterialProperties,
207    pub geometric_properties: GeometricProperties,
208    pub section_properties: SectionProperties,
209}
210
211/// Material properties
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct MaterialProperties {
214    pub youngs_modulus: f64,
215    pub poissons_ratio: f64,
216    pub density: f64,
217    pub thermal_expansion: f64,
218    pub thermal_conductivity: f64,
219    pub specific_heat: f64,
220    pub yield_strength: f64,
221    pub ultimate_strength: f64,
222}
223
224/// Geometric properties
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct GeometricProperties {
227    pub area: f64,
228    pub volume: f64,
229    pub perimeter: f64,
230    pub surface_area: f64,
231}
232
233/// Section properties
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct SectionProperties {
236    pub moment_of_inertia: Vec<f64>,
237    pub torsional_constant: f64,
238    pub section_modulus: Vec<f64>,
239    pub shear_center: Vec<f64>,
240}
241
242/// Solver engine
243pub struct SolverEngine {
244    solvers: HashMap<String, Solver>,
245    solver_parameters: SolverParameters,
246    convergence_criteria: ConvergenceCriteria,
247}
248
249/// Solvers
250#[derive(Debug, Clone)]
251pub struct Solver {
252    pub solver_id: String,
253    pub solver_name: String,
254    pub solver_type: SolverType,
255    pub capabilities: SolverCapabilities,
256}
257
258/// Solver types
259#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
260pub enum SolverType {
261    Direct,
262    Iterative,
263    Eigenvalue,
264    Transient,
265    Nonlinear,
266}
267
268/// Solver capabilities
269#[derive(Debug, Clone)]
270pub struct SolverCapabilities {
271    pub max_dof: u64,
272    pub supported_element_types: Vec<ElementType>,
273    pub analysis_types: Vec<AnalysisType>,
274}
275
276/// Analysis types
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278pub enum AnalysisType {
279    LinearStatic,
280    NonlinearStatic,
281    LinearDynamic,
282    NonlinearDynamic,
283    Thermal,
284    Buckling,
285    Vibration,
286}
287
288/// Solver parameters
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct SolverParameters {
291    pub tolerance: f64,
292    pub max_iterations: u32,
293    pub convergence_acceleration: ConvergenceAcceleration,
294}
295
296/// Convergence acceleration
297#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
298pub enum ConvergenceAcceleration {
299    None,
300    Jacobi,
301    GaussSeidel,
302    SOR,
303    Multigrid,
304}
305
306/// Convergence criteria
307pub struct ConvergenceCriteria {
308    pub criteria_type: ConvergenceType,
309    pub tolerance: f64,
310    pub max_iterations: u32,
311}
312
313/// Convergence types
314#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
315pub enum ConvergenceType {
316    Residual,
317    Energy,
318    Displacement,
319    Force,
320}
321
322/// Post processor
323pub struct PostProcessor {
324    result_extractors: HashMap<String, ResultExtractor>,
325    visualization_engine: VisualizationEngine,
326    report_generator: ReportGenerator,
327}
328
329/// Result extractors
330#[derive(Debug, Clone)]
331pub struct ResultExtractor {
332    pub extractor_id: String,
333    pub extractor_name: String,
334    pub result_type: ResultType,
335    pub extraction_method: ExtractionMethod,
336}
337
338/// Result types
339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
340pub enum ResultType {
341    Displacement,
342    Stress,
343    Strain,
344    Force,
345    Reaction,
346    Energy,
347    Temperature,
348    HeatFlux,
349}
350
351/// Extraction methods
352#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
353pub enum ExtractionMethod {
354    Nodal,
355    Elemental,
356    Gaussian,
357    Custom(String),
358}
359
360/// Visualization engine
361#[derive(Debug, Clone)]
362pub struct VisualizationEngine {
363    visualization_types: HashMap<String, VisualizationType>,
364    rendering_engine: RenderingEngine,
365}
366
367/// Visualization types
368#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
369pub enum VisualizationType {
370    Contour,
371    Vector,
372    Deformed,
373    Animation,
374    Custom(String),
375}
376
377/// Rendering engine
378#[derive(Debug, Clone)]
379pub struct RenderingEngine {
380    pub engine_type: RenderingEngineType,
381    pub rendering_options: RenderingOptions,
382}
383
384/// Rendering engine types
385#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
386pub enum RenderingEngineType {
387    OpenGL,
388    Vulkan,
389    DirectX,
390    Software,
391}
392
393/// Rendering options
394#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct RenderingOptions {
396    pub color_map: String,
397    pub scale_factor: f64,
398    pub line_width: f64,
399    pub transparency: f64,
400}
401
402/// Report generator
403pub struct ReportGenerator {
404    report_templates: HashMap<String, ReportTemplate>,
405    export_formats: Vec<ExportFormat>,
406}
407
408/// Report templates
409#[derive(Debug, Clone)]
410pub struct ReportTemplate {
411    pub template_id: String,
412    pub template_name: String,
413    pub template_type: TemplateType,
414    pub sections: Vec<ReportSection>,
415}
416
417/// Template types
418#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
419pub enum TemplateType {
420    Summary,
421    Detailed,
422    Technical,
423    Executive,
424}
425
426/// Report sections
427#[derive(Debug, Clone)]
428pub struct ReportSection {
429    pub section_id: String,
430    pub section_name: String,
431    pub section_content: SectionContent,
432}
433
434/// Section content
435#[derive(Debug, Clone)]
436pub struct SectionContent {
437    pub content_type: ContentType,
438    pub data: Vec<u8>,
439    pub format: ContentFormat,
440}
441
442/// Content types
443#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
444pub enum ContentType {
445    Text,
446    Table,
447    Chart,
448    Image,
449}
450
451/// Content formats
452#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
453pub enum ContentFormat {
454    Text,
455    HTML,
456    PDF,
457    CSV,
458}
459
460/// Export formats
461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
462pub enum ExportFormat {
463    PDF,
464    HTML,
465    CSV,
466    JSON,
467    XML,
468}
469// Supporting implementations
470
471impl StructuralAnalyzer {
472    pub fn new() -> Self {
473        Self {
474            finite_element_solver: FiniteElementSolver::new(),
475            structural_dynamics: StructuralDynamics::new(),
476            buckling_analysis: BucklingAnalysis::new(),
477            vibration_analysis: VibrationAnalysis::new(),
478            model_store: HashMap::new(),
479            linear_algebra: None,
480        }
481    }
482
483    /// Attach the Phase 2 linear-algebra library for FEA matrix operations.
484    pub fn attach_linear_algebra(&mut self, lib: Option<Arc<Mutex<LinearAlgebraLibrary>>>) {
485        self.linear_algebra = lib;
486    }
487
488    pub fn store_model(&mut self, model: EngineeringModel) {
489        self.model_store.insert(model.model_id.clone(), model);
490    }
491
492    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
493        self.finite_element_solver.initialize()?;
494        self.structural_dynamics.initialize()?;
495        Ok(())
496    }
497
498    pub fn validate_model(&self, model: &EngineeringModel) -> Result<(), EngineeringError> {
499        if model.geometry.dimensions.is_empty() {
500            return Err(EngineeringError::ValidationError(
501                "Model must have dimensions".to_string(),
502            ));
503        }
504        Ok(())
505    }
506
507    pub fn analyze(
508        &mut self,
509        model: &EngineeringModel,
510        analysis_type: AnalysisType,
511    ) -> Result<AnalysisResults, EngineeringError> {
512        // REAL first-principles axial strength-of-materials (a real member analysis, not full FEA):
513        //   stress σ = F / A,  strain ε = σ / E,  axial deflection δ = F·L / (A·E),
514        //   factor of safety FoS = σ_yield / |σ|.
515        // The safety_factor is GENUINELY COMPUTED from the material yield strength and the applied
516        // stress — never a fabricated constant (previously a hardcoded 2.5). Missing inputs are
517        // reported as InsufficientData, not silently defaulted.
518        let material = model.materials.values().next().ok_or_else(|| {
519            EngineeringError::InsufficientData(
520                "model has no material; cannot compute stress / factor of safety".to_string(),
521            )
522        })?;
523        let mp = &material.material_properties;
524        let e = mp.youngs_modulus;
525        let sy = mp.yield_strength;
526        let density = mp.density;
527
528        let dims = &model.geometry.dimensions;
529        if dims.len() < 2 || dims.iter().take(2).any(|&d| !(d > 0.0)) {
530            return Err(EngineeringError::InsufficientData(
531                "geometry needs at least two positive cross-section dimensions to form an area"
532                    .to_string(),
533            ));
534        }
535        let area = dims[0] * dims[1]; // cross-sectional area (m²)
536        let length = dims.get(2).copied().filter(|&l| l > 0.0).unwrap_or(dims[0]); // member length (m)
537
538        if model.loads.is_empty() {
539            return Err(EngineeringError::InsufficientData(
540                "model has no loads; cannot compute stress".to_string(),
541            ));
542        }
543        let force: f64 = model.loads.iter().map(|l| l.load_magnitude).sum(); // total axial load (N)
544
545        let stress = force / area; // Pa
546        let strain = if e > 0.0 { stress / e } else { 0.0 };
547        let displacement = if e > 0.0 {
548            force * length / (area * e)
549        } else {
550            f64::INFINITY
551        };
552        let safety_factor = if stress.abs() > 0.0 && sy > 0.0 {
553            sy / stress.abs()
554        } else if stress.abs() == 0.0 {
555            f64::INFINITY // no load ⇒ unbounded margin
556        } else {
557            0.0 // no yield strength supplied ⇒ no defined margin
558        };
559
560        match analysis_type {
561            AnalysisType::LinearStatic => Ok(AnalysisResults {
562                results_id: "structural_axial".to_string(),
563                analysis_type,
564                displacement_field: vec![displacement],
565                stress_field: vec![stress],
566                strain_field: vec![strain],
567                reaction_forces: vec![-force], // static equilibrium reaction
568                safety_factor,
569                temperature_field: Vec::new(), // mechanical analysis — no thermal output
570                heat_flux_field: Vec::new(),
571            }),
572            AnalysisType::Buckling => {
573                // Euler elastic critical buckling of the same prismatic member,
574                // weak-axis second moment of area I = min(b·h³, h·b³)/12 from the
575                // two cross-section dimensions, pinned–pinned effective length K=1:
576                //   P_cr = π²·E·I / (K·L)².
577                // The reported `safety_factor` is the buckling LOAD FACTOR
578                // λ = P_cr / |P_applied| — the multiplier on the axial load at
579                // which the member buckles (this is exactly the physical margin
580                // against buckling, so it fits the `safety_factor` field). The
581                // critical load itself is exposed via
582                // `BucklingAnalysis::analyze_from_model`.
583                let b = dims[0];
584                let h = dims[1];
585                let i_weak = (b * h * h * h).min(h * b * b * b) / 12.0;
586                let k_factor = 1.0_f64;
587                let le = k_factor * length;
588                let p_cr = std::f64::consts::PI.powi(2) * e * i_weak / (le * le);
589                let load_factor = if force.abs() > 0.0 {
590                    p_cr / force.abs()
591                } else {
592                    f64::INFINITY
593                };
594                Ok(AnalysisResults {
595                    results_id: "structural_buckling_euler".to_string(),
596                    analysis_type,
597                    displacement_field: vec![displacement],
598                    stress_field: vec![stress],
599                    strain_field: vec![strain],
600                    reaction_forces: vec![-force],
601                    safety_factor: load_factor,
602                    temperature_field: Vec::new(),
603                    heat_flux_field: Vec::new(),
604                })
605            }
606            AnalysisType::NonlinearStatic => {
607                // Geometrically-nonlinear axial member (Green–Lagrange strain), fixed–
608                // free, solved by Newton–Raphson (`fem::GeoNonlinearBar`). Reduces to
609                // the linear δ = F·L/(A·E) for small loads and stiffens geometrically
610                // for large loads. Real assembly + iterative solve — no closed form.
611                let ea = e * area;
612                let bar = fem::GeoNonlinearBar { ea, length };
613                let u = bar.solve_static(force, 1e-10, 200)?;
614                let eps = bar.strain(u); // Green–Lagrange axial strain
615                let sigma = e * eps;
616                let sf = if sigma.abs() > 0.0 && sy > 0.0 {
617                    sy / sigma.abs()
618                } else if sigma.abs() == 0.0 {
619                    f64::INFINITY
620                } else {
621                    0.0
622                };
623                Ok(AnalysisResults {
624                    results_id: "structural_nonlinear_static_geom_bar".to_string(),
625                    analysis_type,
626                    displacement_field: vec![u],
627                    stress_field: vec![sigma],
628                    strain_field: vec![eps],
629                    reaction_forces: vec![-bar.internal_force(u)],
630                    safety_factor: sf,
631                    temperature_field: Vec::new(),
632                    heat_flux_field: Vec::new(),
633                })
634            }
635            AnalysisType::LinearDynamic => {
636                // Transient response of the axial member (SDOF: stiffness k = EA/L,
637                // lumped free-node mass m = ½ρAL) to a SUDDENLY-APPLIED constant axial
638                // load, integrated by Newmark-β (avg-acceleration). The reported field
639                // is the PEAK dynamic displacement over the response — for an undamped
640                // step load this is the classic dynamic amplification (→ 2× static).
641                if !(density > 0.0) {
642                    return Err(EngineeringError::InsufficientData(
643                        "dynamic analysis needs a positive material density".to_string(),
644                    ));
645                }
646                let k = e * area / length;
647                let m = density * area * length / 2.0;
648                let omega = (k / m).sqrt();
649                let period = 2.0 * std::f64::consts::PI / omega;
650                let dt = period / 200.0;
651                let nsteps = 260; // > half a period, captures the peak at ωt = π
652                let res = fem::newmark_linear(
653                    &[m],
654                    &[0.0],
655                    &[k],
656                    1,
657                    |_t| vec![force],
658                    &[0.0],
659                    &[0.0],
660                    dt,
661                    nsteps,
662                    0.25,
663                    0.5,
664                )?;
665                let u_peak = res.peak_abs(0);
666                let sigma = e * (u_peak / length); // peak axial stress
667                let sf = if sigma.abs() > 0.0 && sy > 0.0 {
668                    sy / sigma.abs()
669                } else if sigma.abs() == 0.0 {
670                    f64::INFINITY
671                } else {
672                    0.0
673                };
674                Ok(AnalysisResults {
675                    results_id: "structural_linear_dynamic_newmark".to_string(),
676                    analysis_type,
677                    displacement_field: vec![u_peak],
678                    stress_field: vec![sigma],
679                    strain_field: vec![u_peak / length],
680                    reaction_forces: vec![-k * u_peak], // peak base reaction
681                    safety_factor: sf,
682                    temperature_field: Vec::new(),
683                    heat_flux_field: Vec::new(),
684                })
685            }
686            AnalysisType::NonlinearDynamic => {
687                // As LinearDynamic, but the member is the geometrically-nonlinear bar,
688                // integrated by Newmark-β with an inner Newton–Raphson iteration each
689                // step (`fem::newmark_nonlinear`). Reports the peak dynamic response.
690                if !(density > 0.0) {
691                    return Err(EngineeringError::InsufficientData(
692                        "dynamic analysis needs a positive material density".to_string(),
693                    ));
694                }
695                let ea = e * area;
696                let bar = fem::GeoNonlinearBar { ea, length };
697                let k0 = ea / length; // small-strain stiffness for the period estimate
698                let m = density * area * length / 2.0;
699                let omega = (k0 / m).sqrt();
700                let period = 2.0 * std::f64::consts::PI / omega;
701                let dt = period / 200.0;
702                let nsteps = 260;
703                let res = fem::newmark_nonlinear(
704                    &[m],
705                    &[0.0],
706                    1,
707                    |u| vec![bar.internal_force(u[0])],
708                    |u| vec![bar.tangent(u[0])],
709                    |_t| vec![force],
710                    &[0.0],
711                    &[0.0],
712                    dt,
713                    nsteps,
714                    0.25,
715                    0.5,
716                    1e-10,
717                    100,
718                )?;
719                let u_peak = res.peak_abs(0);
720                let eps = bar.strain(u_peak);
721                let sigma = e * eps;
722                let sf = if sigma.abs() > 0.0 && sy > 0.0 {
723                    sy / sigma.abs()
724                } else if sigma.abs() == 0.0 {
725                    f64::INFINITY
726                } else {
727                    0.0
728                };
729                Ok(AnalysisResults {
730                    results_id: "structural_nonlinear_dynamic_newmark_newton".to_string(),
731                    analysis_type,
732                    displacement_field: vec![u_peak],
733                    stress_field: vec![sigma],
734                    strain_field: vec![eps],
735                    reaction_forces: vec![-bar.internal_force(u_peak)],
736                    safety_factor: sf,
737                    temperature_field: Vec::new(),
738                    heat_flux_field: Vec::new(),
739                })
740            }
741            // Modal/thermal results cannot be represented in the scalar-field
742            // `AnalysisResults` shape — they are eigenmodes / temperature fields, not a
743            // structural stress/displacement response. They are genuinely computed, but
744            // through the dedicated methods that return the right result types:
745            //   Vibration → VibrationAnalysis::analyze_free / ModalAnalysis::analyze_modal
746            //   Thermal   → ThermalAnalyzer::analyze
747            AnalysisType::Vibration | AnalysisType::Thermal => {
748                Err(EngineeringError::NotImplemented(format!(
749                    "structural {:?} is not available through the AnalysisResults facade; \
750                     use VibrationAnalysis::analyze_free / ModalAnalysis::analyze_modal for \
751                     modal & free-vibration results and ThermalAnalyzer for thermal response",
752                    analysis_type
753                )))
754            }
755        }
756    }
757
758    /// Real finite-element linear-static solve of an explicit [`fem::FeModel`]
759    /// (assemble `K u = F`, apply displacement BCs, solve via LU, recover reactions
760    /// and element axial forces). This is the direct FE entry point (the abstract
761    /// `analyze` facade interprets a prismatic member; this takes a full mesh).
762    pub fn fem_static(
763        &self,
764        model: &fem::FeModel,
765    ) -> Result<fem::FeStaticResult, EngineeringError> {
766        fem::solve_static(model)
767    }
768
769    pub fn list_analysis_types(&self) -> Vec<String> {
770        vec![
771            "LinearStatic".to_string(),
772            "NonlinearStatic".to_string(),
773            "LinearDynamic".to_string(),
774            "NonlinearDynamic".to_string(),
775            "Buckling".to_string(),
776        ]
777    }
778
779    pub fn get_model(&self, model_id: &str) -> Option<EngineeringModel> {
780        self.model_store.get(model_id).cloned()
781    }
782
783    pub fn get_performance_metrics(&self) -> EngineeringPerformanceMetrics {
784        EngineeringPerformanceMetrics::new()
785    }
786
787    /// Borrow the buckling-analysis sub-analyzer.
788    pub fn buckling_analysis(&self) -> &BucklingAnalysis {
789        &self.buckling_analysis
790    }
791
792    /// Mutably borrow the buckling-analysis sub-analyzer.
793    pub fn buckling_analysis_mut(&mut self) -> &mut BucklingAnalysis {
794        &mut self.buckling_analysis
795    }
796
797    /// Borrow the vibration-analysis sub-analyzer.
798    pub fn vibration_analysis(&self) -> &VibrationAnalysis {
799        &self.vibration_analysis
800    }
801
802    /// Mutably borrow the vibration-analysis sub-analyzer.
803    pub fn vibration_analysis_mut(&mut self) -> &mut VibrationAnalysis {
804        &mut self.vibration_analysis
805    }
806}
807
808impl FiniteElementSolver {
809    pub fn new() -> Self {
810        Self {
811            mesh_generator: MeshGenerator::new(),
812            element_library: ElementLibrary::new(),
813            solver_engine: SolverEngine::new(),
814            post_processor: PostProcessor::new(),
815            zns_manager: None,
816        }
817    }
818
819    /// Attach a ZNS zone manager for zero-copy mesh / element storage.
820    pub fn attach_zns_manager(&mut self, manager: Option<Arc<Mutex<ZnsZoneManager>>>) {
821        self.zns_manager = manager;
822    }
823
824    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
825        self.mesh_generator.initialize()?;
826        self.element_library.initialize()?;
827        self.solver_engine.initialize()?;
828        self.post_processor.initialize()?;
829        Ok(())
830    }
831}
832
833impl MeshGenerator {
834    pub fn new() -> Self {
835        Self {
836            mesh_types: HashMap::new(),
837            mesh_algorithms: HashMap::new(),
838            mesh_quality: MeshQuality::new(),
839        }
840    }
841
842    /// Populate the mesh-type and mesh-algorithm registries with the standard
843    /// engineering set. The `MeshType` enum exposes Triangular, Quadrilateral,
844    /// Tetrahedral, Hexahedral, Mixed, Structured and Unstructured (there are no
845    /// Prism/Pyramid variants, so those two requested topologies are represented
846    /// by the closest available enum members — Mixed for prism/pyramid hybrid
847    /// meshes).
848    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
849        self.mesh_types
850            .insert("triangular".to_string(), MeshType::Triangular);
851        self.mesh_types
852            .insert("quadrilateral".to_string(), MeshType::Quadrilateral);
853        self.mesh_types
854            .insert("tetrahedral".to_string(), MeshType::Tetrahedral);
855        self.mesh_types
856            .insert("hexahedral".to_string(), MeshType::Hexahedral);
857        self.mesh_types.insert("prism".to_string(), MeshType::Mixed);
858        self.mesh_types
859            .insert("pyramid".to_string(), MeshType::Mixed);
860        self.mesh_types.insert("mixed".to_string(), MeshType::Mixed);
861        self.mesh_types
862            .insert("structured".to_string(), MeshType::Structured);
863        self.mesh_types
864            .insert("unstructured".to_string(), MeshType::Unstructured);
865
866        let default_params = MeshAlgorithmParameters {
867            element_size: 1.0,
868            refinement_level: 1,
869            quality_criteria: Vec::new(),
870        };
871        self.mesh_algorithms.insert(
872            "delaunay".to_string(),
873            MeshAlgorithm {
874                algorithm_id: "algo_delaunay".to_string(),
875                algorithm_name: "Delaunay Triangulation".to_string(),
876                algorithm_type: MeshAlgorithmType::Delaunay,
877                parameters: default_params.clone(),
878            },
879        );
880        self.mesh_algorithms.insert(
881            "advancing_front".to_string(),
882            MeshAlgorithm {
883                algorithm_id: "algo_advancing_front".to_string(),
884                algorithm_name: "Advancing Front".to_string(),
885                algorithm_type: MeshAlgorithmType::AdvancingFront,
886                parameters: default_params.clone(),
887            },
888        );
889        self.mesh_algorithms.insert(
890            "octree".to_string(),
891            MeshAlgorithm {
892                algorithm_id: "algo_octree".to_string(),
893                algorithm_name: "Octree Decomposition".to_string(),
894                algorithm_type: MeshAlgorithmType::Octree,
895                parameters: default_params.clone(),
896            },
897        );
898        self.mesh_algorithms.insert(
899            "structured".to_string(),
900            MeshAlgorithm {
901                algorithm_id: "algo_structured".to_string(),
902                algorithm_name: "Structured Grid".to_string(),
903                algorithm_type: MeshAlgorithmType::Custom("Structured".to_string()),
904                parameters: default_params.clone(),
905            },
906        );
907        self.mesh_algorithms.insert(
908            "unstructured".to_string(),
909            MeshAlgorithm {
910                algorithm_id: "algo_unstructured".to_string(),
911                algorithm_name: "Unstructured Mesh".to_string(),
912                algorithm_type: MeshAlgorithmType::Custom("Unstructured".to_string()),
913                parameters: default_params,
914            },
915        );
916
917        Ok(())
918    }
919
920    /// Look up a registered mesh type by name.
921    pub fn get_mesh_type(&self, name: &str) -> Option<&MeshType> {
922        self.mesh_types.get(name)
923    }
924
925    /// Look up a registered mesh algorithm by name.
926    pub fn get_algorithm(&self, name: &str) -> Option<&MeshAlgorithm> {
927        self.mesh_algorithms.get(name)
928    }
929
930    /// List the names of all registered mesh types.
931    pub fn list_mesh_types(&self) -> Vec<String> {
932        let mut names: Vec<String> = self.mesh_types.keys().cloned().collect();
933        names.sort();
934        names
935    }
936
937    /// List the names of all registered mesh algorithms.
938    pub fn list_algorithms(&self) -> Vec<String> {
939        let mut names: Vec<String> = self.mesh_algorithms.keys().cloned().collect();
940        names.sort();
941        names
942    }
943
944    /// Borrow the mesh-quality sub-component.
945    pub fn mesh_quality(&self) -> &MeshQuality {
946        &self.mesh_quality
947    }
948
949    /// Mutably borrow the mesh-quality sub-component.
950    pub fn mesh_quality_mut(&mut self) -> &mut MeshQuality {
951        &mut self.mesh_quality
952    }
953}
954
955impl MeshQuality {
956    pub fn new() -> Self {
957        Self {
958            quality_metrics: HashMap::new(),
959            quality_assessment: QualityAssessment::new(),
960        }
961    }
962
963    /// Register a quality metric under `metric.metric_name`.
964    pub fn add_metric(&mut self, metric: QualityMetric) {
965        self.quality_metrics
966            .insert(metric.metric_name.clone(), metric);
967    }
968
969    /// Look up a registered quality metric by name.
970    pub fn get_metric(&self, name: &str) -> Option<&QualityMetric> {
971        self.quality_metrics.get(name)
972    }
973
974    /// List the names of all registered quality metrics.
975    pub fn list_metrics(&self) -> Vec<String> {
976        let mut names: Vec<String> = self.quality_metrics.keys().cloned().collect();
977        names.sort();
978        names
979    }
980
981    /// Borrow the quality-assessment summary.
982    pub fn quality_assessment(&self) -> &QualityAssessment {
983        &self.quality_assessment
984    }
985}
986
987impl QualityAssessment {
988    pub fn new() -> Self {
989        Self {
990            overall_quality: 0.95,
991            quality_grade: QualityGrade::Excellent,
992            recommendations: Vec::new(),
993        }
994    }
995}
996
997impl ElementLibrary {
998    pub fn new() -> Self {
999        Self {
1000            elements: HashMap::new(),
1001            element_properties: HashMap::new(),
1002        }
1003    }
1004
1005    /// Populate the library with the standard finite-element types used in
1006    /// structural / mechanical FEA. Each element is registered with a default
1007    /// isotropic material (steel-like), unit geometry, and the DOF set appropriate
1008    /// to its kinematics.
1009    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
1010        // Shared default properties (steel-like, unit section).
1011        let default_props = ElementProperties {
1012            material_properties: MaterialProperties {
1013                youngs_modulus: 200_000.0,
1014                poissons_ratio: 0.3,
1015                density: 7850.0,
1016                thermal_expansion: 1.2e-5,
1017                thermal_conductivity: 50.0,
1018                specific_heat: 500.0,
1019                yield_strength: 250.0,
1020                ultimate_strength: 400.0,
1021            },
1022            geometric_properties: GeometricProperties {
1023                area: 1.0,
1024                volume: 1.0,
1025                perimeter: 4.0,
1026                surface_area: 6.0,
1027            },
1028            section_properties: SectionProperties {
1029                moment_of_inertia: vec![1.0 / 12.0, 1.0 / 12.0, 1.0 / 12.0],
1030                torsional_constant: 1.0 / 12.0,
1031                section_modulus: vec![1.0 / 6.0, 1.0 / 6.0, 1.0 / 6.0],
1032                shear_center: vec![0.0, 0.0, 0.0],
1033            },
1034        };
1035
1036        // Helper: build `count` nodes each carrying `dofs` degrees of freedom.
1037        let make_nodes = |count: usize, dofs: &[DOF]| -> Vec<Node> {
1038            (0..count)
1039                .map(|i| Node {
1040                    node_id: format!("n{i}"),
1041                    coordinates: vec![i as f64, 0.0, 0.0],
1042                    degrees_of_freedom: dofs.to_vec(),
1043                    constraints: Vec::new(),
1044                })
1045                .collect()
1046        };
1047
1048        // truss_2node: 2 nodes, 2 DOF/node (UX, UY)
1049        let truss = Element {
1050            element_id: "truss_2node".to_string(),
1051            element_name: "2-Node Truss".to_string(),
1052            element_type: ElementType::Truss,
1053            nodes: make_nodes(2, &[DOF::UX, DOF::UY]),
1054            properties: default_props.clone(),
1055        };
1056        self.elements.insert("truss_2node".to_string(), truss);
1057        self.element_properties
1058            .insert("truss_2node".to_string(), default_props.clone());
1059
1060        // beam_2node: 2 nodes, 3 DOF/node (UX, UY, ROTZ)
1061        let beam = Element {
1062            element_id: "beam_2node".to_string(),
1063            element_name: "2-Node Beam".to_string(),
1064            element_type: ElementType::Beam,
1065            nodes: make_nodes(2, &[DOF::UX, DOF::UY, DOF::ROTZ]),
1066            properties: default_props.clone(),
1067        };
1068        self.elements.insert("beam_2node".to_string(), beam);
1069        self.element_properties
1070            .insert("beam_2node".to_string(), default_props.clone());
1071
1072        // quad_4node: quadrilateral shell, 4 nodes, 2 DOF/node (UX, UY)
1073        let quad = Element {
1074            element_id: "quad_4node".to_string(),
1075            element_name: "4-Node Quadrilateral Shell".to_string(),
1076            element_type: ElementType::Shell,
1077            nodes: make_nodes(4, &[DOF::UX, DOF::UY]),
1078            properties: default_props.clone(),
1079        };
1080        self.elements.insert("quad_4node".to_string(), quad);
1081        self.element_properties
1082            .insert("quad_4node".to_string(), default_props.clone());
1083
1084        // hex_8node: hexahedral solid, 8 nodes, 3 DOF/node (UX, UY, UZ)
1085        let hex = Element {
1086            element_id: "hex_8node".to_string(),
1087            element_name: "8-Node Hexahedral Solid".to_string(),
1088            element_type: ElementType::Hexahedron,
1089            nodes: make_nodes(8, &[DOF::UX, DOF::UY, DOF::UZ]),
1090            properties: default_props.clone(),
1091        };
1092        self.elements.insert("hex_8node".to_string(), hex);
1093        self.element_properties
1094            .insert("hex_8node".to_string(), default_props.clone());
1095
1096        // tet_4node: tetrahedral solid, 4 nodes, 3 DOF/node (UX, UY, UZ)
1097        let tet = Element {
1098            element_id: "tet_4node".to_string(),
1099            element_name: "4-Node Tetrahedral Solid".to_string(),
1100            element_type: ElementType::Tetrahedron,
1101            nodes: make_nodes(4, &[DOF::UX, DOF::UY, DOF::UZ]),
1102            properties: default_props.clone(),
1103        };
1104        self.elements.insert("tet_4node".to_string(), tet);
1105        self.element_properties
1106            .insert("tet_4node".to_string(), default_props.clone());
1107
1108        // shell_8node: shell element, 8 nodes, 6 DOF/node (UX, UY, UZ, ROTX, ROTY, ROTZ)
1109        let shell = Element {
1110            element_id: "shell_8node".to_string(),
1111            element_name: "8-Node Shell".to_string(),
1112            element_type: ElementType::Shell,
1113            nodes: make_nodes(
1114                8,
1115                &[DOF::UX, DOF::UY, DOF::UZ, DOF::ROTX, DOF::ROTY, DOF::ROTZ],
1116            ),
1117            properties: default_props.clone(),
1118        };
1119        self.elements.insert("shell_8node".to_string(), shell);
1120        self.element_properties
1121            .insert("shell_8node".to_string(), default_props);
1122
1123        Ok(())
1124    }
1125
1126    /// Look up a registered element definition by name.
1127    pub fn get_element(&self, name: &str) -> Option<&Element> {
1128        self.elements.get(name)
1129    }
1130
1131    /// Look up the properties registered for an element by name.
1132    pub fn get_properties(&self, name: &str) -> Option<&ElementProperties> {
1133        self.element_properties.get(name)
1134    }
1135
1136    /// List the names of all registered elements.
1137    pub fn list_elements(&self) -> Vec<String> {
1138        let mut names: Vec<String> = self.elements.keys().cloned().collect();
1139        names.sort();
1140        names
1141    }
1142}
1143
1144impl SolverEngine {
1145    pub fn new() -> Self {
1146        Self {
1147            solvers: HashMap::new(),
1148            solver_parameters: SolverParameters::new(),
1149            convergence_criteria: ConvergenceCriteria::new(),
1150        }
1151    }
1152
1153    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
1154        Ok(())
1155    }
1156
1157    /// Register a solver under `solver.solver_id`.
1158    pub fn add_solver(&mut self, solver: Solver) {
1159        self.solvers.insert(solver.solver_id.clone(), solver);
1160    }
1161
1162    /// Look up a registered solver by id.
1163    pub fn get_solver(&self, id: &str) -> Option<&Solver> {
1164        self.solvers.get(id)
1165    }
1166
1167    /// List the ids of all registered solvers.
1168    pub fn list_solvers(&self) -> Vec<String> {
1169        let mut ids: Vec<String> = self.solvers.keys().cloned().collect();
1170        ids.sort();
1171        ids
1172    }
1173
1174    /// Borrow the solver parameters.
1175    pub fn solver_parameters(&self) -> &SolverParameters {
1176        &self.solver_parameters
1177    }
1178
1179    /// Mutably borrow the solver parameters.
1180    pub fn solver_parameters_mut(&mut self) -> &mut SolverParameters {
1181        &mut self.solver_parameters
1182    }
1183
1184    /// Borrow the convergence criteria.
1185    pub fn convergence_criteria(&self) -> &ConvergenceCriteria {
1186        &self.convergence_criteria
1187    }
1188
1189    /// Mutably borrow the convergence criteria.
1190    pub fn convergence_criteria_mut(&mut self) -> &mut ConvergenceCriteria {
1191        &mut self.convergence_criteria
1192    }
1193}
1194
1195impl SolverParameters {
1196    pub fn new() -> Self {
1197        Self {
1198            tolerance: 1e-6,
1199            max_iterations: 1000,
1200            convergence_acceleration: ConvergenceAcceleration::None,
1201        }
1202    }
1203}
1204
1205impl ConvergenceCriteria {
1206    pub fn new() -> Self {
1207        Self {
1208            criteria_type: ConvergenceType::Residual,
1209            tolerance: 1e-6,
1210            max_iterations: 1000,
1211        }
1212    }
1213}
1214
1215impl PostProcessor {
1216    pub fn new() -> Self {
1217        Self {
1218            result_extractors: HashMap::new(),
1219            visualization_engine: VisualizationEngine::new(),
1220            report_generator: ReportGenerator::new(),
1221        }
1222    }
1223
1224    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
1225        self.visualization_engine.initialize()?;
1226        self.report_generator.initialize()?;
1227        Ok(())
1228    }
1229
1230    /// Register a result extractor under `extractor.extractor_id`.
1231    pub fn add_extractor(&mut self, extractor: ResultExtractor) {
1232        self.result_extractors
1233            .insert(extractor.extractor_id.clone(), extractor);
1234    }
1235
1236    /// Look up a registered result extractor by id.
1237    pub fn get_extractor(&self, id: &str) -> Option<&ResultExtractor> {
1238        self.result_extractors.get(id)
1239    }
1240
1241    /// List the ids of all registered result extractors.
1242    pub fn list_extractors(&self) -> Vec<String> {
1243        let mut ids: Vec<String> = self.result_extractors.keys().cloned().collect();
1244        ids.sort();
1245        ids
1246    }
1247}
1248
1249impl VisualizationEngine {
1250    pub fn new() -> Self {
1251        Self {
1252            visualization_types: HashMap::new(),
1253            rendering_engine: RenderingEngine::new(),
1254        }
1255    }
1256
1257    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
1258        Ok(())
1259    }
1260
1261    /// Register a visualization type under `name`.
1262    pub fn add_visualization_type(&mut self, name: impl Into<String>, vtype: VisualizationType) {
1263        self.visualization_types.insert(name.into(), vtype);
1264    }
1265
1266    /// Look up a registered visualization type by name.
1267    pub fn get_visualization_type(&self, name: &str) -> Option<&VisualizationType> {
1268        self.visualization_types.get(name)
1269    }
1270
1271    /// List the names of all registered visualization types.
1272    pub fn list_visualization_types(&self) -> Vec<String> {
1273        let mut names: Vec<String> = self.visualization_types.keys().cloned().collect();
1274        names.sort();
1275        names
1276    }
1277
1278    /// Borrow the rendering engine.
1279    pub fn rendering_engine(&self) -> &RenderingEngine {
1280        &self.rendering_engine
1281    }
1282
1283    /// Mutably borrow the rendering engine.
1284    pub fn rendering_engine_mut(&mut self) -> &mut RenderingEngine {
1285        &mut self.rendering_engine
1286    }
1287}
1288
1289impl RenderingEngine {
1290    pub fn new() -> Self {
1291        Self {
1292            engine_type: RenderingEngineType::OpenGL,
1293            rendering_options: RenderingOptions::new(),
1294        }
1295    }
1296}
1297
1298impl RenderingOptions {
1299    pub fn new() -> Self {
1300        Self {
1301            color_map: "jet".to_string(),
1302            scale_factor: 1.0,
1303            line_width: 1.0,
1304            transparency: 0.0,
1305        }
1306    }
1307}
1308
1309impl ReportGenerator {
1310    pub fn new() -> Self {
1311        Self {
1312            report_templates: HashMap::new(),
1313            export_formats: vec![ExportFormat::PDF, ExportFormat::HTML],
1314        }
1315    }
1316
1317    pub fn initialize(&mut self) -> Result<(), EngineeringError> {
1318        Ok(())
1319    }
1320
1321    /// Register a report template under `template.template_id`.
1322    pub fn add_template(&mut self, template: ReportTemplate) {
1323        self.report_templates
1324            .insert(template.template_id.clone(), template);
1325    }
1326
1327    /// Look up a registered report template by id.
1328    pub fn get_template(&self, id: &str) -> Option<&ReportTemplate> {
1329        self.report_templates.get(id)
1330    }
1331
1332    /// List the ids of all registered report templates.
1333    pub fn list_templates(&self) -> Vec<String> {
1334        let mut ids: Vec<String> = self.report_templates.keys().cloned().collect();
1335        ids.sort();
1336        ids
1337    }
1338
1339    /// Borrow the supported export formats.
1340    pub fn export_formats(&self) -> &[ExportFormat] {
1341        &self.export_formats
1342    }
1343
1344    /// Add a supported export format.
1345    pub fn add_export_format(&mut self, format: ExportFormat) {
1346        if !self.export_formats.contains(&format) {
1347            self.export_formats.push(format);
1348        }
1349    }
1350}