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 = GravityField::from_stor(
86 almanac.frame_info(IAU_EARTH_FRAME)?,
87 GravityFieldData::from_cof(&jgm3_meta.uri, 8, 8, true)?,
88 );
89 orbital_dyn.accel_models.push(harmonics);
90
91 let srp_dyn = SolarPressure::default_flux(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 (const: unstable) · 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 moreSource§impl<'a, 'py> FromPyObject<'a, 'py> for MvnSpacecraftwhere
Self: Clone,
impl<'a, 'py> FromPyObject<'a, 'py> for MvnSpacecraftwhere
Self: Clone,
Source§impl<'py> IntoPyObject<'py> for MvnSpacecraft
impl<'py> IntoPyObject<'py> for MvnSpacecraft
Source§type Target = MvnSpacecraft
type Target = MvnSpacecraft
Source§type Output = Bound<'py, <MvnSpacecraft as IntoPyObject<'py>>::Target>
type Output = Bound<'py, <MvnSpacecraft as IntoPyObject<'py>>::Target>
Source§fn into_pyobject(
self,
py: Python<'py>,
) -> Result<<Self as IntoPyObject<'_>>::Output, <Self as IntoPyObject<'_>>::Error>
fn into_pyobject( self, py: Python<'py>, ) -> Result<<Self as IntoPyObject<'_>>::Output, <Self as IntoPyObject<'_>>::Error>
Source§impl PyClass for MvnSpacecraft
impl PyClass for MvnSpacecraft
Source§impl PyClassImpl for MvnSpacecraft
impl PyClassImpl for MvnSpacecraft
Source§const MODULE: Option<&str> = ::core::option::Option::None
const MODULE: Option<&str> = ::core::option::Option::None
Source§const IS_BASETYPE: bool = false
const IS_BASETYPE: bool = false
Source§const IS_SUBCLASS: bool = false
const IS_SUBCLASS: bool = false
Source§const IS_MAPPING: bool = false
const IS_MAPPING: bool = false
Source§const IS_SEQUENCE: bool = false
const IS_SEQUENCE: bool = false
Source§const IS_IMMUTABLE_TYPE: bool = false
const IS_IMMUTABLE_TYPE: bool = false
Source§const RAW_DOC: &'static CStr = /// 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:
/// 1. **Jacobian Computation**: It computes the Jacobian matrix `J` representing the partial derivatives of the provided dispersion parameters with respect to the Cartesian state elements (x, y, z, vx, vy, vz).
/// 2. **Covariance Transformation**: It constructs a diagonal covariance matrix `P` in the parameter space (assuming input dispersions are independent in that space). It then transforms this into the Cartesian covariance matrix `C` using the linear mapping approximation:
/// `C = J_inv * P * J_inv^T`
/// where `J_inv` is the Moore-Penrose pseudo-inverse of `J`.
/// 3. **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 as `L`.
/// `C = U * S * V^T = (V * sqrt(S)) * (V * sqrt(S))^T` implies `L = V * sqrt(S)`
/// 4. **Sampling**: To generate a sample state `X`, it draws a vector `Z` of 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?"
///
/// 1. **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). `MvnSpacecraft` preserves these physical correlations by mapping the physically meaningful uncertainties (e.g., in SMA or Inclination) into the Cartesian space.
/// 2. **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.
/// 3. **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.
const RAW_DOC: &'static CStr = /// 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: /// 1. **Jacobian Computation**: It computes the Jacobian matrix `J` representing the partial derivatives of the provided dispersion parameters with respect to the Cartesian state elements (x, y, z, vx, vy, vz). /// 2. **Covariance Transformation**: It constructs a diagonal covariance matrix `P` in the parameter space (assuming input dispersions are independent in that space). It then transforms this into the Cartesian covariance matrix `C` using the linear mapping approximation: /// `C = J_inv * P * J_inv^T` /// where `J_inv` is the Moore-Penrose pseudo-inverse of `J`. /// 3. **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 as `L`. /// `C = U * S * V^T = (V * sqrt(S)) * (V * sqrt(S))^T` implies `L = V * sqrt(S)` /// 4. **Sampling**: To generate a sample state `X`, it draws a vector `Z` of 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?" /// /// 1. **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). `MvnSpacecraft` preserves these physical correlations by mapping the physically meaningful uncertainties (e.g., in SMA or Inclination) into the Cartesian space. /// 2. **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. /// 3. **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.
Source§const DOC: &'static CStr
const DOC: &'static CStr
text_signature if a constructor is defined. Read moreSource§type Layout = <<MvnSpacecraft as PyClassImpl>::BaseNativeType as PyClassBaseType>::Layout<MvnSpacecraft>
type Layout = <<MvnSpacecraft as PyClassImpl>::BaseNativeType as PyClassBaseType>::Layout<MvnSpacecraft>
Source§type ThreadChecker = NoopThreadChecker
type ThreadChecker = NoopThreadChecker
type Inventory = Pyo3MethodsInventoryForMvnSpacecraft
Source§type PyClassMutability = <<PyAny as PyClassBaseType>::PyClassMutability as PyClassMutability>::MutableChild
type PyClassMutability = <<PyAny as PyClassBaseType>::PyClassMutability as PyClassMutability>::MutableChild
Source§type BaseNativeType = PyAny
type BaseNativeType = PyAny
PyAny by default, and when you declare
#[pyclass(extends=PyDict)], it’s PyDict.fn items_iter() -> PyClassItemsIter
fn lazy_type_object() -> &'static LazyTypeObject<Self>
§fn dict_offset() -> Option<PyObjectOffset>
fn dict_offset() -> Option<PyObjectOffset>
§fn weaklist_offset() -> Option<PyObjectOffset>
fn weaklist_offset() -> Option<PyObjectOffset>
Source§impl PyClassNewTextSignature for MvnSpacecraft
impl PyClassNewTextSignature for MvnSpacecraft
const TEXT_SIGNATURE: &'static str = "(template, dispersions)"
Source§impl PyTypeInfo for MvnSpacecraft
impl PyTypeInfo for MvnSpacecraft
Source§const NAME: &str = <Self as ::pyo3::PyClass>::NAME
const NAME: &str = <Self as ::pyo3::PyClass>::NAME
prefer using ::type_object(py).name() to get the correct runtime value
Source§const MODULE: Option<&str> = <Self as ::pyo3::impl_::pyclass::PyClassImpl>::MODULE
const MODULE: Option<&str> = <Self as ::pyo3::impl_::pyclass::PyClassImpl>::MODULE
prefer using ::type_object(py).module() to get the correct runtime value
Source§fn type_object_raw(py: Python<'_>) -> *mut PyTypeObject
fn type_object_raw(py: Python<'_>) -> *mut PyTypeObject
§fn type_object(py: Python<'_>) -> Bound<'_, PyType>
fn type_object(py: Python<'_>) -> Bound<'_, PyType>
§fn is_type_of(object: &Bound<'_, PyAny>) -> bool
fn is_type_of(object: &Bound<'_, PyAny>) -> bool
object is an instance of this type or a subclass of this type.§fn is_exact_type_of(object: &Bound<'_, PyAny>) -> bool
fn is_exact_type_of(object: &Bound<'_, PyAny>) -> bool
object is an instance of this type.impl DerefToPyAny for MvnSpacecraft
Auto 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<'py, T> IntoPyObjectExt<'py> for Twhere
T: IntoPyObject<'py>,
impl<'py, T> IntoPyObjectExt<'py> for Twhere
T: IntoPyObject<'py>,
§fn into_bound_py_any(self, py: Python<'py>) -> Result<Bound<'py, PyAny>, PyErr>
fn into_bound_py_any(self, py: Python<'py>) -> Result<Bound<'py, PyAny>, PyErr>
self into an owned Python object, dropping type information.§fn into_py_any(self, py: Python<'py>) -> Result<Py<PyAny>, PyErr>
fn into_py_any(self, py: Python<'py>) -> Result<Py<PyAny>, PyErr>
self into an owned Python object, dropping type information and unbinding it
from the 'py lifetime.§fn into_pyobject_or_pyerr(self, py: Python<'py>) -> Result<Self::Output, PyErr>
fn into_pyobject_or_pyerr(self, py: Python<'py>) -> Result<Self::Output, PyErr>
self into a Python object. Read more§impl<T> Pointable for T
impl<T> Pointable for T
§impl<T> PyErrArguments for T
impl<T> PyErrArguments for T
§impl<T> PyTypeCheck for Twhere
T: PyTypeInfo,
impl<T> PyTypeCheck for Twhere
T: PyTypeInfo,
§const NAME: &'static str = T::NAME
const NAME: &'static str = T::NAME
Use ::classinfo_object() instead and format the type name at runtime. Note that using built-in cast features is often better than manual PyTypeCheck usage.
§fn type_check(object: &Bound<'_, PyAny>) -> bool
fn type_check(object: &Bound<'_, PyAny>) -> bool
§fn classinfo_object(py: Python<'_>) -> Bound<'_, PyAny>
fn classinfo_object(py: Python<'_>) -> Bound<'_, PyAny>
isinstance and issubclass function. Read more§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.