Struct ODProcess

Source
pub struct ODProcess<'a, D: Dynamics, MsrSize: DimName, Accel: DimName, K: Filter<D::StateType, Accel, MsrSize>, Trk: TrackerSensitivity<D::StateType, D::StateType>>
where D::StateType: Interpolatable + Add<OVector<f64, <D::StateType as State>::Size>, Output = D::StateType>, <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send, DefaultAllocator: Allocator<<D::StateType as State>::Size> + Allocator<<D::StateType as State>::VecLength> + Allocator<MsrSize> + Allocator<MsrSize, <D::StateType as State>::Size> + Allocator<MsrSize, MsrSize> + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size> + Allocator<Accel> + Allocator<Accel, Accel> + Allocator<<D::StateType as State>::Size, Accel> + Allocator<Accel, <D::StateType as State>::Size>,
{ pub prop: PropInstance<'a, D>, pub kf: K, pub devices: BTreeMap<String, Trk>, pub estimates: Vec<K::Estimate>, pub residuals: Vec<Option<Residual<MsrSize>>>, pub ekf_trigger: Option<EkfTrigger>, pub resid_crit: Option<ResidRejectCrit>, pub almanac: Arc<Almanac>, /* private fields */ }
Expand description

Sets up an orbit determination process (ODP).

§Algorithm details

§Classical vs. Extended Kalman filter

In Nyx, an ODP configured in Classical Kalman Filter will track the state deviation compared to the nominal state. An ODP configured in Extended Kalman Filter mode will update the propagation state on each (accepted) measurement.

The EKF mode requires a “trigger” which switches the filter from a CKF to an EKF. This prevents quick divergence of a filter.

§Measurement residual ratio and rejection

The measurement residual is a signed scalar, despite ODP being able to process multiple measurements simultaneously. By default, if a measurement is more than 3 measurement sigmas off, it will be rejected to avoid biasing the filter.

Fields§

§prop: PropInstance<'a, D>

PropInstance used for the estimation

§kf: K

Kalman filter itself

§devices: BTreeMap<String, Trk>

Tracking devices

§estimates: Vec<K::Estimate>

Vector of estimates available after a pass

§residuals: Vec<Option<Residual<MsrSize>>>

Vector of residuals available after a pass

§ekf_trigger: Option<EkfTrigger>§resid_crit: Option<ResidRejectCrit>

Residual rejection criteria allows preventing bad measurements from affecting the estimation.

§almanac: Arc<Almanac>

Implementations§

Source§

impl<MsrSize: DimName, Accel: DimName, Trk: TrackerSensitivity<Spacecraft, Spacecraft>> ODProcess<'_, SpacecraftDynamics, MsrSize, Accel, KF<Spacecraft, Accel, MsrSize>, Trk>
where DefaultAllocator: Allocator<MsrSize> + Allocator<MsrSize, <Spacecraft as State>::Size> + Allocator<Const<1>, MsrSize> + Allocator<<Spacecraft as State>::Size> + Allocator<<Spacecraft as State>::Size, <Spacecraft as State>::Size> + Allocator<MsrSize, MsrSize> + Allocator<<Spacecraft as State>::Size, MsrSize> + Allocator<Accel> + Allocator<Accel, Accel> + Allocator<<Spacecraft as State>::VecLength> + Allocator<<Spacecraft as State>::Size, Accel> + Allocator<Accel, <Spacecraft as State>::Size>,

Source

pub fn to_parquet<P: AsRef<Path>>( &self, arc: &TrackingDataArc, path: P, cfg: ExportCfg, ) -> Result<PathBuf, ODError>

Store the estimates and residuals in a parquet file

Examples found in repository?
examples/02_jwst_covar_monte_carlo/main.rs (lines 125-129)
26fn main() -> Result<(), Box<dyn Error>> {
27    pel::init();
28    // Dynamics models require planetary constants and ephemerides to be defined.
29    // Let's start by grabbing those by using ANISE's latest MetaAlmanac.
30    // For details, refer to https://github.com/nyx-space/anise/blob/master/data/latest.dhall.
31
32    // Download the regularly update of the James Webb Space Telescope reconstucted (or definitive) ephemeris.
33    // Refer to https://naif.jpl.nasa.gov/pub/naif/JWST/kernels/spk/aareadme.txt for details.
34    let mut latest_jwst_ephem = MetaFile {
35        uri: "https://naif.jpl.nasa.gov/pub/naif/JWST/kernels/spk/jwst_rec.bsp".to_string(),
36        crc32: None,
37    };
38    latest_jwst_ephem.process(true)?;
39
40    // Load this ephem in the general Almanac we're using for this analysis.
41    let almanac = Arc::new(
42        MetaAlmanac::latest()
43            .map_err(Box::new)?
44            .load_from_metafile(latest_jwst_ephem, true)?,
45    );
46
47    // By loading this ephemeris file in the ANISE GUI or ANISE CLI, we can find the NAIF ID of the JWST
48    // in the BSP. We need this ID in order to query the ephemeris.
49    const JWST_NAIF_ID: i32 = -170;
50    // Let's build a frame in the J2000 orientation centered on the JWST.
51    const JWST_J2000: Frame = Frame::from_ephem_j2000(JWST_NAIF_ID);
52
53    // Since the ephemeris file is updated regularly, we'll just grab the latest state in the ephem.
54    let (earliest_epoch, latest_epoch) = almanac.spk_domain(JWST_NAIF_ID)?;
55    println!("JWST defined from {earliest_epoch} to {latest_epoch}");
56    // Fetch the state, printing it in the Earth J2000 frame.
57    let jwst_orbit = almanac.transform(JWST_J2000, EARTH_J2000, latest_epoch, None)?;
58    println!("{jwst_orbit:x}");
59
60    // Build the spacecraft
61    // SRP area assumed to be the full sunshield and mass if 6200.0 kg, c.f. https://webb.nasa.gov/content/about/faqs/facts.html
62    // SRP Coefficient of reflectivity assumed to be that of Kapton, i.e. 2 - 0.44 = 1.56, table 1 from https://amostech.com/TechnicalPapers/2018/Poster/Bengtson.pdf
63    let jwst = Spacecraft::builder()
64        .orbit(jwst_orbit)
65        .srp(SRPData {
66            area_m2: 21.197 * 14.162,
67            coeff_reflectivity: 1.56,
68        })
69        .mass(Mass::from_dry_mass(6200.0))
70        .build();
71
72    // Build up the spacecraft uncertainty builder.
73    // We can use the spacecraft uncertainty structure to build this up.
74    // We start by specifying the nominal state (as defined above), then the uncertainty in position and velocity
75    // in the RIC frame. We could also specify the Cr, Cd, and mass uncertainties, but these aren't accounted for until
76    // Nyx can also estimate the deviation of the spacecraft parameters.
77    let jwst_uncertainty = SpacecraftUncertainty::builder()
78        .nominal(jwst)
79        .frame(LocalFrame::RIC)
80        .x_km(0.5)
81        .y_km(0.3)
82        .z_km(1.5)
83        .vx_km_s(1e-4)
84        .vy_km_s(0.6e-3)
85        .vz_km_s(3e-3)
86        .build();
87
88    println!("{jwst_uncertainty}");
89
90    // Build the Kalman filter estimate.
91    // Note that we could have used the KfEstimate structure directly (as seen throughout the OD integration tests)
92    // but this approach requires quite a bit more boilerplate code.
93    let jwst_estimate = jwst_uncertainty.to_estimate()?;
94
95    // Set up the spacecraft dynamics.
96    // We'll use the point masses of the Earth, Sun, Jupiter (barycenter, because it's in the DE440), and the Moon.
97    // We'll also enable solar radiation pressure since the James Webb has a huge and highly reflective sun shield.
98
99    let orbital_dyn = OrbitalDynamics::point_masses(vec![MOON, SUN, JUPITER_BARYCENTER]);
100    let srp_dyn = SolarPressure::new(vec![EARTH_J2000, MOON_J2000], almanac.clone())?;
101
102    // Finalize setting up the dynamics.
103    let dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn);
104
105    // Build the propagator set up to use for the whole analysis.
106    let setup = Propagator::default(dynamics);
107
108    // All of the analysis will use this duration.
109    let prediction_duration = 6.5 * Unit::Day;
110
111    // === Covariance mapping ===
112    // For the covariance mapping / prediction, we'll use the common orbit determination approach.
113    // This is done by setting up a spacecraft OD process, and predicting for the analysis duration.
114
115    let ckf = KF::no_snc(jwst_estimate);
116
117    // Build the propagation instance for the OD process.
118    let prop = setup.with(jwst.with_stm(), almanac.clone());
119    let mut odp = SpacecraftODProcess::ckf(prop, ckf, BTreeMap::new(), None, almanac.clone());
120
121    // Define the prediction step, i.e. how often we want to know the covariance.
122    let step = 1_i64.minutes();
123    // Finally, predict, and export the trajectory with covariance to a parquet file.
124    odp.predict_for(step, prediction_duration)?;
125    odp.to_parquet(
126        &TrackingDataArc::default(),
127        "./02_jwst_covar_map.parquet",
128        ExportCfg::default(),
129    )?;
130
131    // === Monte Carlo framework ===
132    // Nyx comes with a complete multi-threaded Monte Carlo frame. It's blazing fast.
133
134    let my_mc = MonteCarlo::new(
135        jwst, // Nominal state
136        jwst_estimate.to_random_variable()?,
137        "02_jwst".to_string(), // Scenario name
138        None, // No specific seed specified, so one will be drawn from the computer's entropy.
139    );
140
141    let num_runs = 5_000;
142    let rslts = my_mc.run_until_epoch(
143        setup,
144        almanac.clone(),
145        jwst.epoch() + prediction_duration,
146        num_runs,
147    );
148
149    assert_eq!(rslts.runs.len(), num_runs);
150    // Finally, export these results, computing the eclipse percentage for all of these results.
151
152    // For all of the resulting trajectories, we'll want to compute the percentage of penumbra and umbra.
153    let eclipse_loc = EclipseLocator::cislunar(almanac.clone());
154    let umbra_event = eclipse_loc.to_umbra_event();
155    let penumbra_event = eclipse_loc.to_penumbra_event();
156
157    rslts.to_parquet(
158        "02_jwst_monte_carlo.parquet",
159        Some(vec![&umbra_event, &penumbra_event]),
160        ExportCfg::default(),
161        almanac,
162    )?;
163
164    Ok(())
165}
More examples
Hide additional examples
examples/04_lro_od/main.rs (line 276)
33fn main() -> Result<(), Box<dyn Error>> {
34    pel::init();
35
36    // ====================== //
37    // === ALMANAC SET UP === //
38    // ====================== //
39
40    // Dynamics models require planetary constants and ephemerides to be defined.
41    // Let's start by grabbing those by using ANISE's MetaAlmanac.
42
43    let data_folder: PathBuf = [env!("CARGO_MANIFEST_DIR"), "examples", "04_lro_od"]
44        .iter()
45        .collect();
46
47    let meta = data_folder.join("lro-dynamics.dhall");
48
49    // Load this ephem in the general Almanac we're using for this analysis.
50    let mut almanac = MetaAlmanac::new(meta.to_string_lossy().to_string())
51        .map_err(Box::new)?
52        .process(true)
53        .map_err(Box::new)?;
54
55    let mut moon_pc = almanac.planetary_data.get_by_id(MOON)?;
56    moon_pc.mu_km3_s2 = 4902.74987;
57    almanac.planetary_data.set_by_id(MOON, moon_pc)?;
58
59    let mut earth_pc = almanac.planetary_data.get_by_id(EARTH)?;
60    earth_pc.mu_km3_s2 = 398600.436;
61    almanac.planetary_data.set_by_id(EARTH, earth_pc)?;
62
63    // Save this new kernel for reuse.
64    // In an operational context, this would be part of the "Lock" process, and should not change throughout the mission.
65    almanac
66        .planetary_data
67        .save_as(&data_folder.join("lro-specific.pca"), true)?;
68
69    // Lock the almanac (an Arc is a read only structure).
70    let almanac = Arc::new(almanac);
71
72    // Orbit determination requires a Trajectory structure, which can be saved as parquet file.
73    // In our case, the trajectory comes from the BSP file, so we need to build a Trajectory from the almanac directly.
74    // To query the Almanac, we need to build the LRO frame in the J2000 orientation in our case.
75    // Inspecting the LRO BSP in the ANISE GUI shows us that NASA has assigned ID -85 to LRO.
76    let lro_frame = Frame::from_ephem_j2000(-85);
77
78    // To build the trajectory we need to provide a spacecraft template.
79    let sc_template = Spacecraft::builder()
80        .mass(Mass::from_dry_and_prop_masses(1018.0, 900.0)) // Launch masses
81        .srp(SRPData {
82            // SRP configuration is arbitrary, but we will be estimating it anyway.
83            area_m2: 3.9 * 2.7,
84            coeff_reflectivity: 0.96,
85        })
86        .orbit(Orbit::zero(MOON_J2000)) // Setting a zero orbit here because it's just a template
87        .build();
88    // Now we can build the trajectory from the BSP file.
89    // We'll arbitrarily set the tracking arc to 24 hours with a five second time step.
90    let traj_as_flown = Traj::from_bsp(
91        lro_frame,
92        MOON_J2000,
93        almanac.clone(),
94        sc_template,
95        5.seconds(),
96        Some(Epoch::from_str("2024-01-01 00:00:00 UTC")?),
97        Some(Epoch::from_str("2024-01-02 00:00:00 UTC")?),
98        Aberration::LT,
99        Some("LRO".to_string()),
100    )?;
101
102    println!("{traj_as_flown}");
103
104    // ====================== //
105    // === MODEL MATCHING === //
106    // ====================== //
107
108    // Set up the spacecraft dynamics.
109
110    // Specify that the orbital dynamics must account for the graviational pull of the Earth and the Sun.
111    // The gravity of the Moon will also be accounted for since the spaceraft in a lunar orbit.
112    let mut orbital_dyn = OrbitalDynamics::point_masses(vec![EARTH, SUN, JUPITER_BARYCENTER]);
113
114    // We want to include the spherical harmonics, so let's download the gravitational data from the Nyx Cloud.
115    // We're using the GRAIL JGGRX model.
116    let mut jggrx_meta = MetaFile {
117        uri: "http://public-data.nyxspace.com/nyx/models/Luna_jggrx_1500e_sha.tab.gz".to_string(),
118        crc32: Some(0x6bcacda8), // Specifying the CRC32 avoids redownloading it if it's cached.
119    };
120    // And let's download it if we don't have it yet.
121    jggrx_meta.process(true)?;
122
123    // Build the spherical harmonics.
124    // The harmonics must be computed in the body fixed frame.
125    // We're using the long term prediction of the Moon principal axes frame.
126    let moon_pa_frame = MOON_PA_FRAME.with_orient(31008);
127    let sph_harmonics = Harmonics::from_stor(
128        almanac.frame_from_uid(moon_pa_frame)?,
129        HarmonicsMem::from_shadr(&jggrx_meta.uri, 80, 80, true)?,
130    );
131
132    // Include the spherical harmonics into the orbital dynamics.
133    orbital_dyn.accel_models.push(sph_harmonics);
134
135    // We define the solar radiation pressure, using the default solar flux and accounting only
136    // for the eclipsing caused by the Earth and Moon.
137    // Note that by default, enabling the SolarPressure model will also enable the estimation of the coefficient of reflectivity.
138    let srp_dyn = SolarPressure::new(vec![EARTH_J2000, MOON_J2000], almanac.clone())?;
139
140    // Finalize setting up the dynamics, specifying the force models (orbital_dyn) separately from the
141    // acceleration models (SRP in this case). Use `from_models` to specify multiple accel models.
142    let dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn);
143
144    println!("{dynamics}");
145
146    // Now we can build the propagator.
147    let setup = Propagator::default_dp78(dynamics.clone());
148
149    // For reference, let's build the trajectory with Nyx's models from that LRO state.
150    let (sim_final, traj_as_sim) = setup
151        .with(*traj_as_flown.first(), almanac.clone())
152        .until_epoch_with_traj(traj_as_flown.last().epoch())?;
153
154    println!("SIM INIT:  {:x}", traj_as_flown.first());
155    println!("SIM FINAL: {sim_final:x}");
156    // Compute RIC difference between SIM and LRO ephem
157    let sim_lro_delta = sim_final
158        .orbit
159        .ric_difference(&traj_as_flown.last().orbit)?;
160    println!("{traj_as_sim}");
161    println!(
162        "SIM v LRO - RIC Position (m): {:.3}",
163        sim_lro_delta.radius_km * 1e3
164    );
165    println!(
166        "SIM v LRO - RIC Velocity (m/s): {:.3}",
167        sim_lro_delta.velocity_km_s * 1e3
168    );
169
170    traj_as_sim.ric_diff_to_parquet(
171        &traj_as_flown,
172        "./04_lro_sim_truth_error.parquet",
173        ExportCfg::default(),
174    )?;
175
176    // ==================== //
177    // === OD SIMULATOR === //
178    // ==================== //
179
180    // After quite some time trying to exactly match the model, we still end up with an oscillatory difference on the order of 150 meters between the propagated state
181    // and the truth LRO state.
182
183    // Therefore, we will actually run an estimation from a dispersed LRO state.
184    // The sc_seed is the true LRO state from the BSP.
185    let sc_seed = *traj_as_flown.first();
186
187    // Load the Deep Space Network ground stations.
188    // Nyx allows you to build these at runtime but it's pretty static so we can just load them from YAML.
189    let ground_station_file: PathBuf = [
190        env!("CARGO_MANIFEST_DIR"),
191        "examples",
192        "04_lro_od",
193        "dsn-network.yaml",
194    ]
195    .iter()
196    .collect();
197
198    let devices = GroundStation::load_named(ground_station_file)?;
199
200    // Typical OD software requires that you specify your own tracking schedule or you'll have overlapping measurements.
201    // Nyx can build a tracking schedule for you based on the first station with access.
202    let trkconfg_yaml: PathBuf = [
203        env!("CARGO_MANIFEST_DIR"),
204        "examples",
205        "04_lro_od",
206        "tracking-cfg.yaml",
207    ]
208    .iter()
209    .collect();
210
211    let configs: BTreeMap<String, TrkConfig> = TrkConfig::load_named(trkconfg_yaml)?;
212
213    // Build the tracking arc simulation to generate a "standard measurement".
214    let mut trk = TrackingArcSim::<Spacecraft, GroundStation>::new(
215        devices.clone(),
216        traj_as_flown.clone(),
217        configs,
218    )?;
219
220    trk.build_schedule(almanac.clone())?;
221    let arc = trk.generate_measurements(almanac.clone())?;
222    // Save the simulated tracking data
223    arc.to_parquet_simple("./04_lro_simulated_tracking.parquet")?;
224
225    // We'll note that in our case, we have continuous coverage of LRO when the vehicle is not behind the Moon.
226    println!("{arc}");
227
228    // Now that we have simulated measurements, we'll run the orbit determination.
229
230    // ===================== //
231    // === OD ESTIMATION === //
232    // ===================== //
233
234    let sc = SpacecraftUncertainty::builder()
235        .nominal(sc_seed)
236        .frame(LocalFrame::RIC)
237        .x_km(0.5)
238        .y_km(0.5)
239        .z_km(0.5)
240        .vx_km_s(5e-3)
241        .vy_km_s(5e-3)
242        .vz_km_s(5e-3)
243        .build();
244
245    // Build the filter initial estimate, which we will reuse in the filter.
246    let initial_estimate = sc.to_estimate()?;
247
248    println!("== FILTER STATE ==\n{sc_seed:x}\n{initial_estimate}");
249
250    let kf = KF::new(
251        // Increase the initial covariance to account for larger deviation.
252        initial_estimate,
253        // Until https://github.com/nyx-space/nyx/issues/351, we need to specify the SNC in the acceleration of the Moon J2000 frame.
254        SNC3::from_diagonal(10 * Unit::Minute, &[1e-12, 1e-12, 1e-12]),
255    );
256
257    // We'll set up the OD process to reject measurements whose residuals are move than 3 sigmas away from what we expect.
258    let mut odp = SpacecraftODProcess::ckf(
259        setup.with(initial_estimate.state().with_stm(), almanac.clone()),
260        kf,
261        devices,
262        Some(ResidRejectCrit::default()),
263        almanac.clone(),
264    );
265
266    odp.process_arc(&arc)?;
267
268    let ric_err = traj_as_flown
269        .at(odp.estimates.last().unwrap().epoch())?
270        .orbit
271        .ric_difference(&odp.estimates.last().unwrap().orbital_state())?;
272    println!("== RIC at end ==");
273    println!("RIC Position (m): {}", ric_err.radius_km * 1e3);
274    println!("RIC Velocity (m/s): {}", ric_err.velocity_km_s * 1e3);
275
276    odp.to_parquet(&arc, "./04_lro_od_results.parquet", ExportCfg::default())?;
277
278    // In our case, we have the truth trajectory from NASA.
279    // So we can compute the RIC state difference between the real LRO ephem and what we've just estimated.
280    // Export the OD trajectory first.
281    let od_trajectory = odp.to_traj()?;
282    // Build the RIC difference.
283    od_trajectory.ric_diff_to_parquet(
284        &traj_as_flown,
285        "./04_lro_od_truth_error.parquet",
286        ExportCfg::default(),
287    )?;
288
289    Ok(())
290}
Source§

