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