Skip to content

Commit e2613c1

Browse files
Merge pull request #350 from nyx-space/94-support-estimation-of-spacecraft-parameters-cr-cd
Support estimation of spacecraft coefficient of reflectivity and drag
2 parents a8db2ce + f188f59 commit e2613c1

File tree

9 files changed

+272
-30
lines changed

9 files changed

+272
-30
lines changed

Diff for: src/cosmic/orbitdual.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ impl OrbitDual {
303303
.cross(&self.hvec());
304304
let aop = (n.dot(&self.evec()?) / (norm(&n) * self.ecc()?.dual)).acos();
305305
if aop.is_nan() {
306-
error!("AoP is NaN");
306+
warn!("AoP is NaN");
307307
Ok(OrbitPartial {
308308
dual: OHyperdual::from(0.0),
309309
param: StateParameter::AoP,

Diff for: src/cosmic/spacecraft.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ impl State for Spacecraft {
497497
self.orbit.epoch = epoch;
498498
self.orbit.radius_km = radius_km;
499499
self.orbit.velocity_km_s = vel_km_s;
500-
self.srp.cr = sc_state[6];
500+
self.srp.cr = sc_state[6].clamp(0.0, 2.0);
501501
self.drag.cd = sc_state[7];
502502
self.fuel_mass_kg = sc_state[8];
503503
}
@@ -788,7 +788,7 @@ impl Add<OVector<f64, Const<9>>> for Spacecraft {
788788

789789
self.orbit.radius_km += radius_km;
790790
self.orbit.velocity_km_s += vel_km_s;
791-
self.srp.cr += other[6];
791+
self.srp.cr = (self.srp.cr + other[6]).clamp(0.0, 2.0);
792792
self.drag.cd += other[7];
793793
self.fuel_mass_kg += other[8];
794794

Diff for: src/dynamics/drag.rs

+25-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use super::{
2424
DynamicsAlmanacSnafu, DynamicsAstroSnafu, DynamicsError, DynamicsPlanetarySnafu, ForceModel,
2525
};
2626
use crate::cosmic::{AstroError, AstroPhysicsSnafu, Frame, Spacecraft};
27-
use crate::linalg::{Matrix3, Vector3};
27+
use crate::linalg::{Matrix4x3, Vector3};
2828
use std::fmt;
2929
use std::sync::Arc;
3030

@@ -47,6 +47,8 @@ pub struct ConstantDrag {
4747
pub rho: f64,
4848
/// Geoid causing the drag
4949
pub drag_frame: Frame,
50+
/// Set to true to estimate the coefficient of drag
51+
pub estimate: bool,
5052
}
5153

5254
impl fmt::Display for ConstantDrag {
@@ -60,6 +62,14 @@ impl fmt::Display for ConstantDrag {
6062
}
6163

6264
impl ForceModel for ConstantDrag {
65+
fn estimation_index(&self) -> Option<usize> {
66+
if self.estimate {
67+
Some(7)
68+
} else {
69+
None
70+
}
71+
}
72+
6373
fn eom(&self, ctx: &Spacecraft, almanac: Arc<Almanac>) -> Result<Vector3<f64>, DynamicsError> {
6474
let osc = almanac
6575
.transform_to(ctx.orbit, self.drag_frame, None)
@@ -76,7 +86,7 @@ impl ForceModel for ConstantDrag {
7686
&self,
7787
_osc_ctx: &Spacecraft,
7888
_almanac: Arc<Almanac>,
79-
) -> Result<(Vector3<f64>, Matrix3<f64>), DynamicsError> {
89+
) -> Result<(Vector3<f64>, Matrix4x3<f64>), DynamicsError> {
8090
Err(DynamicsError::DynamicsAstro {
8191
source: AstroError::PartialsUndefined,
8292
})
@@ -90,6 +100,8 @@ pub struct Drag {
90100
pub density: AtmDensity,
91101
/// Frame to compute the drag in
92102
pub drag_frame: Frame,
103+
/// Set to true to estimate the coefficient of drag
104+
pub estimate: bool,
93105
}
94106

95107
impl Drag {
@@ -106,6 +118,7 @@ impl Drag {
106118
action: "planetary data from third body not loaded",
107119
}
108120
})?,
121+
estimate: false,
109122
}))
110123
}
111124

@@ -120,6 +133,7 @@ impl Drag {
120133
action: "planetary data from third body not loaded",
121134
}
122135
})?,
136+
estimate: false,
123137
}))
124138
}
125139
}
@@ -135,6 +149,14 @@ impl fmt::Display for Drag {
135149
}
136150

