runmat_analysis_core/
validate.rs1use 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}