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