Skip to main content

nyx_space/od/msr/
measurement.rs

1/*
2    Nyx, blazing fast astrodynamics
3    Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19use super::MeasurementType;
20use hifitime::Epoch;
21use indexmap::{IndexMap, IndexSet};
22use log::debug;
23use nalgebra::{allocator::Allocator, DefaultAllocator, DimName, OVector};
24use std::fmt;
25
26#[cfg(feature = "python")]
27use pyo3::prelude::*;
28
29/// A type-agnostic simultaneous measurement storage structure. Allows storing any number of simultaneous measurement of a given taker.
30///
31/// Note that two measurements are considered equal if the tracker and epoch match exactly, and if both have the same measurement types,
32/// and those measurements are equal to within 1e-10 (this allows for some leeway in TDM producers).
33#[cfg_attr(feature = "python", pyclass, pyo3(module = "nyx_space.od"))]
34#[derive(Clone, Debug)]
35pub struct Measurement {
36    /// Tracker alias which made this measurement
37    pub tracker: String,
38    /// Epoch of the measurement
39    pub epoch: Epoch,
40    /// All measurements made simultaneously
41    pub data: IndexMap<MeasurementType, f64>,
42    /// Whether this measurement has been manually rejected
43    pub rejected: bool,
44}
45
46#[cfg_attr(feature = "python", pymethods)]
47impl Measurement {
48    /// Correct the provided measurement type with the provided correction, if that measurement type is available
49    pub fn correct(&mut self, msr_type: MeasurementType, correction: f64) {
50        if let Some(cur_value) = self.data.get_mut(&msr_type) {
51            let new_value = *cur_value + correction;
52            debug!("corrected {msr_type:?} from {cur_value} to {new_value}");
53            *cur_value = new_value;
54        }
55    }
56
57    pub fn push(&mut self, msr_type: MeasurementType, msr_value: f64) {
58        self.data.insert(msr_type, msr_value);
59    }
60}
61
62impl Measurement {
63    pub fn new(tracker: String, epoch: Epoch) -> Self {
64        Self {
65            tracker,
66            epoch,
67            data: IndexMap::new(),
68            rejected: false,
69        }
70    }
71
72    pub fn with(mut self, msr_type: MeasurementType, msr_value: f64) -> Self {
73        self.push(msr_type, msr_value);
74        self
75    }
76
77    /// Builds an observation vector for this measurement provided a set of measurement types.
78    /// If the requested measurement type is not available, then that specific row is set to zero.
79    /// The caller must set the appropriate sensitivity matrix rows to zero.
80    pub fn observation<S: DimName>(&self, types: &IndexSet<MeasurementType>) -> OVector<f64, S>
81    where
82        DefaultAllocator: Allocator<S>,
83    {
84        // Consider adding a modulo modifier here, any bias should be configured by each ground station.
85        let mut obs = OVector::zeros();
86        for (i, t) in types.iter().enumerate() {
87            if let Some(msr_value) = self.data.get(t) {
88                obs[i] = *msr_value;
89            }
90        }
91        obs
92    }
93
94    /// Returns a vector specifying which measurement types are available.
95    pub fn availability(&self, types: &IndexSet<MeasurementType>) -> Vec<bool> {
96        let mut rtn = vec![false; types.len()];
97        for (i, t) in types.iter().enumerate() {
98            if self.data.contains_key(t) {
99                rtn[i] = true;
100            }
101        }
102        rtn
103    }
104}
105
106impl fmt::Display for Measurement {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        let msrs = self
109            .data
110            .iter()
111            .map(|(msr_type, msr_value)| format!("{msr_type:?} = {msr_value} {}", msr_type.unit()))
112            .collect::<Vec<String>>()
113            .join(", ");
114
115        write!(f, "{} measured {} on {}", self.tracker, msrs, self.epoch)
116    }
117}
118
119impl PartialEq for Measurement {
120    fn eq(&self, other: &Self) -> bool {
121        self.tracker == other.tracker
122            && self.epoch == other.epoch
123            && self.data.iter().all(|(key, &value)| {
124                if let Some(&other_value) = other.data.get(key) {
125                    (value - other_value).abs() < 1e-10
126                } else {
127                    false
128                }
129            })
130    }
131}