nyx_space/dynamics/guidance/
mod.rs1use crate::cosmic::{GuidanceMode, Orbit, Spacecraft, STD_GRAVITY};
20use crate::errors::{NyxError, StateError};
21use crate::linalg::Vector3;
22use anise::astro::PhysicsResult;
23use anise::errors::PhysicsError;
24use anise::math::rotation::DCM;
25use anise::prelude::Almanac;
26use serde::{Deserialize, Serialize};
27
28mod finiteburns;
29pub use finiteburns::FiniteBurns;
30
31mod mnvr;
32pub use mnvr::{Maneuver, MnvrRepr};
33
34mod ruggiero;
35pub use ruggiero::{Objective, Ruggiero, StateParameter};
36use snafu::Snafu;
37
38use std::fmt;
39use std::sync::Arc;
40
41#[allow(non_snake_case)]
43#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
44pub struct Thruster {
45 pub thrust_N: f64,
47 pub isp_s: f64,
49}
50
51impl Thruster {
52 pub fn exhaust_velocity_m_s(&self) -> f64 {
54 self.isp_s * STD_GRAVITY
55 }
56}
57
58pub trait GuidanceLaw: fmt::Display + Send + Sync {
62 fn direction(&self, osc_state: &Spacecraft) -> Result<Vector3<f64>, GuidanceError>;
64
65 fn throttle(&self, osc_state: &Spacecraft) -> Result<f64, GuidanceError>;
68
69 fn next(&self, next_state: &mut Spacecraft, almanac: Arc<Almanac>);
71
72 fn achieved(&self, _osc_state: &Spacecraft) -> Result<bool, GuidanceError> {
74 Err(GuidanceError::NoGuidanceObjectiveDefined)
75 }
76}
77
78fn unit_vector_from_plane_angles(alpha: f64, beta: f64) -> Vector3<f64> {
80 Vector3::new(
81 alpha.sin() * beta.cos(),
82 alpha.cos() * beta.cos(),
83 beta.sin(),
84 )
85}
86
87pub fn plane_angles_from_unit_vector(vhat: Vector3<f64>) -> (f64, f64) {
89 (vhat[1].atan2(vhat[0]), vhat[2].asin())
90}
91
92pub(crate) fn unit_vector_from_ra_dec(alpha: f64, delta: f64) -> Vector3<f64> {
94 Vector3::new(
95 delta.cos() * alpha.cos(),
96 delta.cos() * alpha.sin(),
97 delta.sin(),
98 )
99}
100
101pub(crate) fn ra_dec_from_unit_vector(vhat: Vector3<f64>) -> (f64, f64) {
103 let alpha = vhat[1].atan2(vhat[0]);
104 let delta = vhat[2].asin();
105 (alpha, delta)
106}
107
108#[derive(Debug, PartialEq, Snafu)]
109pub enum GuidanceError {
110 #[snafu(display("No thruster attached to spacecraft"))]
111 NoThrustersDefined,
112 #[snafu(display("Throttle is not between 0.0 and 1.0: {ratio}"))]
113 ThrottleRatio { ratio: f64 },
114 #[snafu(display("Invalid finite burn control direction u = [{x}, {y}, {z}] => i-plane = {in_plane_deg} deg, Delta = {out_of_plane_deg} deg",))]
115 InvalidDirection {
116 x: f64,
117 y: f64,
118 z: f64,
119 in_plane_deg: f64,
120 out_of_plane_deg: f64,
121 },
122 #[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",))]
123 InvalidRate {
124 x: f64,
125 y: f64,
126 z: f64,
127 in_plane_deg_s: f64,
128 out_of_plane_deg_s: f64,
129 },
130 #[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",))]
131 InvalidAcceleration {
132 x: f64,
133 y: f64,
134 z: f64,
135 in_plane_deg_s2: f64,
136 out_of_plane_deg_s2: f64,
137 },
138 #[snafu(display("when {action} encountered {source}"))]
139 GuidancePhysicsError {
140 action: &'static str,
141 source: PhysicsError,
142 },
143 #[snafu(display(
144 "An objective based analysis or control was attempted, but no objective was defined"
145 ))]
146 NoGuidanceObjectiveDefined,
147 #[snafu(display("{param} is not a control variable in this guidance law"))]
148 InvalidControl { param: StateParameter },
149 #[snafu(display("guidance encountered {source}"))]
150 GuidState { source: StateError },
151}
152
153#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
155pub enum LocalFrame {
156 Inertial,
157 RIC,
158 VNC,
159 RCN,
160}
161
162impl LocalFrame {
163 pub fn dcm_to_inertial(&self, state: Orbit) -> PhysicsResult<DCM> {
164 match self {
165 LocalFrame::Inertial => Ok(DCM::identity(
166 state.frame.orientation_id,
167 state.frame.orientation_id,
168 )),
169 LocalFrame::RIC => state.dcm_from_ric_to_inertial(),
170 LocalFrame::VNC => state.dcm_from_vnc_to_inertial(),
171 LocalFrame::RCN => state.dcm_from_rcn_to_inertial(),
172 }
173 }
174}
175
176#[test]
177fn ra_dec_from_vec() {
178 use std::f64::consts::{FRAC_PI_2, PI, TAU};
179 let mut delta = -FRAC_PI_2;
180 let mut alpha = 0.0;
181 loop {
182 loop {
183 let unit_v = unit_vector_from_ra_dec(alpha, delta);
184 let (alpha2, delta2) = ra_dec_from_unit_vector(unit_v);
185 assert!((alpha - alpha2).abs() < f64::EPSILON);
186 assert!((delta - delta2).abs() < f64::EPSILON);
187 alpha += TAU * 0.1; if alpha > PI {
189 alpha = 0.0;
190 break;
191 }
192 }
193 delta += TAU * 0.1; if delta > FRAC_PI_2 {
195 break;
196 }
197 }
198}