use crate::errors::QlResult;
use crate::fail;
use crate::types::{Real, Size};
mod gaussianstatistics;
mod generalstatistics;
mod histogram;
mod incrementalstatistics;
mod riskstatistics;
pub use gaussianstatistics::{GaussianStatistics, StatsHolder};
pub use generalstatistics::GeneralStatistics;
pub use histogram::{Histogram, HistogramAlgorithm};
pub use incrementalstatistics::IncrementalStatistics;
pub use riskstatistics::RiskStatistics;
pub(crate) fn check_sample(value: Real, weight: Real) -> QlResult<()> {
if value.is_nan() {
fail!("sample value must not be NaN");
}
#[allow(clippy::neg_cmp_op_on_partial_ord)]
if !(weight >= 0.0) {
fail!("negative weight ({weight}) not allowed");
}
Ok(())
}
pub trait MeanStdDev {
fn mean(&self) -> QlResult<Real>;
fn standard_deviation(&self) -> QlResult<Real>;
}
pub trait Statistics: MeanStdDev {
fn samples(&self) -> Size;
fn weight_sum(&self) -> Real;
fn variance(&self) -> QlResult<Real>;
fn error_estimate(&self) -> QlResult<Real>;
fn skewness(&self) -> QlResult<Real>;
fn kurtosis(&self) -> QlResult<Real>;
fn min(&self) -> QlResult<Real>;
fn max(&self) -> QlResult<Real>;
fn add_weighted(&mut self, value: Real, weight: Real) -> QlResult<()>;
fn reset(&mut self);
fn add(&mut self, value: Real) -> QlResult<()> {
self.add_weighted(value, 1.0)
}
fn add_sequence<I>(&mut self, values: I) -> QlResult<()>
where
I: IntoIterator<Item = Real>,
{
for value in values {
self.add(value)?;
}
Ok(())
}
fn add_sequence_weighted<I>(&mut self, values: I) -> QlResult<()>
where
I: IntoIterator<Item = (Real, Real)>,
{
for (value, weight) in values {
self.add_weighted(value, weight)?;
}
Ok(())
}
}
pub trait EmpiricalStatistics: Statistics {
fn expectation_value<F, P>(&self, f: F, in_range: P) -> Option<(Real, Size)>
where
F: Fn(Real) -> Real,
P: Fn(Real) -> bool;
fn percentile(&mut self, percent: Real) -> QlResult<Real>;
fn top_percentile(&mut self, percent: Real) -> QlResult<Real>;
}
#[cfg(test)]
pub(crate) mod testutil {
use crate::math::distributions::normal::InverseCumulativeNormal;
use crate::types::Real;
pub const AVERAGES: [Real; 5] = [-100.0, -1.0, 0.0, 1.0, 100.0];
pub const SIGMAS: [Real; 3] = [0.1, 1.0, 100.0];
pub const N_SAMPLES: usize = (1 << 16) - 1;
pub fn sobol_normal_samples(average: Real, sigma: Real) -> Vec<Real> {
let inverse =
InverseCumulativeNormal::new(average, sigma).expect("valid normal parameters");
(1..=N_SAMPLES as u32)
.map(|i| {
let gray = i ^ (i >> 1);
let u = Real::from(gray.reverse_bits()) / (1u64 << 32) as Real;
inverse.value(u).expect("u lies in (0, 1)")
})
.collect()
}
pub fn check(
label: &str,
average: Real,
sigma: Real,
calculated: Real,
expected: Real,
tolerance: Real,
) {
assert!(
(calculated - expected).abs() <= tolerance,
"wrong {label} for N({average}, {sigma}): calculated {calculated}, expected {expected}, tolerance {tolerance}"
);
}
}