bufkit_data/
models.rs

1//! Models potentially stored in the archive.
2
3use std::fmt;
4use strum_macros::{EnumIter, EnumString, IntoStaticStr};
5
6/// Models potentially stored in the archive.
7#[derive(
8    Clone, Copy, PartialEq, Eq, Debug, EnumString, IntoStaticStr, EnumIter, Hash, PartialOrd, Ord,
9)]
10pub enum Model {
11    /// The U.S. Global Forecast System
12    #[strum(
13        to_string = "gfs",
14        serialize = "gfs3",
15        serialize = "GFS",
16        serialize = "GFS3"
17    )]
18    GFS,
19    /// The U.S. North American Model
20    #[strum(
21        to_string = "nam",
22        serialize = "namm",
23        serialize = "NAM",
24        serialize = "NAMM"
25    )]
26    NAM,
27    /// The high resolution nest of the `NAM`
28    #[strum(to_string = "nam4km", serialize = "NAM4KM")]
29    NAM4KM,
30}
31
32impl fmt::Display for Model {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        use crate::models::Model::*;
35
36        match *self {
37            GFS => write!(f, "{}", stringify!(GFS)),
38            NAM => write!(f, "{}", stringify!(NAM)),
39            NAM4KM => write!(f, "{}", stringify!(NAM4KM)),
40        }
41    }
42}
43
44impl Model {
45    /// Get the number of hours between runs.
46    pub fn hours_between_runs(self) -> i64 {
47        match self {
48            Model::GFS | Model::NAM | Model::NAM4KM => 6,
49        }
50    }
51
52    /// Get the base hour of a model run.
53    ///
54    /// Most model run times are 0Z, 6Z, 12Z, 18Z. The base hour along with hours between runs
55    /// allows you to reconstruct these times. Note that SREF starts at 03Z and runs every 6 hours,
56    /// so it is different.
57    pub fn base_hour(self) -> i64 {
58        match self {
59            Model::GFS | Model::NAM | Model::NAM4KM => 0,
60        }
61    }
62
63    /// Create an iterator of all the model runs between two times
64    pub fn all_runs(
65        self,
66        start: &chrono::NaiveDateTime,
67        end: &chrono::NaiveDateTime,
68    ) -> impl Iterator<Item = chrono::NaiveDateTime> {
69        let delta_t = self.hours_between_runs();
70
71        // Find a good start time that corresponds with an actual model run time.
72        let round_start = if *start < *end {
73            let mut strt =
74                start.date().and_hms_opt(0, 0, 0).unwrap() + chrono::Duration::hours(self.base_hour());
75            // Make sure we didn't jump ahead into the future.
76            while strt > *start {
77                strt -= chrono::Duration::hours(self.hours_between_runs());
78            }
79            // Make sure we didn't jumb too far back.
80            while strt < *start {
81                strt += chrono::Duration::hours(self.hours_between_runs());
82            }
83
84            strt
85        } else {
86            let mut strt =
87                start.date().and_hms_opt(0, 0, 0).unwrap() + chrono::Duration::hours(self.base_hour());
88            while strt < *start {
89                strt += chrono::Duration::hours(self.hours_between_runs());
90            }
91            while strt > *start {
92                strt -= chrono::Duration::hours(self.hours_between_runs());
93            }
94
95            strt
96        };
97
98        let steps: i64 = (*end - round_start).num_hours() / self.hours_between_runs();
99
100        (0..=steps.abs())
101            .map(move |step| round_start + chrono::Duration::hours(steps.signum() * step * delta_t))
102    }
103
104    /// Get a static str representation
105    pub fn as_static_str(self) -> &'static str {
106        self.into()
107    }
108}
109
110/*--------------------------------------------------------------------------------------------------
111                                          Unit Tests
112--------------------------------------------------------------------------------------------------*/
113#[cfg(test)]
114mod unit {
115    use super::*;
116
117    use chrono::NaiveDate;
118
119    #[test]
120    fn test_all_runs() {
121        assert_eq!(
122            Model::GFS.hours_between_runs(),
123            6,
124            "test pre-condition failed."
125        );
126
127        let start = &NaiveDate::from_ymd_opt(2018, 9, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
128        let end = &NaiveDate::from_ymd_opt(2018, 9, 2).unwrap().and_hms_opt(0, 0, 0).unwrap();
129        assert_eq!(Model::GFS.all_runs(start, end).count(), 5);
130        Model::GFS
131            .all_runs(start, end)
132            .scan(*start, |prev, rt| {
133                eprintln!("{} <= {}", prev, rt);
134                assert!(*prev <= rt);
135                *prev = rt;
136                Some(rt)
137            })
138            .for_each(|rt| assert!(rt >= *start && rt <= *end));
139        eprintln!();
140
141        let start = &NaiveDate::from_ymd_opt(2018, 9, 1).unwrap().and_hms_opt(0, 1, 0).unwrap();
142        let end = &NaiveDate::from_ymd_opt(2018, 9, 2).unwrap().and_hms_opt(0, 0, 0).unwrap();
143        assert_eq!(Model::GFS.all_runs(start, end).count(), 4);
144        Model::GFS
145            .all_runs(start, end)
146            .scan(*start, |prev, rt| {
147                eprintln!("{} <= {}", prev, rt);
148                assert!(*prev <= rt);
149                *prev = rt;
150                Some(rt)
151            })
152            .for_each(|rt| assert!(rt >= *start && rt <= *end));
153        eprintln!();
154
155        let end = &NaiveDate::from_ymd_opt(2018, 9, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
156        let start = &NaiveDate::from_ymd_opt(2018, 9, 2).unwrap().and_hms_opt(0, 0, 0).unwrap();
157        assert_eq!(Model::GFS.all_runs(start, end).count(), 5);
158        Model::GFS
159            .all_runs(start, end)
160            .scan(*start, |prev, rt| {
161                eprintln!("{} >= {}", prev, rt);
162                assert!(*prev >= rt);
163                *prev = rt;
164                Some(rt)
165            })
166            .for_each(|rt| assert!(rt >= *end && rt <= *start));
167        eprintln!();
168
169        let end = &NaiveDate::from_ymd_opt(2018, 9, 1).unwrap().and_hms_opt(0, 1, 0).unwrap();
170        let start = &NaiveDate::from_ymd_opt(2018, 9, 2).unwrap().and_hms_opt(0, 0, 0).unwrap();
171        assert_eq!(Model::GFS.all_runs(start, end).count(), 4);
172        Model::GFS
173            .all_runs(start, end)
174            .scan(*start, |prev, rt| {
175                eprintln!("{} >= {}", prev, rt);
176                assert!(*prev >= rt);
177                *prev = rt;
178                Some(rt)
179            })
180            .for_each(|rt| assert!(rt >= *end && rt <= *start));
181        eprintln!();
182
183        let end = &NaiveDate::from_ymd_opt(2018, 9, 1).unwrap().and_hms_opt(0, 1, 0).unwrap();
184        let start = &NaiveDate::from_ymd_opt(2018, 9, 2).unwrap().and_hms_opt(0, 2, 0).unwrap();
185        assert_eq!(Model::GFS.all_runs(start, end).count(), 4);
186        Model::GFS
187            .all_runs(start, end)
188            .scan(*start, |prev, rt| {
189                eprintln!("{} >= {}", prev, rt);
190                assert!(*prev >= rt);
191                *prev = rt;
192                Some(rt)
193            })
194            .for_each(|rt| assert!(rt >= *end && rt <= *start));
195    }
196}