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