mwalib/timestep/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5//! Structs and helper methods for timestep metadata
6
7use crate::gpubox_files::GpuboxTimeMap;
8use crate::misc;
9use crate::voltage_files::VoltageFileTimeMap;
10use crate::{metafits_context, MWAVersion, MetafitsContext};
11use crate::{MWA_VCS_LEGACY_RECOMBINED_FILE_SECONDS, MWA_VCS_MWAXV2_SUBFILE_SECONDS};
12use std::fmt;
13
14#[cfg(test)]
15mod test;
16
17#[cfg(any(feature = "python", feature = "python-stubgen"))]
18use pyo3::prelude::*;
19#[cfg(feature = "python-stubgen")]
20use pyo3_stub_gen_derive::gen_stub_pyclass;
21
22/// This is a struct for our timesteps
23/// NOTE: correlator timesteps use unix time, voltage timesteps use gpstime, but we convert the two depending on what we are given
24#[cfg_attr(feature = "python-stubgen", gen_stub_pyclass)]
25#[cfg_attr(
26    any(feature = "python", feature = "python-stubgen"),
27    pyclass(get_all, set_all)
28)]
29#[derive(Clone)]
30pub struct TimeStep {
31    /// UNIX time (in milliseconds to avoid floating point inaccuracy)
32    pub unix_time_ms: u64,
33    /// gps time (in milliseconds)
34    pub gps_time_ms: u64,
35}
36
37impl TimeStep {
38    /// Creates a new, populated TimeStep struct
39    ///
40    /// # Arguments
41    ///
42    /// * `unix_time_ms` - The UNIX time for this timestep, in milliseconds
43    ///
44    /// * `gps_time_ms` - The gps time for this timestep, in milliseconds
45    ///
46    ///
47    /// # Returns
48    ///
49    /// * A populated TimeStep struct
50    ///
51    pub(crate) fn new(unix_time_ms: u64, gps_time_ms: u64) -> Self {
52        TimeStep {
53            unix_time_ms,
54            gps_time_ms,
55        }
56    }
57
58    /// Creates a new, populated vector of correlator TimeStep structs.
59    ///    
60    ///
61    /// # Arguments
62    ///
63    /// * `gpubox_time_map` - BTree structure containing the map of what gpubox
64    ///   files and timesteps we were supplied by the client.
65    ///
66    /// * `metafits_timesteps' - Reference to populated metafits timesteps.
67    ///
68    /// * `scheduled_starttime_gps_ms` - Scheduled start time of the observation based on GPSTIME in the metafits (obsid).
69    ///
70    /// * `scheduled_starttime_unix_ms` - Scheduled start time of the observation based on GOODTIME-QUACKTIM in the metafits.
71    ///
72    /// * `corr_int_time_ms` The correlator integration time in ms between each timestep.
73    ///
74    /// # Returns
75    ///
76    /// * A populated vector of TimeStep structs inside an Option. Only
77    ///   timesteps. If the Option has a value of None, then `gpubox_time_map` is empty.
78    ///
79    pub(crate) fn populate_correlator_timesteps(
80        gpubox_time_map: &GpuboxTimeMap,
81        metafits_timesteps: &[TimeStep],
82        scheduled_starttime_gps_ms: u64,
83        scheduled_starttime_unix_ms: u64,
84        corr_int_time_ms: u64,
85    ) -> Option<Vec<Self>> {
86        if gpubox_time_map.is_empty() {
87            return None;
88        }
89        // Create timestep vector from metafits timesteps
90        let mut timesteps: Vec<TimeStep> = Vec::new();
91
92        // Iterate through the gpubox map and insert all timesteps
93        for (unix_time_ms, _) in gpubox_time_map.iter() {
94            let gps_time_ms = misc::convert_unixtime_to_gpstime(
95                *unix_time_ms,
96                scheduled_starttime_gps_ms,
97                scheduled_starttime_unix_ms,
98            );
99            timesteps.push(Self::new(*unix_time_ms, gps_time_ms));
100        }
101
102        // Now that we have finished with the correlator specific GpuBoxTimeMap, pass the details into the generic function to populate the superset
103        // of timesteps
104        Some(TimeStep::populate_metafits_provided_superset_of_timesteps(
105            timesteps,
106            metafits_timesteps,
107            scheduled_starttime_gps_ms,
108            scheduled_starttime_unix_ms,
109            corr_int_time_ms,
110        ))
111    }
112
113    /// Creates a new, populated vector of voltage TimeStep structs.    
114    ///
115    /// # Arguments
116    ///
117    /// * `voltage_time_map` - BTree structure containing the map of what voltage
118    ///   files and timesteps we were supplied by the client.
119    ///
120    /// * `metafits_timesteps' - Reference to populated metafits timesteps.
121    ///
122    /// * `scheduled_starttime_gps_ms` - Scheduled start time of the observation based on GPSTIME in the metafits (obsid).
123    ///
124    /// * `scheduled_starttime_unix_ms` - Scheduled start time of the observation based on GOODTIME-QUACKTIM in the metafits.
125    ///
126    /// * `voltage_timestep_duration_ms` The time in ms between each timestep.
127    ///
128    /// # Returns
129    ///
130    /// * A populated vector of TimeStep structs inside an Option. Only
131    ///   timesteps. If the Option has a value of None, then `voltage_time_map` is empty.
132    ///
133    pub(crate) fn populate_voltage_timesteps(
134        voltage_time_map: &VoltageFileTimeMap,
135        metafits_timesteps: &[TimeStep],
136        scheduled_starttime_gps_ms: u64,
137        scheduled_starttime_unix_ms: u64,
138        voltage_timestep_duration_ms: u64,
139    ) -> Option<Vec<Self>> {
140        if voltage_time_map.is_empty() {
141            return None;
142        }
143        // Create timestep vector from metafits timesteps
144        let mut timesteps: Vec<TimeStep> = Vec::new();
145
146        // Iterate through the voltage time map and insert all timesteps
147        for (gps_time_seconds, _) in voltage_time_map.iter() {
148            let unix_time_ms = misc::convert_gpstime_to_unixtime(
149                *gps_time_seconds * 1000,
150                scheduled_starttime_gps_ms,
151                scheduled_starttime_unix_ms,
152            );
153            timesteps.push(Self::new(unix_time_ms, *gps_time_seconds * 1000));
154        }
155
156        // Now that we have finished with the voltage specific VoltageTimeMap, pass the details into the generic function to populate the superset
157        // of timesteps
158        Some(TimeStep::populate_metafits_provided_superset_of_timesteps(
159            timesteps,
160            metafits_timesteps,
161            scheduled_starttime_gps_ms,
162            scheduled_starttime_unix_ms,
163            voltage_timestep_duration_ms,
164        ))
165    }
166
167    /// Generic helper function for both Correlator and Voltage contexts to, given a provided set of timesteps (from all data files),
168    /// create a new vector of timesteps which is a contiguous superset of metafits and provided timesteps.
169    ///
170    /// This code tries to populate timesteps which:
171    /// * Covers all data provided
172    /// * Does it's best to go from the metafits scheduled start to scheduled end
173    ///   NOTE: this is involved, because in legacy obs, the metafits correlator timesteps can be offset by fractions of an integration from the data timesteps. E.g.
174    ///   metafits timesteps = [0, 2, 4, 6, ..., 30]
175    ///   provided timesteps = [3, 5, 7, 9, ..., 29, 31]
176    ///
177    ///  In this example the superset of timesteps will be:
178    ///  timesteps [1, 3, 5, 7, 9, ..., 29, 31]
179    ///
180    /// # Arguments
181    ///
182    /// * `provided_timesteps` - Vector of timesteps which have been found based on the data files provided.
183    ///
184    /// * `metafits_timesteps' - Reference to populated metafits timesteps.
185    ///
186    /// * `scheduled_starttime_gps_ms` - Scheduled start time of the observation based on GPSTIME in the metafits (obsid).
187    ///
188    /// * `scheduled_starttime_unix_ms` - Scheduled start time of the observation based on GOODTIME-QUACKTIM in the metafits.
189    ///
190    /// * `timestep_duration_ms` The time in ms between each timestep.
191    ///
192    /// # Returns
193    ///
194    /// * A populated vector of superset of TimeSteps.
195    ///
196    fn populate_metafits_provided_superset_of_timesteps(
197        provided_timesteps: Vec<TimeStep>,
198        metafits_timesteps: &[TimeStep],
199        scheduled_starttime_gps_ms: u64,
200        scheduled_starttime_unix_ms: u64,
201        timestep_duration_ms: u64,
202    ) -> Vec<TimeStep> {
203        let mut timesteps: Vec<TimeStep> = provided_timesteps;
204
205        let first_data_timestep_unix_ms: u64 = timesteps[0].unix_time_ms;
206        let last_data_timestep_unix_ms: u64 = timesteps[timesteps.len() - 1].unix_time_ms;
207
208        // Go backwards from the first provided timestep to the scheduled start time of the obs and fill in any missing timesteps
209        // but only if the first provided timestep is AFTER the start time of the obs.
210        if first_data_timestep_unix_ms > metafits_timesteps[0].unix_time_ms {
211            let mut current_timestep_unix_ms: u64 =
212                first_data_timestep_unix_ms - timestep_duration_ms;
213
214            while current_timestep_unix_ms >= metafits_timesteps[0].unix_time_ms {
215                // Create a new timestep
216                let gps_time_ms = misc::convert_unixtime_to_gpstime(
217                    current_timestep_unix_ms,
218                    scheduled_starttime_gps_ms,
219                    scheduled_starttime_unix_ms,
220                );
221                timesteps.push(Self::new(current_timestep_unix_ms, gps_time_ms));
222
223                // Move back by the correlator integration time
224                current_timestep_unix_ms -= timestep_duration_ms;
225            }
226        }
227
228        // Go forwards from the last provided timestep to the scheduled end time of the obs and fill in any missing timesteps
229        // but only if the last provided timestep is BEFORE the end time of the obs.
230        if last_data_timestep_unix_ms
231            < metafits_timesteps[metafits_timesteps.len() - 1].unix_time_ms
232        {
233            let mut current_timestep_unix_ms: u64 =
234                last_data_timestep_unix_ms + timestep_duration_ms;
235
236            while current_timestep_unix_ms
237                <= metafits_timesteps[metafits_timesteps.len() - 1].unix_time_ms
238            {
239                // Create a new timestep
240                let gps_time_ms = misc::convert_unixtime_to_gpstime(
241                    current_timestep_unix_ms,
242                    scheduled_starttime_gps_ms,
243                    scheduled_starttime_unix_ms,
244                );
245                timesteps.push(Self::new(current_timestep_unix_ms, gps_time_ms));
246
247                // Move forward by the correlator integration time
248                current_timestep_unix_ms += timestep_duration_ms;
249            }
250        }
251
252        // Now sort by unix time
253        timesteps.sort_by_key(|t| t.unix_time_ms);
254
255        // We have extended out the first and last provided timesteps
256        // Now we can go from first to last and fill in any gaps
257        for timestep_unix_time_ms in (timesteps[0].unix_time_ms
258            ..timesteps[timesteps.len() - 1].unix_time_ms)
259            .step_by(timestep_duration_ms as usize)
260        {
261            if !&timesteps
262                .iter()
263                .any(|t| t.unix_time_ms == timestep_unix_time_ms)
264            {
265                let gps_time_ms = misc::convert_unixtime_to_gpstime(
266                    timestep_unix_time_ms,
267                    scheduled_starttime_gps_ms,
268                    scheduled_starttime_unix_ms,
269                );
270                timesteps.push(Self::new(timestep_unix_time_ms, gps_time_ms));
271            }
272        }
273
274        // Now sort by unix time one final time
275        timesteps.sort_by_key(|t| t.unix_time_ms);
276
277        timesteps
278    }
279
280    /// This creates a populated vector of `TimeStep` structs
281    ///
282    /// # Arguments    
283    ///
284    /// * `metafits_context` - Reference to populated MetafitsContext
285    ///
286    /// * `mwa_version` - enum representing the version of the correlator this observation was created with.
287    ///
288    /// * `start_gps_time_ms` - GPS time (in ms) of first common voltage file.
289    ///
290    /// * `duration_ms` - Duration (in ms).        
291    ///
292    /// * `scheduled_starttime_gps_ms` - Scheduled start time of the observation based on GPSTIME in the metafits (obsid).
293    ///
294    /// * `scheduled_starttime_unix_ms` - Scheduled start time of the observation based on GOODTIME-QUACKTIM in the metafits.
295    ///
296    /// # Returns
297    ///
298    /// * A populated vector of TimeStep structs from start to end.
299    ///
300    pub(crate) fn populate_timesteps(
301        metafits_context: &MetafitsContext,
302        mwa_version: metafits_context::MWAVersion,
303        start_gps_time_ms: u64,
304        duration_ms: u64,
305        scheduled_starttime_gps_ms: u64,
306        scheduled_starttime_unix_ms: u64,
307    ) -> Vec<Self> {
308        // Determine the interval between timesteps
309        let interval_ms: u64 = match mwa_version {
310            MWAVersion::CorrOldLegacy | MWAVersion::CorrLegacy | MWAVersion::CorrMWAXv2 => {
311                metafits_context.corr_int_time_ms
312            }
313            MWAVersion::VCSLegacyRecombined => MWA_VCS_LEGACY_RECOMBINED_FILE_SECONDS * 1000,
314            MWAVersion::VCSMWAXv2 => MWA_VCS_MWAXV2_SUBFILE_SECONDS * 1000,
315        };
316
317        // Init our vector
318        let mut timesteps_vec: Vec<Self> = vec![];
319
320        // Populate the vector (note use of ..= here for an INCLUSIVE for loop)
321        for gps_time in
322            (start_gps_time_ms..start_gps_time_ms + duration_ms).step_by(interval_ms as usize)
323        {
324            let unix_time_ms = misc::convert_gpstime_to_unixtime(
325                gps_time,
326                scheduled_starttime_gps_ms,
327                scheduled_starttime_unix_ms,
328            );
329
330            timesteps_vec.push(Self::new(unix_time_ms, gps_time));
331        }
332
333        timesteps_vec
334    }
335
336    /// This creates a populated vector of indices from the passed in `all_timesteps' slice of TimeSteps between start and end time.
337    ///
338    /// # Arguments    
339    ///
340    /// * `all_timesteps` - Reference to a slice containing all the timesteps        
341    ///
342    /// * `start_unix_time_ms` - Start time of the first timestep you want the index put in the final array..
343    ///
344    /// * `end_unix_time_ms` - The time (including the timestep duration) up to which you want in the final array.
345    ///
346    /// # Returns
347    ///
348    /// * A populated vector of timestep indices based on the start and end times passed in.
349    ///
350    pub(crate) fn get_timstep_indicies(
351        all_timesteps: &[Self],
352        start_unix_time_ms: u64,
353        end_unix_time_ms: u64,
354    ) -> Vec<usize> {
355        let mut timestep_indices: Vec<usize> = all_timesteps
356            .iter()
357            .filter(|f| f.unix_time_ms >= start_unix_time_ms && f.unix_time_ms < end_unix_time_ms)
358            .map(|t| {
359                all_timesteps
360                    .iter()
361                    .position(|v| v.unix_time_ms == t.unix_time_ms)
362                    .unwrap()
363            })
364            .collect();
365        timestep_indices.sort_unstable();
366
367        timestep_indices
368    }
369}
370
371/// Implements fmt::Debug for TimeStep struct
372///
373/// # Arguments
374///
375/// * `f` - A fmt::Formatter
376///
377///
378/// # Returns
379///
380/// * `fmt::Result` - Result of this method
381///
382///
383impl fmt::Debug for TimeStep {
384    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385        write!(
386            f,
387            "unix={:.3}, gps={:.3}",
388            self.unix_time_ms as f64 / 1000.,
389            self.gps_time_ms as f64 / 1000.,
390        )
391    }
392}