use super::Pcg64Mcg;
use crate::dynamics::Dynamics;
use crate::linalg::allocator::Allocator;
use crate::linalg::DefaultAllocator;
use crate::mc::results::{PropResult, Results, Run};
use crate::mc::DispersedState;
use crate::md::trajectory::Interpolatable;
use crate::md::EventEvaluator;
use crate::propagators::Propagator;
#[cfg(not(target_arch = "wasm32"))]
use crate::time::Unit;
use crate::time::{Duration, Epoch};
use crate::State;
use anise::almanac::Almanac;
use indicatif::{ParallelProgressIterator, ProgressBar, ProgressStyle};
use log::info;
use rand::SeedableRng;
use rand_distr::Distribution;
use rayon::prelude::ParallelIterator;
use rayon::prelude::*;
use std::fmt;
use std::sync::mpsc::channel;
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant as StdInstant;
pub struct MonteCarlo<S: Interpolatable, Distr: Distribution<DispersedState<S>>>
where
DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
{
pub seed: Option<u128>,
pub random_state: Distr,
pub scenario: String,
pub nominal_state: S,
}
impl<S: Interpolatable, Distr: Distribution<DispersedState<S>>> MonteCarlo<S, Distr>
where
DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
{
pub fn new(
nominal_state: S,
random_variable: Distr,
scenario: String,
seed: Option<u128>,
) -> Self {
Self {
random_state: random_variable,
seed,
scenario,
nominal_state,
}
}
fn progress_bar(&self, num_runs: usize) -> ProgressBar {
let pb = ProgressBar::new(num_runs.try_into().unwrap());
pb.set_style(
ProgressStyle::default_bar()
.template("[{elapsed_precise}] {bar:100.cyan/blue} {pos:>7}/{len:7} {msg}")
.unwrap()
.progress_chars("##-"),
);
pb.set_message(format!("{self}"));
pb
}
#[allow(clippy::needless_lifetimes)]
pub fn run_until_nth_event<D, F>(
self,
prop: Propagator<D>,
almanac: Arc<Almanac>,
max_duration: Duration,
event: &F,
trigger: usize,
num_runs: usize,
) -> Results<S, PropResult<S>>
where
D: Dynamics<StateType = S>,
F: EventEvaluator<S>,
DefaultAllocator: Allocator<<D::StateType as State>::Size>
+ Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
+ Allocator<<D::StateType as State>::VecLength>,
<DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
{
self.resume_run_until_nth_event(prop, almanac, 0, max_duration, event, trigger, num_runs)
}
#[must_use = "Monte Carlo result must be used"]
#[allow(clippy::needless_lifetimes)]
pub fn resume_run_until_nth_event<D, F>(
&self,
prop: Propagator<D>,
almanac: Arc<Almanac>,
skip: usize,
max_duration: Duration,
event: &F,
trigger: usize,
num_runs: usize,
) -> Results<S, PropResult<S>>
where
D: Dynamics<StateType = S>,
F: EventEvaluator<S>,
DefaultAllocator: Allocator<<D::StateType as State>::Size>
+ Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
+ Allocator<<D::StateType as State>::VecLength>,
<DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
{
let init_states = self.generate_states(skip, num_runs, self.seed);
let pb = self.progress_bar(num_runs);
let (tx, rx) = channel();
#[cfg(not(target_arch = "wasm32"))]
let start = StdInstant::now();
init_states.par_iter().progress_with(pb).for_each_with(
(prop, tx),
|(prop, tx), (index, dispersed_state)| {
let result = prop
.with(dispersed_state.state, almanac.clone())
.until_nth_event(max_duration, event, trigger);
let run = Run {
index: *index,
dispersed_state: dispersed_state.clone(),
result: result.map(|r| PropResult {
state: r.0,
traj: r.1,
}),
};
tx.send(run).unwrap();
},
);
#[cfg(not(target_arch = "wasm32"))]
{
let clock_time = StdInstant::now() - start;
info!(
"Propagated {} states in {}",
num_runs,
clock_time.as_secs_f64() * Unit::Second
);
}
let mut runs = rx
.iter()
.collect::<Vec<Run<D::StateType, PropResult<D::StateType>>>>();
runs.par_sort_by_key(|run| run.index);
Results {
runs,
scenario: self.scenario.clone(),
}
}
#[must_use = "Monte Carlo result must be used"]
#[allow(clippy::needless_lifetimes)]
pub fn run_until_epoch<D>(
self,
prop: Propagator<D>,
almanac: Arc<Almanac>,
end_epoch: Epoch,
num_runs: usize,
) -> Results<S, PropResult<S>>
where
D: Dynamics<StateType = S>,
DefaultAllocator: Allocator<<D::StateType as State>::Size>
+ Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
+ Allocator<<D::StateType as State>::VecLength>,
<DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
{
self.resume_run_until_epoch(prop, almanac, 0, end_epoch, num_runs)
}
#[must_use = "Monte Carlo result must be used"]
#[allow(clippy::needless_lifetimes)]
pub fn resume_run_until_epoch<D>(
&self,
prop: Propagator<D>,
almanac: Arc<Almanac>,
skip: usize,
end_epoch: Epoch,
num_runs: usize,
) -> Results<S, PropResult<S>>
where
D: Dynamics<StateType = S>,
DefaultAllocator: Allocator<<D::StateType as State>::Size>
+ Allocator<<D::StateType as State>::Size, <D::StateType as State>::Size>
+ Allocator<<D::StateType as State>::VecLength>,
<DefaultAllocator as Allocator<<D::StateType as State>::VecLength>>::Buffer<f64>: Send,
{
let init_states = self.generate_states(skip, num_runs, self.seed);
let pb = self.progress_bar(num_runs);
let (tx, rx) = channel();
#[cfg(not(target_arch = "wasm32"))]
let start = StdInstant::now();
init_states.par_iter().progress_with(pb).for_each_with(
(prop, tx),
|(arc_prop, tx), (index, dispersed_state)| {
let result = arc_prop
.with(dispersed_state.state, almanac.clone())
.quiet()
.until_epoch_with_traj(end_epoch);
let run = Run {
index: *index,
dispersed_state: dispersed_state.clone(),
result: result.map(|r| PropResult {
state: r.0,
traj: r.1,
}),
};
tx.send(run).unwrap();
},
);
#[cfg(not(target_arch = "wasm32"))]
{
let clock_time = StdInstant::now() - start;
info!(
"Propagated {} states in {}",
num_runs,
clock_time.as_secs_f64() * Unit::Second
);
}
let mut runs = rx.iter().collect::<Vec<Run<S, PropResult<S>>>>();
runs.par_sort_by_key(|run| run.index);
Results {
runs,
scenario: self.scenario.clone(),
}
}
#[must_use = "Generated states for a Monte Carlo run must be used"]
pub fn generate_states(
&self,
skip: usize,
num_runs: usize,
seed: Option<u128>,
) -> Vec<(usize, DispersedState<S>)> {
let rng = match seed {
Some(seed) => Pcg64Mcg::new(seed),
None => Pcg64Mcg::from_entropy(),
};
(&self.random_state)
.sample_iter(rng)
.skip(skip)
.take(num_runs)
.enumerate()
.collect::<Vec<(usize, DispersedState<S>)>>()
}
}
impl<S: Interpolatable, Distr: Distribution<DispersedState<S>>> fmt::Display
for MonteCarlo<S, Distr>
where
DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} - Nyx Monte Carlo - seed: {:?}",
self.scenario, self.seed
)
}
}
impl<S: Interpolatable, Distr: Distribution<DispersedState<S>>> fmt::LowerHex
for MonteCarlo<S, Distr>
where
DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"mc-data-{}-seed-{:?}",
self.scenario.replace(' ', "-"),
self.seed
)
}
}