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(0.0, 0.0, 0.0),
75 elevation: CommonPolynomial::Quadratic(0.0, 0.0, 0.0),
76 },
77 frame: LocalFrame::RCN,
78 };
79
80 for (i, var) in self.variables.iter().enumerate() {
81 let corr = self.correction[i];
82
83 match var.component {
85 Vary::Duration => {
86 if corr.abs() > 1e-3 {
87 let init_duration_s = (correction_epoch - achievement_epoch).to_seconds();
89 let acceptable_corr = var.apply_bounds(init_duration_s).seconds();
90 mnvr.end = mnvr.start + acceptable_corr;
91 }
92 }
93 Vary::EndEpoch => {
94 if corr.abs() > 1e-3 {
95 let total_end_corr =
97 (mnvr.end + corr.seconds() - achievement_epoch).to_seconds();
98 let acceptable_corr = var.apply_bounds(total_end_corr).seconds();
99 mnvr.end += acceptable_corr;
100 }
101 }
102 Vary::StartEpoch => {
103 if corr.abs() > 1e-3 {
104 let total_start_corr =
106 (mnvr.start + corr.seconds() - correction_epoch).to_seconds();
107 let acceptable_corr = var.apply_bounds(total_start_corr).seconds();
108 mnvr.end += acceptable_corr;
109
110 mnvr.start += corr.seconds()
111 }
112 }
113 Vary::MnvrAlpha | Vary::MnvrAlphaDot | Vary::MnvrAlphaDDot => {
114 match mnvr.representation {
115 MnvrRepr::Angles { azimuth, elevation } => {
116 let azimuth = azimuth
117 .add_val_in_order(corr, var.component.vec_index())
118 .unwrap();
119 mnvr.representation = MnvrRepr::Angles { azimuth, elevation };
120 }
121 _ => unreachable!(),
122 };
123 }
124 Vary::MnvrDelta | Vary::MnvrDeltaDot | Vary::MnvrDeltaDDot => {
125 match mnvr.representation {
126 MnvrRepr::Angles { azimuth, elevation } => {
127 let elevation = elevation
128 .add_val_in_order(corr, var.component.vec_index())
129 .unwrap();
130 mnvr.representation = MnvrRepr::Angles { azimuth, elevation };
131 }
132 _ => unreachable!(),
133 };
134 }
135 Vary::ThrustX | Vary::ThrustY | Vary::ThrustZ => {
136 let mut vector = mnvr.direction();
137 vector[var.component.vec_index()] += corr;
138 var.ensure_bounds(&mut vector[var.component.vec_index()]);
139 mnvr.set_direction(vector).context(GuidanceSnafu)?;
140 }
141 Vary::ThrustRateX | Vary::ThrustRateY | Vary::ThrustRateZ => {
142 let mut vector = mnvr.rate();
143 let idx = (var.component.vec_index() - 1) % 3;
144 vector[idx] += corr;
145 var.ensure_bounds(&mut vector[idx]);
146 mnvr.set_rate(vector).context(GuidanceSnafu)?;
147 }
148 Vary::ThrustAccelX | Vary::ThrustAccelY | Vary::ThrustAccelZ => {
149 let mut vector = mnvr.accel();
150 let idx = (var.component.vec_index() - 1) % 3;
151 vector[idx] += corr;
152 var.ensure_bounds(&mut vector[idx]);
153 mnvr.set_accel(vector).context(GuidanceSnafu)?;
154 }
155 Vary::ThrustLevel => {
156 mnvr.thrust_prct += corr;
157 var.ensure_bounds(&mut mnvr.thrust_prct);
158 }
159 _ => unreachable!(),
160 }
161 }
162
163 Ok(mnvr)
164 }
165}
166
167impl<const V: usize, const O: usize> fmt::Display for TargeterSolution<V, O> {
168 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
169 let mut objmsg = String::from("");
170 for (i, obj) in self.achieved_objectives.iter().enumerate() {
171 objmsg.push_str(&format!(
172 "\n\t\t{:?} = {:.3} (wanted {:.3} ± {:.1e})",
173 obj.parameter,
174 obj.desired_value + self.achieved_errors[i],
175 obj.desired_value,
176 obj.tolerance
177 ));
178 }
179
180 let mut corrmsg = format!("Correction @ {}:", self.corrected_state.epoch());
181 let mut is_only_position = true;
182 let mut is_only_velocity = true;
183 for (i, var) in self.variables.iter().enumerate() {
184 let unit = match var.component {
185 Vary::PositionX | Vary::PositionY | Vary::PositionZ => {
186 is_only_velocity = false;
187 "m"
188 }
189 Vary::VelocityX | Vary::VelocityY | Vary::VelocityZ => {
190 is_only_position = false;
191 "m/s"
192 }
193 _ => {
194 is_only_position = false;
195 is_only_velocity = false;
196 ""
197 }
198 };
199 corrmsg.push_str(&format!(
200 "\n\t\t{:?} = {:.3} {}",
201 var.component, self.correction[i], unit
202 ));
203 }
204
205 if is_only_position {
206 corrmsg.push_str(&format!(
207 "\n\t\t|Δr| = {:.3} m",
208 self.correction.norm() * 1e3
209 ));
210 } else if is_only_velocity {
211 corrmsg.push_str(&format!(
212 "\n\t\t|Δv| = {:.3} m/s",
213 self.correction.norm() * 1e3
214 ));
215 } else if self.is_finite_burn() {
216 let mnvr = self.to_mnvr().unwrap();
217 corrmsg.push_str(&format!("\n\t\t{mnvr}\n"));
218 }
219
220 writeln!(
221 f,
222 "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}",
223 self.variables.iter().map(|v| format!("{:?}", v.component)).collect::<Vec<String>>(),
224 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
225 )
226 }
227}