Skip to main content

use_wave/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use std::{error::Error, f64::consts::TAU, fmt};
5
6pub mod prelude;
7
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct WaveSpec {
10    speed_m_per_s: f64,
11    frequency_hz: f64,
12}
13
14impl WaveSpec {
15    pub fn new(speed_m_per_s: f64, frequency_hz: f64) -> Result<Self, WaveError> {
16        validate_positive_finite(speed_m_per_s)?;
17        validate_positive_finite(frequency_hz)?;
18
19        Ok(Self {
20            speed_m_per_s,
21            frequency_hz,
22        })
23    }
24
25    #[must_use]
26    pub const fn speed_m_per_s(self) -> f64 {
27        self.speed_m_per_s
28    }
29
30    #[must_use]
31    pub const fn frequency_hz(self) -> f64 {
32        self.frequency_hz
33    }
34
35    #[must_use]
36    pub fn wavelength_m(self) -> f64 {
37        self.speed_m_per_s / self.frequency_hz
38    }
39
40    #[must_use]
41    pub fn period_s(self) -> f64 {
42        1.0 / self.frequency_hz
43    }
44
45    #[must_use]
46    pub fn angular_frequency_rad_s(self) -> f64 {
47        TAU * self.frequency_hz
48    }
49
50    #[must_use]
51    pub fn wave_number_rad_m(self) -> f64 {
52        TAU / self.wavelength_m()
53    }
54
55    pub fn phase_radians(self, time_s: f64, position_m: f64) -> Result<f64, WaveError> {
56        validate_finite(time_s)?;
57        validate_finite(position_m)?;
58
59        Ok(self.angular_frequency_rad_s() * time_s - self.wave_number_rad_m() * position_m)
60    }
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum WaveError {
65    NonFinite,
66    NonPositive,
67}
68
69impl fmt::Display for WaveError {
70    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            Self::NonFinite => formatter.write_str("wave values must be finite"),
73            Self::NonPositive => formatter.write_str("wave values must be greater than zero"),
74        }
75    }
76}
77
78impl Error for WaveError {}
79
80pub fn wavelength_meters(speed_m_per_s: f64, frequency_hz: f64) -> Result<f64, WaveError> {
81    Ok(WaveSpec::new(speed_m_per_s, frequency_hz)?.wavelength_m())
82}
83
84pub fn frequency_hz(speed_m_per_s: f64, wavelength_m: f64) -> Result<f64, WaveError> {
85    validate_positive_finite(speed_m_per_s)?;
86    validate_positive_finite(wavelength_m)?;
87
88    Ok(speed_m_per_s / wavelength_m)
89}
90
91pub fn period_seconds(frequency_hz: f64) -> Result<f64, WaveError> {
92    validate_positive_finite(frequency_hz)?;
93
94    Ok(1.0 / frequency_hz)
95}
96
97fn validate_positive_finite(value: f64) -> Result<(), WaveError> {
98    validate_finite(value)?;
99
100    if value <= 0.0 {
101        return Err(WaveError::NonPositive);
102    }
103
104    Ok(())
105}
106
107fn validate_finite(value: f64) -> Result<(), WaveError> {
108    if !value.is_finite() {
109        return Err(WaveError::NonFinite);
110    }
111
112    Ok(())
113}
114
115#[cfg(test)]
116mod tests {
117    use super::{WaveError, WaveSpec, frequency_hz, period_seconds, wavelength_meters};
118
119    #[test]
120    fn computes_wave_relationships() {
121        let wave = WaveSpec::new(340.0, 170.0).expect("wave should be valid");
122
123        assert_eq!(wave.wavelength_m(), 2.0);
124        assert_eq!(wave.period_s(), 1.0 / 170.0);
125        assert_eq!(wavelength_meters(340.0, 170.0), Ok(2.0));
126        assert_eq!(frequency_hz(340.0, 2.0), Ok(170.0));
127        assert_eq!(period_seconds(4.0), Ok(0.25));
128    }
129
130    #[test]
131    fn computes_phase_and_wave_numbers() {
132        let wave = WaveSpec::new(10.0, 2.0).expect("wave should be valid");
133        let phase = wave
134            .phase_radians(0.25, 1.25)
135            .expect("phase inputs should be valid");
136
137        assert!(phase.is_finite());
138        assert!(wave.angular_frequency_rad_s() > 0.0);
139        assert!(wave.wave_number_rad_m() > 0.0);
140    }
141
142    #[test]
143    fn rejects_invalid_inputs() {
144        assert_eq!(WaveSpec::new(0.0, 1.0), Err(WaveError::NonPositive));
145        assert_eq!(WaveSpec::new(1.0, f64::NAN), Err(WaveError::NonFinite));
146        assert_eq!(frequency_hz(1.0, -1.0), Err(WaveError::NonPositive));
147        assert_eq!(period_seconds(f64::INFINITY), Err(WaveError::NonFinite));
148    }
149}