use crate::cosmic::{GuidanceMode, Orbit, STD_GRAVITY, Spacecraft};
use crate::errors::{NyxError, StateError};
use crate::linalg::Vector3;
pub use anise::ephemerides::ephemeris::LocalFrame;
use anise::errors::PhysicsError;
use anise::prelude::Almanac;
use der::{Decode, Encode, Reader};
use serde::{Deserialize, Serialize};
use serde_dhall::StaticType;
pub mod mnvr;
pub use mnvr::{Maneuver, MnvrRepr};
mod replay;
pub use replay::ThrustDirectionReplay;
mod ruggiero;
pub use ruggiero::{Objective, Ruggiero, StateParameter};
mod kluever;
pub use kluever::Kluever;
use snafu::Snafu;
use std::fmt;
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[allow(non_snake_case)]
#[cfg_attr(feature = "python", pyclass(from_py_object, get_all, set_all))]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, StaticType)]
pub struct Thruster {
pub thrust_N: f64,
pub isp_s: f64,
}
#[cfg_attr(feature = "python", pymethods)]
impl Thruster {
pub fn exhaust_velocity_m_s(&self) -> f64 {
self.isp_s * STD_GRAVITY
}
}
#[cfg(feature = "python")]
#[cfg_attr(feature = "python", pymethods)]
impl Thruster {
#[allow(non_snake_case)]
#[new]
fn py_new(thrust_N: f64, isp_s: f64) -> Self {
Self { thrust_N, isp_s }
}
}
impl Encode for Thruster {
fn encoded_len(&self) -> der::Result<der::Length> {
self.thrust_N.encoded_len()? + self.isp_s.encoded_len()?
}
fn encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
self.thrust_N.encode(encoder)?;
self.isp_s.encode(encoder)
}
}
impl<'a> Decode<'a> for Thruster {
fn decode<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
Ok(Self {
thrust_N: decoder.decode()?,
isp_s: decoder.decode()?,
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize, StaticType)]
pub struct ObjectiveEfficiency {
pub objective: Objective,
pub efficiency: f64,
}
#[derive(Clone, Debug, Serialize, Deserialize, StaticType)]
pub struct ObjectiveWeight {
pub objective: Objective,
pub weight: f64,
}
pub trait GuidanceLaw: fmt::Display + Send + Sync {
fn direction(&self, osc_state: &Spacecraft) -> Result<Vector3<f64>, GuidanceError>;
fn throttle(&self, osc_state: &Spacecraft) -> Result<f64, GuidanceError>;
fn next(&self, next_state: &mut Spacecraft, almanac: &Almanac);
fn achieved(&self, _osc_state: &Spacecraft) -> Result<bool, GuidanceError> {
Err(GuidanceError::NoGuidanceObjectiveDefined)
}
}
fn unit_vector_from_plane_angles(alpha: f64, beta: f64) -> Vector3<f64> {
Vector3::new(
alpha.sin() * beta.cos(),
alpha.cos() * beta.cos(),
beta.sin(),
)
}
pub fn plane_angles_from_unit_vector(vhat: Vector3<f64>) -> (f64, f64) {
(vhat[1].atan2(vhat[0]), vhat[2].asin())
}
pub(crate) fn unit_vector_from_ra_dec(alpha: f64, delta: f64) -> Vector3<f64> {
Vector3::new(
delta.cos() * alpha.cos(),
delta.cos() * alpha.sin(),
delta.sin(),
)
}
pub(crate) fn ra_dec_from_unit_vector(vhat: Vector3<f64>) -> (f64, f64) {
let alpha = vhat[1].atan2(vhat[0]);
let delta = vhat[2].asin();
(alpha, delta)
}
#[derive(Debug, PartialEq, Snafu)]
pub enum GuidanceError {
#[snafu(display("No thruster attached to spacecraft"))]
NoThrustersDefined,
#[snafu(display("Throttle is not between 0.0 and 1.0: {ratio}"))]
ThrottleRatio { ratio: f64 },
#[snafu(display(
"Invalid finite burn control direction u = [{x}, {y}, {z}] => i-plane = {in_plane_deg} deg, Delta = {out_of_plane_deg} deg",
))]
InvalidDirection {
x: f64,
y: f64,
z: f64,
in_plane_deg: f64,
out_of_plane_deg: f64,
},
#[snafu(display(
"Invalid finite burn control rate u = [{x}, {y}, {z}] => in-plane = {in_plane_deg_s} deg/s, out of plane = {out_of_plane_deg_s} deg/s",
))]
InvalidRate {
x: f64,
y: f64,
z: f64,
in_plane_deg_s: f64,
out_of_plane_deg_s: f64,
},
#[snafu(display(
"Invalid finite burn control acceleration u = [{x}, {y}, {z}] => in-plane = {in_plane_deg_s2} deg/s^2, out of plane = {out_of_plane_deg_s2} deg/s^2",
))]
InvalidAcceleration {
x: f64,
y: f64,
z: f64,
in_plane_deg_s2: f64,
out_of_plane_deg_s2: f64,
},
#[snafu(display("when {action} encountered {source}"))]
GuidancePhysicsError {
action: &'static str,
source: PhysicsError,
},
#[snafu(display(
"An objective based analysis or control was attempted, but no objective was defined"
))]
NoGuidanceObjectiveDefined,
#[snafu(display("{param} is not a control variable in this guidance law"))]
InvalidControl { param: StateParameter },
#[snafu(display("guidance encountered {source}"))]
GuidState { source: StateError },
}
#[test]
fn ra_dec_from_vec() {
use std::f64::consts::{FRAC_PI_2, PI, TAU};
let mut delta = -FRAC_PI_2;
let mut alpha = 0.0;
loop {
loop {
let unit_v = unit_vector_from_ra_dec(alpha, delta);
let (alpha2, delta2) = ra_dec_from_unit_vector(unit_v);
assert!((alpha - alpha2).abs() < f64::EPSILON);
assert!((delta - delta2).abs() < f64::EPSILON);
alpha += TAU * 0.1; if alpha > PI {
alpha = 0.0;
break;
}
}
delta += TAU * 0.1; if delta > FRAC_PI_2 {
break;
}
}
}