Skip to content

Commit

Permalink
Ensure the stochastic noise GM constant is optional, prevents breakin…
Browse files Browse the repository at this point in the history
…g changes
  • Loading branch information
ChristopherRabotin committed Jan 12, 2025
1 parent b8bcf27 commit 3b06511
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 89 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "nyx-space"
build = "build.rs"
version = "2.1.0"
version = "2.0.1"
edition = "2021"
authors = ["Christopher Rabotin <christopher.rabotin@gmail.com>"]
description = "A high-fidelity space mission toolkit, with orbit propagation, estimation and some systems engineering"
Expand Down
2 changes: 0 additions & 2 deletions data/tests/config/high-prec-network.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
range_noise_model:
tau: 12 h 159 ms
process_noise: 5.0e-3 # 5 m
constant: 0

doppler_noise_model:
tau: 11 h 59 min
process_noise: 50.0e-6 # 5 cm/s
constant: 0
4 changes: 0 additions & 4 deletions data/tests/config/many_ground_stations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
bias:
tau: 24 h
process_noise: 5.0e-3 # 5 m
constant: 0
doppler_km_s:
bias:
tau: 24 h
process_noise: 50.0e-6 # 5 cm/s
constant: 0
light_time_correction: false
latitude_deg: 2.3522
longitude_deg: 48.8566
Expand All @@ -39,12 +37,10 @@
bias:
tau: 24 h
process_noise: 5.0e-3 # 5 m
constant: 0
doppler_km_s:
bias:
tau: 24 h
process_noise: 50.0e-6 # 5 cm/s
constant: 0
light_time_correction: false
measurement_types:
- range_km
Expand Down
2 changes: 0 additions & 2 deletions data/tests/config/one_ground_station.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ stochastic_noises:
bias:
tau: 24 h
process_noise: 5.0e-3 # 5 m
constant: 0
doppler_km_s:
bias:
tau: 24 h
process_noise: 50.0e-6 # 5 cm/s
constant: 0
light_time_correction: false
measurement_types:
- range_km
Expand Down
2 changes: 1 addition & 1 deletion src/od/ground_station/trk_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ impl TrackingDevice<Spacecraft> for GroundStation {
})?
.bias
{
Ok(gm.constant)
Ok(gm.constant.unwrap_or(0.0))
} else {
Ok(0.0)
}
Expand Down
62 changes: 53 additions & 9 deletions src/od/msr/trackingdata/io_ccsds_tdm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,52 @@ use super::TrackingDataArc;

