// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2026 Martino Ferrari #include #include namespace plot { Measurement Measure::evaluate(const data::Series& s, u64 begin, u64 end) { Measurement m; // Both guards run before any query, so an empty, inverted or out-of-range // interval returns an invalid result without touching the series. if (end <= begin || end > s.size()) return m; // Exact extremes and sums come from Series -- never from the pyramid's // *_buckets forms, which answer in whole buckets only and would drop up to two // partial edge buckets, silently returning wrong extrema and sums for any // interval that is not bucket-aligned. Each query here is O(kBucket) worst // case, so a measurement never scales with series length. const Range extremes = s.reduce(begin, end); const f64 total = s.sum(begin, end); const f64 squared = s.sumsq(begin, end); const f64 n = static_cast(end - begin); const f64 step = s.dx(); m.ymin = extremes.min; m.ymax = extremes.max; m.mean = total / n; m.rms = std::sqrt(squared / n); m.integral = total * step; // left-Riemann sum: see the note in measure.hpp if (end - begin >= 2) { // First and last sample of the interval: a single-sample query reduces to // that sample's value, and costs the same O(kBucket) as any other. const f64 first = s.reduce(begin, begin + 1).min; const f64 last = s.reduce(end - 1, end).min; m.dy = last - first; m.dx = static_cast(end - 1 - begin) * step; m.slope = m.dx != 0.0 ? m.dy / m.dx : 0.0; } m.valid = true; return m; } } // namespace plot