1#![forbid(unsafe_code)]
2#[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}