Skip to main content

qtty_core/units/
frequency.rs

1//! Angular frequency unit aliases (`Angular / Time`).
2//!
3//! This module provides a **dimension alias** [`Frequency`].
4//!
5//! ```rust
6//! use qtty_core::angular::{Degree, Radian};
7//! use qtty_core::time::Day;
8//! use qtty_core::frequency::Frequency;
9//!
10//! let f: Frequency<Degree, Day> = Frequency::new(180.0);
11//! let f_rad: Frequency<Radian, Day> = f.to();
12//! assert!((f_rad.value() - core::f64::consts::PI).abs() < 1e-12);
13//! ```
14
15use crate::{Per, Quantity, Unit};
16
17/// Re-export the frequency dimension from the dimension module.
18pub use crate::dimension::FrequencyDim;
19
20/// Marker trait for any unit with frequency dimension (`Angular / Time`).
21pub trait FrequencyUnit: Unit<Dim = FrequencyDim> {}
22impl<T: Unit<Dim = FrequencyDim>> FrequencyUnit for T {}
23
24/// A frequency quantity parameterized by angular and time units.
25///
26/// # Examples
27///
28/// ```rust
29/// use qtty_core::angular::{Degree, Radian};
30/// use qtty_core::time::{Second, Day};
31/// use qtty_core::frequency::Frequency;
32///
33/// let f1: Frequency<Degree, Second> = Frequency::new(360.0);
34/// let f2: Frequency<Radian, Day> = Frequency::new(6.28);
35/// ```
36pub type Frequency<N, D> = Quantity<Per<N, D>>;
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    use crate::units::angular::{Degree, Degrees, MilliArcsecond, Radian};
42    use crate::units::time::{Day, Days, Year};
43    use crate::Per;
44    use approx::{assert_abs_diff_eq, assert_relative_eq};
45    use proptest::prelude::*;
46    use std::f64::consts::PI;
47
48    // ─────────────────────────────────────────────────────────────────────────────
49    // Basic frequency conversions
50    // ─────────────────────────────────────────────────────────────────────────────
51
52    #[test]
53    fn deg_per_day_to_rad_per_day() {
54        let f: Frequency<Degree, Day> = Frequency::new(180.0);
55        let f_rad: Frequency<Radian, Day> = f.to();
56        // 180 deg = π rad
57        assert_abs_diff_eq!(f_rad.value(), PI, epsilon = 1e-12);
58    }
59
60    #[test]
61    fn rad_per_day_to_deg_per_day() {
62        let f: Frequency<Radian, Day> = Frequency::new(PI);
63        let f_deg: Frequency<Degree, Day> = f.to();
64        assert_abs_diff_eq!(f_deg.value(), 180.0, epsilon = 1e-12);
65    }
66
67    #[test]
68    fn deg_per_day_to_deg_per_year() {
69        let f: Frequency<Degree, Day> = Frequency::new(1.0);
70        let f_year: Frequency<Degree, Year> = f.to();
71        // 1 deg/day = 365.2425 deg/year (tropical year)
72        assert_relative_eq!(f_year.value(), 365.2425, max_relative = 1e-6);
73    }
74
75    #[test]
76    fn deg_per_year_to_deg_per_day() {
77        let f: Frequency<Degree, Year> = Frequency::new(365.2425);
78        let f_day: Frequency<Degree, Day> = f.to();
79        assert_relative_eq!(f_day.value(), 1.0, max_relative = 1e-6);
80    }
81
82    #[test]
83    fn mas_per_day_to_deg_per_day() {
84        let f: Frequency<MilliArcsecond, Day> = Frequency::new(3_600_000.0);
85        let f_deg: Frequency<Degree, Day> = f.to();
86        // 3,600,000 mas = 1 deg
87        assert_abs_diff_eq!(f_deg.value(), 1.0, epsilon = 1e-9);
88    }
89
90    // ─────────────────────────────────────────────────────────────────────────────
91    // Per ratio behavior
92    // ─────────────────────────────────────────────────────────────────────────────
93
94    #[test]
95    fn per_ratio_deg_day() {
96        // Degree::RATIO = 1.0, Day::RATIO = 86400.0
97        // So Per<Degree, Day>::RATIO = 1.0 / 86400.0
98        let ratio = <Per<Degree, Day>>::RATIO;
99        assert_abs_diff_eq!(ratio, 1.0 / 86400.0, epsilon = 1e-12);
100    }
101
102    #[test]
103    fn per_ratio_rad_day() {
104        // Radian::RATIO = 180/π, Day::RATIO = 86400.0
105        let ratio = <Per<Radian, Day>>::RATIO;
106        assert_relative_eq!(ratio, (180.0 / PI) / 86400.0, max_relative = 1e-12);
107    }
108
109    // ─────────────────────────────────────────────────────────────────────────────
110    // Frequency * Time = Angle
111    // ─────────────────────────────────────────────────────────────────────────────
112
113    #[test]
114    fn frequency_times_time() {
115        let f: Frequency<Degree, Day> = Frequency::new(360.0);
116        let t: Days = Days::new(0.5);
117        let angle: Degrees = (f * t).to();
118        assert_abs_diff_eq!(angle.value(), 180.0, epsilon = 1e-9);
119    }
120
121    #[test]
122    fn time_times_frequency() {
123        let f: Frequency<Degree, Day> = Frequency::new(360.0);
124        let t: Days = Days::new(0.5);
125        let angle: Degrees = (t * f).to();
126        assert_abs_diff_eq!(angle.value(), 180.0, epsilon = 1e-9);
127    }
128
129    // ─────────────────────────────────────────────────────────────────────────────
130    // Angle / Time = Frequency
131    // ─────────────────────────────────────────────────────────────────────────────
132
133    #[test]
134    fn angle_div_time() {
135        let angle: Degrees = Degrees::new(360.0);
136        let t: Days = Days::new(1.0);
137        let f: Frequency<Degree, Day> = angle / t;
138        assert_abs_diff_eq!(f.value(), 360.0, epsilon = 1e-9);
139    }
140
141    // ─────────────────────────────────────────────────────────────────────────────
142    // Roundtrip conversions
143    // ─────────────────────────────────────────────────────────────────────────────
144
145    #[test]
146    fn roundtrip_deg_rad_per_day() {
147        let original: Frequency<Degree, Day> = Frequency::new(90.0);
148        let converted: Frequency<Radian, Day> = original.to();
149        let back: Frequency<Degree, Day> = converted.to();
150        assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-9);
151    }
152
153    // ─────────────────────────────────────────────────────────────────────────────
154    // Property-based tests
155    // ─────────────────────────────────────────────────────────────────────────────
156
157    proptest! {
158        #[test]
159        fn prop_roundtrip_deg_rad_per_day(f in 1e-6..1e6f64) {
160            let original: Frequency<Degree, Day> = Frequency::new(f);
161            let converted: Frequency<Radian, Day> = original.to();
162            let back: Frequency<Degree, Day> = converted.to();
163            prop_assert!((back.value() - original.value()).abs() < 1e-9 * f.abs().max(1.0));
164        }
165
166        #[test]
167        fn prop_frequency_time_roundtrip(
168            f_val in 1e-3..1e3f64,
169            t_val in 1e-3..1e3f64
170        ) {
171            let f: Frequency<Degree, Day> = Frequency::new(f_val);
172            let t: Days = Days::new(t_val);
173            let angle: Degrees = (f * t).to();
174            // angle / t should give back f
175            let f_back: Frequency<Degree, Day> = angle / t;
176            prop_assert!((f_back.value() - f.value()).abs() / f.value() < 1e-12);
177        }
178    }
179}