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_os_rng(),
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}