nyx_space/dynamics/
solarpressure.rs1use super::{DynamicsAlmanacSnafu, DynamicsError, DynamicsPlanetarySnafu, ForceModel};
20use crate::cosmic::eclipse::EclipseLocator;
21use crate::cosmic::{Frame, Spacecraft, AU, SPEED_OF_LIGHT_M_S};
22use crate::linalg::{Const, Matrix4x3, Vector3};
23use anise::almanac::Almanac;
24use anise::constants::frames::SUN_J2000;
25use hyperdual::{hyperspace_from_vector, linalg::norm, Float, OHyperdual};
26use log::warn;
27use snafu::ResultExt;
28use std::fmt;
29use std::sync::Arc;
30
31#[allow(non_upper_case_globals)]
33pub const SOLAR_FLUX_W_m2: f64 = 1367.0;
34
35#[derive(Clone)]
37pub struct SolarPressure {
38 pub phi: f64,
40 pub e_loc: EclipseLocator,
41 pub estimate: bool,
43}
44
45impl SolarPressure {
46 pub fn default_raw(
48 shadow_bodies: Vec<Frame>,
49 almanac: Arc<Almanac>,
50 ) -> Result<Self, DynamicsError> {
51 let e_loc = EclipseLocator {
52 light_source: almanac.frame_from_uid(SUN_J2000).context({
53 DynamicsPlanetarySnafu {
54 action: "planetary data from third body not loaded",
55 }
56 })?,
57 shadow_bodies: shadow_bodies
58 .iter()
59 .filter_map(|object| match almanac.frame_from_uid(object) {
60 Ok(loaded_obj) => Some(loaded_obj),
61 Err(e) => {
62 warn!("when initializing SRP model for {object}, {e}");
63 None
64 }
65 })
66 .collect(),
67 };
68 Ok(Self {
69 phi: SOLAR_FLUX_W_m2,
70 e_loc,
71 estimate: true,
72 })
73 }
74
75 pub fn default(shadow_body: Frame, almanac: Arc<Almanac>) -> Result<Arc<Self>, DynamicsError> {
77 Ok(Arc::new(Self::default_raw(vec![shadow_body], almanac)?))
78 }
79
80 pub fn default_no_estimation(
82 shadow_bodies: Vec<Frame>,
83 almanac: Arc<Almanac>,
84 ) -> Result<Arc<Self>, DynamicsError> {
85 let mut srp = Self::default_raw(shadow_bodies, almanac)?;
86 srp.estimate = false;
87 Ok(Arc::new(srp))
88 }
89
90 pub fn with_flux(
92 flux_w_m2: f64,
93 shadow_bodies: Vec<Frame>,
94 almanac: Arc<Almanac>,
95 ) -> Result<Arc<Self>, DynamicsError> {
96 let mut me = Self::default_raw(shadow_bodies, almanac)?;
97 me.phi = flux_w_m2;
98 Ok(Arc::new(me))
99 }
100
101 pub fn new(
103 shadow_bodies: Vec<Frame>,
104 almanac: Arc<Almanac>,
105 ) -> Result<Arc<Self>, DynamicsError> {
106 Ok(Arc::new(Self::default_raw(shadow_bodies, almanac)?))
107 }
108}
109
110impl ForceModel for SolarPressure {
111 fn estimation_index(&self) -> Option<usize> {
112 if self.estimate {
113 Some(6)
114 } else {
115 None
116 }
117 }
118
119 fn eom(&self, ctx: &Spacecraft, almanac: Arc<Almanac>) -> Result<Vector3<f64>, DynamicsError> {
120 let osc = ctx.orbit;
121 let r_sun = almanac
123 .transform_to(ctx.orbit, self.e_loc.light_source, None)
124 .context(DynamicsAlmanacSnafu {
125 action: "transforming state to vector seen from Sun",
126 })?
127 .radius_km;
128
129 let r_sun_unit = r_sun / r_sun.norm();
130
131 let occult = self
133 .e_loc
134 .compute(osc, almanac)
135 .context(DynamicsAlmanacSnafu {
136 action: "solar radiation pressure computation",
137 })?
138 .factor();
139
140 let k: f64 = (occult - 1.0).abs();
142
143 let r_sun_au = r_sun.norm() / AU;
144 let flux_pressure = (k * self.phi / SPEED_OF_LIGHT_M_S) * (1.0 / r_sun_au).powi(2);
146
147 Ok(1e-3 * ctx.srp.coeff_reflectivity * ctx.srp.area_m2 * flux_pressure * r_sun_unit)
149 }
150
151 fn dual_eom(
152 &self,
153 ctx: &Spacecraft,
154 almanac: Arc<Almanac>,
155 ) -> Result<(Vector3<f64>, Matrix4x3<f64>), DynamicsError> {
156 let osc = ctx.orbit;
157
158 let r_sun = almanac
160 .transform_to(ctx.orbit, self.e_loc.light_source, None)
161 .context(DynamicsAlmanacSnafu {
162 action: "transforming state to vector seen from Sun",
163 })?
164 .radius_km;
165
166 let r_sun_d: Vector3<OHyperdual<f64, Const<9>>> = hyperspace_from_vector(&r_sun);
167 let r_sun_unit = r_sun_d / norm(&r_sun_d);
168
169 let occult = self
171 .e_loc
172 .compute(osc, almanac.clone())
173 .context(DynamicsAlmanacSnafu {
174 action: "solar radiation pressure computation",
175 })?
176 .factor();
177
178 let k: f64 = (occult - 1.0).abs();
180
181 let r_sun_au = norm(&r_sun_d) / AU;
182 let inv_r_sun_au = OHyperdual::<f64, Const<9>>::from_real(1.0) / (r_sun_au);
183 let inv_r_sun_au_p2 = inv_r_sun_au.powi(2);
184 let flux_pressure =
186 OHyperdual::<f64, Const<9>>::from_real(k * self.phi / SPEED_OF_LIGHT_M_S)
187 * inv_r_sun_au_p2;
188
189 let dual_force_scalar = OHyperdual::<f64, Const<9>>::from_real(
191 1e-3 * ctx.srp.coeff_reflectivity * ctx.srp.area_m2,
192 );
193 let mut dual_force: Vector3<OHyperdual<f64, Const<9>>> = Vector3::zeros();
194 dual_force[0] = dual_force_scalar * flux_pressure * r_sun_unit[0];
195 dual_force[1] = dual_force_scalar * flux_pressure * r_sun_unit[1];
196 dual_force[2] = dual_force_scalar * flux_pressure * r_sun_unit[2];
197
198 let mut dx = Vector3::zeros();
200 let mut grad = Matrix4x3::zeros();
201 for i in 0..3 {
202 dx[i] += dual_force[i].real();
203 for j in 0..3 {
205 grad[(i, j)] += dual_force[i][j + 1];
206 }
207 }
208
209 let wrt_cr = self.eom(ctx, almanac)? / ctx.srp.coeff_reflectivity;
211 for j in 0..3 {
212 grad[(3, j)] = wrt_cr[j];
213 }
214
215 Ok((dx, grad))
216 }
217}
218
219impl fmt::Display for SolarPressure {
220 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
221 write!(
222 f,
223 "SRP with φ = {} W/m^2 and eclipse {}",
224 self.phi, self.e_loc
225 )
226 }
227}