runmat-analysis-core 0.5.4

Solver-agnostic analysis model and validation for RunMat
Documentation
use runmat_geometry_core::UnitSystem;
use thiserror::Error;

use crate::problem::{
    loads::LoadKind,
    model::{AnalysisModel, ReferenceFrame},
};

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum AnalysisValidationError {
    #[error(
        "ANALYSIS_VALIDATION_MISSING_MATERIALS: analysis model must include at least one material"
    )]
    MissingMaterials,
    #[error("ANALYSIS_VALIDATION_MISSING_BCS: analysis model must include at least one boundary condition")]
    MissingBoundaryConditions,
    #[error("ANALYSIS_VALIDATION_MISSING_LOADS: analysis model must include at least one load")]
    MissingLoads,
    #[error(
        "ANALYSIS_VALIDATION_INVALID_MOMENT: moment load {load_id} must have finite components"
    )]
    InvalidMomentVector { load_id: String },
    #[error("ANALYSIS_VALIDATION_ZERO_MOMENT: moment load {load_id} must have nonzero magnitude")]
    ZeroMomentVector { load_id: String },
    #[error(
        "ANALYSIS_VALIDATION_UNIT_MISMATCH: model units {model:?} do not match geometry units {geometry:?}"
    )]
    UnitMismatch {
        model: UnitSystem,
        geometry: UnitSystem,
    },
    #[error(
        "ANALYSIS_VALIDATION_FRAME_MISMATCH: model frame {model:?} does not match geometry frame {geometry:?}"
    )]
    FrameMismatch {
        model: ReferenceFrame,
        geometry: ReferenceFrame,
    },
}

pub fn validate_model(model: &AnalysisModel) -> Result<(), AnalysisValidationError> {
    if model.materials.is_empty() {
        return Err(AnalysisValidationError::MissingMaterials);
    }
    if model.boundary_conditions.is_empty() {
        return Err(AnalysisValidationError::MissingBoundaryConditions);
    }
    if model.loads.is_empty() {
        return Err(AnalysisValidationError::MissingLoads);
    }
    for load in &model.loads {
        if let LoadKind::Moment { mx, my, mz } = load.kind {
            if !mx.is_finite() || !my.is_finite() || !mz.is_finite() {
                return Err(AnalysisValidationError::InvalidMomentVector {
                    load_id: load.load_id.clone(),
                });
            }
            if mx == 0.0 && my == 0.0 && mz == 0.0 {
                return Err(AnalysisValidationError::ZeroMomentVector {
                    load_id: load.load_id.clone(),
                });
            }
        }
    }
    Ok(())
}

pub fn validate_model_against_geometry(
    model: &AnalysisModel,
    geometry_units: UnitSystem,
    geometry_frame: &ReferenceFrame,
) -> Result<(), AnalysisValidationError> {
    validate_model(model)?;

    if model.units != geometry_units {
        return Err(AnalysisValidationError::UnitMismatch {
            model: model.units,
            geometry: geometry_units,
        });
    }
    if &model.frame != geometry_frame {
        return Err(AnalysisValidationError::FrameMismatch {
            model: model.frame.clone(),
            geometry: geometry_frame.clone(),
        });
    }
    Ok(())
}