Skip to main content

use_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_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.000001);
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
38fn validate_positive(value: f64, error: ElasticityError) -> Result<f64, ElasticityError> {
39    if !value.is_finite() || value <= 0.0 {
40        Err(error)
41    } else {
42        Ok(value)
43    }
44}
45
46fn validate_finite(value: f64, error: ElasticityError) -> Result<f64, ElasticityError> {
47    if !value.is_finite() {
48        Err(error)
49    } else {
50        Ok(value)
51    }
52}
53
54impl ElasticModulus {
55    pub fn new(pascals: f64) -> Result<Self, ElasticityError> {
56        Ok(Self {
57            pascals: validate_positive(pascals, ElasticityError::InvalidModulus)?,
58        })
59    }
60
61    #[must_use]
62    pub fn pascals(&self) -> f64 {
63        self.pascals
64    }
65
66    #[must_use]
67    pub fn gigapascals(&self) -> f64 {
68        self.pascals / 1_000_000_000.0
69    }
70}
71
72pub fn youngs_modulus(stress_pa: f64, strain: f64) -> Result<f64, ElasticityError> {
73    let stress_pa = validate_finite(stress_pa, ElasticityError::InvalidStress)?;
74    let strain = validate_finite(strain, ElasticityError::InvalidStrain)?;
75
76    if strain == 0.0 {
77        return Err(ElasticityError::InvalidStrain);
78    }
79
80    let modulus = stress_pa / strain;
81    validate_positive(modulus, ElasticityError::InvalidModulus)
82}
83
84pub fn stress_from_modulus(modulus_pa: f64, strain: f64) -> Result<f64, ElasticityError> {
85    Ok(
86        validate_positive(modulus_pa, ElasticityError::InvalidModulus)?
87            * validate_finite(strain, ElasticityError::InvalidStrain)?,
88    )
89}
90
91pub fn strain_from_modulus(stress_pa: f64, modulus_pa: f64) -> Result<f64, ElasticityError> {
92    Ok(validate_finite(stress_pa, ElasticityError::InvalidStress)?
93        / validate_positive(modulus_pa, ElasticityError::InvalidModulus)?)
94}
95
96pub fn elastic_deformation(
97    force_newtons: f64,
98    length_m: f64,
99    area_m2: f64,
100    modulus_pa: f64,
101) -> Result<f64, ElasticityError> {
102    Ok(
103        validate_finite(force_newtons, ElasticityError::InvalidForce)?
104            * validate_positive(length_m, ElasticityError::InvalidLength)?
105            / (validate_positive(area_m2, ElasticityError::InvalidArea)?
106                * validate_positive(modulus_pa, ElasticityError::InvalidModulus)?),
107    )
108}
109
110#[cfg(test)]
111mod tests {
112    use super::{
113        ElasticModulus, ElasticityError, elastic_deformation, strain_from_modulus,
114        stress_from_modulus, youngs_modulus,
115    };
116
117    #[test]
118    fn computes_modulus_and_hookes_law_values() {
119        let modulus = ElasticModulus::new(200_000_000_000.0).unwrap();
120
121        assert_eq!(modulus.pascals(), 200_000_000_000.0);
122        assert_eq!(modulus.gigapascals(), 200.0);
123        assert_eq!(
124            youngs_modulus(400_000_000.0, 0.002).unwrap(),
125            200_000_000_000.0
126        );
127        assert_eq!(
128            stress_from_modulus(200_000_000_000.0, 0.002).unwrap(),
129            400_000_000.0
130        );
131        assert_eq!(
132            strain_from_modulus(400_000_000.0, 200_000_000_000.0).unwrap(),
133            0.002
134        );
135    }
136
137    #[test]
138    fn computes_elastic_deformation() {
139        assert_eq!(
140            elastic_deformation(1_000.0, 2.0, 0.01, 200_000_000_000.0).unwrap(),
141            0.000001
142        );
143    }
144
145    #[test]
146    fn rejects_invalid_elasticity_inputs() {
147        assert_eq!(
148            ElasticModulus::new(0.0),
149            Err(ElasticityError::InvalidModulus)
150        );
151        assert_eq!(
152            youngs_modulus(400_000_000.0, 0.0),
153            Err(ElasticityError::InvalidStrain)
154        );
155        assert_eq!(
156            youngs_modulus(-1.0, 0.001),
157            Err(ElasticityError::InvalidModulus)
158        );
159        assert_eq!(
160            stress_from_modulus(f64::NAN, 0.001),
161            Err(ElasticityError::InvalidModulus)
162        );
163        assert_eq!(
164            strain_from_modulus(1.0, -1.0),
165            Err(ElasticityError::InvalidModulus)
166        );
167        assert_eq!(
168            elastic_deformation(1_000.0, 2.0, 0.0, 200_000_000_000.0),
169            Err(ElasticityError::InvalidArea)
170        );
171    }
172}