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 rayon::iter::ParallelBridge;
31use rayon::prelude::ParallelIterator;
32use snafu::ResultExt;
33use std::f64;
34use std::sync::mpsc::{channel, Sender};
35use std::sync::Arc;
36#[cfg(not(target_arch = "wasm32"))]
37use std::time::Instant;
38
39pub struct PropInstance<'a, D: Dynamics>
43where
44 DefaultAllocator: Allocator<<D::StateType as State>::Size>
45 + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
46 + Allocator<<D::StateType as State>::VecLength>,
47{
48 pub state: D::StateType,
50 pub prop: &'a Propagator<D>,
52 pub details: IntegrationDetails,
54 pub log_progress: bool,
56 pub(crate) almanac: Arc<Almanac>,
57 pub(crate) step_size: Duration, pub(crate) fixed_step: bool,
59 pub(crate) k: Vec<OVector<f64, <D::StateType as State>::VecLength>>,
61}
62
63impl<D: Dynamics> PropInstance<'_, D>
64where
65 DefaultAllocator: Allocator<<D::StateType as State>::Size>
66 + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
67 + Allocator<<D::StateType as State>::VecLength>,
68{
69 pub fn quiet(mut self) -> Self {
71 self.log_progress = false;
72 self
73 }
74
75 pub fn verbose(mut self) -> Self {
77 self.log_progress = true;
78 self
79 }
80
81 pub fn set_step(&mut self, step_size: Duration, fixed: bool) {
83 self.step_size = step_size;
84 self.fixed_step = fixed;
85 }
86
87 #[allow(clippy::erasing_op)]
88 fn for_duration_channel_option(
89 &mut self,
90 duration: Duration,
91 maybe_tx_chan: Option<Sender<D::StateType>>,
92 ) -> Result<D::StateType, PropagationError> {
93 if duration == 0 * Unit::Second {
94 return Ok(self.state);
95 }
96 let stop_time = self.state.epoch() + duration;
97
98 if self.log_progress {
99 info!("Propagating for {} until {}", duration, stop_time);
101 }
102 self.state = self
104 .prop
105 .dynamics
106 .finally(self.state, self.almanac.clone())
107 .context(DynamicsSnafu)?;
108
109 let backprop = duration.is_negative();
110 if backprop {
111 self.step_size = -self.step_size; }
113
114 let mut original_frame = None;
116 if let Some(integration_frame) = self.prop.opts.integration_frame {
117 if integration_frame != self.state.orbit().frame {
118 original_frame = Some(self.state.orbit().frame);
119 let mut new_orbit = self
120 .almanac
121 .transform_to(self.state.orbit(), integration_frame, None)
122 .context(DynamicsAlmanacSnafu {
123 action: "transforming state into desired integration frame",
124 })
125 .context(DynamicsSnafu)?;
126 if let Some(mu_km3_s2) = integration_frame.mu_km3_s2 {
128 new_orbit.frame.mu_km3_s2 = Some(mu_km3_s2);
129 }
130 if let Some(shape) = integration_frame.shape {
132 new_orbit.frame.shape = Some(shape);
133 }
134 if self.log_progress {
135 info!("State transformed to the integration frame {integration_frame}");
136 }
137 self.state.set_orbit(new_orbit);
138 }
139 }
140
141 #[cfg(not(target_arch = "wasm32"))]
142 let tick = Instant::now();
143 #[cfg(not(target_arch = "wasm32"))]
144 let mut prev_tick = Instant::now();
145
146 loop {
147 let epoch = self.state.epoch();
148 if (!backprop && epoch + self.step_size > stop_time)
149 || (backprop && epoch + self.step_size <= stop_time)
150 {
151 if stop_time == epoch {
152 #[cfg(not(target_arch = "wasm32"))]
154 {
155 if self.log_progress {
156 let tock: Duration = tick.elapsed().into();
157 debug!("Done in {}", tock);
158 }
159 }
160
161 if let Some(original_frame) = original_frame {
163 let new_orbit = self
164 .almanac
165 .transform_to(self.state.orbit(), original_frame, None)
166 .context(DynamicsAlmanacSnafu {
167 action: "transforming state from desired integration frame",
168 })
169 .context(DynamicsSnafu)?;
170 self.state.set_orbit(new_orbit);
171 }
172
173 return Ok(self.state);
174 }
175 let prev_step_size = self.step_size;
177 let prev_step_kind = self.fixed_step;
178 self.set_step(stop_time - epoch, true);
179
180 self.single_step()?;
181
182 if let Some(ref chan) = maybe_tx_chan {
184 if let Err(e) = chan.send(self.state) {
185 warn!("{} when sending on channel", e)
186 }
187 }
188
189 self.set_step(prev_step_size, prev_step_kind);
191
192 if backprop {
193 self.step_size = -self.step_size; }
195
196 #[cfg(not(target_arch = "wasm32"))]
197 {
198 if self.log_progress {
199 let tock: Duration = tick.elapsed().into();
200 info!("\t... done in {}", tock);
201 }
202 }
203
204 if let Some(original_frame) = original_frame {
206 let new_orbit = self
207 .almanac
208 .transform_to(self.state.orbit(), original_frame, None)
209 .context(DynamicsAlmanacSnafu {
210 action: "transforming state from desired integration frame",
211 })
212 .context(DynamicsSnafu)?;
213 self.state.set_orbit(new_orbit);
214 }
215
216 return Ok(self.state);
217 } else {
218 #[cfg(not(target_arch = "wasm32"))]
219 {
220 if self.log_progress {
221 let tock: Duration = prev_tick.elapsed().into();
222 if tock.to_unit(Unit::Minute) > 1.0 {
223 let cur_epoch = self.state.epoch();
225 let dur_to_go = (stop_time - cur_epoch).floor(Unit::Second * 1);
226 info!(
227 "\t... current epoch {}, remaining {} (step size = {})",
228 cur_epoch, dur_to_go, self.details.step
229 );
230 prev_tick = Instant::now();
231 }
232 }
233 }
234 self.single_step()?;
235 if let Some(ref chan) = maybe_tx_chan {
237 if let Err(e) = chan.send(self.state) {
238 warn!("{} when sending on channel", e)
239 }
240 }
241 }
242 }
243 }
244
245 pub fn for_duration(&mut self, duration: Duration) -> Result<D::StateType, PropagationError> {
247 self.for_duration_channel_option(duration, None)
248 }
249
250 pub fn for_duration_with_channel(
252 &mut self,
253 duration: Duration,
254 tx_chan: Sender<D::StateType>,
255 ) -> Result<D::StateType, PropagationError> {
256 self.for_duration_channel_option(duration, Some(tx_chan))
257 }
258
259 pub fn until_epoch(&mut self, end_time: Epoch) -> Result<D::StateType, PropagationError> {
261 let duration: Duration = end_time - self.state.epoch();
262 self.for_duration(duration)
263 }
264
265 pub fn until_epoch_with_channel(
267 &mut self,
268 end_time: Epoch,
269 tx_chan: Sender<D::StateType>,
270 ) -> Result<D::StateType, PropagationError> {
271 let duration: Duration = end_time - self.state.epoch();
272 self.for_duration_with_channel(duration, tx_chan)
273 }
274
275 #[allow(clippy::map_clone)]
278 pub fn for_duration_with_traj(
279 &mut self,
280 duration: Duration,
281 ) -> Result<(D::StateType, Traj<D::StateType>), PropagationError>
282 where
283 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
284 D::StateType: Interpolatable,
285 {
286 let end_state;
287 let mut traj = Traj::new();
288 let start_state = self.state;
289
290 let rx = {
291 let (tx, rx) = channel();
293 end_state = self.for_duration_with_channel(duration, tx)?;
296 rx
297 };
298
299 traj.states = rx.into_iter().par_bridge().collect();
300 traj.states.push(start_state);
303
304 traj.finalize();
305
306 Ok((end_state, traj))
307 }
308
309 pub fn until_epoch_with_traj(
313 &mut self,
314 end_time: Epoch,
315 ) -> Result<(D::StateType, Traj<D::StateType>), PropagationError>
316 where
317 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
318 D::StateType: Interpolatable,
319 {
320 let duration: Duration = end_time - self.state.epoch();
321 self.for_duration_with_traj(duration)
322 }
323
324 pub fn until_event<F: EventEvaluator<D::StateType>>(
327 &mut self,
328 max_duration: Duration,
329 event: &F,
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 self.until_nth_event(max_duration, event, 0)
336 }
337
338 pub fn until_nth_event<F: EventEvaluator<D::StateType>>(
341 &mut self,
342 max_duration: Duration,
343 event: &F,
344 trigger: usize,
345 ) -> Result<(D::StateType, Traj<D::StateType>), PropagationError>
346 where
347 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
348 D::StateType: Interpolatable,
349 {
350 info!("Searching for {}", event);
351
352 let (_, traj) = self.for_duration_with_traj(max_duration)?;
353 let events = traj
355 .find(event, self.almanac.clone())
356 .context(TrajectoryEventSnafu)?;
357 match events.get(trigger) {
358 Some(event_state) => Ok((event_state.state, traj)),
359 None => Err(PropagationError::NthEventError {
360 nth: trigger,
361 found: events.len(),
362 }),
363 }
364 }
365
366 pub fn single_step(&mut self) -> Result<(), PropagationError> {
368 let (t, state_vec) = self.derive()?;
369 self.state.set(self.state.epoch() + t, &state_vec);
370 self.state = self
371 .prop
372 .dynamics
373 .finally(self.state, self.almanac.clone())
374 .context(DynamicsSnafu)?;
375
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 = if proposed_step > self.prop.opts.max_step.to_seconds() {
482 self.prop.opts.max_step.to_seconds()
483 } else {
484 proposed_step
485 };
486 }
487 self.step_size = step_size_s * Unit::Second;
489 if self.step_size.abs() < self.prop.opts.min_step {
490 let signum = if self.step_size.is_negative() {
492 -1.0
493 } else {
494 1.0
495 };
496 self.step_size = self.prop.opts.min_step * signum;
497 }
498 return Ok((self.details.step, next_state));
499 } else {
500 self.details.attempts += 1;
503 let proposed_step_s = 0.9
504 * step_size_s
505 * (self.prop.opts.tolerance / self.details.error)
506 .powf(1.0 / f64::from(self.prop.method.order() - 1));
507
508 step_size_s = if proposed_step_s < self.prop.opts.min_step.to_seconds() {
509 self.prop.opts.min_step.to_seconds()
510 } else {
511 proposed_step_s
512 };
513 }
515 }
516 }
517 }
518
519 pub fn latest_details(&self) -> IntegrationDetails {
521 self.details
522 }
523}