use rand::distributions::Distribution as RandDistribution;
use statrs::{
distribution::{Continuous, ContinuousCDF},
statistics::{Distribution, Max, Median, Min, Mode},
};
use crate::{Error, EstimationFitQuality, Result};
pub trait EstimationDistribution:
Distribution<f64>
+ Continuous<f64, f64>
+ ContinuousCDF<f64, f64>
+ Median<f64>
+ Mode<f64>
+ Min<f64>
+ Max<f64>
+ RandDistribution<f64> {
fn percentile_estimate(&self, p: f64) -> Result<f64> {
if !(0.0..=1.0).contains(&p) {
Err(Error::InvalidPercentile(p))
} else {
Ok(self.inverse_cdf(p))
}
}
fn optimistic_estimate(&self) -> f64 {
self.percentile_estimate(0.05).unwrap()
}
fn pessimistic_estimate(&self) -> f64 {
self.percentile_estimate(0.95).unwrap()
}
fn most_likely_estimate(&self) -> f64 {
self.mode()
}
fn expected_value(&self) -> f64 {
self.mean().unwrap()
}
fn uncertainty(&self) -> f64 {
self.std_dev().unwrap()
}
fn probability_of_completion(&self, estimate: f64) -> f64 {
self.cdf(estimate)
}
fn risk_of_overrun(&self, estimate: f64) -> f64 {
1.0 - self.probability_of_completion(estimate)
}
fn confidence_interval(&self, confidence_level: f64) -> (f64, f64) {
let lower = self.percentile_estimate((1.0 - confidence_level) / 2.0);
let upper = self.percentile_estimate(1.0 - (1.0 - confidence_level) / 2.0);
(lower.unwrap(), upper.unwrap())
}
fn evaluate_fit_quality(&self, data: &[f64]) -> Result<EstimationFitQuality> {
let n = data.len();
if n == 0 {
return Err(Error::InsufficientData {
required: 1,
provided: 0,
});
}
let mean = data.iter().sum::<f64>() / n as f64;
let median = self.median();
let mode = self.mode();
let mse = data.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
let rmse = mse.sqrt();
let mape = data.iter().map(|&x| ((x - mean) / x).abs()).sum::<f64>() / n as f64 * 100.0;
let within_50 = self.calculate_within_interval(data, 0.25, 0.75)?;
let within_68 = self.calculate_within_interval(data, 0.16, 0.84)?;
let within_95 = self.calculate_within_interval(data, 0.025, 0.975)?;
Ok(EstimationFitQuality {
mean_absolute_percentage_error: mape,
root_mean_square_error: rmse,
distribution_mean: mean,
distribution_median: median,
distribution_mode: mode,
within_50_percent: within_50,
within_68_percent: within_68,
within_95_percent: within_95,
sample_size: n,
})
}
fn calculate_within_interval(
&self,
data: &[f64],
lower_percentile: f64,
upper_percentile: f64,
) -> Result<f64> {
let lower_bound = self
.percentile_estimate(lower_percentile)
.map_err(|e| Error::FitQualityError(format!("Error calculating lower bound: {}", e)))?;
let upper_bound = self
.percentile_estimate(upper_percentile)
.map_err(|e| Error::FitQualityError(format!("Error calculating upper bound: {}", e)))?;
let count_within = data
.iter()
.filter(|&&x| x >= lower_bound && x <= upper_bound)
.count();
Ok(count_within as f64 / data.len() as f64)
}
}