Skip to content

Commit 2b0e148

Browse files
Expose SNC configuration to Python
1 parent 3553b2e commit 2b0e148

File tree

5 files changed

+81
-20
lines changed

5 files changed

+81
-20
lines changed

src/python/orbit_determination/process.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use crate::{
2727
od::{
2828
filter::kalman::KF,
2929
process::{EkfTrigger, FltResid, IterationConf, ODProcess},
30+
snc::SNC3,
3031
},
3132
NyxError, Spacecraft,
3233
};
@@ -55,13 +56,27 @@ pub(crate) fn process_tracking_arc(
5556
predict_step: Option<Duration>,
5657
fixed_step: Option<bool>,
5758
iter_conf: Option<IterationConf>,
59+
snc_disable_time: Option<Duration>,
60+
snc_diagonals: Option<Vec<f64>>,
5861
) -> Result<String, NyxError> {
5962
let msr_noise = Matrix2::from_iterator(measurement_noise);
6063

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

6366
// Build KF without SNC
64-
let kf = KF::no_snc(initial_estimate.0, msr_noise);
67+
let kf = if (snc_disable_time.is_some() && snc_diagonals.as_ref().is_none())
68+
|| (snc_disable_time.is_none() && snc_diagonals.as_ref().is_some())
69+
|| (snc_diagonals.as_ref().is_some() && snc_diagonals.as_ref().unwrap().len() != 3)
70+
{
71+
return Err(NyxError::CustomError(format!(
72+
"SNC set up requirest both a disable time and the snc_diagonals (3 items required)."
73+
)));
74+
} else if snc_disable_time.is_some() && snc_diagonals.is_some() {
75+
let snc = SNC3::from_diagonal(snc_disable_time.unwrap(), &snc_diagonals.unwrap());
76+
KF::new(initial_estimate.0, snc, msr_noise)
77+
} else {
78+
KF::no_snc(initial_estimate.0, msr_noise)
79+
};
6580

6681
let prop = Propagator::default(dynamics);
6782
let prop_est = prop.with(init_sc);

tests/orbit_determination/resid_reject.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ fn epoch() -> Epoch {
2121

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

2626
// Load cosm
2727
let cosm = Cosm::de438();

tests/orbit_determination/robust.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,6 @@ fn od_robust_test_ekf_realistic_two_way() {
244244

245245
// Define the propagator information.
246246
let prop_time = 1 * Unit::Day;
247-
let step_size = 10.0 * Unit::Second;
248-
let opts = PropOpts::with_fixed_step(step_size);
249247

250248
// Define state information.
251249
let eme2k = cosm.frame("EME2000");
@@ -326,7 +324,7 @@ fn od_robust_test_ekf_realistic_two_way() {
326324
Bodies::SaturnBarycenter,
327325
];
328326
let orbital_dyn = OrbitalDynamics::point_masses(&bodies, cosm.clone());
329-
let truth_setup = Propagator::new::<RK4Fixed>(orbital_dyn, opts);
327+
let truth_setup = Propagator::default(orbital_dyn);
330328
let (_, traj) = truth_setup
331329
.with(initial_state)
332330
.for_duration_with_traj(prop_time)
@@ -354,7 +352,7 @@ fn od_robust_test_ekf_realistic_two_way() {
354352
// We expect the estimated orbit to be _nearly_ perfect because we've removed Saturn from the estimated trajectory
355353
let bodies = vec![Bodies::Luna, Bodies::Sun, Bodies::JupiterBarycenter];
356354
let estimator = OrbitalDynamics::point_masses(&bodies, cosm.clone());
357-
let setup = Propagator::new::<RK4Fixed>(estimator, opts);
355+
let setup = Propagator::default(estimator);
358356
let prop_est = setup.with(initial_state_dev.with_stm());
359357

360358
// Define the expected measurement noise (we will then expect the residuals to be within those bounds if we have correctly set up the filter)

tests/propagation/events.rs

+19-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
extern crate nyx_space as nyx;
2+
use std::fmt::Write;
23

34
#[test]
45
fn event_tracker_true_anomaly() {
@@ -35,8 +36,10 @@ fn event_tracker_true_anomaly() {
3536
let found_events = traj.find_all(e).unwrap();
3637
let pretty = found_events
3738
.iter()
38-
.map(|orbit| format!("{:x}\tevent value: {}\n", orbit, e.eval(orbit)))
39-
.collect::<String>();
39+
.fold(String::new(), |mut output, orbit| {
40+
let _ = writeln!(output, "{orbit:x}\tevent value: {}", e.eval(orbit));
41+
output
42+
});
4043
println!("[ta_tracker] {} =>\n{}", e, pretty);
4144
}
4245

@@ -89,32 +92,34 @@ fn event_tracker_true_anomaly() {
8992

9093
let pretty = umbra_events
9194
.iter()
92-
.map(|orbit| {
93-
format!(
94-
"{:x}\tevent value: {}\t(-10s: {}\t+10s: {})\n",
95+
.fold(String::new(), |mut output, orbit| {
96+
let _ = writeln!(
97+
output,
98+
"{:x}\tevent value: {}\t(-10s: {}\t+10s: {})",
9599
orbit,
96100
&e_loc.compute(orbit),
97101
&e_loc.compute(&traj.at(orbit.epoch() - 10 * Unit::Second).unwrap()),
98102
&e_loc.compute(&traj.at(orbit.epoch() + 10 * Unit::Second).unwrap())
99-
)
100-
})
101-
.collect::<String>();
103+
);
104+
output
105+
});
102106
println!("[eclipses] {} =>\n{}", umbra_event_loc, pretty);
103107

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

107111
let pretty = penumbra_events
108112
.iter()
109-
.map(|orbit| {
110-
format!(
111-
"{:x}\tevent value: {}\t(-10s: {}\t+10s: {})\n",
113+
.fold(String::new(), |mut output, orbit| {
114+
let _ = writeln!(
115+
output,
116+
"{:x}\tevent value: {}\t(-10s: {}\t+10s: {})",
112117
orbit,
113118
&e_loc.compute(orbit),
114119
&e_loc.compute(&traj.at(orbit.epoch() - 10 * Unit::Second).unwrap()),
115120
&e_loc.compute(&traj.at(orbit.epoch() + 10 * Unit::Second).unwrap())
116-
)
117-
})
118-
.collect::<String>();
121+
);
122+
output
123+
});
119124
println!("[eclipses] {} =>\n{}", penumbra_event_loc, pretty);
120125
}

tests/python/test_orbit_determination.py

+43
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,26 @@ def test_filter_arc():
132132

133133
print(f"Stored to {rslt_path}")
134134

135+
# Repeat with SNC to compare results
136+
snc_rslt_path = process_tracking_arc(
137+
dynamics["hifi"],
138+
sc,
139+
orbit_est,
140+
msr_noise,
141+
arc,
142+
str(outpath.joinpath("./od_result_snc.parquet")),
143+
cfg,
144+
ekf_num_msr_trig,
145+
ekf_disable_time,
146+
snc_disable_time=Unit.Minute * 10.0,
147+
snc_diagonals=[5e-12, 5e-12, 5e-12]
148+
)
149+
150+
print(f"Stored to {rslt_path}")
151+
135152
# Load the results
136153
oddf = pd.read_parquet(rslt_path)
154+
oddf_snc = pd.read_parquet(snc_rslt_path)
137155
# Load the reference trajectory
138156
ref_traj = pd.read_parquet(traj_file)
139157
# Load the measurements
@@ -153,6 +171,16 @@ def test_filter_arc():
153171
show=False,
154172
)
155173

174+
plot_estimates(
175+
oddf_snc,
176+
"OD with SNC results from Python",
177+
cov_frame="RIC",
178+
ref_traj=ref_traj,
179+
msr_df=msr_df,
180+
html_out=str(outpath.joinpath("./od_estimate_snc_plots.html")),
181+
show=False,
182+
)
183+
156184
# Let's also plot the reference and the OD result's orbital elements
157185
plot_orbit_elements(
158186
[ref_traj, oddf],
@@ -180,6 +208,13 @@ def test_filter_arc():
180208
html_out=str(outpath.joinpath("./od_residual_plots.html")),
181209
show=False,
182210
)
211+
plot_residuals(
212+
oddf_snc,
213+
"OD residuals with SNC enabled",
214+
msr_df=msr_df,
215+
html_out=str(outpath.joinpath("./od_residual_snc_plots.html")),
216+
show=False,
217+
)
183218
# And the postfit histograms
184219
plot_residual_histogram(
185220
oddf,
@@ -198,6 +233,14 @@ def test_filter_arc():
198233
show=False,
199234
)
200235

236+
err_snc_df = diff_traj_parquet(traj_file, snc_rslt_path)
237+
plot_orbit_elements(
238+
err_snc_df,
239+
"Error in orbital elements with SNC",
240+
html_out=str(outpath.joinpath("./od_snc_vs_ref_error_elements.html")),
241+
show=False,
242+
)
243+
201244

202245
def test_one_way_msr():
203246
"""

0 commit comments

Comments
 (0)