impl<'a, D: Dynamics, MsrSize: DimName, Accel: DimName, K: Filter<D::StateType, Accel, MsrSize>, Trk: TrackerSensitivity<D::StateType, D::StateType>> ODProcess<'a, D, MsrSize, Accel, K, Trk>
where D::StateType: Interpolatable + Add<OVector<f64, <D::StateType as State>::Size>, Output = D::StateType>, <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send, DefaultAllocator: Allocator<<D::StateType as State>::Size> + Allocator<<D::StateType as State>::VecLength> + Allocator<MsrSize> + Allocator<MsrSize, <D::StateType as State>::Size> + Allocator<MsrSize, MsrSize> + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size> + Allocator<Accel> + Allocator<Accel, Accel> + Allocator<<D::StateType as State>::Size, Accel> + Allocator<Accel, <D::StateType as State>::Size>,

Source

pub fn new( prop: PropInstance<'a, D>, kf: K, devices: BTreeMap<String, Trk>, ekf_trigger: Option<EkfTrigger>, resid_crit: Option<ResidRejectCrit>, almanac: Arc<Almanac>, ) -> Self

Initialize a new orbit determination process with an optional trigger to switch from a CKF to an EKF.

Source

pub fn ekf( prop: PropInstance<'a, D>, kf: K, devices: BTreeMap<String, Trk>, trigger: EkfTrigger, resid_crit: Option<ResidRejectCrit>, almanac: Arc<Almanac>, ) -> Self

Initialize a new orbit determination process with an Extended Kalman filter. The switch from a classical KF to an EKF is based on the provided trigger.

Source

pub fn smooth( &self, condition: SmoothingArc, ) -> Result<Vec<K::Estimate>, ODError>

Allows to smooth the provided estimates. Returns the smoothed estimates or an error.

Estimates must be ordered in chronological order. This function will smooth the estimates from the last in the list to the first one.

Source

pub fn rms_residual_ratios(&self) -> f64

Returns the root mean square of the prefit residual ratios

Source

pub fn iterate_arc( &mut self, arc: &TrackingDataArc, config: IterationConf, ) -> Result<(), ODError>

Allows iterating on the filter solution. Requires specifying a smoothing condition to know where to stop the smoothing.

Source

pub fn process_arc(&mut self, arc: &TrackingDataArc) -> Result<(), ODError>

Process the provided measurements for this orbit determination process given the associated devices.

§Argument details
  • The measurements must be a list mapping the name of the measurement device to the measurement itself.
  • The name of all measurement devices must be present in the provided devices, i.e. the key set of devices must be a superset of the measurement device names present in the list.
  • The maximum step size to ensure we don’t skip any measurements.
Examples found in repository?
examples/04_lro_od/main.rs (line 266)
33fn main() -> Result<(), Box<dyn Error>> {
34    pel::init();
35
36    // ====================== //
37    // === ALMANAC SET UP === //
38    // ====================== //
39
40    // Dynamics models require planetary constants and ephemerides to be defined.
41    // Let's start by grabbing those by using ANISE's MetaAlmanac.
42
43    let data_folder: PathBuf = [env!("CARGO_MANIFEST_DIR"), "examples", "04_lro_od"]
44        .iter()
45        .collect();
46
47    let meta = data_folder.join("lro-dynamics.dhall");
48
49    // Load this ephem in the general Almanac we're using for this analysis.
50    let mut almanac = MetaAlmanac::new(meta.to_string_lossy().to_string())
51        .map_err(Box::new)?
52        .process(true)
53        .map_err(Box::new)?;
54
55    let mut moon_pc = almanac.planetary_data.get_by_id(MOON)?;
56    moon_pc.mu_km3_s2 = 4902.74987;
57    almanac.planetary_data.set_by_id(MOON, moon_pc)?;
58
59    let mut earth_pc = almanac.planetary_data.get_by_id(EARTH)?;
60    earth_pc.mu_km3_s2 = 398600.436;
61    almanac.planetary_data.set_by_id(EARTH, earth_pc)?;
62
63    // Save this new kernel for reuse.
64    // In an operational context, this would be part of the "Lock" process, and should not change throughout the mission.
65    almanac
66        .planetary_data
67        .save_as(&data_folder.join("lro-specific.pca"), true)?;
68
69    // Lock the almanac (an Arc is a read only structure).
70    let almanac = Arc::new(almanac);
71
72    // Orbit determination requires a Trajectory structure, which can be saved as parquet file.
73    // In our case, the trajectory comes from the BSP file, so we need to build a Trajectory from the almanac directly.
74    // To query the Almanac, we need to build the LRO frame in the J2000 orientation in our case.
75    // Inspecting the LRO BSP in the ANISE GUI shows us that NASA has assigned ID -85 to LRO.
76    let lro_frame = Frame::from_ephem_j2000(-85);
77
78    // To build the trajectory we need to provide a spacecraft template.
79    let sc_template = Spacecraft::builder()
80        .mass(Mass::from_dry_and_prop_masses(1018.0, 900.0)) // Launch masses
81        .srp(SRPData {
82            // SRP configuration is arbitrary, but we will be estimating it anyway.
83            area_m2: 3.9 * 2.7,
84            coeff_reflectivity: 0.96,
85        })
86        .orbit(Orbit::zero(MOON_J2000)) // Setting a zero orbit here because it's just a template
87        .build();
88    // Now we can build the trajectory from the BSP file.
89    // We'll arbitrarily set the tracking arc to 24 hours with a five second time step.
90    let traj_as_flown = Traj::from_bsp(
91        lro_frame,
92        MOON_J2000,
93        almanac.clone(),
94        sc_template,
95        5.seconds(),
96        Some(Epoch::from_str("2024-01-01 00:00:00 UTC")?),
97        Some(Epoch::from_str("2024-01-02 00:00:00 UTC")?),
98        Aberration::LT,
99        Some("LRO".to_string()),
100    )?;
101
102    println!("{traj_as_flown}");
103
104    // ====================== //
105    // === MODEL MATCHING === //
106    // ====================== //
107
108    // Set up the spacecraft dynamics.
109
110    // Specify that the orbital dynamics must account for the graviational pull of the Earth and the Sun.
111    // The gravity of the Moon will also be accounted for since the spaceraft in a lunar orbit.
112    let mut orbital_dyn = OrbitalDynamics::point_masses(vec![EARTH, SUN, JUPITER_BARYCENTER]);
113
114    // We want to include the spherical harmonics, so let's download the gravitational data from the Nyx Cloud.
115    // We're using the GRAIL JGGRX model.
116    let mut jggrx_meta = MetaFile {
117        uri: "http://public-data.nyxspace.com/nyx/models/Luna_jggrx_1500e_sha.tab.gz".to_string(),
118        crc32: Some(0x6bcacda8), // Specifying the CRC32 avoids redownloading it if it's cached.
119    };
120    // And let's download it if we don't have it yet.
121    jggrx_meta.process(true)?;
122
123    // Build the spherical harmonics.
124    // The harmonics must be computed in the body fixed frame.
125    // We're using the long term prediction of the Moon principal axes frame.
126    let moon_pa_frame = MOON_PA_FRAME.with_orient(31008);
127    let sph_harmonics = Harmonics::from_stor(
128        almanac.frame_from_uid(moon_pa_frame)?,
129        HarmonicsMem::from_shadr(&jggrx_meta.uri, 80, 80, true)?,
130    );
131
132    // Include the spherical harmonics into the orbital dynamics.
133    orbital_dyn.accel_models.push(sph_harmonics);
134
135    // We define the solar radiation pressure, using the default solar flux and accounting only
136    // for the eclipsing caused by the Earth and Moon.
137    // Note that by default, enabling the SolarPressure model will also enable the estimation of the coefficient of reflectivity.
138    let srp_dyn = SolarPressure::new(vec![EARTH_J2000, MOON_J2000], almanac.clone())?;
139
140    // Finalize setting up the dynamics, specifying the force models (orbital_dyn) separately from the
141    // acceleration models (SRP in this case). Use `from_models` to specify multiple accel models.
142    let dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn);
143
144    println!("{dynamics}");
145
146    // Now we can build the propagator.
147    let setup = Propagator::default_dp78(dynamics.clone());
148
149    // For reference, let's build the trajectory with Nyx's models from that LRO state.
150    let (sim_final, traj_as_sim) = setup
151        .with(*traj_as_flown.first(), almanac.clone())
152        .until_epoch_with_traj(traj_as_flown.last().epoch())?;
153
154    println!("SIM INIT:  {:x}", traj_as_flown.first());
155    println!("SIM FINAL: {sim_final:x}");
156    // Compute RIC difference between SIM and LRO ephem
157    let sim_lro_delta = sim_final
158        .orbit
159        .ric_difference(&traj_as_flown.last().orbit)?;
160    println!("{traj_as_sim}");
161    println!(
162        "SIM v LRO - RIC Position (m): {:.3}",
163        sim_lro_delta.radius_km * 1e3
164    );
165    println!(
166        "SIM v LRO - RIC Velocity (m/s): {:.3}",
167        sim_lro_delta.velocity_km_s * 1e3
168    );
169
170    traj_as_sim.ric_diff_to_parquet(
171        &traj_as_flown,
172        "./04_lro_sim_truth_error.parquet",
173        ExportCfg::default(),
174    )?;
175
176    // ==================== //
177    // === OD SIMULATOR === //
178    // ==================== //
179
180    // After quite some time trying to exactly match the model, we still end up with an oscillatory difference on the order of 150 meters between the propagated state
181    // and the truth LRO state.
182
183    // Therefore, we will actually run an estimation from a dispersed LRO state.
184    // The sc_seed is the true LRO state from the BSP.
185    let sc_seed = *traj_as_flown.first();
186
187    // Load the Deep Space Network ground stations.
188    // Nyx allows you to build these at runtime but it's pretty static so we can just load them from YAML.
189    let ground_station_file: PathBuf = [
190        env!("CARGO_MANIFEST_DIR"),
191        "examples",
192        "04_lro_od",
193        "dsn-network.yaml",
194    ]
195    .iter()
196    .collect();
197
198    let devices = GroundStation::load_named(ground_station_file)?;
199
200    // Typical OD software requires that you specify your own tracking schedule or you'll have overlapping measurements.
201    // Nyx can build a tracking schedule for you based on the first station with access.
202    let trkconfg_yaml: PathBuf = [
203        env!("CARGO_MANIFEST_DIR"),
204        "examples",
205        "04_lro_od",
206        "tracking-cfg.yaml",
207    ]
208    .iter()
209    .collect();
210
211    let configs: BTreeMap<String, TrkConfig> = TrkConfig::load_named(trkconfg_yaml)?;
212
213    // Build the tracking arc simulation to generate a "standard measurement".
214    let mut trk = TrackingArcSim::<Spacecraft, GroundStation>::new(
215        devices.clone(),
216        traj_as_flown.clone(),
217        configs,
218    )?;
219
220    trk.build_schedule(almanac.clone())?;
221    let arc = trk.generate_measurements(almanac.clone())?;
222    // Save the simulated tracking data
223    arc.to_parquet_simple("./04_lro_simulated_tracking.parquet")?;
224
225    // We'll note that in our case, we have continuous coverage of LRO when the vehicle is not behind the Moon.
226    println!("{arc}");
227
228    // Now that we have simulated measurements, we'll run the orbit determination.
229
230    // ===================== //
231    // === OD ESTIMATION === //
232    // ===================== //
233
234    let sc = SpacecraftUncertainty::builder()
235        .nominal(sc_seed)
236        .frame(LocalFrame::RIC)
237        .x_km(0.5)
238        .y_km(0.5)
239        .z_km(0.5)
240        .vx_km_s(5e-3)
241        .vy_km_s(5e-3)
242        .vz_km_s(5e-3)
243        .build();
244
245    // Build the filter initial estimate, which we will reuse in the filter.
246    let initial_estimate = sc.to_estimate()?;
247
248    println!("== FILTER STATE ==\n{sc_seed:x}\n{initial_estimate}");
249
250    let kf = KF::new(
251        // Increase the initial covariance to account for larger deviation.
252        initial_estimate,
253        // Until https://github.com/nyx-space/nyx/issues/351, we need to specify the SNC in the acceleration of the Moon J2000 frame.
254        SNC3::from_diagonal(10 * Unit::Minute, &[1e-12, 1e-12, 1e-12]),
255    );
256
257    // We'll set up the OD process to reject measurements whose residuals are move than 3 sigmas away from what we expect.
258    let mut odp = SpacecraftODProcess::ckf(
259        setup.with(initial_estimate.state().with_stm(), almanac.clone()),
260        kf,
261        devices,
262        Some(ResidRejectCrit::default()),
263        almanac.clone(),
264    );
265
266    odp.process_arc(&arc)?;
267
268    let ric_err = traj_as_flown
269        .at(odp.estimates.last().unwrap().epoch())?
270        .orbit
271        .ric_difference(&odp.estimates.last().unwrap().orbital_state())?;
272    println!("== RIC at end ==");
273    println!("RIC Position (m): {}", ric_err.radius_km * 1e3);
274    println!("RIC Velocity (m/s): {}", ric_err.velocity_km_s * 1e3);
275
276    odp.to_parquet(&arc, "./04_lro_od_results.parquet", ExportCfg::default())?;
277
278    // In our case, we have the truth trajectory from NASA.
279    // So we can compute the RIC state difference between the real LRO ephem and what we've just estimated.
280    // Export the OD trajectory first.
281    let od_trajectory = odp.to_traj()?;
282    // Build the RIC difference.
283    od_trajectory.ric_diff_to_parquet(
284        &traj_as_flown,
285        "./04_lro_od_truth_error.parquet",
286        ExportCfg::default(),
287    )?;
288
289    Ok(())
290}
Source

pub fn predict_until( &mut self, step: Duration, end_epoch: Epoch, ) -> Result<(), ODError>

Continuously predicts the trajectory until the provided end epoch, with covariance mapping at each step. In other words, this performs a time update.

Source

pub fn predict_for( &mut self, step: Duration, duration: Duration, ) -> Result<(), ODError>

Continuously predicts the trajectory for the provided duration, with covariance mapping at each step. In other words, this performs a time update.

Examples found in repository?
examples/02_jwst_covar_monte_carlo/main.rs (line 124)
26fn main() -> Result<(), Box<dyn Error>> {
27    pel::init();
28    // Dynamics models require planetary constants and ephemerides to be defined.
29    // Let's start by grabbing those by using ANISE's latest MetaAlmanac.
30    // For details, refer to https://github.com/nyx-space/anise/blob/master/data/latest.dhall.
31
32    // Download the regularly update of the James Webb Space Telescope reconstucted (or definitive) ephemeris.
33    // Refer to https://naif.jpl.nasa.gov/pub/naif/JWST/kernels/spk/aareadme.txt for details.
34    let mut latest_jwst_ephem = MetaFile {
35        uri: "https://naif.jpl.nasa.gov/pub/naif/JWST/kernels/spk/jwst_rec.bsp".to_string(),
36        crc32: None,
37    };
38    latest_jwst_ephem.process(true)?;
39
40    // Load this ephem in the general Almanac we're using for this analysis.
41    let almanac = Arc::new(
42        MetaAlmanac::latest()
43            .map_err(Box::new)?
44            .load_from_metafile(latest_jwst_ephem, true)?,
45    );
46
47    // By loading this ephemeris file in the ANISE GUI or ANISE CLI, we can find the NAIF ID of the JWST
48    // in the BSP. We need this ID in order to query the ephemeris.
49    const JWST_NAIF_ID: i32 = -170;
50    // Let's build a frame in the J2000 orientation centered on the JWST.
51    const JWST_J2000: Frame = Frame::from_ephem_j2000(JWST_NAIF_ID);
52
53    // Since the ephemeris file is updated regularly, we'll just grab the latest state in the ephem.
54    let (earliest_epoch, latest_epoch) = almanac.spk_domain(JWST_NAIF_ID)?;
55    println!("JWST defined from {earliest_epoch} to {latest_epoch}");
56    // Fetch the state, printing it in the Earth J2000 frame.
57    let jwst_orbit = almanac.transform(JWST_J2000, EARTH_J2000, latest_epoch, None)?;
58    println!("{jwst_orbit:x}");
59
60    // Build the spacecraft
61    // SRP area assumed to be the full sunshield and mass if 6200.0 kg, c.f. https://webb.nasa.gov/content/about/faqs/facts.html
62    // SRP Coefficient of reflectivity assumed to be that of Kapton, i.e. 2 - 0.44 = 1.56, table 1 from https://amostech.com/TechnicalPapers/2018/Poster/Bengtson.pdf
63    let jwst = Spacecraft::builder()
64        .orbit(jwst_orbit)
65        .srp(SRPData {
66            area_m2: 21.197 * 14.162,
67            coeff_reflectivity: 1.56,
68        })
69        .mass(Mass::from_dry_mass(6200.0))
70        .build();
71
72    // Build up the spacecraft uncertainty builder.
73    // We can use the spacecraft uncertainty structure to build this up.
74    // We start by specifying the nominal state (as defined above), then the uncertainty in position and velocity
75    // in the RIC frame. We could also specify the Cr, Cd, and mass uncertainties, but these aren't accounted for until
76    // Nyx can also estimate the deviation of the spacecraft parameters.
77    let jwst_uncertainty = SpacecraftUncertainty::builder()
78        .nominal(jwst)
79        .frame(LocalFrame::RIC)
80        .x_km(0.5)
81        .y_km(0.3)
82        .z_km(1.5)
83        .vx_km_s(1e-4)
84        .vy_km_s(0.6e-3)
85        .vz_km_s(3e-3)
86        .build();
87
88    println!("{jwst_uncertainty}");
89
90    // Build the Kalman filter estimate.
91    // Note that we could have used the KfEstimate structure directly (as seen throughout the OD integration tests)
92    // but this approach requires quite a bit more boilerplate code.
93    let jwst_estimate = jwst_uncertainty.to_estimate()?;
94
95    // Set up the spacecraft dynamics.
96    // We'll use the point masses of the Earth, Sun, Jupiter (barycenter, because it's in the DE440), and the Moon.
97    // We'll also enable solar radiation pressure since the James Webb has a huge and highly reflective sun shield.
98
99    let orbital_dyn = OrbitalDynamics::point_masses(vec![MOON, SUN, JUPITER_BARYCENTER]);
100    let srp_dyn = SolarPressure::new(vec![EARTH_J2000, MOON_J2000], almanac.clone())?;
101
102    // Finalize setting up the dynamics.
103    let dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn);
104
105    // Build the propagator set up to use for the whole analysis.
106    let setup = Propagator::default(dynamics);
107
108    // All of the analysis will use this duration.
109    let prediction_duration = 6.5 * Unit::Day;
110
111    // === Covariance mapping ===
112    // For the covariance mapping / prediction, we'll use the common orbit determination approach.
113    // This is done by setting up a spacecraft OD process, and predicting for the analysis duration.
114
115    let ckf = KF::no_snc(jwst_estimate);
116
117    // Build the propagation instance for the OD process.
118    let prop = setup.with(jwst.with_stm(), almanac.clone());
119    let mut odp = SpacecraftODProcess::ckf(prop, ckf, BTreeMap::new(), None, almanac.clone());
120
121    // Define the prediction step, i.e. how often we want to know the covariance.
122    let step = 1_i64.minutes();
123    // Finally, predict, and export the trajectory with covariance to a parquet file.
124    odp.predict_for(step, prediction_duration)?;
125    odp.to_parquet(
126        &TrackingDataArc::default(),
127        "./02_jwst_covar_map.parquet",
128        ExportCfg::default(),
129    )?;
130
131    // === Monte Carlo framework ===
132    // Nyx comes with a complete multi-threaded Monte Carlo frame. It's blazing fast.
133
134    let my_mc = MonteCarlo::new(
135        jwst, // Nominal state
136        jwst_estimate.to_random_variable()?,
137        "02_jwst".to_string(), // Scenario name
138        None, // No specific seed specified, so one will be drawn from the computer's entropy.
139    );
140
141    let num_runs = 5_000;
142    let rslts = my_mc.run_until_epoch(
143        setup,
144        almanac.clone(),
145        jwst.epoch() + prediction_duration,
146        num_runs,
147    );
148
149    assert_eq!(rslts.runs.len(), num_runs);
150    // Finally, export these results, computing the eclipse percentage for all of these results.
151
152    // For all of the resulting trajectories, we'll want to compute the percentage of penumbra and umbra.
153    let eclipse_loc = EclipseLocator::cislunar(almanac.clone());
154    let umbra_event = eclipse_loc.to_umbra_event();
155    let penumbra_event = eclipse_loc.to_penumbra_event();
156
157    rslts.to_parquet(
158        "02_jwst_monte_carlo.parquet",
159        Some(vec![&umbra_event, &penumbra_event]),
160        ExportCfg::default(),
161        almanac,
162    )?;
163
164    Ok(())
165}
Source

