#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct EnsembleConfig {
pub n_ensemble: usize,
pub n_threads: usize,
pub rtol: f64,
pub atol: f64,
pub t_span: (f64, f64),
pub max_steps: usize,
pub h_init: f64,
}
impl Default for EnsembleConfig {
fn default() -> Self {
let n_threads = num_cpus::get().max(1);
Self {
n_ensemble: 100,
n_threads,
rtol: 1e-6,
atol: 1e-9,
t_span: (0.0, 1.0),
max_steps: 100_000,
h_init: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct EnsembleResult {
pub trajectories: Vec<Vec<Vec<f64>>>,
pub times: Vec<Vec<f64>>,
pub converged: Vec<bool>,
pub n_steps: Vec<usize>,
}
impl EnsembleResult {
pub fn mean_trajectory(&self) -> Option<Vec<Vec<f64>>> {
if self.trajectories.is_empty() {
return None;
}
let ref_len = self.trajectories[0].len();
if ref_len == 0 {
return None;
}
let n_state = self.trajectories[0][0].len();
let n_members = self.trajectories.len();
let min_len = self
.trajectories
.iter()
.map(|traj| traj.len())
.min()
.unwrap_or(0);
if min_len == 0 {
return None;
}
let mut mean = vec![vec![0.0_f64; n_state]; min_len];
for traj in &self.trajectories {
for (k, step) in traj.iter().take(min_len).enumerate() {
for (j, &val) in step.iter().enumerate() {
mean[k][j] += val;
}
}
}
let n_f = n_members as f64;
for step in mean.iter_mut() {
for val in step.iter_mut() {
*val /= n_f;
}
}
Some(mean)
}
pub fn std_trajectory(&self) -> Option<Vec<Vec<f64>>> {
if self.trajectories.len() < 2 {
return None;
}
let mean = self.mean_trajectory()?;
let min_len = mean.len();
let n_state = mean[0].len();
let n_members = self.trajectories.len();
let mut variance = vec![vec![0.0_f64; n_state]; min_len];
for traj in &self.trajectories {
for (k, step) in traj.iter().take(min_len).enumerate() {
for (j, &val) in step.iter().enumerate() {
let diff = val - mean[k][j];
variance[k][j] += diff * diff;
}
}
}
let n_f = (n_members - 1) as f64;
for step in variance.iter_mut() {
for val in step.iter_mut() {
*val = (*val / n_f).sqrt();
}
}
Some(variance)
}
pub fn quantile_trajectories(&self, q: f64) -> Option<Vec<Vec<f64>>> {
if self.trajectories.is_empty() {
return None;
}
let min_len = self.trajectories.iter().map(|t| t.len()).min().unwrap_or(0);
if min_len == 0 {
return None;
}
let n_state = self.trajectories[0][0].len();
let n_members = self.trajectories.len();
let mut result = vec![vec![0.0_f64; n_state]; min_len];
for k in 0..min_len {
for j in 0..n_state {
let mut vals: Vec<f64> = self
.trajectories
.iter()
.filter(|traj| traj.len() > k)
.map(|traj| traj[k][j])
.collect();
vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let idx = ((q * (n_members - 1) as f64).round() as usize).min(n_members - 1);
result[k][j] = vals[idx];
}
}
Some(result)
}
}