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