nyx_space/md/opti/
solution.rs1use hifitime::TimeUnits;
20use snafu::{ensure, ResultExt};
21
22use crate::dynamics::guidance::{LocalFrame, Maneuver, MnvrRepr};
23use crate::linalg::SVector;
24use crate::md::objective::Objective;
25use crate::md::{prelude::*, GuidanceSnafu, NotFiniteSnafu, TargetingError};
26pub use crate::md::{Variable, Vary};
27use crate::polyfit::CommonPolynomial;
28use std::fmt;
29use std::time::Duration;
30
31#[derive(Clone, Debug)]
33pub struct TargeterSolution<const V: usize, const O: usize> {
34 pub corrected_state: Spacecraft,
36 pub achieved_state: Spacecraft,
38 pub correction: SVector<f64, V>,
40 pub variables: [Variable; V],
42 pub achieved_errors: SVector<f64, O>,
44 pub achieved_objectives: [Objective; O],
46 pub iterations: usize,
48 pub computation_dur: Duration,
50}
51
52impl<const V: usize, const O: usize> TargeterSolution<V, O> {
53 pub fn is_finite_burn(&self) -> bool {
55 for var in &self.variables {
56 if var.component.is_finite_burn() {
57 return true;
58 }
59 }
60 false
61 }
62
63 pub fn to_mnvr(&self) -> Result<Maneuver, TargetingError> {
65 ensure!(self.is_finite_burn(), NotFiniteSnafu);
66
67 let correction_epoch = self.corrected_state.epoch();
68 let achievement_epoch = self.achieved_state.epoch();
69 let mut mnvr = Maneuver {
70 start: correction_epoch,
71 end: achievement_epoch,
72 thrust_prct: 1.0,
73 representation: MnvrRepr::Angles {
74 azimuth: CommonPolynomial::Quadratic {
75 a: 0.0,
76 b: 0.0,
77 c: 0.0,
78 },
79 elevation: CommonPolynomial::Quadratic {
80 a: 0.0,
81 b: 0.0,
82 c: 0.0,
83 },
84 },
85 frame: LocalFrame::RCN,
86 };
87
88 for (i, var) in self.variables.iter().enumerate() {
89 let corr = self.correction[i];
90
91 match var.component {
93 Vary::Duration => {
94 if corr.abs() > 1e-3 {
95 let init_duration_s = (correction_epoch - achievement_epoch).to_seconds();
97 let acceptable_corr = var.apply_bounds(init_duration_s).seconds();
98 mnvr.end = mnvr.start + acceptable_corr;
99 }
100 }
101 Vary::EndEpoch => {
102 if corr.abs() > 1e-3 {
103 let total_end_corr =
105 (mnvr.end + corr.seconds() - achievement_epoch).to_seconds();
106 let acceptable_corr = var.apply_bounds(total_end_corr).seconds();
107 mnvr.end += acceptable_corr;
108 }
109 }
110 Vary::StartEpoch => {
111 if corr.abs() > 1e-3 {
112 let total_start_corr =
114 (mnvr.start + corr.seconds() - correction_epoch).to_seconds();
115 let acceptable_corr = var.apply_bounds(total_start_corr).seconds();
116 mnvr.end += acceptable_corr;
117
118 mnvr.start += corr.seconds()
119 }
120 }
121 Vary::MnvrAlpha | Vary::MnvrAlphaDot | Vary::MnvrAlphaDDot => {
122 match mnvr.representation {
123 MnvrRepr::Angles { azimuth, elevation } => {
124 let azimuth = azimuth
125 .add_val_in_order(corr, var.component.vec_index())
126 .unwrap();
127 mnvr.representation = MnvrRepr::Angles { azimuth, elevation };
128 }
129 _ => unreachable!(),
130 };
131 }
132 Vary::MnvrDelta | Vary::MnvrDeltaDot | Vary::MnvrDeltaDDot => {
133 match mnvr.representation {
134 MnvrRepr::Angles { azimuth, elevation } => {
135 let elevation = elevation
136 .add_val_in_order(corr, var.component.vec_index())
137 .unwrap();
138 mnvr.representation = MnvrRepr::Angles { azimuth, elevation };
139 }
140 _ => unreachable!(),
141 };
142 }
143 Vary::ThrustX | Vary::ThrustY | Vary::ThrustZ => {
144 let mut vector = mnvr.direction();
145 vector[var.component.vec_index()] += corr;
146 var.ensure_bounds(&mut vector[var.component.vec_index()]);
147 mnvr.set_direction(vector).context(GuidanceSnafu)?;
148 }
149 Vary::ThrustRateX | Vary::ThrustRateY | Vary::ThrustRateZ => {
150 let mut vector = mnvr.rate();
151 let idx = (var.component.vec_index() - 1) % 3;
152 vector[idx] += corr;
153 var.ensure_bounds(&mut vector[idx]);
154 mnvr.set_rate(vector).context(GuidanceSnafu)?;
155 }
156 Vary::ThrustAccelX | Vary::ThrustAccelY | Vary::ThrustAccelZ => {
157 let mut vector = mnvr.accel();
158 let idx = (var.component.vec_index() - 1) % 3;
159 vector[idx] += corr;
160 var.ensure_bounds(&mut vector[idx]);
161 mnvr.set_accel(vector).context(GuidanceSnafu)?;
162 }
163 Vary::ThrustLevel => {
164 mnvr.thrust_prct += corr;
165 var.ensure_bounds(&mut mnvr.thrust_prct);
166 }
167 _ => unreachable!(),
168 }
169 }
170
171 Ok(mnvr)
172 }
173}
174
175impl<const V: usize, const O: usize> fmt::Display for TargeterSolution<V, O> {
176 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177 let mut objmsg = String::from("");
178 for (i, obj) in self.achieved_objectives.iter().enumerate() {
179 objmsg.push_str(&format!(
180 "\n\t\t{:?} = {:.3} (wanted {:.3} ± {:.1e})",
181 obj.parameter,
182 obj.desired_value + self.achieved_errors[i],
183 obj.desired_value,
184 obj.tolerance
185 ));
186 }
187
188 let mut corrmsg = format!("Correction @ {}:", self.corrected_state.epoch());
189 let mut is_only_position = true;
190 let mut is_only_velocity = true;
191 for (i, var) in self.variables.iter().enumerate() {
192 let (unit, mulp) = match var.component {
193 Vary::PositionX | Vary::PositionY | Vary::PositionZ => {
194 is_only_velocity = false;
195 ("m", 1e3)
196 }
197 Vary::VelocityX | Vary::VelocityY | Vary::VelocityZ => {
198 is_only_position = false;
199 ("m/s", 1e3)
200 }
201 _ => {
202 is_only_position = false;
203 is_only_velocity = false;
204 ("", 1.0)
205 }
206 };
207 corrmsg.push_str(&format!(
208 "\n\t\t{:?} = {:.3} {}",
209 var.component,
210 self.correction[i] * mulp,
211 unit
212 ));
213 }
214
215 if is_only_position {
216 corrmsg.push_str(&format!(
217 "\n\t\t|Δr| = {:.3} m",
218 self.correction.norm() * 1e3
219 ));
220 } else if is_only_velocity {
221 corrmsg.push_str(&format!(
222 "\n\t\t|Δv| = {:.3} m/s",
223 self.correction.norm() * 1e3
224 ));
225 } else if self.is_finite_burn() {
226 let mnvr = self.to_mnvr().unwrap();
227 corrmsg.push_str(&format!("\n\t\t{mnvr}\n"));
228 }
229
230 writeln!(
231 f,
232 "Targeter solution correcting {:?} (converged in {:.3} seconds, {} iterations):\n\t{}\n\tAchieved @ {}:{}\n\tCorrected state:\n\t\t{}\n\t\t{:x}\n\tAchieved state:\n\t\t{}\n\t\t{:x}",
233 self.variables.iter().map(|v| format!("{:?}", v.component)).collect::<Vec<String>>(),
234 self.computation_dur.as_secs_f64(), self.iterations, corrmsg, self.achieved_state.epoch(), objmsg, self.corrected_state, self.corrected_state, self.achieved_state, self.achieved_state
235 )
236 }
237}