pub struct MvnSpacecraft {
pub template: Spacecraft,
pub dispersions: Vec<StateDispersion>,
pub mean: SVector<f64, 9>,
pub sqrt_s_v: SMatrix<f64, 9, 9>,
pub std_norm_distr: Normal<f64>,
}Expand description
A multivariate spacecraft state generator for Monte Carlo analyses. Ensures that the covariance is properly applied on all provided state variables.
§Algorithm
The MvnSpacecraft allows sampling from a multivariate normal distribution defined by a template state (mean) and a set of state dispersions (uncertainties).
The core difficulty is that dispersions are often defined in non-Cartesian spaces (like Keplerian orbital elements or B-Plane parameters), while the spacecraft state is represented in Cartesian coordinates (Position and Velocity).
The algorithm proceeds as follows:
- Jacobian Computation: It computes the Jacobian matrix
Jrepresenting the partial derivatives of the provided dispersion parameters with respect to the Cartesian state elements (x, y, z, vx, vy, vz). - Covariance Transformation: It constructs a diagonal covariance matrix
Pin the parameter space (assuming input dispersions are independent in that space). It then transforms this into the Cartesian covariance matrixCusing the linear mapping approximation:C = J_inv * P * J_inv^TwhereJ_invis the Moore-Penrose pseudo-inverse ofJ. - Decomposition: It performs a Singular Value Decomposition (SVD) on
C(or the full state covariance including mass, Cr, Cd) to obtain the square root of the covariance matrix, denoted asL.C = U * S * V^T = (V * sqrt(S)) * (V * sqrt(S))^TimpliesL = V * sqrt(S) - Sampling: To generate a sample state
X, it draws a vectorZof independent standard normal variables (N(0, 1)) and applies the transformation:X = mu + L * Z
§Correctness vs. Independent Sampling
One might ask: “Why not just sample each Cartesian coordinate independently using a normal distribution?”
- Correlations: Independent sampling of Cartesian coordinates assumes a diagonal covariance matrix, implying no correlation between position and velocity components. In orbital mechanics, states are highly correlated (e.g., velocity magnitude and radial distance are coupled by energy).
MvnSpacecraftpreserves these physical correlations by mapping the physically meaningful uncertainties (e.g., in SMA or Inclination) into the Cartesian space. - Geometry: Uncertainties defined in orbital elements form complex shapes (like “bananas”) in Cartesian space. A multivariate normal approximation in Cartesian space captures the principal axes and orientation of this uncertainty volume, which an axis-aligned bounding box (implied by independent sampling) effectively destroys.
- Consistency: By using the Jacobian transformation, we ensure that the generated samples, when mapped back to the parameter space (linearized), reproduce the input statistics (mean and standard deviation) provided by the user.
Fields§
§template: SpacecraftThe template state
dispersions: Vec<StateDispersion>§mean: SVector<f64, 9>The mean of the multivariate normal distribution
sqrt_s_v: SMatrix<f64, 9, 9>The dot product \sqrt{\vec s} \cdot \vec v, where S is the singular values and V the V matrix from the SVD decomp of the covariance of multivariate normal distribution
std_norm_distr: Normal<f64>The standard normal distribution used to seed the multivariate normal distribution
Implementations§
Source§impl MvnSpacecraft
impl MvnSpacecraft
Sourcepub fn new(
template: Spacecraft,
dispersions: Vec<StateDispersion>,
) -> Result<Self, Box<dyn Error>>
pub fn new( template: Spacecraft, dispersions: Vec<StateDispersion>, ) -> Result<Self, Box<dyn Error>>
Creates a new mulivariate state generator from a mean and covariance on the set of state parameters. The covariance must be positive semi definite.
§Algorithm
This function will build the rotation matrix to rotate the requested dispersions into the Spacecraft state space using [OrbitDual]. If there are any dispersions on the Cr and Cd, then these are dispersed independently (because they are iid).
Examples found in repository?
28fn main() -> Result<(), Box<dyn Error>> {
29 pel::init();
30 // Set up the dynamics like in the orbit raise.
31 let almanac = Arc::new(MetaAlmanac::latest().map_err(Box::new)?);
32 let epoch = Epoch::from_gregorian_utc_hms(2024, 2, 29, 12, 13, 14);
33
34 // Define the GEO orbit, and we're just going to maintain it very tightly.
35 let earth_j2000 = almanac.frame_info(EARTH_J2000)?;
36 let orbit = Orbit::try_keplerian(42164.0, 1e-5, 0., 163.0, 75.0, 0.0, epoch, earth_j2000)?;
37 println!("{orbit:x}");
38
39 let sc = Spacecraft::builder()
40 .orbit(orbit)
41 .mass(Mass::from_dry_and_prop_masses(1000.0, 1000.0)) // 1000 kg of dry mass and prop, totalling 2.0 tons
42 .srp(SRPData::from_area(3.0 * 6.0)) // Assuming 1 kW/m^2 or 18 kW, giving a margin of 4.35 kW for on-propulsion consumption
43 .thruster(Thruster {
44 // "NEXT-STEP" row in Table 2
45 isp_s: 4435.0,
46 thrust_N: 0.472,
47 })
48 .mode(GuidanceMode::Thrust) // Start thrusting immediately.
49 .build();
50
51 // Set up the spacecraft dynamics like in the orbit raise example.
52
53 let prop_time = 30.0 * Unit::Day;
54
55 // Define the guidance law -- we're just using a Ruggiero controller as demonstrated in AAS-2004-5089.
56 let objectives = &[
57 Objective::within_tolerance(
58 StateParameter::Element(OrbitalElement::SemiMajorAxis),
59 42_165.0,
60 20.0,
61 ),
62 Objective::within_tolerance(
63 StateParameter::Element(OrbitalElement::Eccentricity),
64 0.001,
65 5e-5,
66 ),
67 Objective::within_tolerance(
68 StateParameter::Element(OrbitalElement::Inclination),
69 0.05,
70 1e-2,
71 ),
72 ];
73
74 let ruggiero_ctrl = Ruggiero::from_max_eclipse(objectives, sc, 0.2)?;
75 println!("{ruggiero_ctrl}");
76
77 let mut orbital_dyn = OrbitalDynamics::point_masses(vec![MOON, SUN]);
78
79 let mut jgm3_meta = MetaFile {
80 uri: "http://public-data.nyxspace.com/nyx/models/JGM3.cof.gz".to_string(),
81 crc32: Some(0xF446F027), // Specifying the CRC32 avoids redownloading it if it's cached.
82 };
83 jgm3_meta.process(true)?;
84
85 let harmonics = Harmonics::from_stor(
86 almanac.frame_info(IAU_EARTH_FRAME)?,
87 HarmonicsMem::from_cof(&jgm3_meta.uri, 8, 8, true)?,
88 );
89 orbital_dyn.accel_models.push(harmonics);
90
91 let srp_dyn = SolarPressure::default(EARTH_J2000, almanac.clone())?;
92 let sc_dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn)
93 .with_guidance_law(ruggiero_ctrl.clone());
94
95 println!("{sc_dynamics}");
96
97 // Finally, let's use the Monte Carlo framework built into Nyx to propagate spacecraft.
98
99 // Let's start by defining the dispersion.
100 // The MultivariateNormal structure allows us to define the dispersions in any of the orbital parameters, but these are applied directly in the Cartesian state space.
101 // Note that additional validation on the MVN is in progress -- https://github.com/nyx-space/nyx/issues/339.
102 let mc_rv = MvnSpacecraft::new(
103 sc,
104 vec![StateDispersion::zero_mean(
105 StateParameter::Element(OrbitalElement::SemiMajorAxis),
106 3.0,
107 )],
108 )?;
109
110 let my_mc = MonteCarlo::new(
111 sc, // Nominal state
112 mc_rv,
113 "03_geo_sk".to_string(), // Scenario name
114 None, // No specific seed specified, so one will be drawn from the computer's entropy.
115 );
116
117 // Build the propagator setup.
118 let setup = Propagator::rk89(
119 sc_dynamics.clone(),
120 IntegratorOptions::builder()
121 .min_step(10.0_f64.seconds())
122 .error_ctrl(ErrorControl::RSSCartesianStep)
123 .build(),
124 );
125
126 let num_runs = 25;
127 let rslts = my_mc.run_until_epoch(setup, almanac.clone(), sc.epoch() + prop_time, num_runs);
128
129 assert_eq!(rslts.runs.len(), num_runs);
130
131 rslts.to_parquet("03_geo_sk.parquet", ExportCfg::default())?;
132
133 Ok(())
134}Sourcepub fn zero_mean(
template: Spacecraft,
dispersions: Vec<StateDispersion>,
) -> Result<Self, Box<dyn Error>>
pub fn zero_mean( template: Spacecraft, dispersions: Vec<StateDispersion>, ) -> Result<Self, Box<dyn Error>>
Same as new but with a zero mean
Sourcepub fn from_spacecraft_cov(
template: Spacecraft,
cov: SMatrix<f64, 9, 9>,
mean: SVector<f64, 9>,
) -> Result<Self, Box<dyn Error>>
pub fn from_spacecraft_cov( template: Spacecraft, cov: SMatrix<f64, 9, 9>, mean: SVector<f64, 9>, ) -> Result<Self, Box<dyn Error>>
Initializes a new multivariate distribution using the state data in the spacecraft state space.
Trait Implementations§
Source§impl Clone for MvnSpacecraft
impl Clone for MvnSpacecraft
Source§fn clone(&self) -> MvnSpacecraft
fn clone(&self) -> MvnSpacecraft
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for MvnSpacecraft
impl Debug for MvnSpacecraft
Source§impl Distribution<DispersedState<Spacecraft>> for MvnSpacecraft
impl Distribution<DispersedState<Spacecraft>> for MvnSpacecraft
Source§fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DispersedState<Spacecraft>
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DispersedState<Spacecraft>
T, using rng as the source of randomness.§fn sample_iter<R>(self, rng: R) -> Iter<Self, R, T>
fn sample_iter<R>(self, rng: R) -> Iter<Self, R, T>
T, using rng as
the source of randomness. Read moreAuto Trait Implementations§
impl Freeze for MvnSpacecraft
impl RefUnwindSafe for MvnSpacecraft
impl Send for MvnSpacecraft
impl Sync for MvnSpacecraft
impl Unpin for MvnSpacecraft
impl UnsafeUnpin for MvnSpacecraft
impl UnwindSafe for MvnSpacecraft
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> Pointable for T
impl<T> Pointable for T
§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read more§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.