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 nalgebra::{allocator::Allocator, DefaultAllocator, DimName, OVector};
23use std::fmt;
24
25/// A type-agnostic simultaneous measurement storage structure. Allows storing any number of simultaneous measurement of a given taker.
26///
27/// Note that two measurements are considered equal if the tracker and epoch match exactly, and if both have the same measurement types,
28/// and those measurements are equal to within 1e-10 (this allows for some leeway in TDM producers).
29#[derive(Clone, Debug)]
30pub struct Measurement {
31    /// Tracker alias which made this measurement
32    pub tracker: String,
33    /// Epoch of the measurement
34    pub epoch: Epoch,
35    /// All measurements made simultaneously
36    pub data: IndexMap<MeasurementType, f64>,
37}
38
39impl Measurement {
40    pub fn new(tracker: String, epoch: Epoch) -> Self {
41        Self {
42            tracker,
43            epoch,
44            data: IndexMap::new(),
45        }
46    }
47
48    pub fn push(&mut self, msr_type: MeasurementType, msr_value: f64) {
49        self.data.insert(msr_type, msr_value);
50    }
51
52    pub fn with(mut self, msr_type: MeasurementType, msr_value: f64) -> Self {
53        self.push(msr_type, msr_value);
54        self
55    }
56
57    /// Builds an observation vector for this measurement provided a set of measurement types.
58    /// If the requested measurement type is not available, then that specific row is set to zero.
59    /// The caller must set the appropriate sensitivity matrix rows to zero.
60    pub fn observation<S: DimName>(&self, types: &IndexSet<MeasurementType>) -> OVector<f64, S>
61    where
62        DefaultAllocator: Allocator<S>,
63    {
64        // Consider adding a modulo modifier here, any bias should be configured by each ground station.
65        let mut obs = OVector::zeros();
66        for (i, t) in types.iter().enumerate() {
67            if let Some(msr_value) = self.data.get(t) {
68                obs[i] = *msr_value;
69            }
70        }
71        obs
72    }
73
74    /// Returns a vector specifying which measurement types are available.
75    pub fn availability(&self, types: &IndexSet<MeasurementType>) -> Vec<bool> {
76        let mut rtn = vec![false; types.len()];
77        for (i, t) in types.iter().enumerate() {
78            if self.data.contains_key(t) {
79                rtn[i] = true;
80            }
81        }
82        rtn
83    }
84}
85
86impl fmt::Display for Measurement {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        let msrs = self
89            .data
90            .iter()
91            .map(|(msr_type, msr_value)| format!("{msr_type:?} = {msr_value} {}", msr_type.unit()))
92            .collect::<Vec<String>>()
93            .join(", ");
94
95        write!(f, "{} measured {} on {}", self.tracker, msrs, self.epoch)
96    }
97}
98
99impl PartialEq for Measurement {
100    fn eq(&self, other: &Self) -> bool {
101        self.tracker == other.tracker
102            && self.epoch == other.epoch
103            && self.data.iter().all(|(key, &value)| {
104                if let Some(&other_value) = other.data.get(key) {
105                    (value - other_value).abs() < 1e-10
106                } else {
107                    false
108                }
109            })
110    }
111}