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}