impl TrackingDataArc {
/// Loads a tracking arc from its serialization in CCSDS TDM.
///
/// # Support level
///
/// - Only the KVN format is supported.
/// - Support is limited to orbit determination in "xGEO", i.e. cislunar and deep space missions.
/// - Only one metadata and data section per file is tested.
///
/// ## Data types
///
/// Fully supported:
/// - RANGE
/// - DOPPLER_INSTANTANEOUS, DOPPLER_INTEGRATED
/// - ANGLE_1 / ANGLE_2, as azimuth/elevation only
///
/// Partially supported:
/// - TRANSMIT_FREQ / RECEIVE_FREQ : these will be converted to Doppler measurements using the TURNAROUND_NUMERATOR and TURNAROUND_DENOMINATOR in the TDM. The freq rate is _not_ supported.
///
/// ## Metadata support
///
/// ### Mode
///
/// Only the MODE = SEQUENTIAL is supported.
///
/// ### Time systems / time scales
///
/// All timescales supported by hifitime are supported here. This includes: UTC, TAI, GPS, TT, TDB, TAI, GST, QZSST.
///
/// ### Path
///
/// Only one way or two way data is supported, i.e. path must be either `PATH n,m,n` or `PATH n,m`.
///
/// Note that the actual indexes of the path are ignored.
///
/// ### Participants
///
/// `PARTICIPANT_1` must be the ground station / tracker.
/// The second participant is ignored: the user must ensure that the Orbit Determination Process is properly configured and the proper arc is given.
///
/// ### Turnaround ratio
///
/// The turnaround ratio is only accounted for when the data contains RECEIVE_FREQ and TRANSMIT_FREQ data.
///
/// ### Range and modulus
///
/// Only kilometers are supported in range units. Range modulus is accounted for to compute range ambiguity.
///
pub fn from_tdm<P: AsRef<Path>>(
path: P,
aliases: Option<HashMap<String, String>>,
Expand Down Expand Up @@ -83,15 +129,13 @@ impl TrackingDataArc {
}
} else if line.starts_with("TIME_SYSTEM") {
let ts = line.split('=').nth(1).unwrap_or("UTC").trim();
match ts {
"UTC" => time_system = TimeScale::UTC,
"TAI" => time_system = TimeScale::TAI,
"GPS" => time_system = TimeScale::GPST,
_ => {
return Err(InputOutputError::UnsupportedData {
which: format!("time scale `{ts}` not supported"),
})
}
// Support for all time scales of hifitime
if let Ok(ts) = TimeScale::from_str(ts) {
time_system = ts;
} else {
return Err(InputOutputError::UnsupportedData {
which: format!("time scale `{ts}` not supported"),
});
}
} else if line.starts_with("PATH") {
match line.split(",").count() {
Expand Down
38 changes: 38 additions & 0 deletions src/od/msr/trackingdata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,44 @@ mod io_parquet;

/// Tracking data storing all of measurements as a B-Tree.
/// It inherently does NOT support multiple concurrent measurements from several trackers.
///
/// # Measurement Moduli, e.g. range modulus
///
/// In the case of ranging, and possibly other data types, a code is used to measure the range to the spacecraft. The length of this code
/// determines the ambiguity resolution, as per equation 9 in section 2.2.2.2 of the JPL DESCANSO, document 214, _Pseudo-Noise and Regenerative Ranging_.
/// For example, using the JPL Range Code and a frequency range clock of 1 MHz, the range ambiguity is 75,660 km. In other words,
/// as soon as the spacecraft is at a range of 75,660 + 1 km the JPL Range Code will report the vehicle to be at a range of 1 km.
/// This is simply because the range code overlaps with itself, effectively loosing track of its own reference:
/// it's due to the phase shift of the signal "lapping" the original signal length.
///
/// ```text
/// (Spacecraft)
/// ^
/// | Actual Distance = 75,661 km
/// |
/// 0 km 75,660 km (Wrap-Around)
/// |-----------------------------------------------|
/// When the "code length" is exceeded,
/// measurements wrap back to 0.
///
/// So effectively:
/// Observed code range = Actual range (mod 75,660 km)
/// 75,661 km → 1 km
///
/// ```
///
/// Nyx can only resolve the range ambiguity if the tracking data specifies a modulus for this specific measurement type.
/// For example, in the case of the JPL Range Code and a 1 MHz range clock, the ambiguity interval is 75,660 km.
///
/// The measurement used in the Orbit Determination Process then becomes the following, where `//` represents the [Euclidian division](https://doc.rust-lang.org/std/primitive.f64.html#method.div_euclid).
///
/// ```text
/// k = computed_obs // ambiguity_interval
/// real_obs = measured_obs + k * modulus
/// ```
///
/// Reference: JPL DESCANSO, document 214, _Pseudo-Noise and Regenerative Ranging_.
///
#[derive(Clone, Default)]
pub struct TrackingDataArc {
/// All measurements in this data arc
Expand Down
16 changes: 8 additions & 8 deletions src/od/noise/gauss_markov.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ pub struct GaussMarkov {
/// The time constant, tau gives the correlation time, or the time over which the intensity of the time correlation will fade to 1/e of its prior value. (This is sometimes incorrectly referred to as the "half-life" of the process.)
pub tau: Duration,
pub process_noise: f64,
/// A constant offset on top of the noise.
pub constant: f64,
/// An optional constant offset on top of the noise, defaults to zero.
pub constant: Option<f64>,
/// Epoch of the previous realization, used to compute the time delta for the process noise.
#[serde(skip)]
pub prev_epoch: Option<Epoch>,
Expand Down Expand Up @@ -78,7 +78,7 @@ impl GaussMarkov {
Ok(Self {
tau,
process_noise,
constant: 0.0,
constant: None,
init_sample: None,
prev_epoch: None,
})
Expand All @@ -88,7 +88,7 @@ impl GaussMarkov {
pub const ZERO: Self = Self {
tau: Duration::MAX,
process_noise: 0.0,
constant: 0.0,
constant: None,
init_sample: None,
prev_epoch: None,
};
Expand All @@ -99,7 +99,7 @@ impl GaussMarkov {
Self {
tau: 1.minutes(),
process_noise: 60.0e-5,
constant: 0.0,
constant: None,
init_sample: None,
prev_epoch: None,
}
Expand All @@ -111,7 +111,7 @@ impl GaussMarkov {
Self {
tau: 1.minutes(),
process_noise: 0.03e-6,
constant: 0.0,
constant: None,
init_sample: None,
prev_epoch: None,
}
Expand Down Expand Up @@ -145,7 +145,7 @@ impl Stochastics for GaussMarkov {
let steady_noise = 0.5 * self.process_noise * self.tau.to_seconds() * anti_decay;
let ss_sample = rng.sample(Normal::new(0.0, steady_noise).unwrap());

self.init_sample.unwrap() * decay + ss_sample + self.constant
self.init_sample.unwrap() * decay + ss_sample + self.constant.unwrap_or(0.0)
}
}

Expand All @@ -157,7 +157,7 @@ impl Mul<f64> for GaussMarkov {
Self {
tau: self.tau,
process_noise: self.process_noise * rhs,
constant: 0.0,
constant: None,
init_sample: None,
prev_epoch: None,
}
Expand Down
71 changes: 9 additions & 62 deletions src/od/process/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ pub use crate::od::*;
use crate::propagators::PropInstance;
pub use crate::time::{Duration, Unit};
use anise::prelude::Almanac;
use indexmap::{IndexMap, IndexSet};
use indexmap::IndexSet;
use msr::sensitivity::TrackerSensitivity;
use msr::MeasurementType;
use snafu::prelude::*;
mod conf;
pub use conf::{IterationConf, SmoothingArc};
Expand Down Expand Up @@ -58,45 +57,6 @@ mod export;
/// The measurement residual is a signed scalar, despite ODP being able to process multiple measurements simultaneously.
/// By default, if a measurement is more than 3 measurement sigmas off, it will be rejected to avoid biasing the filter.
///
/// ## Ambiguity intervals
///
/// In the case of ranging, and possibly other data types, a code is used to measure the range to the spacecraft. The length of this code
/// determines the ambiguity resolution, as per equation 9 in section 2.2.2.2 of the reference. For example, using the JPL Range Code and
/// a frequency range clock of 1 MHz, the range ambiguity is 75,660 km. In other words, as soon as the spacecraft is at a range of 75,660 + 1 km
/// the JPL Range Code will report the vehicle to be at a range of 1 km. This is simply because the range code overlaps with itself, effectively
/// loosing track of its own reference: it's due to the phase shift of the signal "lapping" the original signal length.
///
/// ```text
/// (Spacecraft)
/// ^
/// | Actual Distance = 75,661 km
/// |
/// 0 km 75,660 km (Wrap-Around)
/// |-----------------------------------------------|
/// When the "code length" is exceeded,
/// measurements wrap back to 0.
///
/// So effectively:
/// Observed code range = Actual range (mod 75,660 km)
/// 75,661 km → 1 km
///
/// ```
///
/// Nyx solves this problem in two ways: the tracking data must specify a modulus for a specific measurement type. Then, the ambiguity interval
/// of the ODP must be configured appropriately. In the case of the JPL Range Code and a 1 MHz range clock, the ambiguity interval is 75,660 km.
/// The ranging equipment will provide a range modulus value, e.g. 123,000 km, which will need to be added to the reported range.
///
/// The equipment's modulus is provided in the TDM and must be specified in Nyx's TrackingDataArc `moduli` field.
/// The ambiguity interval must be specified in the ODP.
/// The measurement used in the ODP then becomes the following, where `//` represents the [Euclidian division](https://doc.rust-lang.org/std/primitive.f64.html#method.div_euclid).
///
/// ```text
/// k = computed_obs // ambiguity_interval
/// real_obs = measured_obs + k * modulus
/// ```
///
/// Reference: JPL DESCANSO, document 214, _Pseudo-Noise and Regenerative Ranging_.
///
#[allow(clippy::upper_case_acronyms)]
pub struct ODProcess<
'a,
Expand Down Expand Up @@ -133,8 +93,6 @@ pub struct ODProcess<
pub ekf_trigger: Option<EkfTrigger>,
/// Residual rejection criteria allows preventing bad measurements from affecting the estimation.
pub resid_crit: Option<ResidRejectCrit>,
/// Specifies the ambiguity interval of a specific measurement type.
pub ambiguity_intervals: Option<IndexMap<MeasurementType, f64>>,
pub almanac: Arc<Almanac>,
init_state: D::StateType,
_marker: PhantomData<Accel>,
Expand Down Expand Up @@ -181,7 +139,6 @@ where
residuals: Vec::with_capacity(10_000),
ekf_trigger,
resid_crit,
ambiguity_intervals: None,
almanac,
init_state,
_marker: PhantomData::<Accel>,
Expand All @@ -206,7 +163,6 @@ where
residuals: Vec::with_capacity(10_000),
ekf_trigger: Some(trigger),
resid_crit,
ambiguity_intervals: None,
almanac,
init_state,
_marker: PhantomData::<Accel>,
Expand Down Expand Up @@ -628,28 +584,20 @@ where

let mut real_obs = msr.observation(&cur_msr_types);

// If there is an ambiguity, apply it.
if let Some(ambiguity) = &self.ambiguity_intervals {
// Rebuild the R matrix of the measurement noise.
if let Some(moduli) = &arc.moduli {
let mut obs_ambiguity = OVector::<f64, MsrSize>::zeros();

for (i, msr_type) in cur_msr_types.iter().enumerate() {
if let Some(interval) = ambiguity.get(msr_type) {
if let Some(moduli) = &arc.moduli {
if let Some(modulus) = moduli.get(msr_type) {
let k =
computed_obs[i].div_euclid(*interval);
// real_obs = measured_obs + k * modulus
obs_ambiguity[i] = k * *modulus;
}
} else {
// No need to account for ambiguity intervals if there is no moduli configured.
break;
}
if let Some(modulus) = moduli.get(msr_type) {
let k = computed_obs[i].div_euclid(*modulus);
// real_obs = measured_obs + k * modulus
obs_ambiguity[i] = k * *modulus;
}
}

real_obs += obs_ambiguity;
} else {
// No need to account for ambiguity intervals if there is no moduli configured.
break;
}

match self.kf.measurement_update(
Expand Down Expand Up @@ -867,7 +815,6 @@ where
resid_crit,
ekf_trigger: None,
init_state,
ambiguity_intervals: None,
almanac,
_marker: PhantomData::<Accel>,
}
Expand Down

0 comments on commit 3b06511

Please sign in to comment.