pub fn to_traj(&self) -> Result<Traj<D::StateType>, NyxError>

Builds the navigation trajectory for the estimated state only

Examples found in repository?
examples/04_lro_od/main.rs (line 281)
33fn main() -> Result<(), Box<dyn Error>> {
34    pel::init();
35
36    // ====================== //
37    // === ALMANAC SET UP === //
38    // ====================== //
39
40    // Dynamics models require planetary constants and ephemerides to be defined.
41    // Let's start by grabbing those by using ANISE's MetaAlmanac.
42
43    let data_folder: PathBuf = [env!("CARGO_MANIFEST_DIR"), "examples", "04_lro_od"]
44        .iter()
45        .collect();
46
47    let meta = data_folder.join("lro-dynamics.dhall");
48
49    // Load this ephem in the general Almanac we're using for this analysis.
50    let mut almanac = MetaAlmanac::new(meta.to_string_lossy().to_string())
51        .map_err(Box::new)?
52        .process(true)
53        .map_err(Box::new)?;
54
55    let mut moon_pc = almanac.planetary_data.get_by_id(MOON)?;
56    moon_pc.mu_km3_s2 = 4902.74987;
57    almanac.planetary_data.set_by_id(MOON, moon_pc)?;
58
59    let mut earth_pc = almanac.planetary_data.get_by_id(EARTH)?;
60    earth_pc.mu_km3_s2 = 398600.436;
61    almanac.planetary_data.set_by_id(EARTH, earth_pc)?;
62
63    // Save this new kernel for reuse.
64    // In an operational context, this would be part of the "Lock" process, and should not change throughout the mission.
65    almanac
66        .planetary_data
67        .save_as(&data_folder.join("lro-specific.pca"), true)?;
68
69    // Lock the almanac (an Arc is a read only structure).
70    let almanac = Arc::new(almanac);
71
72    // Orbit determination requires a Trajectory structure, which can be saved as parquet file.
73    // In our case, the trajectory comes from the BSP file, so we need to build a Trajectory from the almanac directly.
74    // To query the Almanac, we need to build the LRO frame in the J2000 orientation in our case.
75    // Inspecting the LRO BSP in the ANISE GUI shows us that NASA has assigned ID -85 to LRO.
76    let lro_frame = Frame::from_ephem_j2000(-85);
77
78    // To build the trajectory we need to provide a spacecraft template.
79    let sc_template = Spacecraft::builder()
80        .mass(Mass::from_dry_and_prop_masses(1018.0, 900.0)) // Launch masses
81        .srp(SRPData {
82            // SRP configuration is arbitrary, but we will be estimating it anyway.
83            area_m2: 3.9 * 2.7,
84            coeff_reflectivity: 0.96,
85        })
86        .orbit(Orbit::zero(MOON_J2000)) // Setting a zero orbit here because it's just a template
87        .build();
88    // Now we can build the trajectory from the BSP file.
89    // We'll arbitrarily set the tracking arc to 24 hours with a five second time step.
90    let traj_as_flown = Traj::from_bsp(
91        lro_frame,
92        MOON_J2000,
93        almanac.clone(),
94        sc_template,
95        5.seconds(),
96        Some(Epoch::from_str("2024-01-01 00:00:00 UTC")?),
97        Some(Epoch::from_str("2024-01-02 00:00:00 UTC")?),
98        Aberration::LT,
99        Some("LRO".to_string()),
100    )?;
101
102    println!("{traj_as_flown}");
103
104    // ====================== //
105    // === MODEL MATCHING === //
106    // ====================== //
107
108    // Set up the spacecraft dynamics.
109
110    // Specify that the orbital dynamics must account for the graviational pull of the Earth and the Sun.
111    // The gravity of the Moon will also be accounted for since the spaceraft in a lunar orbit.
112    let mut orbital_dyn = OrbitalDynamics::point_masses(vec![EARTH, SUN, JUPITER_BARYCENTER]);
113
114    // We want to include the spherical harmonics, so let's download the gravitational data from the Nyx Cloud.
115    // We're using the GRAIL JGGRX model.
116    let mut jggrx_meta = MetaFile {
117        uri: "http://public-data.nyxspace.com/nyx/models/Luna_jggrx_1500e_sha.tab.gz".to_string(),
118        crc32: Some(0x6bcacda8), // Specifying the CRC32 avoids redownloading it if it's cached.
119    };
120    // And let's download it if we don't have it yet.
121    jggrx_meta.process(true)?;
122
123    // Build the spherical harmonics.
124    // The harmonics must be computed in the body fixed frame.
125    // We're using the long term prediction of the Moon principal axes frame.
126    let moon_pa_frame = MOON_PA_FRAME.with_orient(31008);
127    let sph_harmonics = Harmonics::from_stor(
128        almanac.frame_from_uid(moon_pa_frame)?,
129        HarmonicsMem::from_shadr(&jggrx_meta.uri, 80, 80, true)?,
130    );
131
132    // Include the spherical harmonics into the orbital dynamics.
133    orbital_dyn.accel_models.push(sph_harmonics);
134
135    // We define the solar radiation pressure, using the default solar flux and accounting only
136    // for the eclipsing caused by the Earth and Moon.
137    // Note that by default, enabling the SolarPressure model will also enable the estimation of the coefficient of reflectivity.
138    let srp_dyn = SolarPressure::new(vec![EARTH_J2000, MOON_J2000], almanac.clone())?;
139
140    // Finalize setting up the dynamics, specifying the force models (orbital_dyn) separately from the
141    // acceleration models (SRP in this case). Use `from_models` to specify multiple accel models.
142    let dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn);
143
144    println!("{dynamics}");
145
146    // Now we can build the propagator.
147    let setup = Propagator::default_dp78(dynamics.clone());
148
149    // For reference, let's build the trajectory with Nyx's models from that LRO state.
150    let (sim_final, traj_as_sim) = setup
151        .with(*traj_as_flown.first(), almanac.clone())
152        .until_epoch_with_traj(traj_as_flown.last().epoch())?;
153
154    println!("SIM INIT:  {:x}", traj_as_flown.first());
155    println!("SIM FINAL: {sim_final:x}");
156    // Compute RIC difference between SIM and LRO ephem
157    let sim_lro_delta = sim_final
158        .orbit
159        .ric_difference(&traj_as_flown.last().orbit)?;
160    println!("{traj_as_sim}");
161    println!(
162        "SIM v LRO - RIC Position (m): {:.3}",
163        sim_lro_delta.radius_km * 1e3
164    );
165    println!(
166        "SIM v LRO - RIC Velocity (m/s): {:.3}",
167        sim_lro_delta.velocity_km_s * 1e3
168    );
169
170    traj_as_sim.ric_diff_to_parquet(
171        &traj_as_flown,
172        "./04_lro_sim_truth_error.parquet",
173        ExportCfg::default(),
174    )?;
175
176    // ==================== //
177    // === OD SIMULATOR === //
178    // ==================== //
179
180    // After quite some time trying to exactly match the model, we still end up with an oscillatory difference on the order of 150 meters between the propagated state
181    // and the truth LRO state.
182
183    // Therefore, we will actually run an estimation from a dispersed LRO state.
184    // The sc_seed is the true LRO state from the BSP.
185    let sc_seed = *traj_as_flown.first();
186
187    // Load the Deep Space Network ground stations.
188    // Nyx allows you to build these at runtime but it's pretty static so we can just load them from YAML.
189    let ground_station_file: PathBuf = [
190        env!("CARGO_MANIFEST_DIR"),
191        "examples",
192        "04_lro_od",
193        "dsn-network.yaml",
194    ]
195    .iter()
196    .collect();
197
198    let devices = GroundStation::load_named(ground_station_file)?;
199
200    // Typical OD software requires that you specify your own tracking schedule or you'll have overlapping measurements.
201    // Nyx can build a tracking schedule for you based on the first station with access.
202    let trkconfg_yaml: PathBuf = [
203        env!("CARGO_MANIFEST_DIR"),
204        "examples",
205        "04_lro_od",
206        "tracking-cfg.yaml",
207    ]
208    .iter()
209    .collect();
210
211    let configs: BTreeMap<String, TrkConfig> = TrkConfig::load_named(trkconfg_yaml)?;
212
213    // Build the tracking arc simulation to generate a "standard measurement".
214    let mut trk = TrackingArcSim::<Spacecraft, GroundStation>::new(
215        devices.clone(),
216        traj_as_flown.clone(),
217        configs,
218    )?;
219
220    trk.build_schedule(almanac.clone())?;
221    let arc = trk.generate_measurements(almanac.clone())?;
222    // Save the simulated tracking data
223    arc.to_parquet_simple("./04_lro_simulated_tracking.parquet")?;
224
225    // We'll note that in our case, we have continuous coverage of LRO when the vehicle is not behind the Moon.
226    println!("{arc}");
227
228    // Now that we have simulated measurements, we'll run the orbit determination.
229
230    // ===================== //
231    // === OD ESTIMATION === //
232    // ===================== //
233
234    let sc = SpacecraftUncertainty::builder()
235        .nominal(sc_seed)
236        .frame(LocalFrame::RIC)
237        .x_km(0.5)
238        .y_km(0.5)
239        .z_km(0.5)
240        .vx_km_s(5e-3)
241        .vy_km_s(5e-3)
242        .vz_km_s(5e-3)
243        .build();
244
245    // Build the filter initial estimate, which we will reuse in the filter.
246    let initial_estimate = sc.to_estimate()?;
247
248    println!("== FILTER STATE ==\n{sc_seed:x}\n{initial_estimate}");
249
250    let kf = KF::new(
251        // Increase the initial covariance to account for larger deviation.
252        initial_estimate,
253        // Until https://github.com/nyx-space/nyx/issues/351, we need to specify the SNC in the acceleration of the Moon J2000 frame.
254        SNC3::from_diagonal(10 * Unit::Minute, &[1e-12, 1e-12, 1e-12]),
255    );
256
257    // We'll set up the OD process to reject measurements whose residuals are move than 3 sigmas away from what we expect.
258    let mut odp = SpacecraftODProcess::ckf(
259        setup.with(initial_estimate.state().with_stm(), almanac.clone()),
260        kf,
261        devices,
262        Some(ResidRejectCrit::default()),
263        almanac.clone(),
264    );
265
266    odp.process_arc(&arc)?;
267
268    let ric_err = traj_as_flown
269        .at(odp.estimates.last().unwrap().epoch())?
270        .orbit
271        .ric_difference(&odp.estimates.last().unwrap().orbital_state())?;
272    println!("== RIC at end ==");
273    println!("RIC Position (m): {}", ric_err.radius_km * 1e3);
274    println!("RIC Velocity (m/s): {}", ric_err.velocity_km_s * 1e3);
275
276    odp.to_parquet(&arc, "./04_lro_od_results.parquet", ExportCfg::default())?;
277
278    // In our case, we have the truth trajectory from NASA.
279    // So we can compute the RIC state difference between the real LRO ephem and what we've just estimated.
280    // Export the OD trajectory first.
281    let od_trajectory = odp.to_traj()?;
282    // Build the RIC difference.
283    od_trajectory.ric_diff_to_parquet(
284        &traj_as_flown,
285        "./04_lro_od_truth_error.parquet",
286        ExportCfg::default(),
287    )?;
288
289    Ok(())
290}
Source§

