use_thermal_expansion/
lib.rs1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq)]
29pub struct LinearExpansionCoefficient {
30 per_kelvin: f64,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ThermalExpansionError {
35 InvalidCoefficient,
36 InvalidInitialLength,
37 InvalidFinalLength,
38 InvalidTemperatureChange,
39}
40
41fn validate_coefficient(value: f64) -> Result<f64, ThermalExpansionError> {
42 if !value.is_finite() {
43 Err(ThermalExpansionError::InvalidCoefficient)
44 } else {
45 Ok(value)
46 }
47}
48
49fn validate_initial_length(value: f64) -> Result<f64, ThermalExpansionError> {
50 if !value.is_finite() || value <= 0.0 {
51 Err(ThermalExpansionError::InvalidInitialLength)
52 } else {
53 Ok(value)
54 }
55}
56
57fn validate_temperature_change(value: f64, allow_zero: bool) -> Result<f64, ThermalExpansionError> {
58 if !value.is_finite() || (!allow_zero && value == 0.0) {
59 Err(ThermalExpansionError::InvalidTemperatureChange)
60 } else {
61 Ok(value)
62 }
63}
64
65impl LinearExpansionCoefficient {
66 pub fn new(per_kelvin: f64) -> Result<Self, ThermalExpansionError> {
67 Ok(Self {
68 per_kelvin: validate_coefficient(per_kelvin)?,
69 })
70 }
71
72 #[must_use]
73 pub fn per_kelvin(&self) -> f64 {
74 self.per_kelvin
75 }
76}
77
78pub fn linear_expansion(
79 initial_length_m: f64,
80 coefficient_per_k: f64,
81 delta_temp_k: f64,
82) -> Result<f64, ThermalExpansionError> {
83 Ok(validate_initial_length(initial_length_m)?
84 * validate_coefficient(coefficient_per_k)?
85 * validate_temperature_change(delta_temp_k, true)?)
86}
87
88pub fn final_length(
89 initial_length_m: f64,
90 coefficient_per_k: f64,
91 delta_temp_k: f64,
92) -> Result<f64, ThermalExpansionError> {
93 Ok(validate_initial_length(initial_length_m)?
94 + linear_expansion(initial_length_m, coefficient_per_k, delta_temp_k)?)
95}
96
97pub fn coefficient_from_lengths(
98 initial_length_m: f64,
99 final_length_m: f64,
100 delta_temp_k: f64,
101) -> Result<f64, ThermalExpansionError> {
102 let initial_length_m = validate_initial_length(initial_length_m)?;
103
104 if !final_length_m.is_finite() {
105 return Err(ThermalExpansionError::InvalidFinalLength);
106 }
107
108 Ok((final_length_m - initial_length_m)
109 / (initial_length_m * validate_temperature_change(delta_temp_k, false)?))
110}
111
112pub fn area_expansion_coefficient(
113 linear_coefficient_per_k: f64,
114) -> Result<f64, ThermalExpansionError> {
115 Ok(2.0 * validate_coefficient(linear_coefficient_per_k)?)
116}
117
118pub fn volume_expansion_coefficient(
119 linear_coefficient_per_k: f64,
120) -> Result<f64, ThermalExpansionError> {
121 Ok(3.0 * validate_coefficient(linear_coefficient_per_k)?)
122}
123
124#[cfg(test)]
125mod tests {
126 use super::{
127 LinearExpansionCoefficient, ThermalExpansionError, area_expansion_coefficient,
128 coefficient_from_lengths, final_length, linear_expansion, volume_expansion_coefficient,
129 };
130
131 #[test]
132 fn computes_thermal_expansion_values() {
133 let coefficient = LinearExpansionCoefficient::new(12.0e-6).unwrap();
134 let expansion = linear_expansion(2.0, 12.0e-6, 50.0).unwrap();
135 let expanded_length = final_length(2.0, 12.0e-6, 50.0).unwrap();
136 let inferred = coefficient_from_lengths(2.0, 2.0012, 50.0).unwrap();
137
138 assert_eq!(coefficient.per_kelvin(), 12.0e-6);
139 assert!((expansion - 0.0012).abs() < 1.0e-12);
140 assert!((expanded_length - 2.0012).abs() < 1.0e-12);
141 assert!((inferred - 12.0e-6).abs() < 1.0e-12);
142 assert_eq!(area_expansion_coefficient(12.0e-6).unwrap(), 24.0e-6);
143 assert_eq!(volume_expansion_coefficient(12.0e-6).unwrap(), 36.0e-6);
144 }
145
146 #[test]
147 fn allows_negative_coefficients_and_temperature_differences() {
148 assert!((linear_expansion(1.0, -1.0e-6, 10.0).unwrap() + 0.00001).abs() < 1.0e-12);
149 assert!((linear_expansion(1.0, 1.0e-6, -10.0).unwrap() + 0.00001).abs() < 1.0e-12);
150 }
151
152 #[test]
153 fn rejects_invalid_thermal_expansion_inputs() {
154 assert_eq!(
155 LinearExpansionCoefficient::new(f64::NAN),
156 Err(ThermalExpansionError::InvalidCoefficient)
157 );
158 assert_eq!(
159 linear_expansion(0.0, 12.0e-6, 50.0),
160 Err(ThermalExpansionError::InvalidInitialLength)
161 );
162 assert_eq!(
163 coefficient_from_lengths(2.0, 2.0012, 0.0),
164 Err(ThermalExpansionError::InvalidTemperatureChange)
165 );
166 assert_eq!(
167 coefficient_from_lengths(2.0, f64::INFINITY, 50.0),
168 Err(ThermalExpansionError::InvalidFinalLength)
169 );
170 }
171}