1use super::{DynamicsSnafu, IntegrationDetails, PropagationError, Propagator};
20use crate::State;
21use crate::dynamics::{Dynamics, DynamicsAlmanacSnafu};
22use crate::linalg::allocator::Allocator;
23use crate::linalg::{DefaultAllocator, OVector};
24use crate::md::trajectory::{Interpolatable, Traj};
25use crate::time::{Duration, Epoch, Unit};
26use anise::almanac::Almanac;
27use anise::errors::MathError;
28use log::{info, warn};
29use snafu::ResultExt;
30use std::f64;
31use std::sync::Arc;
32use std::sync::mpsc::{Sender, channel};
33#[cfg(not(target_arch = "wasm32"))]
34use std::time::Instant;
35
36pub struct PropInstance<'a, D: Dynamics>
40where
41 DefaultAllocator: Allocator<<D::StateType as State>::Size>
42 + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
43 + Allocator<<D::StateType as State>::VecLength>,
44{
45 pub state: D::StateType,
47 pub prop: &'a Propagator<D>,
49 pub details: IntegrationDetails,
51 pub log_progress: bool,
53 pub(crate) almanac: Arc<Almanac>,
54 pub(crate) step_size: Duration, pub(crate) fixed_step: bool,
56 pub(crate) k: Vec<OVector<f64, <D::StateType as State>::VecLength>>,
58}
59
60impl<D: Dynamics> PropInstance<'_, D>
61where
62 DefaultAllocator: Allocator<<D::StateType as State>::Size>
63 + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
64 + Allocator<<D::StateType as State>::VecLength>,
65{
66 pub fn quiet(mut self) -> Self {
68 self.log_progress = false;
69 self
70 }
71
72 pub fn verbose(mut self) -> Self {
74 self.log_progress = true;
75 self
76 }
77
78 pub fn set_step(&mut self, step_size: Duration, fixed: bool) {
80 self.step_size = step_size;
81 self.fixed_step = fixed;
82 }
83
84 #[allow(clippy::erasing_op)]
85 pub(crate) fn propagate<F>(
86 &mut self,
87 duration: Duration,
88 maybe_tx_chan: Option<Sender<D::StateType>>,
89 mut stop_condition: Option<F>,
90 ) -> Result<D::StateType, PropagationError>
91 where
92 F: FnMut(D::StateType) -> Result<bool, PropagationError>,
93 {
94 if duration == 0 * Unit::Second {
95 return Ok(self.state);
96 }
97 let stop_time = self.state.epoch() + duration;
98
99 if self.log_progress {
100 info!("Propagating for {duration} until {stop_time}");
102 }
103 self.state = self
105 .prop
106 .dynamics
107 .finally(self.state, &self.almanac)
108 .context(DynamicsSnafu)?;
109
110 let backprop = duration.is_negative();
111 if backprop {
112 self.step_size = -self.step_size; }
114
115 let mut original_frame = None;
117 if let Some(integration_frame) = self.prop.opts.integration_frame
118 && integration_frame != self.state.orbit().frame
119 {
120 original_frame = Some(self.state.orbit().frame);
121 let mut new_orbit = self
122 .almanac
123 .transform_to(self.state.orbit(), integration_frame, None)
124 .context(DynamicsAlmanacSnafu {
125 action: "transforming state into desired integration frame",
126 })
127 .context(DynamicsSnafu)?;
128 if let Some(mu_km3_s2) = integration_frame.mu_km3_s2 {
130 new_orbit.frame.mu_km3_s2 = Some(mu_km3_s2);
131 }
132 if let Some(shape) = integration_frame.shape {
134 new_orbit.frame.shape = Some(shape);
135 }
136 if self.log_progress {
137 info!("State transformed to the integration frame {integration_frame}");
138 }
139 self.state.set_orbit(new_orbit);
140 }
141
142 #[cfg(not(target_arch = "wasm32"))]
143 let tick = Instant::now();
144 #[cfg(not(target_arch = "wasm32"))]
145 let mut prev_tick = Instant::now();
146
147 loop {
148 let epoch = self.state.epoch();
149 if (!backprop && epoch + self.step_size > stop_time)
150 || (backprop && epoch + self.step_size <= stop_time)
151 {
152 if stop_time == epoch {
155 #[cfg(not(target_arch = "wasm32"))]
157 {
158 if self.log_progress {
159 let tock: Duration = tick.elapsed().into();
160 info!("\t... done in {tock}");
161 }
162 }
163
164 if let Some(original_frame) = original_frame {
166 let new_orbit = self
167 .almanac
168 .transform_to(self.state.orbit(), original_frame, None)
169 .context(DynamicsAlmanacSnafu {
170 action: "transforming state from desired integration frame",
171 })
172 .context(DynamicsSnafu)?;
173 self.state.set_orbit(new_orbit);
174 }
175
176 return Ok(self.state);
177 }
178
179 let prev_step_size = self.step_size;
181 let prev_step_kind = self.fixed_step;
182 self.set_step(stop_time - epoch, true);
183
184 self.single_step()?;
185
186 if let Some(ref chan) = maybe_tx_chan
188 && let Err(e) = chan.send(self.state)
189 {
190 warn!("{e} when sending on channel")
191 }
192
193 self.set_step(prev_step_size, prev_step_kind);
195
196 if backprop {
197 self.step_size = -self.step_size; }
199
200 #[cfg(not(target_arch = "wasm32"))]
201 {
202 if self.log_progress {
203 let tock: Duration = tick.elapsed().into();
204 info!("\t... done in {tock}");
205 }
206 }
207
208 if let Some(original_frame) = original_frame {
210 let new_orbit = self
211 .almanac
212 .transform_to(self.state.orbit(), original_frame, None)
213 .context(DynamicsAlmanacSnafu {
214 action: "transforming state from desired integration frame",
215 })
216 .context(DynamicsSnafu)?;
217 self.state.set_orbit(new_orbit);
218 }
219
220 return Ok(self.state);
221 } else {
222 #[cfg(not(target_arch = "wasm32"))]
223 {
224 if self.log_progress {
225 let tock: Duration = prev_tick.elapsed().into();
226 if tock.to_unit(Unit::Minute) > 1.0 {
227 let cur_epoch = self.state.epoch();
229 let dur_to_go = (stop_time - cur_epoch).floor(Unit::Second * 1);
230 info!(
231 "\t... current epoch {}, remaining {} (step size = {})",
232 cur_epoch, dur_to_go, self.details.step
233 );
234 prev_tick = Instant::now();
235 }
236 }
237 }
238
239 self.single_step()?;
240
241 if let Some(ref mut condition) = stop_condition
242 && condition(self.state)?
243 {
244 return Ok(self.state);
250 }
251
252 if let Some(ref chan) = maybe_tx_chan
254 && let Err(e) = chan.send(self.state)
255 {
256 warn!("{e} when sending on channel")
257 }
258 }
259 }
260 }
261
262 pub fn for_duration(&mut self, duration: Duration) -> Result<D::StateType, PropagationError> {
264 self.propagate(duration, None, Some(|_| Ok(false)))
265 }
266
267 pub fn for_duration_with_channel(
269 &mut self,
270 duration: Duration,
271 tx_chan: Sender<D::StateType>,
272 ) -> Result<D::StateType, PropagationError> {
273 self.propagate(duration, Some(tx_chan), Some(|_| Ok(false)))
274 }
275
276 pub fn until_epoch(&mut self, end_time: Epoch) -> Result<D::StateType, PropagationError> {
278 let duration: Duration = end_time - self.state.epoch();
279 self.for_duration(duration)
280 }
281
282 pub fn until_epoch_with_channel(
284 &mut self,
285 end_time: Epoch,
286 tx_chan: Sender<D::StateType>,
287 ) -> Result<D::StateType, PropagationError> {
288 let duration: Duration = end_time - self.state.epoch();
289 self.for_duration_with_channel(duration, tx_chan)
290 }
291
292 #[allow(clippy::map_clone)]
295 pub fn for_duration_with_traj(
296 &mut self,
297 duration: Duration,
298 ) -> Result<(D::StateType, Traj<D::StateType>), PropagationError>
299 where
300 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
301 D::StateType: Interpolatable,
302 {
303 let end_state;
304 let mut traj = Traj::new();
305 let start_state = self.state;
306
307 let rx = {
308 let (tx, rx) = channel();
310 end_state = self.for_duration_with_channel(duration, tx)?;
313 rx
314 };
315
316 traj.states = rx.into_iter().collect();
317 traj.states.push(start_state);
319
320 traj.finalize();
321
322 Ok((end_state, traj))
323 }
324
325 pub fn until_epoch_with_traj(
328 &mut self,
329 end_time: Epoch,
330 ) -> Result<(D::StateType, Traj<D::StateType>), PropagationError>
331 where
332 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
333 D::StateType: Interpolatable,
334 {
335 let duration: Duration = end_time - self.state.epoch();
336 self.for_duration_with_traj(duration)
337 }
338
339 pub fn single_step(&mut self) -> Result<(), PropagationError> {
341 let (t, state_vec) = self.derive()?;
342 self.state.set(self.state.epoch() + t, &state_vec);
343 self.state = self
344 .prop
345 .dynamics
346 .finally(self.state, &self.almanac)
347 .context(DynamicsSnafu)?;
348 Ok(())
349 }
350
351 fn derive(
356 &mut self,
357 ) -> Result<(Duration, OVector<f64, <D::StateType as State>::VecLength>), PropagationError>
358 {
359 let state_vec = &self.state.to_vector();
360 let state_ctx = &self.state;
361 self.details.attempts = 1;
363 let mut step_size_s = self.step_size.to_seconds();
365 loop {
366 let ki = self
367 .prop
368 .dynamics
369 .eom(0.0, state_vec, state_ctx, &self.almanac)
370 .context(DynamicsSnafu)?;
371 self.k[0] = ki;
372 let mut a_idx: usize = 0;
373 for i in 0..(self.prop.method.stages() - 1) {
374 let mut ci: f64 = 0.0;
377 let mut wi = OVector::<f64, <D::StateType as State>::VecLength>::from_element(0.0);
379 for kj in &self.k[0..i + 1] {
380 let a_ij = self.prop.method.a_coeffs()[a_idx];
381 ci += a_ij;
382 wi += a_ij * kj;
383 a_idx += 1;
384 }
385
386 let ki = self
387 .prop
388 .dynamics
389 .eom(
390 ci * step_size_s,
391 &(state_vec + step_size_s * wi),
392 state_ctx,
393 &self.almanac,
394 )
395 .context(DynamicsSnafu)?;
396 self.k[i + 1] = ki;
397 }
398 let mut next_state = state_vec.clone();
400 let mut error_est =
403 OVector::<f64, <D::StateType as State>::VecLength>::from_element(0.0);
404 for (i, ki) in self.k.iter().enumerate() {
405 let b_i = self.prop.method.b_coeffs()[i];
406 if !self.fixed_step {
407 let b_i_star = self.prop.method.b_coeffs()[i + self.prop.method.stages()];
408 error_est += step_size_s * (b_i - b_i_star) * ki;
409 }
410 next_state += step_size_s * b_i * ki;
411 }
412
413 if self.fixed_step {
414 self.details.step = self.step_size;
416 return Ok(((self.details.step), next_state));
417 } else {
418 self.details.error =
420 self.prop
421 .opts
422 .error_ctrl
423 .estimate(&error_est, &next_state, state_vec);
424
425 if self.details.error <= self.prop.opts.tolerance
426 || step_size_s <= self.prop.opts.min_step.to_seconds()
427 || self.details.attempts >= self.prop.opts.attempts
428 {
429 if next_state.iter().any(|x| x.is_nan()) {
430 return Err(PropagationError::PropMathError {
431 source: MathError::DomainError {
432 value: f64::NAN,
433 msg: "try another integration method, or decrease step size; part of state vector is",
434 },
435 });
436 }
437 if self.details.attempts >= self.prop.opts.attempts {
438 warn!(
439 "Could not further decrease step size: maximum number of attempts reached ({})",
440 self.details.attempts
441 );
442 }
443
444 self.details.step = step_size_s * Unit::Second;
445 if self.details.error < self.prop.opts.tolerance {
446 let proposed_step_s = 0.9
449 * step_size_s
450 * (self.prop.opts.tolerance / self.details.error)
451 .powf(1.0 / f64::from(self.prop.method.order()));
452
453 step_size_s =
454 if proposed_step_s.abs() > self.prop.opts.max_step.to_seconds().abs() {
455 self.prop.opts.max_step.to_seconds() * proposed_step_s.signum()
456 } else {
457 proposed_step_s
458 };
459 }
460 self.step_size = step_size_s * Unit::Second;
462 if self.step_size.abs() < self.prop.opts.min_step {
463 let signum = if self.step_size.is_negative() {
465 -1.0
466 } else {
467 1.0
468 };
469 self.step_size = self.prop.opts.min_step * signum;
470 }
471 return Ok((self.details.step, next_state));
472 } else {
473 self.details.attempts += 1;
476 let proposed_step_s = 0.9
477 * step_size_s
478 * (self.prop.opts.tolerance / self.details.error)
479 .powf(1.0 / f64::from(self.prop.method.order() - 1));
480
481 step_size_s = if proposed_step_s < self.prop.opts.min_step.to_seconds() {
482 self.prop.opts.min_step.to_seconds()
483 } else {
484 proposed_step_s
485 };
486 }
488 }
489 }
490 }
491
492 pub fn latest_details(&self) -> IntegrationDetails {
494 self.details
495 }
496}