Files
plotter/tests/test_measure.cpp
T
Martino Ferrari ed3a8ce645 licence: MIT LICENSE and SPDX identifiers in every first-party file
The repository had no licence at all, so every source was all-rights-reserved
by default. Add the verbatim MIT text at LICENSE (Copyright (c) 2026 Martino
Ferrari) and a two-line SPDX header to each of the 113 first-party sources --
48 include/plot, 26 src, 10 adapters, 5 examples, 21 tests, 3 tools -- so the
grant travels with each file rather than relying on the root document alone.

Scripts keep their shebang on line 1; the SPDX lines follow it. Genuinely
vendored code is untouched: third_party/stb_truetype.h carries no header of
ours. src/render/embedded_mono_font.cpp is generated, so tools/fonts/gen_atlas.py
emit() now writes the two lines into its header comment and the file was
regenerated -- gen_atlas.py --check passes and the regeneration adds only those
comment lines to the previously committed file.

README's provenance section now states MIT and links LICENSE.

No code, test or public API change.
2026-09-28 22:13:40 +02:00

68 lines
3.2 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 Martino Ferrari
#include "harness.hpp"
#include <plot/plot/measure.hpp>
#include <plot/data/array_series.hpp>
#include <plot/data/detail/cover_count.hpp>
#include <algorithm>
#include <atomic>
#include <cmath>
#include <vector>
PLOT_TEST(measurement_over_interval_matches_direct_computation) {
std::vector<f32> y; for (int i = 0; i < 1000; ++i) y.push_back(f32(std::sin(static_cast<double>(i) * 0.01)));
plot::data::ArraySeries s(std::move(y), 0.0, 1.0);
const auto m = plot::Measure::evaluate(s, 100, 800);
double sum = 0, sumsq = 0, integral = 0; float lo = 1e30f, hi = -1e30f;
for (u64 i = 100; i < 800; ++i) {
const double v = std::sin(static_cast<double>(i) * 0.01);
sum += v; sumsq += v * v; integral += v; // dx == 1
lo = std::min(lo, float(v)); hi = std::max(hi, float(v));
}
const double n = 700;
PLOT_CHECK_NEAR(m.mean, sum / n, 1e-5);
PLOT_CHECK_NEAR(m.rms, std::sqrt(sumsq / n), 1e-5);
PLOT_CHECK_NEAR(m.integral, integral, 1e-3);
PLOT_CHECK_NEAR(m.ymin, lo, 1e-6);
PLOT_CHECK_NEAR(m.ymax, hi, 1e-6);
PLOT_CHECK_NEAR(m.dx, 699.0, 1e-9);
}
PLOT_TEST(measurement_uses_pyramid_not_scan) {
// Guard against a future regression that linear-scans the series, counted rather
// than timed: a query's cost is the pyramid's covering decomposition -- at most
// two entries per level -- so the entry count must depend on the *interval* and
// never on the series length. The wall-clock bound this replaced (1000 evals in
// < 50 ms) failed on a loaded machine and passed vacuously on a fast one, while
// saying nothing about the shape of the work.
std::vector<f32> big(1u << 22, 1.0f); // 4M samples
plot::data::ArraySeries s_big(std::move(big), 0.0, 1.0);
std::vector<f32> small(1u << 13, 1.0f); // 8k samples: 512x shorter
plot::data::ArraySeries s_small(std::move(small), 0.0, 1.0);
auto covers = [](const plot::data::Series& s, u64 begin, u64 end) {
plot::data::detail::cover_entry_count().store(0, std::memory_order_relaxed);
(void)plot::Measure::evaluate(s, begin, end);
return plot::data::detail::cover_entry_count().load(std::memory_order_relaxed);
};
// The same absolute interval over both series walks the same run of level-0
// buckets, so it must fold the same entries: a decomposition that degenerated
// with series length -- one bucket at a time, or a scan -- shows up here.
const u64 big_covers = covers(s_big, 1024, 7000);
PLOT_CHECK(big_covers == covers(s_small, 1024, 7000));
PLOT_CHECK(big_covers > 0u); // the interior came from the pyramid
PLOT_CHECK(big_covers <= 32u); // ~2 entries per level, few levels here
// A 1-sample interval needs no whole bucket at all: its answer is the two partial
// edge buckets a pyramid of complete buckets cannot cover (at most 2 * kBucket raw
// samples through copy_raw), so it folds nothing whatever the series holds.
PLOT_CHECK(covers(s_big, 1'000'000, 1'000'001) == 0u);
}
PLOT_TEST(empty_and_single_sample_intervals_are_valid_false) {
std::vector<f32> y{3.0f};
plot::data::ArraySeries s(std::move(y), 0.0, 1.0);
PLOT_CHECK(!plot::Measure::evaluate(s, 0, 0).valid);
PLOT_CHECK(!plot::Measure::evaluate(s, 1, 1).valid);
}