impl<'a, D: Dynamics, MsrSize: DimName, Accel: DimName, K: Filter<D::StateType, Accel, MsrSize>, Trk: TrackerSensitivity<D::StateType, D::StateType>> ODProcess<'a, D, MsrSize, Accel, K, Trk>
where D::StateType: Interpolatable + Add<OVector<f64, <D::StateType as State>::Size>, Output = D::StateType>, <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send, DefaultAllocator: Allocator<<D::StateType as State>::Size> + Allocator<<D::StateType as State>::VecLength> + Allocator<MsrSize> + Allocator<MsrSize, <D::StateType as State>::Size> + Allocator<MsrSize, MsrSize> + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size> + Allocator<Accel> + Allocator<Accel, Accel> + Allocator<<D::StateType as State>::Size, Accel> + Allocator<Accel, <D::StateType as State>::Size>,

Source

pub fn ckf( prop: PropInstance<'a, D>, kf: K, devices: BTreeMap<String, Trk>, resid_crit: Option<ResidRejectCrit>, almanac: Arc<Almanac>, ) -> Self

Examples found in repository?
examples/02_jwst_covar_monte_carlo/main.rs (line 119)
26fn main() -> Result<(), Box<dyn Error>> {
27    pel::init();
28    // Dynamics models require planetary constants and ephemerides to be defined.
29    // Let's start by grabbing those by using ANISE's latest MetaAlmanac.
30    // For details, refer to https://github.com/nyx-space/anise/blob/master/data/latest.dhall.
31
32    // Download the regularly update of the James Webb Space Telescope reconstucted (or definitive) ephemeris.
33    // Refer to https://naif.jpl.nasa.gov/pub/naif/JWST/kernels/spk/aareadme.txt for details.
34    let mut latest_jwst_ephem = MetaFile {
35        uri: "https://naif.jpl.nasa.gov/pub/naif/JWST/kernels/spk/jwst_rec.bsp".to_string(),
36        crc32: None,
37    };
38    latest_jwst_ephem.process(true)?;
39
40    // Load this ephem in the general Almanac we're using for this analysis.
41    let almanac = Arc::new(
42        MetaAlmanac::latest()
43            .map_err(Box::new)?
44            .load_from_metafile(latest_jwst_ephem, true)?,
45    );
46
47    // By loading this ephemeris file in the ANISE GUI or ANISE CLI, we can find the NAIF ID of the JWST
48    // in the BSP. We need this ID in order to query the ephemeris.
49    const JWST_NAIF_ID: i32 = -170;
50    // Let's build a frame in the J2000 orientation centered on the JWST.
51    const JWST_J2000: Frame = Frame::from_ephem_j2000(JWST_NAIF_ID);
52
53    // Since the ephemeris file is updated regularly, we'll just grab the latest state in the ephem.
54    let (earliest_epoch, latest_epoch) = almanac.spk_domain(JWST_NAIF_ID)?;
55    println!("JWST defined from {earliest_epoch} to {latest_epoch}");
56    // Fetch the state, printing it in the Earth J2000 frame.
57    let jwst_orbit = almanac.transform(JWST_J2000, EARTH_J2000, latest_epoch, None)?;
58    println!("{jwst_orbit:x}");
59
60    // Build the spacecraft
61    // SRP area assumed to be the full sunshield and mass if 6200.0 kg, c.f. https://webb.nasa.gov/content/about/faqs/facts.html
62    // SRP Coefficient of reflectivity assumed to be that of Kapton, i.e. 2 - 0.44 = 1.56, table 1 from https://amostech.com/TechnicalPapers/2018/Poster/Bengtson.pdf
63    let jwst = Spacecraft::builder()
64        .orbit(jwst_orbit)
65        .srp(SRPData {
66            area_m2: 21.197 * 14.162,
67            coeff_reflectivity: 1.56,
68        })
69        .mass(Mass::from_dry_mass(6200.0))
70        .build();
71
72    // Build up the spacecraft uncertainty builder.
73    // We can use the spacecraft uncertainty structure to build this up.
74    // We start by specifying the nominal state (as defined above), then the uncertainty in position and velocity
75    // in the RIC frame. We could also specify the Cr, Cd, and mass uncertainties, but these aren't accounted for until
76    // Nyx can also estimate the deviation of the spacecraft parameters.
77    let jwst_uncertainty = SpacecraftUncertainty::builder()
78        .nominal(jwst)
79        .frame(LocalFrame::RIC)
80        .x_km(0.5)
81        .y_km(0.3)
82        .z_km(1.5)
83        .vx_km_s(1e-4)
84        .vy_km_s(0.6e-3)
85        .vz_km_s(3e-3)
86        .build();
87
88    println!("{jwst_uncertainty}");
89
90    // Build the Kalman filter estimate.
91    // Note that we could have used the KfEstimate structure directly (as seen throughout the OD integration tests)
92    // but this approach requires quite a bit more boilerplate code.
93    let jwst_estimate = jwst_uncertainty.to_estimate()?;
94
95    // Set up the spacecraft dynamics.
96    // We'll use the point masses of the Earth, Sun, Jupiter (barycenter, because it's in the DE440), and the Moon.
97    // We'll also enable solar radiation pressure since the James Webb has a huge and highly reflective sun shield.
98
99    let orbital_dyn = OrbitalDynamics::point_masses(vec![MOON, SUN, JUPITER_BARYCENTER]);
100    let srp_dyn = SolarPressure::new(vec![EARTH_J2000, MOON_J2000], almanac.clone())?;
101
102    // Finalize setting up the dynamics.
103    let dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn);
104
105    // Build the propagator set up to use for the whole analysis.
106    let setup = Propagator::default(dynamics);
107
108    // All of the analysis will use this duration.
109    let prediction_duration = 6.5 * Unit::Day;
110
111    // === Covariance mapping ===
112    // For the covariance mapping / prediction, we'll use the common orbit determination approach.
113    // This is done by setting up a spacecraft OD process, and predicting for the analysis duration.
114
115    let ckf = KF::no_snc(jwst_estimate);
116
117    // Build the propagation instance for the OD process.
118    let prop = setup.with(jwst.with_stm(), almanac.clone());
119    let mut odp = SpacecraftODProcess::ckf(prop, ckf, BTreeMap::new(), None, almanac.clone());
120
121    // Define the prediction step, i.e. how often we want to know the covariance.
122    let step = 1_i64.minutes();
123    // Finally, predict, and export the trajectory with covariance to a parquet file.
124    odp.predict_for(step, prediction_duration)?;
125    odp.to_parquet(
126        &TrackingDataArc::default(),
127        "./02_jwst_covar_map.parquet",
128        ExportCfg::default(),
129    )?;
130
131    // === Monte Carlo framework ===
132    // Nyx comes with a complete multi-threaded Monte Carlo frame. It's blazing fast.
133
134    let my_mc = MonteCarlo::new(
135        jwst, // Nominal state
136        jwst_estimate.to_random_variable()?,
137        "02_jwst".to_string(), // Scenario name
138        None, // No specific seed specified, so one will be drawn from the computer's entropy.
139    );
140
141    let num_runs = 5_000;
142    let rslts = my_mc.run_until_epoch(
143        setup,
144        almanac.clone(),
145        jwst.epoch() + prediction_duration,
146        num_runs,
147    );
148
149    assert_eq!(rslts.runs.len(), num_runs);
150    // Finally, export these results, computing the eclipse percentage for all of these results.
151
152    // For all of the resulting trajectories, we'll want to compute the percentage of penumbra and umbra.
153    let eclipse_loc = EclipseLocator::cislunar(almanac.clone());
154    let umbra_event = eclipse_loc.to_umbra_event();
155    let penumbra_event = eclipse_loc.to_penumbra_event();
156
157    rslts.to_parquet(
158        "02_jwst_monte_carlo.parquet",
159        Some(vec![&umbra_event, &penumbra_event]),
160        ExportCfg::default(),
161        almanac,
162    )?;
163
164    Ok(())
165}
More examples
Hide additional examples
examples/04_lro_od/main.rs (lines 258-264)
33fn main() -> Result<(), Box<dyn Error>> {
34    pel::init();
35
36    // ====================== //
37    // === ALMANAC SET UP === //
38    // ====================== //
39
40    // Dynamics models require planetary constants and ephemerides to be defined.
41    // Let's start by grabbing those by using ANISE's MetaAlmanac.
42
43    let data_folder: PathBuf = [env!("CARGO_MANIFEST_DIR"), "examples", "04_lro_od"]
44        .iter()
45        .collect();
46
47    let meta = data_folder.join("lro-dynamics.dhall");
48
49    // Load this ephem in the general Almanac we're using for this analysis.
50    let mut almanac = MetaAlmanac::new(meta.to_string_lossy().to_string())
51        .map_err(Box::new)?
52        .process(true)
53        .map_err(Box::new)?;
54
55    let mut moon_pc = almanac.planetary_data.get_by_id(MOON)?;
56    moon_pc.mu_km3_s2 = 4902.74987;
57    almanac.planetary_data.set_by_id(MOON, moon_pc)?;
58
59    let mut earth_pc = almanac.planetary_data.get_by_id(EARTH)?;
60    earth_pc.mu_km3_s2 = 398600.436;
61    almanac.planetary_data.set_by_id(EARTH, earth_pc)?;
62
63    // Save this new kernel for reuse.
64    // In an operational context, this would be part of the "Lock" process, and should not change throughout the mission.
65    almanac
66        .planetary_data
67        .save_as(&data_folder.join("lro-specific.pca"), true)?;
68
69    // Lock the almanac (an Arc is a read only structure).
70    let almanac = Arc::new(almanac);
71
72    // Orbit determination requires a Trajectory structure, which can be saved as parquet file.
73    // In our case, the trajectory comes from the BSP file, so we need to build a Trajectory from the almanac directly.
74    // To query the Almanac, we need to build the LRO frame in the J2000 orientation in our case.
75    // Inspecting the LRO BSP in the ANISE GUI shows us that NASA has assigned ID -85 to LRO.
76    let lro_frame = Frame::from_ephem_j2000(-85);
77
78    // To build the trajectory we need to provide a spacecraft template.
79    let sc_template = Spacecraft::builder()
80        .mass(Mass::from_dry_and_prop_masses(1018.0, 900.0)) // Launch masses
81        .srp(SRPData {
82            // SRP configuration is arbitrary, but we will be estimating it anyway.
83            area_m2: 3.9 * 2.7,
84            coeff_reflectivity: 0.96,
85        })
86        .orbit(Orbit::zero(MOON_J2000)) // Setting a zero orbit here because it's just a template
87        .build();
88    // Now we can build the trajectory from the BSP file.
89    // We'll arbitrarily set the tracking arc to 24 hours with a five second time step.
90    let traj_as_flown = Traj::from_bsp(
91        lro_frame,
92        MOON_J2000,
93        almanac.clone(),
94        sc_template,
95        5.seconds(),
96        Some(Epoch::from_str("2024-01-01 00:00:00 UTC")?),
97        Some(Epoch::from_str("2024-01-02 00:00:00 UTC")?),
98        Aberration::LT,
99        Some("LRO".to_string()),
100    )?;
101
102    println!("{traj_as_flown}");
103
104    // ====================== //
105    // === MODEL MATCHING === //
106    // ====================== //
107
108    // Set up the spacecraft dynamics.
109
110    // Specify that the orbital dynamics must account for the graviational pull of the Earth and the Sun.
111    // The gravity of the Moon will also be accounted for since the spaceraft in a lunar orbit.
112    let mut orbital_dyn = OrbitalDynamics::point_masses(vec![EARTH, SUN, JUPITER_BARYCENTER]);
113
114    // We want to include the spherical harmonics, so let's download the gravitational data from the Nyx Cloud.
115    // We're using the GRAIL JGGRX model.
116    let mut jggrx_meta = MetaFile {
117        uri: "http://public-data.nyxspace.com/nyx/models/Luna_jggrx_1500e_sha.tab.gz".to_string(),
118        crc32: Some(0x6bcacda8), // Specifying the CRC32 avoids redownloading it if it's cached.
119    };
120    // And let's download it if we don't have it yet.
121    jggrx_meta.process(true)?;
122
123    // Build the spherical harmonics.
124    // The harmonics must be computed in the body fixed frame.
125    // We're using the long term prediction of the Moon principal axes frame.
126    let moon_pa_frame = MOON_PA_FRAME.with_orient(31008);
127    let sph_harmonics = Harmonics::from_stor(
128        almanac.frame_from_uid(moon_pa_frame)?,
129        HarmonicsMem::from_shadr(&jggrx_meta.uri, 80, 80, true)?,
130    );
131
132    // Include the spherical harmonics into the orbital dynamics.
133    orbital_dyn.accel_models.push(sph_harmonics);
134
135    // We define the solar radiation pressure, using the default solar flux and accounting only
136    // for the eclipsing caused by the Earth and Moon.
137    // Note that by default, enabling the SolarPressure model will also enable the estimation of the coefficient of reflectivity.
138    let srp_dyn = SolarPressure::new(vec![EARTH_J2000, MOON_J2000], almanac.clone())?;
139
140    // Finalize setting up the dynamics, specifying the force models (orbital_dyn) separately from the
141    // acceleration models (SRP in this case). Use `from_models` to specify multiple accel models.
142    let dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn);
143
144    println!("{dynamics}");
145
146    // Now we can build the propagator.
147    let setup = Propagator::default_dp78(dynamics.clone());
148
149    // For reference, let's build the trajectory with Nyx's models from that LRO state.
150    let (sim_final, traj_as_sim) = setup
151        .with(*traj_as_flown.first(), almanac.clone())
152        .until_epoch_with_traj(traj_as_flown.last().epoch())?;
153
154    println!("SIM INIT:  {:x}", traj_as_flown.first());
155    println!("SIM FINAL: {sim_final:x}");
156    // Compute RIC difference between SIM and LRO ephem
157    let sim_lro_delta = sim_final
158        .orbit
159        .ric_difference(&traj_as_flown.last().orbit)?;
160    println!("{traj_as_sim}");
161    println!(
162        "SIM v LRO - RIC Position (m): {:.3}",
163        sim_lro_delta.radius_km * 1e3
164    );
165    println!(
166        "SIM v LRO - RIC Velocity (m/s): {:.3}",
167        sim_lro_delta.velocity_km_s * 1e3
168    );
169
170    traj_as_sim.ric_diff_to_parquet(
171        &traj_as_flown,
172        "./04_lro_sim_truth_error.parquet",
173        ExportCfg::default(),
174    )?;
175
176    // ==================== //
177    // === OD SIMULATOR === //
178    // ==================== //
179
180    // After quite some time trying to exactly match the model, we still end up with an oscillatory difference on the order of 150 meters between the propagated state
181    // and the truth LRO state.
182
183    // Therefore, we will actually run an estimation from a dispersed LRO state.
184    // The sc_seed is the true LRO state from the BSP.
185    let sc_seed = *traj_as_flown.first();
186
187    // Load the Deep Space Network ground stations.
188    // Nyx allows you to build these at runtime but it's pretty static so we can just load them from YAML.
189    let ground_station_file: PathBuf = [
190        env!("CARGO_MANIFEST_DIR"),
191        "examples",
192        "04_lro_od",
193        "dsn-network.yaml",
194    ]
195    .iter()
196    .collect();
197
198    let devices = GroundStation::load_named(ground_station_file)?;
199
200    // Typical OD software requires that you specify your own tracking schedule or you'll have overlapping measurements.
201    // Nyx can build a tracking schedule for you based on the first station with access.
202    let trkconfg_yaml: PathBuf = [
203        env!("CARGO_MANIFEST_DIR"),
204        "examples",
205        "04_lro_od",
206        "tracking-cfg.yaml",
207    ]
208    .iter()
209    .collect();
210
211    let configs: BTreeMap<String, TrkConfig> = TrkConfig::load_named(trkconfg_yaml)?;
212
213    // Build the tracking arc simulation to generate a "standard measurement".
214    let mut trk = TrackingArcSim::<Spacecraft, GroundStation>::new(
215        devices.clone(),
216        traj_as_flown.clone(),
217        configs,
218    )?;
219
220    trk.build_schedule(almanac.clone())?;
221    let arc = trk.generate_measurements(almanac.clone())?;
222    // Save the simulated tracking data
223    arc.to_parquet_simple("./04_lro_simulated_tracking.parquet")?;
224
225    // We'll note that in our case, we have continuous coverage of LRO when the vehicle is not behind the Moon.
226    println!("{arc}");
227
228    // Now that we have simulated measurements, we'll run the orbit determination.
229
230    // ===================== //
231    // === OD ESTIMATION === //
232    // ===================== //
233
234    let sc = SpacecraftUncertainty::builder()
235        .nominal(sc_seed)
236        .frame(LocalFrame::RIC)
237        .x_km(0.5)
238        .y_km(0.5)
239        .z_km(0.5)
240        .vx_km_s(5e-3)
241        .vy_km_s(5e-3)
242        .vz_km_s(5e-3)
243        .build();
244
245    // Build the filter initial estimate, which we will reuse in the filter.
246    let initial_estimate = sc.to_estimate()?;
247
248    println!("== FILTER STATE ==\n{sc_seed:x}\n{initial_estimate}");
249
250    let kf = KF::new(
251        // Increase the initial covariance to account for larger deviation.
252        initial_estimate,
253        // Until https://github.com/nyx-space/nyx/issues/351, we need to specify the SNC in the acceleration of the Moon J2000 frame.
254        SNC3::from_diagonal(10 * Unit::Minute, &[1e-12, 1e-12, 1e-12]),
255    );
256
257    // We'll set up the OD process to reject measurements whose residuals are move than 3 sigmas away from what we expect.
258    let mut odp = SpacecraftODProcess::ckf(
259        setup.with(initial_estimate.state().with_stm(), almanac.clone()),
260        kf,
261        devices,
262        Some(ResidRejectCrit::default()),
263        almanac.clone(),
264    );
265
266    odp.process_arc(&arc)?;
267
268    let ric_err = traj_as_flown
269        .at(odp.estimates.last().unwrap().epoch())?
270        .orbit
271        .ric_difference(&odp.estimates.last().unwrap().orbital_state())?;
272    println!("== RIC at end ==");
273    println!("RIC Position (m): {}", ric_err.radius_km * 1e3);
274    println!("RIC Velocity (m/s): {}", ric_err.velocity_km_s * 1e3);
275
276    odp.to_parquet(&arc, "./04_lro_od_results.parquet", ExportCfg::default())?;
277
278    // In our case, we have the truth trajectory from NASA.
279    // So we can compute the RIC state difference between the real LRO ephem and what we've just estimated.
280    // Export the OD trajectory first.
281    let od_trajectory = odp.to_traj()?;
282    // Build the RIC difference.
283    od_trajectory.ric_diff_to_parquet(
284        &traj_as_flown,
285        "./04_lro_od_truth_error.parquet",
286        ExportCfg::default(),
287    )?;
288
289    Ok(())
290}

Auto Trait Implementations§

§

impl<'a, D, MsrSize, Accel, K, Trk> Freeze for ODProcess<'a, D, MsrSize, Accel, K, Trk>

§

impl<'a, D, MsrSize, Accel, K, Trk> !RefUnwindSafe for ODProcess<'a, D, MsrSize, Accel, K, Trk>

§

impl<'a, D, MsrSize, Accel, K, Trk> !Send for ODProcess<'a, D, MsrSize, Accel, K, Trk>

§

impl<'a, D, MsrSize, Accel, K, Trk> !Sync for ODProcess<'a, D, MsrSize, Accel, K, Trk>

§

impl<'a, D, MsrSize, Accel, K, Trk> !Unpin for ODProcess<'a, D, MsrSize, Accel, K, Trk>

§

impl<'a, D, MsrSize, Accel, K, Trk> !UnwindSafe for ODProcess<'a, D, MsrSize, Accel, K, Trk>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<T> Allocation for T
where T: RefUnwindSafe + Send + Sync,

§

impl<T> ErasedDestructor for T
where T: 'static,

§

impl<T> MaybeSendSync for T