use super::{MilestoneFinish, SimulationSummary, percentile};
use crate::ScheduleError;
use crate::network::{EPSILON, Network, Times};
use dpm_model::WorkItemId;
#[derive(Debug, Clone, Default, PartialEq)]
pub(crate) struct SortedSamples(Vec<f64>);
impl SortedSamples {
pub(crate) fn new(mut values: Vec<f64>) -> Self {
values.sort_by(f64::total_cmp);
Self(values)
}
pub(crate) fn percentile(&self, p: f64) -> f64 {
percentile(&self.0, p)
}
pub(crate) fn probability_by(&self, horizon_hours: f64) -> f64 {
if self.0.is_empty() {
return 0.0;
}
self.0.partition_point(|hours| *hours <= horizon_hours) as f64 / self.0.len() as f64
}
pub(crate) fn std_dev(&self) -> f64 {
let count = self.0.len();
if count < 2 {
return 0.0;
}
let mean = self.0.iter().sum::<f64>() / count as f64;
let squares: f64 = self
.0
.iter()
.map(|hours| (hours - mean) * (hours - mean))
.sum();
(squares / (count - 1) as f64).sqrt()
}
}
pub(crate) struct Samples {
finishes: Vec<f64>,
critical_counts: Vec<usize>,
milestones: Vec<(WorkItemId, usize, Vec<f64>)>,
}
impl Samples {
pub(crate) fn new(
network: &Network,
iterations: usize,
milestones: Vec<(WorkItemId, usize)>,
) -> Self {
Self {
finishes: Vec::with_capacity(iterations),
critical_counts: vec![0; network.order().len()],
milestones: milestones
.into_iter()
.map(|(id, at)| (id, at, Vec::with_capacity(iterations)))
.collect(),
}
}
pub(crate) fn record(
&mut self,
network: &Network,
durations: &[f64],
times: &Times,
) -> Result<(), ScheduleError> {
self.finishes.push(times.finish);
for (at, count) in self.critical_counts.iter_mut().enumerate() {
if network.total_float(times, at)? <= EPSILON {
*count = count.wrapping_add(1);
}
}
for (_, at, finishes) in &mut self.milestones {
finishes.push(times.finishes(network, durations, *at)?.0);
}
Ok(())
}
pub(crate) fn summarize(self, network: &Network, iterations: usize) -> SimulationSummary {
let finishes = SortedSamples::new(self.finishes);
let criticality = network
.order()
.iter()
.zip(self.critical_counts)
.map(|(id, count)| (*id, count as f64 / iterations as f64))
.collect();
let milestones = self
.milestones
.into_iter()
.map(|(id, _, samples)| (id, MilestoneFinish::new(SortedSamples::new(samples))))
.collect();
SimulationSummary {
iterations,
p50_finish_hours: finishes.percentile(0.50),
p80_finish_hours: finishes.percentile(0.80),
p95_finish_hours: finishes.percentile(0.95),
criticality,
sampled_std_dev_hours: finishes.std_dev(),
milestones,
finishes,
}
}
}