use super::{
GuidanceError, GuidanceLaw, GuidancePhysicsSnafu, LocalFrame, ra_dec_from_unit_vector,
};
use crate::State;
use crate::cosmic::{GuidanceMode, Spacecraft};
use crate::dynamics::guidance::unit_vector_from_ra_dec;
use crate::linalg::Vector3;
use crate::polyfit::CommonPolynomial;
use crate::time::{Epoch, Unit};
use anise::prelude::Almanac;
use hifitime::{Duration, TimeUnits};
use serde::{Deserialize, Serialize};
use serde_dhall::{SimpleType, StaticType};
use snafu::ResultExt;
use std::collections::HashMap;
use std::fmt;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ImpulsiveManeuver {
pub local_frame: LocalFrame,
pub dv_km_s: Vector3<f64>,
}
impl fmt::Display for ImpulsiveManeuver {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.3} m/s in {:?}", self.dv_km_s * 1e3, self.local_frame)
}
}
impl StaticType for ImpulsiveManeuver {
fn static_type() -> serde_dhall::SimpleType {
let mut fields = HashMap::new();
let mut dv_rcrd = HashMap::new();
dv_rcrd.insert("_1".to_string(), f64::static_type());
dv_rcrd.insert("_2".to_string(), f64::static_type());
dv_rcrd.insert("_3".to_string(), f64::static_type());
fields.insert("local_frame".to_string(), LocalFrame::static_type());
fields.insert("dv_km_s".to_string(), SimpleType::Record(dv_rcrd));
SimpleType::Record(fields)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Maneuver {
pub start: Epoch,
pub end: Epoch,
pub thrust_prct: f64,
pub representation: MnvrRepr,
pub frame: LocalFrame,
}
impl fmt::Display for Maneuver {
#[allow(clippy::identity_op)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.end != self.start {
let start_vec = self.vector(self.start);
let end_vec = self.vector(self.end);
write!(
f,
"Finite burn maneuver @ {:.2}% on {} for {} (ending on {})",
100.0 * self.thrust_prct,
self.start,
self.end - self.start,
self.end,
)?;
write!(f, "\n{}", self.representation)?;
write!(
f,
"\n\tinitial dir: [{:.6}, {:.6}, {:.6}]\n\tfinal dir : [{:.6}, {:.6}, {:.6}]",
start_vec[0], start_vec[1], start_vec[2], end_vec[0], end_vec[1], end_vec[2]
)
} else {
write!(
f,
"Impulsive maneuver @ {}\n{}",
self.start, self.representation
)
}
}
}
impl StaticType for Maneuver {
fn static_type() -> SimpleType {
let mut fields = HashMap::new();
fields.insert("start".to_string(), SimpleType::Text);
fields.insert("end".to_string(), SimpleType::Text);
fields.insert("thrust_prct".to_string(), SimpleType::Double);
fields.insert("representation".to_string(), MnvrRepr::static_type());
fields.insert("frame".to_string(), LocalFrame::static_type());
SimpleType::Record(fields)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum MnvrRepr {
Vector(Vector3<f64>),
Angles {
azimuth: CommonPolynomial,
elevation: CommonPolynomial,
},
}
impl fmt::Display for MnvrRepr {
#[allow(clippy::identity_op)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MnvrRepr::Vector(vector) => write!(f, "{vector}"),
MnvrRepr::Angles { azimuth, elevation } => write!(
f,
"\tazimuth (in-plane) α: {azimuth}\n\televation (out-of-plane) β: {elevation}"
),
}
}
}
impl StaticType for MnvrRepr {
fn static_type() -> SimpleType {
let mut variants = HashMap::new();
let mut vec_repr = HashMap::new();
vec_repr.insert("_1".to_string(), f64::static_type());
vec_repr.insert("_2".to_string(), f64::static_type());
vec_repr.insert("_3".to_string(), f64::static_type());
variants.insert("Vector".to_string(), Some(SimpleType::Record(vec_repr)));
let mut angles_fields = HashMap::new();
angles_fields.insert("azimuth".to_string(), CommonPolynomial::static_type());
angles_fields.insert("elevation".to_string(), CommonPolynomial::static_type());
variants.insert(
"Angles".to_string(),
Some(SimpleType::Record(angles_fields)),
);
SimpleType::Union(variants)
}
}
impl Maneuver {
pub fn from_impulsive(dt: Epoch, vector: Vector3<f64>, frame: LocalFrame) -> Self {
Self::from_time_invariant(dt, dt + Unit::Millisecond, 1.0, vector, frame)
}
pub fn from_time_invariant(
start: Epoch,
end: Epoch,
thrust_lvl: f64,
vector: Vector3<f64>,
frame: LocalFrame,
) -> Self {
Self {
start,
end,
thrust_prct: thrust_lvl,
representation: MnvrRepr::Vector(vector),
frame,
}
}
pub fn vector(&self, epoch: Epoch) -> Vector3<f64> {
match self.representation {
MnvrRepr::Vector(vector) => vector,
MnvrRepr::Angles { azimuth, elevation } => {
let t = (epoch - self.start).to_seconds();
let alpha = azimuth.eval(t);
let delta = elevation.eval(t);
unit_vector_from_ra_dec(alpha, delta)
}
}
}
pub fn duration(&self) -> Duration {
self.end - self.start
}
pub fn antichronological(&self) -> bool {
self.duration().abs() > 1.microseconds() && self.duration() < 1.microseconds()
}
pub fn direction(&self) -> Vector3<f64> {
match self.representation {
MnvrRepr::Vector(vector) => vector / vector.norm(),
MnvrRepr::Angles { azimuth, elevation } => {
let alpha = azimuth.coeff_in_order(0).unwrap();
let delta = elevation.coeff_in_order(0).unwrap();
unit_vector_from_ra_dec(alpha, delta)
}
}
}
pub fn set_direction(&mut self, vector: Vector3<f64>) -> Result<(), GuidanceError> {
self.set_direction_and_rates(vector, self.rate(), self.accel())
}
pub fn rate(&self) -> Vector3<f64> {
match self.representation {
MnvrRepr::Vector(_) => Vector3::zeros(),
MnvrRepr::Angles { azimuth, elevation } => match azimuth.coeff_in_order(1) {
Ok(alpha) => {
let delta = elevation.coeff_in_order(1).unwrap();
unit_vector_from_ra_dec(alpha, delta)
}
Err(_) => Vector3::zeros(),
},
}
}
pub fn set_rate(&mut self, rate: Vector3<f64>) -> Result<(), GuidanceError> {
self.set_direction_and_rates(self.direction(), rate, self.accel())
}
pub fn accel(&self) -> Vector3<f64> {
match self.representation {
MnvrRepr::Vector(_) => Vector3::zeros(),
MnvrRepr::Angles { azimuth, elevation } => match azimuth.coeff_in_order(2) {
Ok(alpha) => {
let delta = elevation.coeff_in_order(2).unwrap();
unit_vector_from_ra_dec(alpha, delta)
}
Err(_) => Vector3::zeros(),
},
}
}
pub fn set_accel(&mut self, accel: Vector3<f64>) -> Result<(), GuidanceError> {
self.set_direction_and_rates(self.direction(), self.rate(), accel)
}
pub fn set_direction_and_rates(
&mut self,
dir: Vector3<f64>,
rate: Vector3<f64>,
accel: Vector3<f64>,
) -> Result<(), GuidanceError> {
if rate.norm() < f64::EPSILON && accel.norm() < f64::EPSILON {
self.representation = MnvrRepr::Vector(dir)
} else {
let (alpha, delta) = ra_dec_from_unit_vector(dir);
if alpha.is_nan() || delta.is_nan() {
return Err(GuidanceError::InvalidDirection {
x: dir[0],
y: dir[1],
z: dir[2],
in_plane_deg: alpha.to_degrees(),
out_of_plane_deg: delta.to_degrees(),
});
}
if rate.norm() < f64::EPSILON && accel.norm() < f64::EPSILON {
self.representation = MnvrRepr::Angles {
azimuth: CommonPolynomial::Constant { a: alpha },
elevation: CommonPolynomial::Constant { a: delta },
};
} else {
let (alpha_dt, delta_dt) = ra_dec_from_unit_vector(rate);
if alpha_dt.is_nan() || delta_dt.is_nan() {
return Err(GuidanceError::InvalidRate {
x: rate[0],
y: rate[1],
z: rate[2],
in_plane_deg_s: alpha_dt.to_degrees(),
out_of_plane_deg_s: delta_dt.to_degrees(),
});
}
if accel.norm() < f64::EPSILON {
self.representation = MnvrRepr::Angles {
azimuth: CommonPolynomial::Linear {
a: alpha_dt,
b: alpha,
},
elevation: CommonPolynomial::Linear {
a: delta_dt,
b: delta,
},
};
} else {
let (alpha_ddt, delta_ddt) = ra_dec_from_unit_vector(accel);
if alpha_ddt.is_nan() || delta_ddt.is_nan() {
return Err(GuidanceError::InvalidAcceleration {
x: accel[0],
y: accel[1],
z: accel[2],
in_plane_deg_s2: alpha_ddt.to_degrees(),
out_of_plane_deg_s2: delta_ddt.to_degrees(),
});
}
self.representation = MnvrRepr::Angles {
azimuth: CommonPolynomial::Quadratic {
a: alpha_ddt,
b: alpha_dt,
c: alpha,
},
elevation: CommonPolynomial::Quadratic {
a: delta_ddt,
b: delta_dt,
c: delta,
},
};
}
}
}
Ok(())
}
}
impl GuidanceLaw for Maneuver {
fn direction(&self, osc: &Spacecraft) -> Result<Vector3<f64>, GuidanceError> {
match osc.mode() {
GuidanceMode::Thrust => match self.frame {
LocalFrame::Inertial => Ok(self.vector(osc.epoch())),
_ => Ok(osc.orbit.dcm_to_inertial(self.frame).context({
GuidancePhysicsSnafu {
action: "computing RCN frame",
}
})? * self.vector(osc.epoch())),
},
_ => Ok(Vector3::zeros()),
}
}
fn throttle(&self, osc: &Spacecraft) -> Result<f64, GuidanceError> {
match osc.mode() {
GuidanceMode::Thrust => Ok(self.thrust_prct),
_ => {
Ok(0.0)
}
}
}
fn next(&self, sc: &mut Spacecraft, _almanac: &Almanac) {
let next_mode = if sc.epoch() >= self.start && sc.epoch() <= self.end {
GuidanceMode::Thrust
} else {
GuidanceMode::Coast
};
sc.mut_mode(next_mode);
}
}
#[cfg(test)]
mod ut_mnvr {
use hifitime::Epoch;
use nalgebra::Vector3;
use crate::dynamics::guidance::LocalFrame;
use super::Maneuver;
#[test]
fn serde_mnvr() {
let epoch = Epoch::from_gregorian_utc_at_midnight(2012, 2, 29);
let mnvr = Maneuver::from_impulsive(epoch, Vector3::new(1.0, 1.0, 0.0), LocalFrame::RCN);
let mnvr_yml = serde_yml::to_string(&mnvr).unwrap();
println!("{mnvr_yml}");
let mnvr2 = serde_yml::from_str(&mnvr_yml).unwrap();
assert_eq!(mnvr, mnvr2);
}
}