1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct Strain {
23 ratio: f64,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum StrainError {
28 InvalidStrain,
29 InvalidOriginalLength,
30 InvalidFinalLength,
31 InvalidChangeInLength,
32}
33
34fn validate_finite(value: f64, error: StrainError) -> Result<f64, StrainError> {
35 if !value.is_finite() {
36 Err(error)
37 } else {
38 Ok(value)
39 }
40}
41
42fn validate_original_length(value: f64) -> Result<f64, StrainError> {
43 if !value.is_finite() || value <= 0.0 {
44 Err(StrainError::InvalidOriginalLength)
45 } else {
46 Ok(value)
47 }
48}
49
50impl Strain {
51 pub fn new(ratio: f64) -> Result<Self, StrainError> {
52 Ok(Self {
53 ratio: validate_finite(ratio, StrainError::InvalidStrain)?,
54 })
55 }
56
57 #[must_use]
58 pub fn ratio(&self) -> f64 {
59 self.ratio
60 }
61
62 #[must_use]
63 pub fn percent(&self) -> f64 {
64 self.ratio * 100.0
65 }
66}
67
68pub fn engineering_strain(original_length_m: f64, final_length_m: f64) -> Result<f64, StrainError> {
69 let original_length_m = validate_original_length(original_length_m)?;
70 let final_length_m = validate_finite(final_length_m, StrainError::InvalidFinalLength)?;
71
72 Ok((final_length_m - original_length_m) / original_length_m)
73}
74
75pub fn strain_from_change(
76 original_length_m: f64,
77 change_in_length_m: f64,
78) -> Result<f64, StrainError> {
79 Ok(
80 validate_finite(change_in_length_m, StrainError::InvalidChangeInLength)?
81 / validate_original_length(original_length_m)?,
82 )
83}
84
85pub fn percent_strain(strain: f64) -> Result<f64, StrainError> {
86 Ok(validate_finite(strain, StrainError::InvalidStrain)? * 100.0)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::{Strain, StrainError, engineering_strain, percent_strain, strain_from_change};
92
93 #[test]
94 fn computes_strain_values() {
95 let strain = Strain::new(0.05).unwrap();
96 let engineering = engineering_strain(2.0, 2.1).unwrap();
97 let from_change = strain_from_change(2.0, 0.1).unwrap();
98
99 assert_eq!(strain.ratio(), 0.05);
100 assert_eq!(strain.percent(), 5.0);
101 assert!((engineering - 0.05).abs() < 1.0e-12);
102 assert!((from_change - 0.05).abs() < 1.0e-12);
103 assert_eq!(percent_strain(0.05).unwrap(), 5.0);
104 }
105
106 #[test]
107 fn allows_negative_strain_for_contraction() {
108 assert!((engineering_strain(2.0, 1.9).unwrap() + 0.05).abs() < 1.0e-12);
109 assert_eq!(Strain::new(-0.02).unwrap().percent(), -2.0);
110 }
111
112 #[test]
113 fn rejects_invalid_length_and_strain_inputs() {
114 assert_eq!(Strain::new(f64::NAN), Err(StrainError::InvalidStrain));
115 assert_eq!(
116 engineering_strain(0.0, 2.0),
117 Err(StrainError::InvalidOriginalLength)
118 );
119 assert_eq!(
120 engineering_strain(2.0, f64::INFINITY),
121 Err(StrainError::InvalidFinalLength)
122 );
123 assert_eq!(
124 strain_from_change(-1.0, 0.1),
125 Err(StrainError::InvalidOriginalLength)
126 );
127 assert_eq!(
128 percent_strain(f64::INFINITY),
129 Err(StrainError::InvalidStrain)
130 );
131 }
132}