Skip to main content

rfham_core/
frequencies.rs

1//! RF frequency, wavelength, and frequency-range types.
2//!
3//! [`Frequency`] and [`Wavelength`] are thin wrappers around [`uom`](https://docs.rs/uom)
4//! SI quantities. They add ham-radio-centric constructors, a smart `Display` that chooses
5//! the most readable unit, and bidirectional conversion via λ = c / f.
6//!
7//! [`FrequencyRange`] represents a contiguous band segment and supports overlap and
8//! containment queries.
9//!
10//! # Display formats
11//!
12//! The default formatter (`{}`) always uses MHz. The alternate formatter (`{:#}`) selects
13//! the most natural unit based on the value:
14//!
15//! ```rust
16//! use rfham_core::frequency::Frequency;
17//!
18//! assert_eq!(format!("{:#}", Frequency::hertz(440.0)),      "440 hertz");
19//! assert_eq!(format!("{:#}", Frequency::kilohertz(7.074)),  "7.074 kilohertz");
20//! assert_eq!(format!("{:#}", Frequency::megahertz(146.52)), "146.52 megahertz");
21//! assert_eq!(format!("{:#}", Frequency::gigahertz(2.4)),    "2.4 gigahertz");
22//! ```
23//!
24//! # Examples
25//!
26//! ```rust
27//! use rfham_core::frequency::{Frequency, FrequencyRange};
28//!
29//! // Construct and display
30//! let f = Frequency::megahertz(144.0);
31//! assert_eq!(f.to_string(), "144 MHz");
32//!
33//! // Convert to wavelength (~2 m band)
34//! let wl = f.to_wavelength();
35//! assert!((wl.value() - 2.082).abs() < 0.001);
36//!
37//! // Range queries
38//! let band = FrequencyRange::new_mhz(144.0, 148.0);
39//! assert!(band.contains(Frequency::megahertz(146.52)));
40//! assert!(!band.contains(Frequency::megahertz(150.0)));
41//! ```
42
43use crate::error::CoreError;
44use serde::{Deserialize, Serialize};
45use std::{fmt::Display, ops::Range, str::FromStr};
46use uom::{
47    fmt::DisplayStyle,
48    si::{
49        f64::{Frequency as BaseFrequency, Length as BaseLength},
50        frequency as frequency_unit, length as length_unit,
51    },
52};
53
54// ------------------------------------------------------------------------------------------------
55// Public Macros
56// ------------------------------------------------------------------------------------------------
57
58// ------------------------------------------------------------------------------------------------
59// Public Types
60// ------------------------------------------------------------------------------------------------
61
62#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Deserialize, Serialize)]
63pub struct Frequency(BaseFrequency);
64
65#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Deserialize, Serialize)]
66pub struct Wavelength(BaseLength);
67
68pub const SPEED_OF_LIGHT: f64 = 299792458.0; // m/s
69
70#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
71pub struct FrequencyRange(Range<Frequency>);
72
73// ------------------------------------------------------------------------------------------------
74// Public Functions
75// ------------------------------------------------------------------------------------------------
76
77// ------------------------------------------------------------------------------------------------
78// Private Macros
79// ------------------------------------------------------------------------------------------------
80
81// ------------------------------------------------------------------------------------------------
82// Private Types
83// ------------------------------------------------------------------------------------------------
84
85// ------------------------------------------------------------------------------------------------
86// Implementations ❯ Frequency
87// ------------------------------------------------------------------------------------------------
88
89impl Display for Frequency {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        if f.alternate() {
92            match self.value() {
93                0.0..1_000.0 => self
94                    .0
95                    .into_format_args(frequency_unit::hertz, DisplayStyle::Description)
96                    .fmt(f),
97                1_000.0..1_000_000.0 => self
98                    .0
99                    .into_format_args(frequency_unit::kilohertz, DisplayStyle::Description)
100                    .fmt(f),
101                1_000_000.0..1_000_000_000.0 => self
102                    .0
103                    .into_format_args(frequency_unit::megahertz, DisplayStyle::Description)
104                    .fmt(f),
105                1_000_000_000.0..1_000_000_000_000.0 => self
106                    .0
107                    .into_format_args(frequency_unit::gigahertz, DisplayStyle::Description)
108                    .fmt(f),
109                _ => self
110                    .0
111                    .into_format_args(frequency_unit::terahertz, DisplayStyle::Description)
112                    .fmt(f),
113            }
114        } else {
115            self.0
116                .into_format_args(frequency_unit::megahertz, DisplayStyle::Abbreviation)
117                .fmt(f)
118        }
119    }
120}
121
122impl FromStr for Frequency {
123    type Err = CoreError;
124
125    fn from_str(s: &str) -> Result<Self, Self::Err> {
126        BaseFrequency::from_str(s)
127            .map(Self)
128            .map_err(|_| CoreError::InvalidValueFromStr(s.to_string(), "Frequency"))
129    }
130}
131
132impl From<BaseFrequency> for Frequency {
133    fn from(value: BaseFrequency) -> Self {
134        Self(value)
135    }
136}
137
138impl From<f64> for Frequency {
139    fn from(value: f64) -> Self {
140        Frequency::megahertz(value)
141    }
142}
143
144impl From<Frequency> for BaseFrequency {
145    fn from(value: Frequency) -> Self {
146        value.0
147    }
148}
149
150impl From<Frequency> for f64 {
151    fn from(value: Frequency) -> Self {
152        value.0.value
153    }
154}
155
156impl AsRef<BaseFrequency> for Frequency {
157    fn as_ref(&self) -> &BaseFrequency {
158        &self.0
159    }
160}
161
162impl Frequency {
163    pub fn gigahertz(value: f64) -> Self {
164        Self(BaseFrequency::new::<frequency_unit::gigahertz>(value))
165    }
166    pub fn megahertz(value: f64) -> Self {
167        Self(BaseFrequency::new::<frequency_unit::megahertz>(value))
168    }
169    pub fn kilohertz(value: f64) -> Self {
170        Self(BaseFrequency::new::<frequency_unit::kilohertz>(value))
171    }
172    pub fn hertz(value: f64) -> Self {
173        Self(BaseFrequency::new::<frequency_unit::hertz>(value))
174    }
175
176    pub const fn value(&self) -> f64 {
177        self.0.value
178    }
179
180    pub fn to_wavelength(&self) -> Wavelength {
181        Wavelength::meters(SPEED_OF_LIGHT / self.value())
182    }
183}
184
185// ------------------------------------------------------------------------------------------------
186// Implementations ❯ FrequencyRange
187// ------------------------------------------------------------------------------------------------
188
189impl Display for FrequencyRange {
190    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191        self.0.start.fmt(f)?;
192        write!(f, " {} ", if f.alternate() { "to" } else { "-" },)?;
193        self.0.end.fmt(f)
194    }
195}
196
197impl From<Range<Frequency>> for FrequencyRange {
198    fn from(range: Range<Frequency>) -> Self {
199        Self(range)
200    }
201}
202
203impl From<Range<f64>> for FrequencyRange {
204    fn from(range: Range<f64>) -> Self {
205        Self::new(
206            Frequency::megahertz(range.start),
207            Frequency::megahertz(range.end),
208        )
209    }
210}
211
212impl From<(Frequency, Frequency)> for FrequencyRange {
213    fn from(range: (Frequency, Frequency)) -> Self {
214        Self::new(range.0, range.1)
215    }
216}
217
218impl From<(f64, f64)> for FrequencyRange {
219    fn from(range: (f64, f64)) -> Self {
220        Self::new(Frequency::megahertz(range.0), Frequency::megahertz(range.1))
221    }
222}
223
224impl From<FrequencyRange> for Range<Frequency> {
225    fn from(range: FrequencyRange) -> Self {
226        range.0
227    }
228}
229
230impl From<FrequencyRange> for Range<f64> {
231    fn from(range: FrequencyRange) -> Self {
232        Range {
233            start: range.0.start.value(),
234            end: range.0.end.value(),
235        }
236    }
237}
238
239impl FrequencyRange {
240    pub fn new(start: Frequency, end: Frequency) -> Self {
241        assert!(start <= end);
242        Self(Range { start, end })
243    }
244    pub fn new_mhz(start: f64, end: f64) -> Self {
245        Self::new(Frequency::megahertz(start), Frequency::megahertz(end))
246    }
247
248    pub const fn start(&self) -> Frequency {
249        self.0.start
250    }
251
252    pub const fn end(&self) -> Frequency {
253        self.0.end
254    }
255
256    pub fn bandwidth(&self) -> Frequency {
257        Frequency::hertz(self.0.end.value() - self.0.start.value())
258    }
259
260    pub fn mid_band(&self) -> Frequency {
261        Frequency::hertz(self.0.start.value() + (self.bandwidth().value() / 2.0))
262    }
263
264    pub fn contains(&self, frequency: Frequency) -> bool {
265        self.0.contains(&frequency)
266    }
267
268    pub fn contains_mhz(&self, frequency: f64) -> bool {
269        self.0.contains(&Frequency::megahertz(frequency))
270    }
271
272    pub fn is_empty(&self) -> bool {
273        self.0.is_empty()
274    }
275
276    pub fn starts_before(&self, other: &Self) -> bool {
277        self.start() < other.start()
278    }
279
280    pub fn starts_with(&self, other: &Self) -> bool {
281        self.start() == other.start() && self.contains(other.end())
282    }
283
284    pub fn ends_before(&self, other: &Self) -> bool {
285        self.end() < other.end()
286    }
287
288    pub fn ends_with(&self, other: &Self) -> bool {
289        self.contains(other.start()) && self.end() == other.end()
290    }
291
292    pub fn is_subrange(&self, other: &Self) -> bool {
293        self.start() <= other.start() && self.end() >= other.end()
294    }
295
296    pub fn is_overlapping(&self, other: &Self) -> bool {
297        self.contains(other.start()) || self.contains(other.end())
298    }
299}
300
301// ------------------------------------------------------------------------------------------------
302// Implementations ❯ Wavelength
303// ------------------------------------------------------------------------------------------------
304
305impl Display for Wavelength {
306    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307        if f.alternate() {
308            match self.value() {
309                0.001..0.01 => self
310                    .0
311                    .into_format_args(length_unit::millimeter, DisplayStyle::Description)
312                    .fmt(f),
313                0.01..1.0 => self
314                    .0
315                    .into_format_args(length_unit::centimeter, DisplayStyle::Description)
316                    .fmt(f),
317                1_000.0.. => self
318                    .0
319                    .into_format_args(length_unit::kilometer, DisplayStyle::Description)
320                    .fmt(f),
321                _ => self
322                    .0
323                    .into_format_args(length_unit::meter, DisplayStyle::Description)
324                    .fmt(f),
325            }
326        } else {
327            self.0
328                .into_format_args(length_unit::meter, DisplayStyle::Abbreviation)
329                .fmt(f)
330        }
331    }
332}
333
334impl FromStr for Wavelength {
335    type Err = CoreError;
336
337    fn from_str(s: &str) -> Result<Self, Self::Err> {
338        BaseLength::from_str(s)
339            .map(Self)
340            .map_err(|_| CoreError::InvalidValueFromStr(s.to_string(), "Wavelength"))
341    }
342}
343
344impl From<BaseLength> for Wavelength {
345    fn from(value: BaseLength) -> Self {
346        Self(value)
347    }
348}
349
350impl From<f64> for Wavelength {
351    fn from(value: f64) -> Self {
352        Wavelength::meters(value)
353    }
354}
355
356impl From<Wavelength> for BaseLength {
357    fn from(value: Wavelength) -> Self {
358        value.0
359    }
360}
361
362impl From<Wavelength> for f64 {
363    fn from(value: Wavelength) -> Self {
364        value.0.value
365    }
366}
367
368impl AsRef<BaseLength> for Wavelength {
369    fn as_ref(&self) -> &BaseLength {
370        &self.0
371    }
372}
373
374impl Wavelength {
375    pub fn millimeters(value: f64) -> Self {
376        Self(BaseLength::new::<length_unit::millimeter>(value))
377    }
378
379    pub fn centimeters(value: f64) -> Self {
380        Self(BaseLength::new::<length_unit::centimeter>(value))
381    }
382
383    pub fn meters(value: f64) -> Self {
384        Self(BaseLength::new::<length_unit::meter>(value))
385    }
386
387    pub fn kilometers(value: f64) -> Self {
388        Self(BaseLength::new::<length_unit::kilometer>(value))
389    }
390
391    pub const fn value(&self) -> f64 {
392        self.0.value
393    }
394
395    pub fn to_frequency(&self) -> Frequency {
396        Frequency::hertz(SPEED_OF_LIGHT / self.value())
397    }
398}
399
400// ------------------------------------------------------------------------------------------------
401// Public Functions
402// ------------------------------------------------------------------------------------------------
403
404pub fn gigahertz(value: f64) -> Frequency {
405    Frequency::gigahertz(value)
406}
407
408pub fn megahertz(value: f64) -> Frequency {
409    Frequency::megahertz(value)
410}
411
412pub fn kilohertz(value: f64) -> Frequency {
413    Frequency::kilohertz(value)
414}
415
416pub fn hertz(value: f64) -> Frequency {
417    Frequency::hertz(value)
418}
419
420pub fn meters(value: f64) -> Wavelength {
421    Wavelength::meters(value)
422}
423
424// ------------------------------------------------------------------------------------------------
425// Private Functions
426// ------------------------------------------------------------------------------------------------
427
428// ------------------------------------------------------------------------------------------------
429// Sub-Modules
430// ------------------------------------------------------------------------------------------------
431
432// ------------------------------------------------------------------------------------------------
433// Unit Tests
434// ------------------------------------------------------------------------------------------------
435
436#[cfg(test)]
437mod tests {
438    use super::{Frequency, FrequencyRange};
439    use pretty_assertions::assert_eq;
440
441    #[test]
442    fn test_default_fmt_frequency() {
443        assert_eq!("0.000001 MHz", &Frequency::hertz(1.0).to_string());
444        assert_eq!("0.001 MHz", &Frequency::kilohertz(1.0).to_string());
445        assert_eq!("1 MHz", &Frequency::megahertz(1.0).to_string());
446        assert_eq!("1000 MHz", &Frequency::gigahertz(1.0).to_string());
447    }
448
449    #[test]
450    fn test_default_fmt_frequency_precision() {
451        assert_eq!("0.000001 MHz", &format!("{:.6}", Frequency::hertz(1.0)));
452        assert_eq!("0.001000 MHz", &format!("{:.6}", Frequency::kilohertz(1.0)));
453        assert_eq!("1.000000 MHz", &format!("{:.6}", Frequency::megahertz(1.0)));
454        assert_eq!(
455            "1000.000000 MHz",
456            &format!("{:.6}", Frequency::gigahertz(1.0))
457        );
458    }
459
460    #[test]
461    fn test_default_fmt_frequency_width_and_precision() {
462        assert_eq!(
463            "      0.000001 MHz",
464            &format!("{:>14.6}", Frequency::hertz(1.0))
465        );
466        assert_eq!(
467            "      0.001000 MHz",
468            &format!("{:>14.6}", Frequency::kilohertz(1.0))
469        );
470        assert_eq!(
471            "      1.000000 MHz",
472            &format!("{:>14.6}", Frequency::megahertz(1.0))
473        );
474        assert_eq!(
475            "   1000.000000 MHz",
476            &format!("{:>14.6}", Frequency::gigahertz(1.0))
477        );
478    }
479
480    #[test]
481    fn test_alternate_fmt_frequency() {
482        assert_eq!("1 hertz", &format!("{:#}", Frequency::hertz(1.0)));
483        assert_eq!("1 kilohertz", &format!("{:#}", Frequency::kilohertz(1.0)));
484        assert_eq!("1 megahertz", &format!("{:#}", Frequency::megahertz(1.0)));
485        assert_eq!("1 gigahertz", &format!("{:#}", Frequency::gigahertz(1.0)));
486    }
487
488    #[test]
489    fn test_default_fmt_range() {
490        assert_eq!(
491            "0.000001 MHz - 0.00001 MHz",
492            &FrequencyRange::new(Frequency::hertz(1.0), Frequency::hertz(10.0)).to_string()
493        );
494        assert_eq!(
495            "0.001 MHz - 0.01 MHz",
496            &FrequencyRange::new(Frequency::kilohertz(1.0), Frequency::kilohertz(10.0)).to_string()
497        );
498        assert_eq!(
499            "1 MHz - 10 MHz",
500            &FrequencyRange::new(Frequency::megahertz(1.0), Frequency::megahertz(10.0)).to_string()
501        );
502        assert_eq!(
503            "1000 MHz - 10000 MHz",
504            &FrequencyRange::new(Frequency::gigahertz(1.0), Frequency::gigahertz(10.0)).to_string()
505        );
506    }
507
508    #[test]
509    fn test_frequency_wavelength_roundtrip() {
510        let f = Frequency::megahertz(144.0);
511        let wl = f.to_wavelength();
512        let f2 = wl.to_frequency();
513        assert!((f.value() - f2.value()).abs() < 1e-6);
514    }
515
516    #[test]
517    fn test_frequency_range_bandwidth() {
518        let r = FrequencyRange::new_mhz(144.0, 148.0);
519        // Internally stored in Hz; 4 MHz = 4_000_000 Hz
520        assert!((r.bandwidth().value() - 4_000_000.0).abs() < 1.0);
521    }
522
523    #[test]
524    fn test_frequency_range_mid_band() {
525        let r = FrequencyRange::new_mhz(144.0, 148.0);
526        // Mid-band at 146 MHz = 146_000_000 Hz
527        assert!((r.mid_band().value() - 146_000_000.0).abs() < 1.0);
528    }
529
530    #[test]
531    fn test_frequency_range_contains() {
532        let r = FrequencyRange::new_mhz(144.0, 148.0);
533        assert!(r.contains_mhz(144.0));
534        assert!(r.contains_mhz(146.52));
535        assert!(!r.contains_mhz(148.0)); // Range end is exclusive
536        assert!(!r.contains_mhz(150.0));
537    }
538
539    #[test]
540    fn test_frequency_range_overlap() {
541        let a = FrequencyRange::new_mhz(144.0, 148.0);
542        let b = FrequencyRange::new_mhz(146.0, 150.0);
543        let c = FrequencyRange::new_mhz(150.0, 160.0);
544        assert!(a.is_overlapping(&b));
545        assert!(!a.is_overlapping(&c));
546    }
547
548    #[test]
549    fn test_frequency_range_subrange() {
550        let outer = FrequencyRange::new_mhz(144.0, 148.0);
551        let inner = FrequencyRange::new_mhz(145.0, 147.0);
552        assert!(outer.is_subrange(&inner));
553        assert!(!inner.is_subrange(&outer));
554    }
555
556    #[test]
557    fn test_frequency_from_f64_is_megahertz() {
558        let f: Frequency = 146.52_f64.into();
559        assert_eq!(f.to_string(), "146.52 MHz");
560    }
561}