runmat-analysis-fea 0.5.6

Finite element assembly/solve/post scaffolding for RunMat
Documentation
use runmat_analysis_core::AnalysisModel;

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ThermalConstitutiveStats {
    pub conductivity_mean: f64,
    pub heat_capacity_mean: f64,
    pub density_mean: f64,
    pub conductivity_spread_ratio: f64,
    pub heat_capacity_spread_ratio: f64,
    pub diffusivity_estimate: f64,
    pub response_rate: f64,
}

pub fn constitutive_stats(model: &AnalysisModel) -> ThermalConstitutiveStats {
    let mut conductivities = Vec::new();
    let mut heat_capacities = Vec::new();
    for material in &model.materials {
        conductivities.push(material.thermal.conductivity_w_per_mk.max(1.0e-6));
        heat_capacities.push(material.thermal.specific_heat_j_per_kgk.max(1.0));
    }
    let conductivity_mean = if conductivities.is_empty() {
        1.0
    } else {
        conductivities.iter().sum::<f64>() / conductivities.len() as f64
    };
    let heat_capacity_mean = if heat_capacities.is_empty() {
        1.0
    } else {
        heat_capacities.iter().sum::<f64>() / heat_capacities.len() as f64
    };
    let density_mean = 7_800.0;
    let conductivity_spread_ratio = if conductivities.is_empty() {
        1.0
    } else {
        let min = conductivities
            .iter()
            .copied()
            .reduce(f64::min)
            .unwrap_or(1.0);
        let max = conductivities
            .iter()
            .copied()
            .reduce(f64::max)
            .unwrap_or(1.0);
        (max / min.max(1.0e-9)).clamp(1.0, 32.0)
    };
    let heat_capacity_spread_ratio = if heat_capacities.is_empty() {
        1.0
    } else {
        let min = heat_capacities
            .iter()
            .copied()
            .reduce(f64::min)
            .unwrap_or(1.0);
        let max = heat_capacities
            .iter()
            .copied()
            .reduce(f64::max)
            .unwrap_or(1.0);
        (max / min.max(1.0e-9)).clamp(1.0, 32.0)
    };
    let diffusivity_estimate = conductivity_mean / (density_mean * heat_capacity_mean).max(1.0e-9);
    let response_rate = (diffusivity_estimate * 5.0e6).clamp(0.02, 2.0);

    ThermalConstitutiveStats {
        conductivity_mean,
        heat_capacity_mean,
        density_mean,
        conductivity_spread_ratio,
        heat_capacity_spread_ratio,
        diffusivity_estimate,
        response_rate,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::fixtures::{fixture_model, FixtureId};

    #[test]
    fn constitutive_stats_are_finite_for_fixture_model() {
        let model = fixture_model(FixtureId::ThermoRampSmooth);
        let stats = constitutive_stats(&model);
        assert!(stats.conductivity_mean.is_finite());
        assert!(stats.heat_capacity_mean.is_finite());
        assert!(stats.diffusivity_estimate.is_finite());
        assert!(stats.response_rate >= 0.02 && stats.response_rate <= 2.0);
    }
}