mist-hep 0.1.0
ROOT-backed analysis helpers built on mist
Loading...
Searching...
No Matches
stats.h
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2//
3// mist/hep/stats.h — small analysis-stats primitives.
4//
5// Header-only and ROOT-free. They live under mist::hep (not mist::)
6// because the rest of mist-hep is the natural caller and we don't want
7// to creep this kind of helper into the ROOT-free core.
8//
9// Ports / rewrites of:
10// - AAU SquareSum -> quadrature_sum
11// - AAU uBarlowPar -> barlow_parameter
12// - AAU fBarlowCheck -> barlow_passes
13// - AAU uCleanOutsiders -> clip_outliers_nsigma
14//
15#pragma once
16
17#include <array>
18#include <cmath>
19#include <concepts>
20#include <cstddef>
21#include <initializer_list>
22#include <ranges>
23#include <type_traits>
24#include <vector>
25
27
28// ---------------------------------------------------------------------------
29// quadrature_sum: sqrt(sum_i x_i^2).
30//
31// Generic over any input range of arithmetic values, plus a convenience
32// initializer_list overload for callers that want to write the sum inline.
33// ---------------------------------------------------------------------------
34template <std::ranges::input_range R>
35 requires std::is_arithmetic_v<std::ranges::range_value_t<R>>
36[[nodiscard]] double quadrature_sum(R&& xs)
37{
38 double acc = 0.0;
39 for (auto x : xs) acc += static_cast<double>(x) * static_cast<double>(x);
40 return std::sqrt(acc);
41}
42
43[[nodiscard]] inline double quadrature_sum(std::initializer_list<double> xs)
44{
45 double acc = 0.0;
46 for (auto x : xs) acc += x * x;
47 return std::sqrt(acc);
48}
49
50// ---------------------------------------------------------------------------
51// Barlow parameter (Barlow 2002, "Systematic errors: facts and fictions"):
52//
53// t = (x_std - x_var) / sqrt(|sigma_std^2 - sigma_var^2|)
54//
55// Returns NaN when the two errors are identical (the formula is undefined
56// there — the original AAU returned `false` cast to Double_t, which silently
57// turned an "undefined" answer into a passing 0; NaN forces the caller to
58// notice).
59// ---------------------------------------------------------------------------
60[[nodiscard]] inline double barlow_parameter(
61 double x_std, double sigma_std,
62 double x_var, double sigma_var)
63{
64 const double s2 = sigma_std * sigma_std;
65 const double v2 = sigma_var * sigma_var;
66 const double denom = std::sqrt(std::fabs(s2 - v2));
67 if (denom == 0.0) return std::nan("");
68 return (x_std - x_var) / denom;
69}
70
71// True when |t| <= threshold (default 1, i.e. the standard Barlow criterion).
72[[nodiscard]] inline bool barlow_passes(
73 double x_std, double sigma_std,
74 double x_var, double sigma_var,
75 double threshold = 1.0)
76{
77 const double t = barlow_parameter(x_std, sigma_std, x_var, sigma_var);
78 if (std::isnan(t)) return true; // identical errors — no information, don't flag
79 return std::fabs(t) <= threshold;
80}
81
82// ---------------------------------------------------------------------------
83// clip_outliers_nsigma: iterative N-sigma outlier rejection.
84//
85// Recomputes mean/std after each pass and removes any sample beyond
86// n_sigma * std from the running mean. Terminates when a full pass removes
87// nothing. Returns the number of samples dropped.
88//
89// Notes vs. the AAU original:
90// - returns count of removals so callers can detect "nothing to clean"
91// - guarded against empty / 1-element inputs (originally would divide by zero)
92// - std uses N (population), not N-1, matching the AAU behaviour
93// ---------------------------------------------------------------------------
94template <typename T>
95 requires std::is_arithmetic_v<T>
96std::size_t clip_outliers_nsigma(std::vector<T>& xs, double n_sigma = 10.0)
97{
98 std::size_t removed = 0;
99 bool changed = true;
100 while (changed) {
101 changed = false;
102 if (xs.size() < 2) return removed;
103
104 double mean = 0.0;
105 for (auto v : xs) mean += static_cast<double>(v);
106 mean /= static_cast<double>(xs.size());
107
108 double var = 0.0;
109 for (auto v : xs) {
110 const double d = static_cast<double>(v) - mean;
111 var += d * d;
112 }
113 var /= static_cast<double>(xs.size());
114 const double sd = std::sqrt(var);
115 if (sd == 0.0) return removed;
116
117 const double cut = n_sigma * sd;
118 for (auto it = xs.begin(); it != xs.end();) {
119 if (std::fabs(static_cast<double>(*it) - mean) >= cut) {
120 it = xs.erase(it);
121 ++removed;
122 changed = true;
123 } else {
124 ++it;
125 }
126 }
127 }
128 return removed;
129}
130
131// ---------------------------------------------------------------------------
132// weighted_mean_rms: reduce a set of {value, error} measurements to
133// {mean, rms}. The values are first skimmed with clip_outliers_nsigma (the
134// per-measurement errors are retained in the input but not used for weighting
135// — an unweighted mean and the population RMS of the survivors are returned).
136// Empty input, or everything clipped away, yields {NaN, NaN} (no div-by-zero).
137// ---------------------------------------------------------------------------
138[[nodiscard]] inline std::array<double, 2>
139weighted_mean_rms(const std::vector<std::array<double, 2>>& measurements,
140 double clip_nsigma = 10.0)
141{
142 const double nan = std::nan("");
143 std::vector<double> values;
144 values.reserve(measurements.size());
145 for (const auto& m : measurements) values.push_back(m[0]);
146
147 clip_outliers_nsigma(values, clip_nsigma);
148 if (values.empty()) return {nan, nan};
149
150 double sum = 0.0;
151 for (const double v : values) sum += v;
152 const double mean = sum / static_cast<double>(values.size());
153
154 double var = 0.0;
155 for (const double v : values) {
156 const double d = v - mean;
157 var += d * d;
158 }
159 var /= static_cast<double>(values.size());
160 return {mean, std::sqrt(var)};
161}
162
163} // namespace mist::hep::stats
Definition stats.h:26
bool barlow_passes(double x_std, double sigma_std, double x_var, double sigma_var, double threshold=1.0)
Definition stats.h:72
std::size_t clip_outliers_nsigma(std::vector< T > &xs, double n_sigma=10.0)
Definition stats.h:96
double quadrature_sum(R &&xs)
Definition stats.h:36
std::array< double, 2 > weighted_mean_rms(const std::vector< std::array< double, 2 > > &measurements, double clip_nsigma=10.0)
Definition stats.h:139
double barlow_parameter(double x_std, double sigma_std, double x_var, double sigma_var)
Definition stats.h:60