MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Estimate.cpp
Go to the documentation of this file.
1#include "Estimate.hpp"
2
4
5Estimate::Estimate(EstimateModel model, double adapt_rate, size_t window)
6 : m_model(model)
7 , m_adapt_rate(adapt_rate)
8 , m_window(window == 0 ? 1 : window)
9 , m_history(m_window)
10{
11}
12
14{
15 if (m_model != model) {
16 m_model = model;
17 reset();
18 }
19}
20
21void Estimate::set_window(size_t window)
22{
23 m_window = window == 0 ? 1 : window;
26}
27
29{
30 m_state.reset();
32}
33
34double Estimate::update(double sample)
35{
36 m_state.last_raw_sample = sample;
38
39 switch (m_model) {
41 return update_rolling_variance(sample);
43 return update_ewm_variance(sample);
45 return update_mad(sample);
47 return update_quiet_period(sample);
49 return update_trend(sample);
50 default:
51 return update_ewm_variance(sample);
52 }
53}
54
55double Estimate::confidence(double step) const
56{
57 const double f = m_state.floor;
58 if (f < 1e-12)
59 return (std::abs(step) > 0.0) ? 1.0 : 0.0;
60
61 const double ratio = std::abs(step) / f;
62 return std::clamp(ratio / 3.0, 0.0, 1.0);
63}
64
66{
67 m_history.push(sample);
68 const auto view = m_history.linearized_view();
69
70 const double v = variance(view);
71 const double mean = std::accumulate(view.begin(), view.end(), 0.0) / static_cast<double>(view.size());
72
75 m_state.floor = std::sqrt(v);
77
78 return m_state.floor;
79}
80
81double Estimate::update_ewm_variance(double sample)
82{
83 if (m_state.sample_count == 1) {
84 m_state.running_mean = sample;
86 m_state.floor = 0.0;
87 m_state.filtered_value = sample;
88 return m_state.floor;
89 }
90
91 const double delta = sample - m_state.running_mean;
93
94 const double delta2 = sample - m_state.running_mean;
96
99
100 return m_state.floor;
101}
102
103double Estimate::update_mad(double sample)
104{
105 m_history.push(sample);
106 const auto view = m_history.linearized_view();
107
108 std::vector<double> sorted(view.begin(), view.end());
109 std::ranges::sort(sorted);
110 const double median = sorted[sorted.size() / 2];
111
112 m_state.running_mean = median;
114 m_state.filtered_value = median;
115
116 return m_state.floor;
117}
118
119double Estimate::update_quiet_period(double sample)
120{
121 m_history.push(sample);
122
124 m_state.filtered_value = sample;
125 return m_state.floor;
126 }
127
128 const auto view = m_history.linearized_view();
129
130 std::vector<double> chronological(view.rbegin(), view.rend());
131 std::span<const double> ordered(chronological);
132
133 const double mean = std::accumulate(chronological.begin(), chronological.end(), 0.0) / static_cast<double>(chronological.size());
134 const double window_stddev = stddev(view);
135 const double trend_ratio = trend_explained_ratio(ordered);
136
137 const bool looks_quiet = trend_ratio < 0.5;
138
139 if (looks_quiet) {
141 m_state.running_variance = window_stddev * window_stddev;
142 m_state.floor = window_stddev;
144 } else {
145 m_state.filtered_value = sample;
146 }
147
148 return m_state.floor;
149}
150
151double Estimate::update_trend(double sample)
152{
153 m_history.push(sample);
154
155 if (m_state.sample_count < 2) {
156 m_state.trend_slope = 0.0;
158 m_state.floor = 0.0;
159 m_state.filtered_value = sample;
160 return m_state.floor;
161 }
162
163 const auto view = m_history.linearized_view();
164
165 std::vector<double> chronological(view.rbegin(), view.rend());
166 std::span<const double> ordered(chronological);
167
168 const double slope = trend_slope(ordered);
169 m_state.trend_slope = slope;
171
172 const double mean = std::accumulate(chronological.begin(), chronological.end(), 0.0) / static_cast<double>(chronological.size());
174
175 const auto n = static_cast<double>(chronological.size());
176 double sum_x = 0.0;
177 for (size_t i = 0; i < chronological.size(); ++i)
178 sum_x += static_cast<double>(i);
179 const double mean_x = sum_x / n;
180 const double intercept = mean - slope * mean_x;
181 const auto newest_x = static_cast<double>(chronological.size() - 1);
182 m_state.filtered_value = intercept + slope * newest_x;
183
184 const double first = chronological.front();
185 const double last = chronological.back();
186 const auto span_n = static_cast<double>(chronological.size() - 1);
187
188 double residual_sq_sum = 0.0;
189 for (size_t i = 0; i < chronological.size(); ++i) {
190 const double t = static_cast<double>(i) / span_n;
191 const double trend_value = first + t * (last - first);
192 const double residual = chronological[i] - trend_value;
193 residual_sq_sum += residual * residual;
194 }
195
196 const double residual_var = (chronological.size() > 2)
197 ? residual_sq_sum / static_cast<double>(chronological.size() - 2)
198 : 0.0;
199
200 m_state.running_variance = residual_var;
201 m_state.floor = std::sqrt(residual_var);
202
203 return m_state.floor;
204}
205
206// =============================================================================
207// Stateless span characterization
208// =============================================================================
209
210double Estimate::variance(std::span<const double> samples) noexcept
211{
212 if (samples.size() < 2)
213 return 0.0;
214
215 const double mean = std::accumulate(samples.begin(), samples.end(), 0.0) / static_cast<double>(samples.size());
216
217 double sq_sum = 0.0;
218 for (double v : samples)
219 sq_sum += (v - mean) * (v - mean);
220
221 return sq_sum / static_cast<double>(samples.size() - 1);
222}
223
224double Estimate::stddev(std::span<const double> samples) noexcept
225{
226 return std::sqrt(variance(samples));
227}
228
229double Estimate::median_absolute_deviation(std::span<const double> samples) noexcept
230{
231 if (samples.empty())
232 return 0.0;
233
234 std::vector<double> sorted(samples.begin(), samples.end());
235 std::ranges::sort(sorted);
236 const double median = sorted[sorted.size() / 2];
237
238 std::vector<double> deviations;
239 deviations.reserve(sorted.size());
240 for (double v : sorted)
241 deviations.push_back(std::abs(v - median));
242 std::ranges::sort(deviations);
243
244 return deviations[deviations.size() / 2] * 1.4826;
245}
246
247std::vector<size_t> Estimate::flag_outliers(std::span<const double> samples, double threshold_mad) noexcept
248{
249 std::vector<size_t> result;
250 if (samples.size() < 2)
251 return result;
252
253 std::vector<double> sorted(samples.begin(), samples.end());
254 std::ranges::sort(sorted);
255 const double median = sorted[sorted.size() / 2];
256 const double scaled_mad = median_absolute_deviation(samples);
257
258 if (scaled_mad < 1e-12)
259 return result;
260
261 for (size_t i = 0; i < samples.size(); ++i) {
262 const double dev = std::abs(samples[i] - median) / scaled_mad;
263 if (dev > threshold_mad)
264 result.push_back(i);
265 }
266
267 return result;
268}
269
270double Estimate::trend_explained_ratio(std::span<const double> samples) noexcept
271{
272 if (samples.size() < 2)
273 return 0.0;
274
275 const double first = samples.front();
276 const double last = samples.back();
277 const auto n = static_cast<double>(samples.size() - 1);
278
279 const double total_mean = std::accumulate(samples.begin(), samples.end(), 0.0) / static_cast<double>(samples.size());
280
281 double total_var = 0.0;
282 double residual_var = 0.0;
283
284 for (size_t i = 0; i < samples.size(); ++i) {
285 const double t = static_cast<double>(i) / n;
286 const double trend_value = first + t * (last - first);
287
288 const double total_dev = samples[i] - total_mean;
289 total_var += total_dev * total_dev;
290
291 const double residual = samples[i] - trend_value;
292 residual_var += residual * residual;
293 }
294
295 if (total_var < 1e-12)
296 return 0.0;
297
298 return std::clamp(1.0 - (residual_var / total_var), 0.0, 1.0);
299}
300
301double Estimate::trend_slope(std::span<const double> samples) noexcept
302{
303 if (samples.size() < 2)
304 return 0.0;
305
306 const auto n = static_cast<double>(samples.size());
307
308 double sum_x = 0.0;
309 double sum_y = 0.0;
310 double sum_xy = 0.0;
311 double sum_xx = 0.0;
312
313 for (size_t i = 0; i < samples.size(); ++i) {
314 const auto x = static_cast<double>(i);
315 const double y = samples[i];
316 sum_x += x;
317 sum_y += y;
318 sum_xy += x * y;
319 sum_xx += x * x;
320 }
321
322 const double denom = (n * sum_xx) - (sum_x * sum_x);
323 if (std::abs(denom) < 1e-12)
324 return 0.0;
325
326 return ((n * sum_xy) - (sum_x * sum_y)) / denom;
327}
328
329} // namespace MayaFlux::Kinesis::Stochastic
void reset()
Resets internal state.
Definition Estimate.cpp:28
static double trend_slope(std::span< const double > samples) noexcept
Linear trend slope across a span.
Definition Estimate.cpp:301
double update_rolling_variance(double sample)
Definition Estimate.cpp:65
Estimate(EstimateModel model=EstimateModel::EWM_VARIANCE, double adapt_rate=0.05, size_t window=32)
Constructs an estimator with the specified model.
Definition Estimate.cpp:5
double update(double sample)
Feed one new sample, updating the running estimate.
Definition Estimate.cpp:34
static std::vector< size_t > flag_outliers(std::span< const double > samples, double threshold_mad=3.0) noexcept
Flags samples whose deviation from the median exceeds a threshold.
Definition Estimate.cpp:247
static double variance(std::span< const double > samples) noexcept
Sample variance of a span.
Definition Estimate.cpp:210
void set_window(size_t window)
Sets the window size used by windowed models.
Definition Estimate.cpp:21
double update_ewm_variance(double sample)
Definition Estimate.cpp:81
double confidence(double step) const
Confidence that a step is signal, not floor.
Definition Estimate.cpp:55
static double trend_explained_ratio(std::span< const double > samples) noexcept
Fraction of a span's variance attributable to its linear trend.
Definition Estimate.cpp:270
double update_quiet_period(double sample)
Definition Estimate.cpp:119
Memory::HistoryBuffer< double > m_history
Definition Estimate.hpp:400
void set_model(EstimateModel model)
Changes active model.
Definition Estimate.cpp:13
static double median_absolute_deviation(std::span< const double > samples) noexcept
Median absolute deviation of a span.
Definition Estimate.cpp:229
static double stddev(std::span< const double > samples) noexcept
Standard deviation of a span.
Definition Estimate.cpp:224
void resize(size_t new_capacity)
Resize buffer capacity.
std::span< T > linearized_view()
Get mutable linearized view of entire history.
void push(const T &value)
Push new value to front of history.
void reset()
Reset buffer to initial state (all zeros)
EstimateModel
Strategies for characterizing an evolving stream's statistical behavior.
Definition Estimate.hpp:20
double mean(const std::vector< double > &data)
Calculate mean of single-channel data.
Definition Yantra.cpp:55