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 !×teps
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}