Skip to main content

runmat_analysis_core/
validate.rs

1use runmat_geometry_core::UnitSystem;
2use thiserror::Error;
3
4use crate::problem::{
5    loads::LoadKind,
6    model::{AnalysisModel, ReferenceFrame},
7};
8
9#[derive(Debug, Clone, PartialEq, Eq, Error)]
10pub enum AnalysisValidationError {
11    #[error(
12        "ANALYSIS_VALIDATION_MISSING_MATERIALS: analysis model must include at least one material"
13    )]
14    MissingMaterials,
15    #[error("ANALYSIS_VALIDATION_MISSING_BCS: analysis model must include at least one boundary condition")]
16    MissingBoundaryConditions,
17    #[error("ANALYSIS_VALIDATION_MISSING_LOADS: analysis model must include at least one load")]
18    MissingLoads,
19    #[error(
20        "ANALYSIS_VALIDATION_INVALID_MOMENT: moment load {load_id} must have finite components"
21    )]
22    InvalidMomentVector { load_id: String },
23    #[error("ANALYSIS_VALIDATION_ZERO_MOMENT: moment load {load_id} must have nonzero magnitude")]
24    ZeroMomentVector { load_id: String },
25    #[error(
26        "ANALYSIS_VALIDATION_INVALID_WRENCH: wrench load {load_id} must have finite force, moment, and point components"
27    )]
28    InvalidWrench { load_id: String },
29    #[error(
30        "ANALYSIS_VALIDATION_ZERO_WRENCH: wrench load {load_id} must have nonzero force or moment"
31    )]
32    ZeroWrench { load_id: String },
33    #[error(
34        "ANALYSIS_VALIDATION_UNIT_MISMATCH: model units {model:?} do not match geometry units {geometry:?}"
35    )]
36    UnitMismatch {
37        model: UnitSystem,
38        geometry: UnitSystem,
39    },
40    #[error(
41        "ANALYSIS_VALIDATION_FRAME_MISMATCH: model frame {model:?} does not match geometry frame {geometry:?}"
42    )]
43    FrameMismatch {
44        model: ReferenceFrame,
45        geometry: ReferenceFrame,
46    },
47}
48
49pub fn validate_model(model: &AnalysisModel) -> Result<(), AnalysisValidationError> {
50    if model.materials.is_empty() {
51        return Err(AnalysisValidationError::MissingMaterials);
52    }
53    if model.boundary_conditions.is_empty() {
54        return Err(AnalysisValidationError::MissingBoundaryConditions);
55    }
56    if model.loads.is_empty() {
57        return Err(AnalysisValidationError::MissingLoads);
58    }
59    for load in &model.loads {
60        if let LoadKind::Moment { mx, my, mz } = load.kind {
61            if !mx.is_finite() || !my.is_finite() || !mz.is_finite() {
62                return Err(AnalysisValidationError::InvalidMomentVector {
63                    load_id: load.load_id.clone(),
64                });
65            }
66            if mx == 0.0 && my == 0.0 && mz == 0.0 {
67                return Err(AnalysisValidationError::ZeroMomentVector {
68                    load_id: load.load_id.clone(),
69                });
70            }
71        }
72        if let LoadKind::Wrench {
73            fx,
74            fy,
75            fz,
76            mx,
77            my,
78            mz,
79            px,
80            py,
81            pz,
82        } = load.kind
83        {
84            if !fx.is_finite()
85                || !fy.is_finite()
86                || !fz.is_finite()
87                || !mx.is_finite()
88                || !my.is_finite()
89                || !mz.is_finite()
90                || !px.is_finite()
91                || !py.is_finite()
92                || !pz.is_finite()
93            {
94                return Err(AnalysisValidationError::InvalidWrench {
95                    load_id: load.load_id.clone(),
96                });
97            }
98            if fx == 0.0 && fy == 0.0 && fz == 0.0 && mx == 0.0 && my == 0.0 && mz == 0.0 {
99                return Err(AnalysisValidationError::ZeroWrench {
100                    load_id: load.load_id.clone(),
101                });
102            }
103        }
104    }
105    Ok(())
106}
107
108pub fn validate_model_against_geometry(
109    model: &AnalysisModel,
110    geometry_units: UnitSystem,
111    geometry_frame: &ReferenceFrame,
112) -> Result<(), AnalysisValidationError> {
113    validate_model(model)?;
114
115    if model.units != geometry_units {
116        return Err(AnalysisValidationError::UnitMismatch {
117            model: model.units,
118            geometry: geometry_units,
119        });
120    }
121    if &model.frame != geometry_frame {
122        return Err(AnalysisValidationError::FrameMismatch {
123            model: model.frame.clone(),
124            geometry: geometry_frame.clone(),
125        });
126    }
127    Ok(())
128}