1use super::Pcg64Mcg;
20use crate::dynamics::Dynamics;
21use crate::linalg::allocator::Allocator;
22use crate::linalg::DefaultAllocator;
23use crate::mc::results::{PropResult, Results, Run};
24use crate::mc::DispersedState;
25use crate::md::trajectory::Interpolatable;
26use crate::md::EventEvaluator;
27use crate::propagators::Propagator;
28#[cfg(not(target_arch = "wasm32"))]
29use crate::time::Unit;
30use crate::time::{Duration, Epoch};
31use crate::State;
32use anise::almanac::Almanac;
33use indicatif::{ParallelProgressIterator, ProgressBar, ProgressStyle};
34use log::info;
35use rand::SeedableRng;
36use rand_distr::Distribution;
37use rayon::prelude::ParallelIterator;
38use rayon::prelude::*;
39use std::fmt;
40use std::sync::mpsc::channel;
41use std::sync::Arc;
42#[cfg(not(target_arch = "wasm32"))]
43use std::time::Instant as StdInstant;
44
45pub struct MonteCarlo<S: Interpolatable, Distr: Distribution<DispersedState<S>>>
48where
49 DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
50{
51 pub seed: Option<u128>,
53 pub random_state: Distr,
55 pub scenario: String,
57 pub nominal_state: S,
58}
59
60impl<S: Interpolatable, Distr: Distribution<DispersedState<S>>> MonteCarlo<S, Distr>
61where
62 DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
63{
64 pub fn new(
65 nominal_state: S,
66 random_variable: Distr,
67 scenario: String,
68 seed: Option<u128>,
69 ) -> Self {
70 Self {
71 random_state: random_variable,
72 seed,
73 scenario,
74 nominal_state,
75 }
76 }
77 fn progress_bar(&self, num_runs: usize) -> ProgressBar {
79 let pb = ProgressBar::new(num_runs.try_into().unwrap());
80 pb.set_style(
81 ProgressStyle::default_bar()
82 .template("[{elapsed_precise}] {bar:100.cyan/blue} {pos:>7}/{len:7} {msg}")
83 .unwrap()
84 .progress_chars("##-"),
85 );
86 pb.set_message(format!("{self}"));
87 pb
88 }
89
90 #[allow(clippy::needless_lifetimes)]
92 pub fn run_until_nth_event<D, F>(
93 self,
94 prop: Propagator<D>,
95 almanac: Arc<Almanac>,
96 max_duration: Duration,
97 event: &F,
98 trigger: usize,
99 num_runs: usize,
100 ) -> Results<S, PropResult<S>>
101 where
102 D: Dynamics<StateType = S>,
103
104 F: EventEvaluator<S>,
105 DefaultAllocator: Allocator<<D::StateType as State>::Size>
106 + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
107 + Allocator<<D::StateType as State>::VecLength>,
108 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
109 {
110 self.resume_run_until_nth_event(prop, almanac, 0, max_duration, event, trigger, num_runs)
111 }
112
113 #[must_use = "Monte Carlo result must be used"]
115 #[allow(clippy::needless_lifetimes)]
116 pub fn resume_run_until_nth_event<D, F>(
117 &self,
118 prop: Propagator<D>,
119 almanac: Arc<Almanac>,
120 skip: usize,
121 max_duration: Duration,
122 event: &F,
123 trigger: usize,
124 num_runs: usize,
125 ) -> Results<S, PropResult<S>>
126 where
127 D: Dynamics<StateType = S>,
128
129 F: EventEvaluator<S>,
130 DefaultAllocator: Allocator<<D::StateType as State>::Size>
131 + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
132 + Allocator<<D::StateType as State>::VecLength>,
133 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
134 {
135 let init_states = self.generate_states(skip, num_runs, self.seed);
137 let pb = self.progress_bar(num_runs);
139 let (tx, rx) = channel();
141
142 #[cfg(not(target_arch = "wasm32"))]
144 let start = StdInstant::now();
145
146 init_states.par_iter().progress_with(pb).for_each_with(
147 (prop, tx),
148 |(prop, tx), (index, dispersed_state)| {
149 let result = prop
150 .with(dispersed_state.state, almanac.clone())
151 .until_nth_event(max_duration, event, trigger);
152
153 let run = Run {
155 index: *index,
156 dispersed_state: dispersed_state.clone(),
157 result: result.map(|r| PropResult {
158 state: r.0,
159 traj: r.1,
160 }),
161 };
162 tx.send(run).unwrap();
163 },
164 );
165
166 #[cfg(not(target_arch = "wasm32"))]
167 {
168 let clock_time = StdInstant::now() - start;
169 info!(
170 "Propagated {} states in {}",
171 num_runs,
172 clock_time.as_secs_f64() * Unit::Second
173 );
174 }
175
176 let mut runs = rx
178 .iter()
179 .collect::<Vec<Run<D::StateType, PropResult<D::StateType>>>>();
180 runs.par_sort_by_key(|run| run.index);
181
182 Results {
183 runs,
184 scenario: self.scenario.clone(),
185 }
186 }
187
188 #[must_use = "Monte Carlo result must be used"]
190 #[allow(clippy::needless_lifetimes)]
191 pub fn run_until_epoch<D>(
192 self,
193 prop: Propagator<D>,
194 almanac: Arc<Almanac>,
195 end_epoch: Epoch,
196 num_runs: usize,
197 ) -> Results<S, PropResult<S>>
198 where
199 D: Dynamics<StateType = S>,
200
201 DefaultAllocator: Allocator<<D::StateType as State>::Size>
202 + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
203 + Allocator<<D::StateType as State>::VecLength>,
204 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
205 {
206 self.resume_run_until_epoch(prop, almanac, 0, end_epoch, num_runs)
207 }
208
209 #[must_use = "Monte Carlo result must be used"]
211 #[allow(clippy::needless_lifetimes)]
212 pub fn resume_run_until_epoch<D>(
213 &self,
214 prop: Propagator<D>,
215 almanac: Arc<Almanac>,
216 skip: usize,
217 end_epoch: Epoch,
218 num_runs: usize,
219 ) -> Results<S, PropResult<S>>
220 where
221 D: Dynamics<StateType = S>,
222
223 DefaultAllocator: Allocator<<D::StateType as State>::Size>
224 + Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
225 + Allocator<<D::StateType as State>::VecLength>,
226 <DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
227 {
228 let init_states = self.generate_states(skip, num_runs, self.seed);
230 let pb = self.progress_bar(num_runs);
232 let (tx, rx) = channel();
234
235 #[cfg(not(target_arch = "wasm32"))]
237 let start = StdInstant::now();
238 init_states.par_iter().progress_with(pb).for_each_with(
239 (prop, tx),
240 |(arc_prop, tx), (index, dispersed_state)| {
241 let result = arc_prop
242 .with(dispersed_state.state, almanac.clone())
243 .quiet()
244 .until_epoch_with_traj(end_epoch);
245
246 let run = Run {
248 index: *index,
249 dispersed_state: dispersed_state.clone(),
250 result: result.map(|r| PropResult {
251 state: r.0,
252 traj: r.1,
253 }),
254 };
255
256 tx.send(run).unwrap();
257 },
258 );
259
260 #[cfg(not(target_arch = "wasm32"))]
261 {
262 let clock_time = StdInstant::now() - start;
263 info!(
264 "Propagated {} states in {}",
265 num_runs,
266 clock_time.as_secs_f64() * Unit::Second
267 );
268 }
269
270 let mut runs = rx.iter().collect::<Vec<Run<S, PropResult<S>>>>();
272 runs.par_sort_by_key(|run| run.index);
273
274 Results {
275 runs,
276 scenario: self.scenario.clone(),
277 }
278 }
279
280 #[must_use = "Generated states for a Monte Carlo run must be used"]
282 pub fn generate_states(
283 &self,
284 skip: usize,
285 num_runs: usize,
286 seed: Option<u128>,
287 ) -> Vec<(usize, DispersedState<S>)> {
288 let rng = match seed {
290 Some(seed) => Pcg64Mcg::new(seed),
291 None => Pcg64Mcg::from_entropy(),
292 };
293
294 (&self.random_state)
296 .sample_iter(rng)
297 .skip(skip)
298 .take(num_runs)
299 .enumerate()
300 .collect::<Vec<(usize, DispersedState<S>)>>()
301 }
302}
303
304impl<S: Interpolatable, Distr: Distribution<DispersedState<S>>> fmt::Display
305 for MonteCarlo<S, Distr>
306where
307 DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
308{
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 write!(
311 f,
312 "{} - Nyx Monte Carlo - seed: {:?}",
313 self.scenario, self.seed
314 )
315 }
316}
317
318impl<S: Interpolatable, Distr: Distribution<DispersedState<S>>> fmt::LowerHex
319 for MonteCarlo<S, Distr>
320where
321 DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
322{
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 write!(
326 f,
327 "mc-data-{}-seed-{:?}",
328 self.scenario.replace(' ', "-"),
329 self.seed
330 )
331 }
332}