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::{DefaultAllocator, DimName, OVector, allocator::Allocator};
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(
34    feature = "python",
35    pyclass(from_py_object),
36    pyo3(module = "nyx_space.od")
37)]
38#[derive(Clone, Debug)]
39pub struct Measurement {
40    /// Tracker alias which made this measurement
41    pub tracker: String,
42    /// Epoch of the measurement
43    pub epoch: Epoch,
44    /// All measurements made simultaneously
45    pub data: IndexMap<MeasurementType, f64>,
46    /// Whether this measurement has been manually rejected
47    pub rejected: bool,
48}
49
50#[cfg_attr(feature = "python", pymethods)]
51impl Measurement {
52    /// Correct the provided measurement type with the provided correction, if that measurement type is available
53    pub fn correct(&mut self, msr_type: MeasurementType, correction: f64) {
54        if let Some(cur_value) = self.data.get_mut(&msr_type) {
55            let new_value = *cur_value + correction;
56            debug!("corrected {msr_type:?} from {cur_value} to {new_value}");
57            *cur_value = new_value;
58        }
59    }
60
61    pub fn push(&mut self, msr_type: MeasurementType, msr_value: f64) {
62        self.data.insert(msr_type, msr_value);
63    }
64}
65
66impl Measurement {
67    pub fn new(tracker: String, epoch: Epoch) -> Self {
68        Self {
69            tracker,
70            epoch,
71            data: IndexMap::new(),
72            rejected: false,
73        }
74    }
75
76    pub fn with(mut self, msr_type: MeasurementType, msr_value: f64) -> Self {
77        self.push(msr_type, msr_value);
78        self
79    }
80
81    /// Builds an observation vector for this measurement provided a set of measurement types.
82    /// If the requested measurement type is not available, then that specific row is set to zero.
83    /// The caller must set the appropriate sensitivity matrix rows to zero.
84    pub fn observation<S: DimName>(&self, types: &IndexSet<MeasurementType>) -> OVector<f64, S>
85    where
86        DefaultAllocator: Allocator<S>,
87    {
88        // Consider adding a modulo modifier here, any bias should be configured by each ground station.
89        let mut obs = OVector::zeros();
90        for (i, t) in types.iter().enumerate() {
91            if let Some(msr_value) = self.data.get(t) {
92                obs[i] = *msr_value;
93            }
94        }
95        obs
96    }
97
98    /// Returns a vector specifying which measurement types are available.
99    pub fn availability(&self, types: &IndexSet<MeasurementType>) -> Vec<bool> {
100        let mut rtn = vec![false; types.len()];
101        for (i, t) in types.iter().enumerate() {
102            if self.data.contains_key(t) {
103                rtn[i] = true;
104            }
105        }
106        rtn
107    }
108}
109
110impl fmt::Display for Measurement {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        let msrs = self
113            .data
114            .iter()
115            .map(|(msr_type, msr_value)| format!("{msr_type:?} = {msr_value} {}", msr_type.unit()))
116            .collect::<Vec<String>>()
117            .join(", ");
118
119        write!(f, "{} measured {} on {}", self.tracker, msrs, self.epoch)
120    }
121}
122
123impl PartialEq for Measurement {
124    fn eq(&self, other: &Self) -> bool {
125        self.tracker == other.tracker
126            && self.epoch == other.epoch
127            && self.data.iter().all(|(key, &value)| {
128                if let Some(&other_value) = other.data.get(key) {
129                    (value - other_value).abs() < 1e-10
130                } else {
131                    false
132                }
133            })
134    }
135}