1use super::{DynamicsSnafu, IntegrationDetails, PropagationError, Propagator};
20use crate::dynamics::{Dynamics, DynamicsAlmanacSnafu};
21use crate::linalg::allocator::Allocator;
22use crate::linalg::{DefaultAllocator, OVector};
23use crate::md::trajectory::{Interpolatable, Traj};
24use crate::md::EventEvaluator;
25use crate::propagators::TrajectoryEventSnafu;
26use crate::time::{Duration, Epoch, Unit};
27use crate::State;
28use anise::almanac::Almanac;
29use anise::errors::MathError;
30use log::{debug, info, warn};
31use rayon::iter::ParallelBridge;
32use rayon::prelude::ParallelIterator;
33use snafu::ResultExt;
34use std::f64;
35use std::sync::mpsc::{channel, Sender};
36use std::sync::Arc;
37#[cfg(not(target_arch = "wasm32"))]
38use std::time::Instant;
39
40pub struct PropInstance<'a, D: Dynamics>
44where
45 DefaultAllocator: Allocator<<D::StateType as State>::Size>
46 + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
47 + Allocator<<D::StateType as State>::VecLength>,
48{
49 pub state: D::StateType,
51 pub prop: &'a Propagator<D>,
53 pub details: IntegrationDetails,
55 pub log_progress: bool,
57 pub(crate) almanac: Arc<Almanac>,
58 pub(crate) step_size: Duration, pub(crate) fixed_step: bool,
60 pub(crate) k: Vec<OVector<f64, <D::StateType as State>::VecLength>>,
62}
63
64impl<D: Dynamics> PropInstance<'_, D>
65where
66 DefaultAllocator: Allocator<<D::StateType as State>::Size>
67 + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
68 + Allocator<<D::StateType as State>::VecLength>,
69{
70 pub fn quiet(mut self) -> Self {
72 self.log_progress = false;
73 self
74 }
75
76 pub fn verbose(mut self) -> Self {
78 self.log_progress = true;
79 self
80 }
81
82 pub fn set_step(&mut self, step_size: Duration, fixed: bool) {
84 self.step_size = step_size;
85 self.fixed_step = fixed;
86 }
87
88 #[allow(clippy::erasing_op)]
89 fn for_duration_channel_option(
90 &mut self,
91 duration: Duration,
92 maybe_tx_chan: Option<Sender<D::StateType>>,
93 ) -> Result<D::StateType, PropagationError> {
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.clone())
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 if integration_frame != self.state.orbit().frame {
119 original_frame = Some(self.state.orbit().frame);
120 let mut new_orbit = self
121 .almanac
122 .transform_to(self.state.orbit(), integration_frame, None)
123 .context(DynamicsAlmanacSnafu {
124 action: "transforming state into desired integration frame",
125 })
126 .context(DynamicsSnafu)?;
127 if let Some(mu_km3_s2) = integration_frame.mu_km3_s2 {
129 new_orbit.frame.mu_km3_s2 = Some(mu_km3_s2);
130 }
131 if let Some(shape) = integration_frame.shape {
133 new_orbit.frame.shape = Some(shape);
134 }
135 if self.log_progress {
136 info!("State transformed to the integration frame {integration_frame}");
137 }
138 self.state.set_orbit(new_orbit);
139 }
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 {
153 #[cfg(not(target_arch = "wasm32"))]
155 {
156 if self.log_progress {
157 let tock: Duration = tick.elapsed().into();
158 debug!("Done in {tock}");
159 }
160 }
161
162 if let Some(original_frame) = original_frame {
164 let new_orbit = self
165 .almanac
166 .transform_to(self.state.orbit(), original_frame, None)
167 .context(DynamicsAlmanacSnafu {
168 action: "transforming state from desired integration frame",
169 })
170 .context(DynamicsSnafu)?;
171 self.state.set_orbit(new_orbit);
172 }
173
174 return Ok(self.state);
175 }
176 let prev_step_size = self.step_size;
178 let prev_step_kind = self.fixed_step;
179 self.set_step(stop_time - epoch, true);
180
181 self.single_step()?;
182
183 if let Some(ref chan) = maybe_tx_chan {
185 if let Err(e) = chan.send(self.state) {
186 warn!("{e} when sending on channel")
187 }
188 }
189
190 self.set_step(prev_step_size, prev_step_kind);
192
193 if backprop {
194 self.step_size = -self.step_size; }
196
197 #[cfg(not(target_arch = "wasm32"))]
198 {
199 if self.log_progress {
200 let tock: Duration = tick.elapsed().into();
201 info!("\t... done in {tock}");
202 }
203 }
204
205 if let Some(original_frame) = original_frame {
207 let new_orbit = self
208 .almanac
209 .transform_to(self.state.orbit(), original_frame, None)
210 .context(DynamicsAlmanacSnafu {
211 action: "transforming state from desired integration frame",
212 })
213 .context(DynamicsSnafu)?;
214 self.state.set_orbit(new_orbit);
215 }
216
217 return Ok(self.state);
218 } else {
219 #[cfg(not(target_arch = "wasm32"))]
220 {
221 if self.log_progress {
222 let tock: Duration = prev_tick.elapsed().into();
223 if tock.to_unit(Unit::Minute) > 1.0 {
224 let cur_epoch = self.state.epoch();
226 let dur_to_go = (stop_time - cur_epoch).floor(Unit::Second * 1);
227 info!(
228 "\t... current epoch {}, remaining {} (step size = {})",
229 cur_epoch, dur_to_go, self.details.step
230 );
231 prev_tick = Instant::now();
232 }
233 }
234 }
235 self.single_step()?;
236 if let Some(ref chan) = maybe_tx_chan {
238 if let Err(e) = chan.send(self.state) {
239 warn!("{e} when sending on channel")
240 }
241 }
242 }
243 }
244 }
245
246 pub fn for_duration(&mut self, duration: Duration) -> Result<D::StateType, PropagationError> {
248 self.for_duration_channel_option(duration, None)
249 }
250
251 pub fn for_duration_with_channel(
253 &mut self,
254 duration: Duration,
255 tx_chan: Sender<D::StateType>,
256 ) -> Result<D::StateType, PropagationError> {
257 self.for_duration_channel_option(duration, Some(tx_chan))
258 }
259
260 pub fn until_epoch(&mut self, end_time: Epoch) -> Result<D::StateType, PropagationError> {
262 let duration: Duration = end_time - self.state.epoch();
263 self.for_duration(duration)
264 }
265
266 pub fn until_epoch_with_channel(
268 &mut self,
269 end_time: Epoch,
270 tx_chan: Sender<D::StateType>,
271 ) -> Result<D::StateType, PropagationError> {
272 let duration: Duration = end_time - self.state.epoch();
273 self.for_duration_with_channel(duration, tx_chan)
274 }
275
276 #[allow(clippy::map_clone)]
279 pub fn for_duration_with_traj(
280 &mut self,
281 duration: Duration,
282 ) -> Result<(D::StateType, Traj<D::StateType>), PropagationError>
283 where
284 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
285 D::StateType: Interpolatable,
286 {
287 let end_state;
288 let mut traj = Traj::new();
289 let start_state = self.state;
290
291 let rx = {
292 let (tx, rx) = channel();
294 end_state = self.for_duration_with_channel(duration, tx)?;
297 rx
298 };
299
300 traj.states = rx.into_iter().par_bridge().collect();
301 traj.states.push(start_state);
304
305 traj.finalize();
306
307 Ok((end_state, traj))
308 }
309
310 pub fn until_epoch_with_traj(
314 &mut self,
315 end_time: Epoch,
316 ) -> Result<(D::StateType, Traj<D::StateType>), PropagationError>
317 where
318 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
319 D::StateType: Interpolatable,
320 {
321 let duration: Duration = end_time - self.state.epoch();
322 self.for_duration_with_traj(duration)
323 }
324
325 pub fn until_event<F: EventEvaluator<D::StateType>>(
328 &mut self,
329 max_duration: Duration,
330 event: &F,
331 ) -> Result<(D::StateType, Traj<D::StateType>), PropagationError>
332 where
333 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
334 D::StateType: Interpolatable,
335 {
336 self.until_nth_event(max_duration, event, 0)
337 }
338
339 pub fn until_nth_event<F: EventEvaluator<D::StateType>>(
342 &mut self,
343 max_duration: Duration,
344 event: &F,
345 trigger: usize,
346 ) -> Result<(D::StateType, Traj<D::StateType>), PropagationError>
347 where
348 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
349 D::StateType: Interpolatable,
350 {
351 info!("Searching for {event}");
352
353 let (_, traj) = self.for_duration_with_traj(max_duration)?;
354 let events = traj
356 .find(event, None, self.almanac.clone())
357 .context(TrajectoryEventSnafu)?;
358 match events.get(trigger) {
359 Some(event_state) => Ok((event_state.state, traj)),
360 None => Err(PropagationError::NthEventError {
361 nth: trigger,
362 found: events.len(),
363 }),
364 }
365 }
366
367 pub fn single_step(&mut self) -> Result<(), PropagationError> {
369 let (t, state_vec) = self.derive()?;
370 self.state.set(self.state.epoch() + t, &state_vec);
371 self.state = self
372 .prop
373 .dynamics
374 .finally(self.state, self.almanac.clone())
375 .context(DynamicsSnafu)?;
376 Ok(())
377 }
378
379 fn derive(
384 &mut self,
385 ) -> Result<(Duration, OVector<f64, <D::StateType as State>::VecLength>), PropagationError>
386 {
387 let state_vec = &self.state.to_vector();
388 let state_ctx = &self.state;
389 self.details.attempts = 1;
391 let mut step_size_s = self.step_size.to_seconds();
393 loop {
394 let ki = self
395 .prop
396 .dynamics
397 .eom(0.0, state_vec, state_ctx, self.almanac.clone())
398 .context(DynamicsSnafu)?;
399 self.k[0] = ki;
400 let mut a_idx: usize = 0;
401 for i in 0..(self.prop.method.stages() - 1) {
402 let mut ci: f64 = 0.0;
405 let mut wi = OVector::<f64, <D::StateType as State>::VecLength>::from_element(0.0);
407 for kj in &self.k[0..i + 1] {
408 let a_ij = self.prop.method.a_coeffs()[a_idx];
409 ci += a_ij;
410 wi += a_ij * kj;
411 a_idx += 1;
412 }
413
414 let ki = self
415 .prop
416 .dynamics
417 .eom(
418 ci * step_size_s,
419 &(state_vec + step_size_s * wi),
420 state_ctx,
421 self.almanac.clone(),
422 )
423 .context(DynamicsSnafu)?;
424 self.k[i + 1] = ki;
425 }
426 let mut next_state = state_vec.clone();
428 let mut error_est =
431 OVector::<f64, <D::StateType as State>::VecLength>::from_element(0.0);
432 for (i, ki) in self.k.iter().enumerate() {
433 let b_i = self.prop.method.b_coeffs()[i];
434 if !self.fixed_step {
435 let b_i_star = self.prop.method.b_coeffs()[i + self.prop.method.stages()];
436 error_est += step_size_s * (b_i - b_i_star) * ki;
437 }
438 next_state += step_size_s * b_i * ki;
439 }
440
441 if self.fixed_step {
442 self.details.step = self.step_size;
444 return Ok(((self.details.step), next_state));
445 } else {
446 self.details.error =
448 self.prop
449 .opts
450 .error_ctrl
451 .estimate(&error_est, &next_state, state_vec);
452
453 if self.details.error <= self.prop.opts.tolerance
454 || step_size_s <= self.prop.opts.min_step.to_seconds()
455 || self.details.attempts >= self.prop.opts.attempts
456 {
457 if next_state.iter().any(|x| x.is_nan()) {
458 return Err(PropagationError::PropMathError {
459 source: MathError::DomainError {
460 value: f64::NAN,
461 msg: "try another integration method, or decrease step size; part of state vector is",
462 },
463 });
464 }
465 if self.details.attempts >= self.prop.opts.attempts {
466 warn!(
467 "Could not further decrease step size: maximum number of attempts reached ({})",
468 self.details.attempts
469 );
470 }
471
472 self.details.step = step_size_s * Unit::Second;
473 if self.details.error < self.prop.opts.tolerance {
474 let proposed_step = 0.9
477 * step_size_s
478 * (self.prop.opts.tolerance / self.details.error)
479 .powf(1.0 / f64::from(self.prop.method.order()));
480
481 step_size_s =
482 if proposed_step.abs() > self.prop.opts.max_step.to_seconds().abs() {
483 self.prop.opts.max_step.to_seconds() * proposed_step.signum()
484 } else {
485 proposed_step
486 };
487 }
488 self.step_size = step_size_s * Unit::Second;
490 if self.step_size.abs() < self.prop.opts.min_step {
491 let signum = if self.step_size.is_negative() {
493 -1.0
494 } else {
495 1.0
496 };
497 self.step_size = self.prop.opts.min_step * signum;
498 }
499 return Ok((self.details.step, next_state));
500 } else {
501 self.details.attempts += 1;
504 let proposed_step_s = 0.9
505 * step_size_s
506 * (self.prop.opts.tolerance / self.details.error)
507 .powf(1.0 / f64::from(self.prop.method.order() - 1));
508
509 step_size_s = if proposed_step_s < self.prop.opts.min_step.to_seconds() {
510 self.prop.opts.min_step.to_seconds()
511 } else {
512 proposed_step_s
513 };
514 }
516 }
517 }
518 }
519
520 pub fn latest_details(&self) -> IntegrationDetails {
522 self.details
523 }
524}