Skip to main content

use_material_elasticity/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive elasticity helpers.
3//!
4//! Initial calculations assume SI units unless otherwise documented.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use use_material_elasticity::{
10//!     ElasticModulus, elastic_deformation, strain_from_modulus, stress_from_modulus,
11//!     youngs_modulus,
12//! };
13//!
14//! let modulus = ElasticModulus::new(200_000_000_000.0).unwrap();
15//!
16//! assert_eq!(modulus.gigapascals(), 200.0);
17//! assert_eq!(youngs_modulus(400_000_000.0, 0.002).unwrap(), 200_000_000_000.0);
18//! assert_eq!(stress_from_modulus(200_000_000_000.0, 0.002).unwrap(), 400_000_000.0);
19//! assert_eq!(strain_from_modulus(400_000_000.0, 200_000_000_000.0).unwrap(), 0.002);
20//! assert_eq!(elastic_deformation(1_000.0, 2.0, 0.01, 200_000_000_000.0).unwrap(), 0.000_001);
21//! ```
22
23#[derive(Debug, Clone, Copy, PartialEq)]
24pub struct ElasticModulus {
25    pascals: f64,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ElasticityError {
30    InvalidStress,
31    InvalidStrain,
32    InvalidModulus,
33    InvalidForce,
34    InvalidLength,
35    InvalidArea,
36}
37
38const fn validate_positive(value: f64, error: ElasticityError) -> Result<f64, ElasticityError> {
39    if value.is_finite() && value > 0.0 {
40        Ok(value)
41    } else {
42        Err(error)
43    }
44}
45
46const fn validate_finite(value: f64, error: ElasticityError) -> Result<f64, ElasticityError> {
47    if value.is_finite() {
48        Ok(value)
49    } else {
50        Err(error)
51    }
52}
53
54impl ElasticModulus {
55    /// Creates an elastic modulus from pascals.
56    ///
57    /// # Errors
58    ///
59    /// Returns [`ElasticityError::InvalidModulus`] when `pascals` is not finite or is less than
60    /// or equal to zero.
61    pub fn new(pascals: f64) -> Result<Self, ElasticityError> {
62        Ok(Self {
63            pascals: validate_positive(pascals, ElasticityError::InvalidModulus)?,
64        })
65    }
66
67    #[must_use]
68    pub const fn pascals(&self) -> f64 {
69        self.pascals
70    }
71
72    #[must_use]
73    pub const fn gigapascals(&self) -> f64 {
74        self.pascals / 1_000_000_000.0
75    }
76}
77
78/// Computes Young's modulus from stress and strain.
79///
80/// # Errors
81///
82/// Returns [`ElasticityError::InvalidStress`] when `stress_pa` is not finite,
83/// [`ElasticityError::InvalidStrain`] when `strain` is not finite or is zero, and
84/// [`ElasticityError::InvalidModulus`] when the computed modulus is not positive.
85pub fn youngs_modulus(stress_pa: f64, strain: f64) -> Result<f64, ElasticityError> {
86    let stress_pa = validate_finite(stress_pa, ElasticityError::InvalidStress)?;
87    let strain = validate_finite(strain, ElasticityError::InvalidStrain)?;
88
89    if strain == 0.0 {
90        return Err(ElasticityError::InvalidStrain);
91    }
92
93    let modulus = stress_pa / strain;
94    validate_positive(modulus, ElasticityError::InvalidModulus)
95}
96
97/// Computes stress from modulus and strain.
98///
99/// # Errors
100///
101/// Returns [`ElasticityError::InvalidModulus`] when `modulus_pa` is not finite or is less than or
102/// equal to zero, and [`ElasticityError::InvalidStrain`] when `strain` is not finite.
103pub fn stress_from_modulus(modulus_pa: f64, strain: f64) -> Result<f64, ElasticityError> {
104    Ok(
105        validate_positive(modulus_pa, ElasticityError::InvalidModulus)?
106            * validate_finite(strain, ElasticityError::InvalidStrain)?,
107    )
108}
109
110/// Computes strain from stress and modulus.
111///
112/// # Errors
113///
114/// Returns [`ElasticityError::InvalidStress`] when `stress_pa` is not finite, and
115/// [`ElasticityError::InvalidModulus`] when `modulus_pa` is not finite or is less than or equal to
116/// zero.
117pub fn strain_from_modulus(stress_pa: f64, modulus_pa: f64) -> Result<f64, ElasticityError> {
118    Ok(validate_finite(stress_pa, ElasticityError::InvalidStress)?
119        / validate_positive(modulus_pa, ElasticityError::InvalidModulus)?)
120}
121
122/// Computes axial elastic deformation.
123///
124/// # Errors
125///
126/// Returns an [`ElasticityError`] when any input is not finite, or when length, area, or modulus is
127/// less than or equal to zero.
128pub fn elastic_deformation(
129    force_newtons: f64,
130    length_m: f64,
131    area_m2: f64,
132    modulus_pa: f64,
133) -> Result<f64, ElasticityError> {
134    Ok(
135        validate_finite(force_newtons, ElasticityError::InvalidForce)?
136            * validate_positive(length_m, ElasticityError::InvalidLength)?
137            / (validate_positive(area_m2, ElasticityError::InvalidArea)?
138                * validate_positive(modulus_pa, ElasticityError::InvalidModulus)?),
139    )
140}
141
142#[cfg(test)]
143mod tests {
144    use super::{
145        ElasticModulus, ElasticityError, elastic_deformation, strain_from_modulus,
146        stress_from_modulus, youngs_modulus,
147    };
148
149    fn assert_close(actual: f64, expected: f64) {
150        assert!((actual - expected).abs() < 1.0e-12);
151    }
152
153    #[test]
154    fn computes_modulus_and_hookes_law_values() {
155        let modulus = ElasticModulus::new(200_000_000_000.0).unwrap();
156
157        assert_close(modulus.pascals(), 200_000_000_000.0);
158        assert_close(modulus.gigapascals(), 200.0);
159        assert_close(
160            youngs_modulus(400_000_000.0, 0.002).unwrap(),
161            200_000_000_000.0,
162        );
163        assert_close(
164            stress_from_modulus(200_000_000_000.0, 0.002).unwrap(),
165            400_000_000.0,
166        );
167        assert_close(
168            strain_from_modulus(400_000_000.0, 200_000_000_000.0).unwrap(),
169            0.002,
170        );
171    }
172
173    #[test]
174    fn computes_elastic_deformation() {
175        assert_close(
176            elastic_deformation(1_000.0, 2.0, 0.01, 200_000_000_000.0).unwrap(),
177            0.000_001,
178        );
179    }
180
181    #[test]
182    fn rejects_invalid_elasticity_inputs() {
183        assert_eq!(
184            ElasticModulus::new(0.0),
185            Err(ElasticityError::InvalidModulus)
186        );
187        assert_eq!(
188            youngs_modulus(400_000_000.0, 0.0),
189            Err(ElasticityError::InvalidStrain)
190        );
191        assert_eq!(
192            youngs_modulus(-1.0, 0.001),
193            Err(ElasticityError::InvalidModulus)
194        );
195        assert_eq!(
196            stress_from_modulus(f64::NAN, 0.001),
197            Err(ElasticityError::InvalidModulus)
198        );
199        assert_eq!(
200            strain_from_modulus(1.0, -1.0),
201            Err(ElasticityError::InvalidModulus)
202        );
203        assert_eq!(
204            elastic_deformation(1_000.0, 2.0, 0.0, 200_000_000_000.0),
205            Err(ElasticityError::InvalidArea)
206        );
207    }
208}