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