Skip to content

Commit

Permalink
Merge pull request #398 from nyx-space/feat/gh-396-moar-serde
Browse files Browse the repository at this point in the history
Orbit determination constant biases + measurement ambiguity and moduli
  • Loading branch information
ChristopherRabotin authored Jan 14, 2025
2 parents 0f9ed48 + 84e4f43 commit b03b1cb
Show file tree
Hide file tree
Showing 27 changed files with 732 additions and 152 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.0.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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ For Python projects, get started by installing the library via `pip`: `pip insta

Nyx is provided under the [AGPLv3 License](./LICENSE). By using this software, you assume responsibility for adhering to the license. Refer to [the pricing page](https://nyxspace.com/pricing/?utm_source=readme-price) for an FAQ on the AGPLv3 license. Notably, any software that incorporates, links to, or depends on Nyx must also be released under the AGPLv3 license, even if you distribute an unmodified version of Nyx.

# Versioning

Nyx mostly adheres to SemVer. New patch versions should be rare. Updated dependencies trigger a new minor version. _However_ new fields in structures and new behavior may also be added with minor releases, but the public facing initializers and functions should not significantly change (but may still change).

Major releases are for dramatic changes.


[cratesio-image]: https://img.shields.io/crates/v/nyx-space.svg
[cratesio]: https://crates.io/crates/nyx-space
Expand Down
6 changes: 5 additions & 1 deletion examples/04_lro_od/plot_od_rslt.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

@click.command
@click.option("-p", "--path", type=str, default="./04_lro_od_results.parquet")
def main(path: str):
@click.option("-f", "--full", type=bool, default=True)
def main(path: str, full: bool):
df = pl.read_parquet(path)

df = (
Expand Down Expand Up @@ -123,6 +124,9 @@ def main(path: str):
y=["Sigma Vx (RIC) (km/s)", "Sigma Vy (RIC) (km/s)", "Sigma Vz (RIC) (km/s)"],
).show()

if not full:
return

# Load the RIC diff.
for fname, errname in [
("04_lro_od_truth_error", "OD vs Flown"),
Expand Down
2 changes: 1 addition & 1 deletion src/cosmic/orbitdual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ impl OrbitDual {
param: StateParameter::MeanAnomaly,
})
} else if self.ecc()?.real() > 1.0 {
info!("computing the hyperbolic anomaly");
debug!("computing the hyperbolic anomaly");
// From GMAT's TrueToHyperbolicAnomaly
Ok(OrbitPartial {
dual: ((self.ta_deg()?.dual.to_radians().sin()
Expand Down
3 changes: 2 additions & 1 deletion src/dynamics/guidance/ruggiero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

use anise::prelude::Almanac;
use serde::{Deserialize, Serialize};
use snafu::ResultExt;

use super::{
Expand All @@ -32,7 +33,7 @@ use std::fmt;
use std::sync::Arc;

/// Ruggiero defines the closed loop guidance law from IEPC 2011-102
#[derive(Copy, Clone, Default)]
#[derive(Copy, Clone, Default, Serialize, Deserialize)]
pub struct Ruggiero {
/// Stores the objectives
pub objectives: [Option<Objective>; 5],
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub mod cosmic;
/// Utility functions shared by different modules, and which may be useful to engineers.
pub mod utils;

mod errors;
pub mod errors;
/// Nyx will (almost) never panic and functions which may fail will return an error.
pub use self::errors::NyxError;

Expand Down
3 changes: 2 additions & 1 deletion src/mc/dispersion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
*/

use crate::md::StateParameter;
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;

/// A dispersions configuration, allows specifying min/max bounds (by default, they are not set)
#[derive(Copy, Clone, TypedBuilder)]
#[derive(Copy, Clone, TypedBuilder, Serialize, Deserialize)]
pub struct StateDispersion {
pub param: StateParameter,
#[builder(default, setter(strip_option))]
Expand Down
7 changes: 4 additions & 3 deletions src/md/events/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ use crate::md::prelude::{Interpolatable, Traj};
use crate::md::EventEvaluator;
use crate::time::Duration;
use core::fmt;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

/// Enumerates the possible edges of an event in a trajectory.
///
/// `EventEdge` is used to describe the nature of a trajectory event, particularly in terms of its temporal dynamics relative to a specified condition or threshold. This enum helps in distinguishing whether the event is occurring at a rising edge, a falling edge, or if the edge is unclear due to insufficient data or ambiguous conditions.
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum EventEdge {
/// Represents a rising edge of the event. This indicates that the event is transitioning from a lower to a higher evaluation of the event. For example, in the context of elevation, a rising edge would indicate an increase in elevation from a lower angle.
Rising,
Expand Down Expand Up @@ -73,14 +74,14 @@ where
{
/// Generates detailed information about an event at a specific epoch in a trajectory.
///
/// This takes an `Epoch` as an input and returns a `Result<Self, NyxError>`.
/// This takes an `Epoch` as an input and returns a `Result<Self, EventError>`.
/// It is designed to determine the state of a trajectory at a given epoch, evaluate the specific event at that state, and ascertain the nature of the event (rising, falling, or unclear).
/// The initialization intelligently determines the edge type of the event by comparing the event's value at the current, previous, and next epochs.
/// It ensures robust event characterization in trajectories.
///
/// # Returns
/// - `Ok(EventDetails<S>)` if the state at the given epoch can be determined and the event details are successfully evaluated.
/// - `Err(NyxError)` if there is an error in retrieving the state at the specified epoch.
/// - `Err(EventError)` if there is an error in retrieving the state at the specified epoch.
///
pub fn new<E: EventEvaluator<S>>(
state: S,
Expand Down
16 changes: 8 additions & 8 deletions src/md/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use crate::time::{Duration, Unit};
use crate::State;
use anise::prelude::{Almanac, Frame};
use anise::structure::planetocentric::ellipsoid::Ellipsoid;
use serde::{Deserialize, Serialize};

use std::default::Default;
use std::fmt;
Expand Down Expand Up @@ -59,15 +60,14 @@ where
}

/// Defines a state parameter event finder
#[derive(Clone, Debug)]

#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Event {
/// The state parameter
pub parameter: StateParameter,
/// The desired self.desired_value, must be in the same units as the state parameter
pub desired_value: f64,
/// The time precision after which the solver will report that it cannot find any more precise
pub epoch_precision: Unit,
/// The duration precision after which the solver will report that it cannot find any more precise
pub epoch_precision: Duration,
/// The precision on the desired value
pub value_precision: f64,
/// An optional frame in which to search this -- it IS recommended to convert the whole trajectory instead of searching in a given frame!
Expand Down Expand Up @@ -133,12 +133,12 @@ impl Event {
parameter: StateParameter,
desired_value: f64,
value_precision: f64,
epoch_precision: Unit,
unit_precision: Unit,
) -> Self {
Self {
parameter,
desired_value,
epoch_precision,
epoch_precision: 1 * unit_precision,
value_precision,
obs_frame: None,
}
Expand Down Expand Up @@ -166,7 +166,7 @@ impl Event {
Self {
parameter,
desired_value,
epoch_precision: Unit::Millisecond,
epoch_precision: Unit::Millisecond * 1,
value_precision: 1e-3,
obs_frame: Some(target_frame),
}
Expand All @@ -179,7 +179,7 @@ impl Default for Event {
parameter: StateParameter::Periapsis,
desired_value: 0.0,
value_precision: 1e-3,
epoch_precision: Unit::Second,
epoch_precision: Unit::Second * 1,
obs_frame: None,
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/md/objective.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@

use super::StateParameter;
use crate::{errors::StateError, Spacecraft, State};
use serde::{Deserialize, Serialize};
use std::fmt;

/// Defines a state parameter event finder
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Objective {
/// The state parameter to target
pub parameter: StateParameter,
Expand Down
9 changes: 8 additions & 1 deletion src/od/filter/kalman.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,14 @@ where
// Compute the prefit ratio for the automatic rejection.
// The measurement covariance is the square of the measurement itself.
// So we compute its Cholesky decomposition to return to the non squared values.
let r_k_chol = s_k.clone().cholesky().ok_or(ODError::SingularNoiseRk)?.l();
let r_k_chol = match s_k.clone().cholesky() {
Some(r_k_clone) => r_k_clone.l(),
None => {
// In very rare case, when there isn't enough noise in the measurements,
// the inverting of S_k fails. If so, we revert back to the nominal Kalman derivation.
r_k.clone().cholesky().ok_or(ODError::SingularNoiseRk)?.l()
}
};

// Compute the ratio as the average of each component of the prefit over the square root of the measurement
// matrix r_k. Refer to ODTK MathSpec equation 4.10.
Expand Down
30 changes: 29 additions & 1 deletion src/od/ground_station/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use indexmap::{IndexMap, IndexSet};
use snafu::ensure;

use super::msr::MeasurementType;
use super::noise::StochasticNoise;
use super::noise::{GaussMarkov, StochasticNoise};
use super::{ODAlmanacSnafu, ODError, ODTrajSnafu, TrackingDevice};
use crate::io::ConfigRepr;
use crate::od::NoiseNotConfiguredSnafu;
Expand Down Expand Up @@ -119,6 +119,34 @@ impl GroundStation {
self
}

/// Returns a copy of this ground station with the measurement type noises' constant bias set to the provided value.
pub fn with_msr_bias_constant(
mut self,
msr_type: MeasurementType,
bias_constant: f64,
) -> Result<Self, ODError> {
if self.stochastic_noises.is_none() {
self.stochastic_noises = Some(IndexMap::new());
}

let stochastics = self.stochastic_noises.as_mut().unwrap();

let this_noise = stochastics
.get_mut(&msr_type)
.ok_or(ODError::NoiseNotConfigured {
kind: format!("{msr_type:?}"),
})
.unwrap();

if this_noise.bias.is_none() {
this_noise.bias = Some(GaussMarkov::ZERO);
}

this_noise.bias.unwrap().constant = Some(bias_constant);

Ok(self)
}

/// Computes the azimuth and elevation of the provided object seen from this ground station, both in degrees.
/// This is a shortcut to almanac.azimuth_elevation_range_sez.
pub fn azimuth_elevation_of(
Expand Down
16 changes: 16 additions & 0 deletions src/od/ground_station/trk_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,20 @@ impl TrackingDevice<Spacecraft> for GroundStation {
})?
.covariance(epoch))
}

fn measurement_bias(&self, msr_type: MeasurementType, _epoch: Epoch) -> Result<f64, ODError> {
let stochastics = self.stochastic_noises.as_ref().unwrap();

if let Some(gm) = stochastics
.get(&msr_type)
.ok_or(ODError::NoiseNotConfigured {
kind: format!("{msr_type:?}"),
})?
.bias
{
Ok(gm.constant.unwrap_or(0.0))
} else {
Ok(0.0)
}
}
}
20 changes: 19 additions & 1 deletion src/od/msr/measurement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ use nalgebra::{allocator::Allocator, DefaultAllocator, DimName, OVector};
use std::fmt;

/// A type-agnostic simultaneous measurement storage structure. Allows storing any number of simultaneous measurement of a given taker.
#[derive(Clone, Debug, PartialEq)]
///
/// Note that two measurements are considered equal if the tracker and epoch match exactly, and if both have the same measurement types,
/// and those measurements are equal to within 1e-10 (this allows for some leeway in TDM producers).
#[derive(Clone, Debug)]
pub struct Measurement {
/// Tracker alias which made this measurement
pub tracker: String,
Expand Down Expand Up @@ -58,6 +61,7 @@ impl Measurement {
where
DefaultAllocator: Allocator<S>,
{
// Consider adding a modulo modifier here, any bias should be configured by each ground station.
let mut obs = OVector::zeros();
for (i, t) in types.iter().enumerate() {
if let Some(msr_value) = self.data.get(t) {
Expand Down Expand Up @@ -91,3 +95,17 @@ impl fmt::Display for Measurement {
write!(f, "{} measured {} on {}", self.tracker, msrs, self.epoch)
}
}

impl PartialEq for Measurement {
fn eq(&self, other: &Self) -> bool {
self.tracker == other.tracker
&& self.epoch == other.epoch
&& self.data.iter().all(|(key, &value)| {
if let Some(&other_value) = other.data.get(key) {
(value - other_value).abs() < 1e-10
} else {
false
}
})
}
}
Loading

0 comments on commit b03b1cb

Please sign in to comment.