converge_analytics/packs/descriptive_stats/
solver.rs1use super::types::*;
2use converge_pack::gate::GateResult as Result;
3use converge_pack::gate::{ProblemSpec, ReplayEnvelope, SolverReport};
4
5pub struct DescriptiveStatsSolver;
6
7impl DescriptiveStatsSolver {
8 pub fn solve(
9 &self,
10 input: &DescriptiveStatsInput,
11 spec: &ProblemSpec,
12 ) -> Result<(DescriptiveStatsOutput, SolverReport)> {
13 let n = input.values.len() as f64;
14 let mut sorted = input.values.clone();
15 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
16
17 let min = sorted[0];
18 let max = sorted[sorted.len() - 1];
19 let range = max - min;
20 let mean = input.values.iter().sum::<f64>() / n;
21 let median = percentile_sorted(&sorted, 50.0);
22
23 let variance = input.values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
24 let std_dev = variance.sqrt();
25
26 let skewness = if std_dev > 0.0 {
27 input
28 .values
29 .iter()
30 .map(|v| ((v - mean) / std_dev).powi(3))
31 .sum::<f64>()
32 / n
33 } else {
34 0.0
35 };
36
37 let kurtosis = if std_dev > 0.0 {
38 input
39 .values
40 .iter()
41 .map(|v| ((v - mean) / std_dev).powi(4))
42 .sum::<f64>()
43 / n
44 - 3.0 } else {
46 0.0
47 };
48
49 let percentiles: Vec<PercentileResult> = input
50 .percentiles
51 .iter()
52 .map(|&p| PercentileResult {
53 percentile: p,
54 value: percentile_sorted(&sorted, p),
55 })
56 .collect();
57
58 let output = DescriptiveStatsOutput {
59 count: input.values.len(),
60 mean,
61 median,
62 std_dev,
63 variance,
64 min,
65 max,
66 range,
67 skewness,
68 kurtosis,
69 percentiles,
70 };
71
72 let replay = ReplayEnvelope::minimal(spec.seed());
73 let report = SolverReport::optimal("descriptive-stats-v1", 1.0, replay);
74
75 Ok((output, report))
76 }
77}
78
79fn percentile_sorted(sorted: &[f64], p: f64) -> f64 {
80 if sorted.is_empty() {
81 return 0.0;
82 }
83 if sorted.len() == 1 {
84 return sorted[0];
85 }
86 let rank = p / 100.0 * (sorted.len() - 1) as f64;
87 let lower = rank.floor() as usize;
88 let upper = rank.ceil() as usize;
89 let frac = rank - lower as f64;
90 if lower == upper {
91 sorted[lower]
92 } else {
93 sorted[lower] * (1.0 - frac) + sorted[upper] * frac
94 }
95}