Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose SNC configuration to Python #257

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-beta.0"
version = "2.0.0-beta.1"
edition = "2021"
authors = ["Christopher Rabotin <[email protected]>"]
description = "A high-fidelity space mission toolkit, with orbit propagation, estimation and some systems engineering"
Expand Down
17 changes: 16 additions & 1 deletion src/python/orbit_determination/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use crate::{
od::{
filter::kalman::KF,
process::{EkfTrigger, FltResid, IterationConf, ODProcess},
snc::SNC3,
},
NyxError, Spacecraft,
};
Expand Down Expand Up @@ -55,13 +56,27 @@ pub(crate) fn process_tracking_arc(
predict_step: Option<Duration>,
fixed_step: Option<bool>,
iter_conf: Option<IterationConf>,
snc_disable_time: Option<Duration>,
snc_diagonals: Option<Vec<f64>>,
) -> Result<String, NyxError> {
let msr_noise = Matrix2::from_iterator(measurement_noise);

let init_sc = spacecraft.with_orbit(initial_estimate.0.nominal_state.with_stm());

// Build KF without SNC
let kf = KF::no_snc(initial_estimate.0, msr_noise);
let kf = if (snc_disable_time.is_some() && snc_diagonals.as_ref().is_none())
|| (snc_disable_time.is_none() && snc_diagonals.as_ref().is_some())
|| (snc_diagonals.as_ref().is_some() && snc_diagonals.as_ref().unwrap().len() != 3)
{
return Err(NyxError::CustomError(format!(
"SNC set up requirest both a disable time and the snc_diagonals (3 items required)."
)));
} else if snc_disable_time.is_some() && snc_diagonals.is_some() {
let snc = SNC3::from_diagonal(snc_disable_time.unwrap(), &snc_diagonals.unwrap());
KF::new(initial_estimate.0, snc, msr_noise)
} else {
KF::no_snc(initial_estimate.0, msr_noise)
};

let prop = Propagator::default(dynamics);
let prop_est = prop.with(init_sc);
Expand Down
2 changes: 1 addition & 1 deletion tests/orbit_determination/resid_reject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn epoch() -> Epoch {

#[fixture]
fn traj(epoch: Epoch) -> Traj<Orbit> {
if try_init().is_err() {}
let _ = try_init().is_err();

// Load cosm
let cosm = Cosm::de438();
Expand Down
6 changes: 2 additions & 4 deletions tests/orbit_determination/robust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,6 @@ fn od_robust_test_ekf_realistic_two_way() {

// Define the propagator information.
let prop_time = 1 * Unit::Day;
let step_size = 10.0 * Unit::Second;
let opts = PropOpts::with_fixed_step(step_size);

// Define state information.
let eme2k = cosm.frame("EME2000");
Expand Down Expand Up @@ -326,7 +324,7 @@ fn od_robust_test_ekf_realistic_two_way() {
Bodies::SaturnBarycenter,
];
let orbital_dyn = OrbitalDynamics::point_masses(&bodies, cosm.clone());
let truth_setup = Propagator::new::<RK4Fixed>(orbital_dyn, opts);
let truth_setup = Propagator::default(orbital_dyn);
let (_, traj) = truth_setup
.with(initial_state)
.for_duration_with_traj(prop_time)
Expand Down Expand Up @@ -354,7 +352,7 @@ fn od_robust_test_ekf_realistic_two_way() {
// We expect the estimated orbit to be _nearly_ perfect because we've removed Saturn from the estimated trajectory
let bodies = vec![Bodies::Luna, Bodies::Sun, Bodies::JupiterBarycenter];
let estimator = OrbitalDynamics::point_masses(&bodies, cosm.clone());
let setup = Propagator::new::<RK4Fixed>(estimator, opts);
let setup = Propagator::default(estimator);
let prop_est = setup.with(initial_state_dev.with_stm());

// Define the expected measurement noise (we will then expect the residuals to be within those bounds if we have correctly set up the filter)
Expand Down
33 changes: 19 additions & 14 deletions tests/propagation/events.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
extern crate nyx_space as nyx;
use std::fmt::Write;

#[test]
fn event_tracker_true_anomaly() {
Expand Down Expand Up @@ -35,8 +36,10 @@ fn event_tracker_true_anomaly() {
let found_events = traj.find_all(e).unwrap();
let pretty = found_events
.iter()
.map(|orbit| format!("{:x}\tevent value: {}\n", orbit, e.eval(orbit)))
.collect::<String>();
.fold(String::new(), |mut output, orbit| {
let _ = writeln!(output, "{orbit:x}\tevent value: {}", e.eval(orbit));
output
});
println!("[ta_tracker] {} =>\n{}", e, pretty);
}

Expand Down Expand Up @@ -89,32 +92,34 @@ fn event_tracker_true_anomaly() {

let pretty = umbra_events
.iter()
.map(|orbit| {
format!(
"{:x}\tevent value: {}\t(-10s: {}\t+10s: {})\n",
.fold(String::new(), |mut output, orbit| {
let _ = writeln!(
output,
"{:x}\tevent value: {}\t(-10s: {}\t+10s: {})",
orbit,
&e_loc.compute(orbit),
&e_loc.compute(&traj.at(orbit.epoch() - 10 * Unit::Second).unwrap()),
&e_loc.compute(&traj.at(orbit.epoch() + 10 * Unit::Second).unwrap())
)
})
.collect::<String>();
);
output
});
println!("[eclipses] {} =>\n{}", umbra_event_loc, pretty);

let penumbra_event_loc = e_loc.to_penumbra_event();
let penumbra_events = traj.find_all(&penumbra_event_loc).unwrap();

let pretty = penumbra_events
.iter()
.map(|orbit| {
format!(
"{:x}\tevent value: {}\t(-10s: {}\t+10s: {})\n",
.fold(String::new(), |mut output, orbit| {
let _ = writeln!(
output,
"{:x}\tevent value: {}\t(-10s: {}\t+10s: {})",
orbit,
&e_loc.compute(orbit),
&e_loc.compute(&traj.at(orbit.epoch() - 10 * Unit::Second).unwrap()),
&e_loc.compute(&traj.at(orbit.epoch() + 10 * Unit::Second).unwrap())
)
})
.collect::<String>();
);
output
});
println!("[eclipses] {} =>\n{}", penumbra_event_loc, pretty);
}
4 changes: 2 additions & 2 deletions tests/python/test_cosmic.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def test_generate_states():
100,
kind="prct",
)
assert len(orbits) == 100
assert len(orbits) >= 98

# Define the SRP
srp = SrpConfig(2.0)
Expand Down Expand Up @@ -160,7 +160,7 @@ def test_generate_states():
kind="abs",
seed=42,
)
assert len(spacecraft) == 100
assert len(spacecraft) >= 98


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion tests/python/test_mission_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def test_two_body():

# And propagate in parallel using a single duration
proped_orbits = two_body(orbits, durations=[Unit.Day * 531.5])
assert len(proped_orbits) == len(orbits)
assert len(proped_orbits) >= len(orbits) - 2

# And propagate in parallel using many epochs
ts = TimeSeries(e, e + Unit.Day * 1000, step=Unit.Day * 1, inclusive=False)
Expand Down
43 changes: 43 additions & 0 deletions tests/python/test_orbit_determination.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,26 @@ def test_filter_arc():

print(f"Stored to {rslt_path}")

# Repeat with SNC to compare results
snc_rslt_path = process_tracking_arc(
dynamics["hifi"],
sc,
orbit_est,
msr_noise,
arc,
str(outpath.joinpath("./od_result_snc.parquet")),
cfg,
ekf_num_msr_trig,
ekf_disable_time,
snc_disable_time=Unit.Minute * 10.0,
snc_diagonals=[5e-12, 5e-12, 5e-12]
)

print(f"Stored to {rslt_path}")

# Load the results
oddf = pd.read_parquet(rslt_path)
oddf_snc = pd.read_parquet(snc_rslt_path)
# Load the reference trajectory
ref_traj = pd.read_parquet(traj_file)
# Load the measurements
Expand All @@ -153,6 +171,16 @@ def test_filter_arc():
show=False,
)

plot_estimates(
oddf_snc,
"OD with SNC results from Python",
cov_frame="RIC",
ref_traj=ref_traj,
msr_df=msr_df,
html_out=str(outpath.joinpath("./od_estimate_snc_plots.html")),
show=False,
)

# Let's also plot the reference and the OD result's orbital elements
plot_orbit_elements(
[ref_traj, oddf],
Expand Down Expand Up @@ -180,6 +208,13 @@ def test_filter_arc():
html_out=str(outpath.joinpath("./od_residual_plots.html")),
show=False,
)
plot_residuals(
oddf_snc,
"OD residuals with SNC enabled",
msr_df=msr_df,
html_out=str(outpath.joinpath("./od_residual_snc_plots.html")),
show=False,
)
# And the postfit histograms
plot_residual_histogram(
oddf,
Expand All @@ -198,6 +233,14 @@ def test_filter_arc():
show=False,
)

err_snc_df = diff_traj_parquet(traj_file, snc_rslt_path)
plot_orbit_elements(
err_snc_df,
"Error in orbital elements with SNC",
html_out=str(outpath.joinpath("./od_snc_vs_ref_error_elements.html")),
show=False,
)


def test_one_way_msr():
"""
Expand Down