Struct SRPData
pub struct SRPData {
pub area_m2: f64,
pub coeff_reflectivity: f64,
}
Fields§
§area_m2: f64
Solar radiation pressure area in m^2 – default 0.0
coeff_reflectivity: f64
Solar radiation pressure coefficient of reflectivity (C_r) – default 1.8
Implementations§
§impl SRPData
impl SRPData
pub fn from_area(area_m2: f64) -> SRPData
pub fn from_area(area_m2: f64) -> SRPData
Examples found in repository?
examples/03_geo_analysis/stationkeeping.rs (line 42)
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
fn main() -> Result<(), Box<dyn Error>> {
pel::init();
// Set up the dynamics like in the orbit raise.
let almanac = Arc::new(MetaAlmanac::latest().map_err(Box::new)?);
let epoch = Epoch::from_gregorian_utc_hms(2024, 2, 29, 12, 13, 14);
// Define the GEO orbit, and we're just going to maintain it very tightly.
let earth_j2000 = almanac.frame_from_uid(EARTH_J2000)?;
let orbit = Orbit::try_keplerian(42164.0, 1e-5, 0., 163.0, 75.0, 0.0, epoch, earth_j2000)?;
println!("{orbit:x}");
let sc = Spacecraft::builder()
.orbit(orbit)
.mass(Mass::from_dry_and_prop_masses(1000.0, 1000.0)) // 1000 kg of dry mass and prop, totalling 2.0 tons
.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
.thruster(Thruster {
// "NEXT-STEP" row in Table 2
isp_s: 4435.0,
thrust_N: 0.472,
})
.mode(GuidanceMode::Thrust) // Start thrusting immediately.
.build();
// Set up the spacecraft dynamics like in the orbit raise example.
let prop_time = 30.0 * Unit::Day;
// Define the guidance law -- we're just using a Ruggiero controller as demonstrated in AAS-2004-5089.
let objectives = &[
Objective::within_tolerance(StateParameter::SMA, 42_164.0, 5.0), // 5 km
Objective::within_tolerance(StateParameter::Eccentricity, 0.001, 5e-5),
Objective::within_tolerance(StateParameter::Inclination, 0.05, 1e-2),
];
let ruggiero_ctrl = Ruggiero::from_max_eclipse(objectives, sc, 0.2)?;
println!("{ruggiero_ctrl}");
let mut orbital_dyn = OrbitalDynamics::point_masses(vec![MOON, SUN]);
let mut jgm3_meta = MetaFile {
uri: "http://public-data.nyxspace.com/nyx/models/JGM3.cof.gz".to_string(),
crc32: Some(0xF446F027), // Specifying the CRC32 avoids redownloading it if it's cached.
};
jgm3_meta.process(true)?;
let harmonics = Harmonics::from_stor(
almanac.frame_from_uid(IAU_EARTH_FRAME)?,
HarmonicsMem::from_cof(&jgm3_meta.uri, 8, 8, true)?,
);
orbital_dyn.accel_models.push(harmonics);
let srp_dyn = SolarPressure::default(EARTH_J2000, almanac.clone())?;
let sc_dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn)
.with_guidance_law(ruggiero_ctrl.clone());
println!("{sc_dynamics}");
// Finally, let's use the Monte Carlo framework built into Nyx to propagate spacecraft.
// Let's start by defining the dispersion.
// 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.
// Note that additional validation on the MVN is in progress -- https://github.com/nyx-space/nyx/issues/339.
let mc_rv = MvnSpacecraft::new(
sc,
vec![StateDispersion::zero_mean(StateParameter::SMA, 3.0)],
)?;
let my_mc = MonteCarlo::new(
sc, // Nominal state
mc_rv,
"03_geo_sk".to_string(), // Scenario name
None, // No specific seed specified, so one will be drawn from the computer's entropy.
);
// Build the propagator setup.
let setup = Propagator::rk89(
sc_dynamics.clone(),
IntegratorOptions::builder()
.min_step(10.0_f64.seconds())
.error_ctrl(ErrorControl::RSSCartesianStep)
.build(),
);
let num_runs = 25;
let rslts = my_mc.run_until_epoch(setup, almanac.clone(), sc.epoch() + prop_time, num_runs);
assert_eq!(rslts.runs.len(), num_runs);
// For all of the resulting trajectories, we'll want to compute the percentage of penumbra and umbra.
rslts.to_parquet(
"03_geo_sk.parquet",
Some(vec![
&EclipseLocator::cislunar(almanac.clone()).to_penumbra_event()
]),
ExportCfg::default(),
almanac,
)?;
Ok(())
}
More examples
examples/03_geo_analysis/raise.rs (line 55)
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
fn main() -> Result<(), Box<dyn Error>> {
pel::init();
// Dynamics models require planetary constants and ephemerides to be defined.
// Let's start by grabbing those by using ANISE's latest MetaAlmanac.
// This will automatically download the DE440s planetary ephemeris,
// the daily-updated Earth Orientation Parameters, the high fidelity Moon orientation
// parameters (for the Moon Mean Earth and Moon Principal Axes frames), and the PCK11
// planetary constants kernels.
// For details, refer to https://github.com/nyx-space/anise/blob/master/data/latest.dhall.
// Note that we place the Almanac into an Arc so we can clone it cheaply and provide read-only
// references to many functions.
let almanac = Arc::new(MetaAlmanac::latest().map_err(Box::new)?);
// Fetch the EME2000 frame from the Almabac
let eme2k = almanac.frame_from_uid(EARTH_J2000).unwrap();
// Define the orbit epoch
let epoch = Epoch::from_gregorian_utc_hms(2024, 2, 29, 12, 13, 14);
// Build the spacecraft itself.
// Using slide 6 of https://aerospace.org/sites/default/files/2018-11/Davis-Mayberry_HPSEP_11212018.pdf
// for the "next gen" SEP characteristics.
// GTO start
let orbit = Orbit::keplerian(24505.9, 0.725, 7.05, 0.0, 0.0, 0.0, epoch, eme2k);
let sc = Spacecraft::builder()
.orbit(orbit)
.mass(Mass::from_dry_and_prop_masses(1000.0, 1000.0)) // 1000 kg of dry mass and prop, totalling 2.0 tons
.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
.thruster(Thruster {
// "NEXT-STEP" row in Table 2
isp_s: 4435.0,
thrust_N: 0.472,
})
.mode(GuidanceMode::Thrust) // Start thrusting immediately.
.build();
let prop_time = 180.0 * Unit::Day;
// Define the guidance law -- we're just using a Ruggiero controller as demonstrated in AAS-2004-5089.
let objectives = &[
Objective::within_tolerance(StateParameter::SMA, 42_165.0, 20.0),
Objective::within_tolerance(StateParameter::Eccentricity, 0.001, 5e-5),
Objective::within_tolerance(StateParameter::Inclination, 0.05, 1e-2),
];
// Ensure that we only thrust if we have more than 20% illumination.
let ruggiero_ctrl = Ruggiero::from_max_eclipse(objectives, sc, 0.2).unwrap();
println!("{ruggiero_ctrl}");
// Define the high fidelity dynamics
// Set up the spacecraft dynamics.
// Specify that the orbital dynamics must account for the graviational pull of the Moon and the Sun.
// The gravity of the Earth will also be accounted for since the spaceraft in an Earth orbit.
let mut orbital_dyn = OrbitalDynamics::point_masses(vec![MOON, SUN]);
// We want to include the spherical harmonics, so let's download the gravitational data from the Nyx Cloud.
// We're using the JGM3 model here, which is the default in GMAT.
let mut jgm3_meta = MetaFile {
uri: "http://public-data.nyxspace.com/nyx/models/JGM3.cof.gz".to_string(),
crc32: Some(0xF446F027), // Specifying the CRC32 avoids redownloading it if it's cached.
};
// And let's download it if we don't have it yet.
jgm3_meta.process(true)?;
// Build the spherical harmonics.
// The harmonics must be computed in the body fixed frame.
// We're using the long term prediction of the Earth centered Earth fixed frame, IAU Earth.
let harmonics = Harmonics::from_stor(
almanac.frame_from_uid(IAU_EARTH_FRAME)?,
HarmonicsMem::from_cof(&jgm3_meta.uri, 8, 8, true).unwrap(),
);
// Include the spherical harmonics into the orbital dynamics.
orbital_dyn.accel_models.push(harmonics);
// We define the solar radiation pressure, using the default solar flux and accounting only
// for the eclipsing caused by the Earth.
let srp_dyn = SolarPressure::default(EARTH_J2000, almanac.clone())?;
// Finalize setting up the dynamics, specifying the force models (orbital_dyn) separately from the
// acceleration models (SRP in this case). Use `from_models` to specify multiple accel models.
let sc_dynamics = SpacecraftDynamics::from_model(orbital_dyn, srp_dyn)
.with_guidance_law(ruggiero_ctrl.clone());
println!("{:x}", orbit);
// We specify a minimum step in the propagator because the Ruggiero control would otherwise drive this step very low.
let (final_state, traj) = Propagator::rk89(
sc_dynamics.clone(),
IntegratorOptions::builder()
.min_step(10.0_f64.seconds())
.error_ctrl(ErrorControl::RSSCartesianStep)
.build(),
)
.with(sc, almanac.clone())
.for_duration_with_traj(prop_time)?;
let prop_usage = sc.mass.prop_mass_kg - final_state.mass.prop_mass_kg;
println!("{:x}", final_state.orbit);
println!("prop usage: {:.3} kg", prop_usage);
// Finally, export the results for analysis, including the penumbra percentage throughout the orbit raise.
traj.to_parquet(
"./03_geo_raise.parquet",
Some(vec![
&EclipseLocator::cislunar(almanac.clone()).to_penumbra_event()
]),
ExportCfg::default(),
almanac,
)?;
for status_line in ruggiero_ctrl.status(&final_state) {
println!("{status_line}");
}
ruggiero_ctrl
.achieved(&final_state)
.expect("objective not achieved");
Ok(())
}
Trait Implementations§
§impl<'de> Deserialize<'de> for SRPData
impl<'de> Deserialize<'de> for SRPData
§fn deserialize<__D>(
__deserializer: __D,
) -> Result<SRPData, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(
__deserializer: __D,
) -> Result<SRPData, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
Deserialize this value from the given Serde deserializer. Read more
§impl Encode for SRPData
impl Encode for SRPData
§fn encoded_len(&self) -> Result<Length, Error>
fn encoded_len(&self) -> Result<Length, Error>
Compute the length of this value in bytes when encoded as ASN.1 DER.
§fn encode(&self, encoder: &mut impl Writer) -> Result<(), Error>
fn encode(&self, encoder: &mut impl Writer) -> Result<(), Error>
Encode this value as ASN.1 DER using the provided [
Writer
].§fn encode_to_slice<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8], Error>
fn encode_to_slice<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8], Error>
Encode this value to the provided byte slice, returning a sub-slice
containing the encoded message.
§fn encode_to_vec(&self, buf: &mut Vec<u8>) -> Result<Length, Error>
fn encode_to_vec(&self, buf: &mut Vec<u8>) -> Result<Length, Error>
Encode this message as ASN.1 DER, appending it to the provided
byte vector.
§impl Serialize for SRPData
impl Serialize for SRPData
§fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
Serialize this value into the given Serde serializer. Read more
impl Copy for SRPData
impl StructuralPartialEq for SRPData
Auto Trait Implementations§
impl Freeze for SRPData
impl RefUnwindSafe for SRPData
impl Send for SRPData
impl Sync for SRPData
impl Unpin for SRPData
impl UnwindSafe for SRPData
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
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> FromDhall for Twhere
T: DeserializeOwned,
impl<T> FromDhall for Twhere
T: DeserializeOwned,
fn from_dhall(v: &Value) -> Result<T, Error>
§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>
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 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>
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
impl<T> Pointable for T
§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
The inverse inclusion map: attempts to construct
self
from the equivalent element of its
superset. Read more§fn is_in_subset(&self) -> bool
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
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
fn from_subset(element: &SS) -> SP
The inclusion map: converts
self
to the equivalent element of its superset.