137151
impl ForceModel for Drag {
152+
fn estimation_index(&self) -> Option<usize> {
153+
if self.estimate {
154+
Some(7)
155+
} else {
156+
None
157+
}
158+
}
159+
138160
fn eom(&self, ctx: &Spacecraft, almanac: Arc<Almanac>) -> Result<Vector3<f64>, DynamicsError> {
139161
let integration_frame = ctx.orbit.frame;
140162

@@ -227,7 +249,7 @@ impl ForceModel for Drag {
227249
&self,
228250
_osc_ctx: &Spacecraft,
229251
_almanac: Arc<Almanac>,
230-
) -> Result<(Vector3<f64>, Matrix3<f64>), DynamicsError> {
252+
) -> Result<(Vector3<f64>, Matrix4x3<f64>), DynamicsError> {
231253
Err(DynamicsError::DynamicsAstro {
232254
source: AstroError::PartialsUndefined,
233255
})

Diff for: src/dynamics/mod.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
use crate::cosmic::{AstroError, Orbit};
2020
use crate::linalg::allocator::Allocator;
21-
use crate::linalg::{DefaultAllocator, DimName, Matrix3, OMatrix, OVector, Vector3};
21+
use crate::linalg::{DefaultAllocator, DimName, Matrix3, Matrix4x3, OMatrix, OVector, Vector3};
2222
use crate::State;
2323
use anise::almanac::planetary::PlanetaryDataError;
2424
use anise::almanac::Almanac;
@@ -138,16 +138,20 @@ where
138138
///
139139
/// Examples include Solar Radiation Pressure, drag, etc., i.e. forces which do not need to save the current state, only act on it.
140140
pub trait ForceModel: Send + Sync + fmt::Display {
141+
/// If a parameter of this force model is stored in the spacecraft state, then this function should return the index where this parameter is being affected
142+
fn estimation_index(&self) -> Option<usize>;
143+
141144
/// Defines the equations of motion for this force model from the provided osculating state.
142145
fn eom(&self, ctx: &Spacecraft, almanac: Arc<Almanac>) -> Result<Vector3<f64>, DynamicsError>;
143146

144147
/// Force models must implement their partials, although those will only be called if the propagation requires the
145148
/// computation of the STM. The `osc_ctx` is the osculating context, i.e. it changes for each sub-step of the integrator.
149+
/// The last row corresponds to the partials of the parameter of this force model wrt the position, i.e. this only applies to conservative forces.
146150
fn dual_eom(
147151
&self,
148152
osc_ctx: &Spacecraft,
149153
almanac: Arc<Almanac>,
150-
) -> Result<(Vector3<f64>, Matrix3<f64>), DynamicsError>;
154+
) -> Result<(Vector3<f64>, Matrix4x3<f64>), DynamicsError>;
151155
}
152156

153157
/// The `AccelModel` trait handles immutable dynamics which return an acceleration. Those can be added directly to Orbital Dynamics for example.

Diff for: src/dynamics/solarpressure.rs

+31-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
use super::{DynamicsAlmanacSnafu, DynamicsError, DynamicsPlanetarySnafu, ForceModel};
2020
use crate::cosmic::eclipse::EclipseLocator;
2121
use crate::cosmic::{Frame, Spacecraft, AU, SPEED_OF_LIGHT_M_S};
22-
use crate::linalg::{Const, Matrix3, Vector3};
22+
use crate::linalg::{Const, Matrix4x3, Vector3};
2323
use anise::almanac::Almanac;
2424
use anise::constants::frames::SUN_J2000;
2525
use hyperdual::{hyperspace_from_vector, linalg::norm, Float, OHyperdual};
@@ -38,6 +38,8 @@ pub struct SolarPressure {
3838
/// solar flux at 1 AU, in W/m^2
3939
pub phi: f64,
4040
pub e_loc: EclipseLocator,
41+
/// Set to true to estimate the coefficient of reflectivity
42+
pub estimate: bool,
4143
}
4244

4345
impl SolarPressure {
@@ -66,6 +68,7 @@ impl SolarPressure {
6668
Ok(Self {
6769
phi: SOLAR_FLUX_W_m2,
6870
e_loc,
71+
estimate: true,
6972
})
7073
}
7174

@@ -74,6 +77,16 @@ impl SolarPressure {
7477
Ok(Arc::new(Self::default_raw(vec![shadow_body], almanac)?))
7578
}
7679

80+
/// Accounts for the shadowing of only one body and will set the solar flux at 1 AU to: Phi = 1367.0
81+
pub fn default_no_estimation(
82+
shadow_body: Frame,
83+
almanac: Arc<Almanac>,
84+
) -> Result<Arc<Self>, DynamicsError> {
85+
let mut srp = Self::default_raw(vec![shadow_body], almanac)?;
86+
srp.estimate = false;
87+
Ok(Arc::new(srp))
88+
}
89+
7790
/// Must provide the flux in W/m^2
7891
pub fn with_flux(
7992
flux_w_m2: f64,
@@ -95,6 +108,14 @@ impl SolarPressure {
95108
}
96109

97110
impl ForceModel for SolarPressure {
111+
fn estimation_index(&self) -> Option<usize> {
112+
if self.estimate {
113+
Some(6)
114+
} else {
115+
None
116+
}
117+
}
118+
98119
fn eom(&self, ctx: &Spacecraft, almanac: Arc<Almanac>) -> Result<Vector3<f64>, DynamicsError> {
99120
let osc = ctx.orbit;
100121
// Compute the position of the Sun as seen from the spacecraft
@@ -128,7 +149,7 @@ impl ForceModel for SolarPressure {
128149
&self,
129150
ctx: &Spacecraft,
130151
almanac: Arc<Almanac>,
131-
) -> Result<(Vector3<f64>, Matrix3<f64>), DynamicsError> {
152+
) -> Result<(Vector3<f64>, Matrix4x3<f64>), DynamicsError> {
132153
let osc = ctx.orbit;
133154

134155
// Compute the position of the Sun as seen from the spacecraft
@@ -145,7 +166,7 @@ impl ForceModel for SolarPressure {
145166
// Compute the shadowing factor.
146167
let k: f64 = self
147168
.e_loc
148-
.compute(osc, almanac)
169+
.compute(osc, almanac.clone())
149170
.context(DynamicsAlmanacSnafu {
150171
action: "solar radiation pressure computation",
151172
})?
@@ -169,7 +190,7 @@ impl ForceModel for SolarPressure {
169190

170191
// Extract result into Vector6 and Matrix6
171192
let mut dx = Vector3::zeros();
172-
let mut grad = Matrix3::zeros();
193+
let mut grad = Matrix4x3::zeros();
173194
for i in 0..3 {
174195
dx[i] += dual_force[i].real();
175196
// NOTE: Although the hyperdual state is of size 7, we're only setting the values up to 3 (Matrix3)
@@ -178,6 +199,12 @@ impl ForceModel for SolarPressure {
178199
}
179200
}
180201

202+
// Compute the partial wrt to Cr.
203+
let wrt_cr = self.eom(ctx, almanac)? / ctx.srp.cr;
204+
for j in 0..3 {
205+
grad[(3, j)] = wrt_cr[j];
206+
}
207+
181208
Ok((dx, grad))
182209
}
183210
}

Diff for: src/dynamics/spacecraft.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,8 @@ impl Dynamics for SpacecraftDynamics {
281281
d_x[i] = *val;
282282
}
283283

284-
for (i, val) in stm_dt.iter().enumerate() {
285-
d_x[i + <Spacecraft as State>::Size::dim()] = *val;
284+
for (i, val) in stm_dt.iter().copied().enumerate() {
285+
d_x[i + <Spacecraft as State>::Size::dim()] = val;
286286
}
287287
}
288288
None => {
@@ -291,9 +291,10 @@ impl Dynamics for SpacecraftDynamics {
291291
.orbital_dyn
292292
.eom(&osc_sc.orbit, almanac.clone())?
293293
.iter()
294+
.copied()
294295
.enumerate()
295296
{
296-
d_x[i] = *val;
297+
d_x[i] = val;
297298
}
298299

299300
// Apply the force models for non STM propagation
@@ -411,8 +412,14 @@ impl Dynamics for SpacecraftDynamics {
411412
// Add the velocity changes
412413
d_x[i + 3] += model_frc[i] / total_mass;
413414
// Add the velocity partials
414-
for j in 1..4 {
415-
grad[(i + 3, j - 1)] += model_grad[(i, j - 1)] / total_mass;
415+
for j in 0..3 {
416+
grad[(i + 3, j)] += model_grad[(i, j)] / total_mass;
417+
}
418+
}
419+
// Add this force model's estimation if applicable.
420+
if let Some(idx) = model.estimation_index() {
421+
for j in 0..3 {
422+
grad[(j + 3, idx)] += model_grad[(3, j)] / total_mass;
416423
}
417424
}
418425
}

Diff for: src/od/filter/kalman.rs

+3-9
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,6 @@ where
288288
let epoch = nominal_state.epoch();
289289

290290
let mut covar_bar = stm * self.prev_estimate.covar * stm.transpose();
291-
let mut snc_used = false;
292291
// Try to apply an SNC, if applicable
293292
for (i, snc) in self.process_noise.iter().enumerate().rev() {
294293
if let Some(snc_matrix) = snc.to_matrix(epoch) {
@@ -327,16 +326,11 @@ where
327326
}
328327
// Let's add the process noise
329328
covar_bar += &gamma * snc_matrix * &gamma.transpose();
330-
snc_used = true;
331329
// And break so we don't add any more process noise
332330
break;
333331
}
334332
}
335333

336-
if !snc_used {
337-
debug!("@{} No SNC", epoch);
338-
}
339-
340334
let h_tilde_t = &self.h_tilde.transpose();
341335
let h_p_ht = &self.h_tilde * covar_bar * h_tilde_t;
342336
// Account for state uncertainty in the measurement noise. Equation 4.10 of ODTK MathSpec.
@@ -363,12 +357,12 @@ where
363357
}
364358

365359
// Compute the Kalman gain but first adding the measurement noise to H⋅P⋅H^T
366-
let mut invertible_part = h_p_ht + &r_k;
367-
if !invertible_part.try_inverse_mut() {
360+
let mut innovation_covar = h_p_ht + &r_k;
361+
if !innovation_covar.try_inverse_mut() {
368362
return Err(ODError::SingularKalmanGain);
369363
}
370364

371-
let gain = covar_bar * h_tilde_t * &invertible_part;
365+
let gain = covar_bar * h_tilde_t * &innovation_covar;
372366

373367
// Compute the state estimate
374368
let (state_hat, res) = if self.ekf {

Diff for: src/od/process/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ where
153153
) -> Self {
154154
let init_state = prop.state;
155155
Self {
156-
prop,
156+
prop: prop.quiet(),
157157
kf,
158158
estimates: Vec::with_capacity(10_000),
159159
residuals: Vec::with_capacity(10_000),
@@ -790,7 +790,7 @@ where
790790
) -> Self {
791791
let init_state = prop.state;
792792
Self {
793-
prop,
793+
prop: prop.quiet(),
794794
kf,
795795
estimates: Vec::with_capacity(10_000),
796796
residuals: Vec::with_capacity(10_000),

0 commit comments

Comments
 (0)