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