1use super::{
20 ra_dec_from_unit_vector, GuidanceError, GuidanceLaw, GuidancePhysicsSnafu, LocalFrame,
21};
22use crate::cosmic::{GuidanceMode, Spacecraft};
23use crate::dynamics::guidance::unit_vector_from_ra_dec;
24use crate::linalg::Vector3;
25use crate::polyfit::CommonPolynomial;
26use crate::time::{Epoch, Unit};
27use crate::State;
28use anise::prelude::Almanac;
29use hifitime::{Duration, TimeUnits};
30use serde::{Deserialize, Serialize};
31use serde_dhall::{SimpleType, StaticType};
32use snafu::ResultExt;
33use std::collections::HashMap;
34use std::fmt;
35use std::sync::Arc;
36
37#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
40pub struct ImpulsiveManeuver {
41 pub local_frame: LocalFrame,
42 pub dv_km_s: Vector3<f64>,
43}
44
45impl fmt::Display for ImpulsiveManeuver {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 write!(f, "{:.3} m/s in {:?}", self.dv_km_s * 1e3, self.local_frame)
48 }
49}
50
51impl StaticType for ImpulsiveManeuver {
52 fn static_type() -> serde_dhall::SimpleType {
53 let mut fields = HashMap::new();
54
55 let mut dv_rcrd = HashMap::new();
56 dv_rcrd.insert("_1".to_string(), f64::static_type());
57 dv_rcrd.insert("_2".to_string(), f64::static_type());
58 dv_rcrd.insert("_3".to_string(), f64::static_type());
59
60 fields.insert("local_frame".to_string(), LocalFrame::static_type());
61 fields.insert("dv_km_s".to_string(), SimpleType::Record(dv_rcrd));
62
63 SimpleType::Record(fields)
64 }
65}
66
67#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
70pub struct Maneuver {
71 pub start: Epoch,
73 pub end: Epoch,
75 pub thrust_prct: f64,
79 pub representation: MnvrRepr,
81 pub frame: LocalFrame,
83}
84
85impl fmt::Display for Maneuver {
86 #[allow(clippy::identity_op)]
88 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89 if self.end != self.start {
90 let start_vec = self.vector(self.start);
91 let end_vec = self.vector(self.end);
92 write!(
93 f,
94 "Finite burn maneuver @ {:.2}% on {} for {} (ending on {})",
95 100.0 * self.thrust_prct,
96 self.start,
97 self.end - self.start,
98 self.end,
99 )?;
100 write!(f, "\n{}", self.representation)?;
101 write!(
102 f,
103 "\n\tinitial dir: [{:.6}, {:.6}, {:.6}]\n\tfinal dir : [{:.6}, {:.6}, {:.6}]",
104 start_vec[0], start_vec[1], start_vec[2], end_vec[0], end_vec[1], end_vec[2]
105 )
106 } else {
107 write!(
108 f,
109 "Impulsive maneuver @ {}\n{}",
110 self.start, self.representation
111 )
112 }
113 }
114}
115
116impl StaticType for Maneuver {
117 fn static_type() -> SimpleType {
118 let mut fields = HashMap::new();
119
120 fields.insert("start".to_string(), SimpleType::Text);
121 fields.insert("end".to_string(), SimpleType::Text);
122 fields.insert("thrust_prct".to_string(), SimpleType::Double);
123 fields.insert("representation".to_string(), MnvrRepr::static_type());
124 fields.insert("frame".to_string(), LocalFrame::static_type());
125
126 SimpleType::Record(fields)
127 }
128}
129
130#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
132pub enum MnvrRepr {
133 Vector(Vector3<f64>),
135 Angles {
137 azimuth: CommonPolynomial,
138 elevation: CommonPolynomial,
139 },
140}
141
142impl fmt::Display for MnvrRepr {
143 #[allow(clippy::identity_op)]
145 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146 match self {
147 MnvrRepr::Vector(vector) => write!(f, "{vector}"),
148 MnvrRepr::Angles { azimuth, elevation } => write!(
149 f,
150 "\tazimuth (in-plane) α: {azimuth}\n\televation (out-of-plane) β: {elevation}"
151 ),
152 }
153 }
154}
155
156impl StaticType for MnvrRepr {
157 fn static_type() -> SimpleType {
158 let mut variants = HashMap::new();
159
160 let mut vec_repr = HashMap::new();
161
162 vec_repr.insert("_1".to_string(), f64::static_type());
163 vec_repr.insert("_2".to_string(), f64::static_type());
164 vec_repr.insert("_3".to_string(), f64::static_type());
165
166 variants.insert("Vector".to_string(), Some(SimpleType::Record(vec_repr)));
167
168 let mut angles_fields = HashMap::new();
170 angles_fields.insert("azimuth".to_string(), CommonPolynomial::static_type());
171 angles_fields.insert("elevation".to_string(), CommonPolynomial::static_type());
172
173 variants.insert(
174 "Angles".to_string(),
175 Some(SimpleType::Record(angles_fields)),
176 );
177
178 SimpleType::Union(variants)
179 }
180}
181
182impl Maneuver {
183 pub fn from_impulsive(dt: Epoch, vector: Vector3<f64>, frame: LocalFrame) -> Self {
186 Self::from_time_invariant(dt, dt + Unit::Millisecond, 1.0, vector, frame)
187 }
188
189 pub fn from_time_invariant(
191 start: Epoch,
192 end: Epoch,
193 thrust_lvl: f64,
194 vector: Vector3<f64>,
195 frame: LocalFrame,
196 ) -> Self {
197 Self {
198 start,
199 end,
200 thrust_prct: thrust_lvl,
201 representation: MnvrRepr::Vector(vector),
202 frame,
203 }
204 }
205
206 pub fn vector(&self, epoch: Epoch) -> Vector3<f64> {
208 match self.representation {
209 MnvrRepr::Vector(vector) => vector,
210 MnvrRepr::Angles { azimuth, elevation } => {
211 let t = (epoch - self.start).to_seconds();
212 let alpha = azimuth.eval(t);
213 let delta = elevation.eval(t);
214 unit_vector_from_ra_dec(alpha, delta)
215 }
216 }
217 }
218
219 pub fn duration(&self) -> Duration {
221 self.end - self.start
222 }
223
224 pub fn antichronological(&self) -> bool {
226 self.duration().abs() > 1.microseconds() && self.duration() < 1.microseconds()
227 }
228
229 pub fn direction(&self) -> Vector3<f64> {
231 match self.representation {
232 MnvrRepr::Vector(vector) => vector / vector.norm(),
233 MnvrRepr::Angles { azimuth, elevation } => {
234 let alpha = azimuth.coeff_in_order(0).unwrap();
235 let delta = elevation.coeff_in_order(0).unwrap();
236 unit_vector_from_ra_dec(alpha, delta)
237 }
238 }
239 }
240
241 pub fn set_direction(&mut self, vector: Vector3<f64>) -> Result<(), GuidanceError> {
243 self.set_direction_and_rates(vector, self.rate(), self.accel())
244 }
245
246 pub fn rate(&self) -> Vector3<f64> {
248 match self.representation {
249 MnvrRepr::Vector(_) => Vector3::zeros(),
250 MnvrRepr::Angles { azimuth, elevation } => match azimuth.coeff_in_order(1) {
251 Ok(alpha) => {
252 let delta = elevation.coeff_in_order(1).unwrap();
253 unit_vector_from_ra_dec(alpha, delta)
254 }
255 Err(_) => Vector3::zeros(),
256 },
257 }
258 }
259
260 pub fn set_rate(&mut self, rate: Vector3<f64>) -> Result<(), GuidanceError> {
262 self.set_direction_and_rates(self.direction(), rate, self.accel())
263 }
264
265 pub fn accel(&self) -> Vector3<f64> {
267 match self.representation {
268 MnvrRepr::Vector(_) => Vector3::zeros(),
269 MnvrRepr::Angles { azimuth, elevation } => match azimuth.coeff_in_order(2) {
270 Ok(alpha) => {
271 let delta = elevation.coeff_in_order(2).unwrap();
272 unit_vector_from_ra_dec(alpha, delta)
273 }
274 Err(_) => Vector3::zeros(),
275 },
276 }
277 }
278
279 pub fn set_accel(&mut self, accel: Vector3<f64>) -> Result<(), GuidanceError> {
281 self.set_direction_and_rates(self.direction(), self.rate(), accel)
282 }
283
284 pub fn set_direction_and_rates(
286 &mut self,
287 dir: Vector3<f64>,
288 rate: Vector3<f64>,
289 accel: Vector3<f64>,
290 ) -> Result<(), GuidanceError> {
291 if rate.norm() < f64::EPSILON && accel.norm() < f64::EPSILON {
292 self.representation = MnvrRepr::Vector(dir)
294 } else {
295 let (alpha, delta) = ra_dec_from_unit_vector(dir);
296 if alpha.is_nan() || delta.is_nan() {
297 return Err(GuidanceError::InvalidDirection {
298 x: dir[0],
299 y: dir[1],
300 z: dir[2],
301 in_plane_deg: alpha.to_degrees(),
302 out_of_plane_deg: delta.to_degrees(),
303 });
304 }
305 if rate.norm() < f64::EPSILON && accel.norm() < f64::EPSILON {
306 self.representation = MnvrRepr::Angles {
307 azimuth: CommonPolynomial::Constant { a: alpha },
308 elevation: CommonPolynomial::Constant { a: delta },
309 };
310 } else {
311 let (alpha_dt, delta_dt) = ra_dec_from_unit_vector(rate);
312 if alpha_dt.is_nan() || delta_dt.is_nan() {
313 return Err(GuidanceError::InvalidRate {
314 x: rate[0],
315 y: rate[1],
316 z: rate[2],
317 in_plane_deg_s: alpha_dt.to_degrees(),
318 out_of_plane_deg_s: delta_dt.to_degrees(),
319 });
320 }
321 if accel.norm() < f64::EPSILON {
322 self.representation = MnvrRepr::Angles {
323 azimuth: CommonPolynomial::Linear {
324 a: alpha_dt,
325 b: alpha,
326 },
327 elevation: CommonPolynomial::Linear {
328 a: delta_dt,
329 b: delta,
330 },
331 };
332 } else {
333 let (alpha_ddt, delta_ddt) = ra_dec_from_unit_vector(accel);
334 if alpha_ddt.is_nan() || delta_ddt.is_nan() {
335 return Err(GuidanceError::InvalidAcceleration {
336 x: accel[0],
337 y: accel[1],
338 z: accel[2],
339 in_plane_deg_s2: alpha_ddt.to_degrees(),
340 out_of_plane_deg_s2: delta_ddt.to_degrees(),
341 });
342 }
343
344 self.representation = MnvrRepr::Angles {
345 azimuth: CommonPolynomial::Quadratic {
346 a: alpha_ddt,
347 b: alpha_dt,
348 c: alpha,
349 },
350 elevation: CommonPolynomial::Quadratic {
351 a: delta_ddt,
352 b: delta_dt,
353 c: delta,
354 },
355 };
356 }
357 }
358 }
359 Ok(())
360 }
361}
362
363impl GuidanceLaw for Maneuver {
364 fn direction(&self, osc: &Spacecraft) -> Result<Vector3<f64>, GuidanceError> {
365 match osc.mode() {
366 GuidanceMode::Thrust => match self.frame {
367 LocalFrame::Inertial => Ok(self.vector(osc.epoch())),
368 _ => Ok(self.frame.dcm_to_inertial(osc.orbit).context({
369 GuidancePhysicsSnafu {
370 action: "computing RCN frame",
371 }
372 })? * self.vector(osc.epoch())),
373 },
374 _ => Ok(Vector3::zeros()),
375 }
376 }
377
378 fn throttle(&self, osc: &Spacecraft) -> Result<f64, GuidanceError> {
379 match osc.mode() {
381 GuidanceMode::Thrust => Ok(self.thrust_prct),
382 _ => {
383 Ok(0.0)
385 }
386 }
387 }
388
389 fn next(&self, sc: &mut Spacecraft, _almanac: Arc<Almanac>) {
390 let next_mode = if sc.epoch() >= self.start && sc.epoch() <= self.end {
391 GuidanceMode::Thrust
392 } else {
393 GuidanceMode::Coast
394 };
395 sc.mut_mode(next_mode);
396 }
397}
398
399#[cfg(test)]
400mod ut_mnvr {
401 use hifitime::Epoch;
402 use nalgebra::Vector3;
403
404 use crate::dynamics::guidance::LocalFrame;
405
406 use super::Maneuver;
407
408 #[test]
409 fn serde_mnvr() {
410 let epoch = Epoch::from_gregorian_utc_at_midnight(2012, 2, 29);
411 let mnvr = Maneuver::from_impulsive(epoch, Vector3::new(1.0, 1.0, 0.0), LocalFrame::RCN);
412
413 let mnvr_yml = serde_yml::to_string(&mnvr).unwrap();
414 println!("{mnvr_yml}");
415
416 let mnvr2 = serde_yml::from_str(&mnvr_yml).unwrap();
417 assert_eq!(mnvr, mnvr2);
418 }
419}