docs: exact range queries belong on Series, not on the bucket-grained pyramid
This commit is contained in:
1 parent
6866457155
commit
c93c98002b
2 files changed
+133
-28
No files matched your search
@@ -115,9 +115,17 @@ one stage macOS cannot be relied on for.
|
||||
### 4.4 Interval reduction reuses the pyramid
|
||||
|
||||
Cursor measurements (`Δx`, `Δy`, slope, RMS, mean, min/max, integral over a
|
||||
dragged region) walk the same min/max chain and return in `O(log n)`; the series
|
||||
is never scanned linearly. `pyramid().reduce(a, b)` is the single implementation
|
||||
used both by measurement and by auto-scale.
|
||||
dragged region) are answered from the same pyramid instead of by scanning the
|
||||
series. `Series::reduce`/`sum`/`sumsq` cover the bucket-aligned interior with
|
||||
pyramid aggregates and scan at most two partial edge buckets (≤ 2 × kBucket = 128
|
||||
samples), so a query's cost is independent of series length. `Series::reduce` is
|
||||
the single implementation used by measurement, auto-scale and hit-testing.
|
||||
|
||||
The split matters: a min/max pyramid stores *aggregates per bucket*, so it can
|
||||
only answer exactly for whole buckets — a range with partial edge buckets would
|
||||
otherwise silently include samples outside the range. Exactness therefore lives
|
||||
on `Series` (which owns the raw samples), and `MinMaxPyramid` exposes only
|
||||
bucket-exact queries, named `*_buckets` so they cannot be mistaken for exact ones.
|
||||
|
||||
### 4.5 Decimation approach — decision
|
||||
|
||||
@@ -189,20 +197,41 @@ namespace data {
|
||||
public:
|
||||
void reserve(u64 n); void append(f32 v); void clear();
|
||||
u64 size() const;
|
||||
Range reduce(u64 begin, u64 end) const; // O(log n)
|
||||
u32 level_for(u64 n, f64 samples_per_pixel) const;
|
||||
std::span<const MinMax> level(u32 l) const;
|
||||
u64 snap(f64 x, f64 x0, f64 dx) const; // nearest sample index
|
||||
// Whole-bucket only: aggregates over the level-0 buckets fully contained in
|
||||
// [begin, end). Partial edges are excluded, not approximated. Callers wanting
|
||||
// an exact answer over an arbitrary range use Series::reduce/sum/sumsq.
|
||||
Range reduce_buckets(u64 begin, u64 end) const;
|
||||
f64 sum_buckets(u64 begin, u64 end) const;
|
||||
f64 sumsq_buckets(u64 begin, u64 end) const;
|
||||
};
|
||||
|
||||
class Series { // abstract
|
||||
public:
|
||||
virtual ~Series() = default;
|
||||
virtual u64 size() const = 0;
|
||||
virtual f64 x0() const = 0; // x of sample 0
|
||||
virtual f64 dx() const = 0; // uniform x spacing
|
||||
virtual Range x_bounds() const = 0;
|
||||
virtual Range y_bounds() const = 0;
|
||||
virtual const MinMaxPyramid& pyramid() const = 0;
|
||||
virtual void gather(u32 level, u64 begin, u64 end, std::vector<f32>& out_xy) const = 0;
|
||||
|
||||
// Exact, O(kBucket) worst case regardless of series length: the bucket-aligned
|
||||
// interior comes from the pyramid, the at-most-two partial edge buckets are
|
||||
// read through copy_raw() and scanned. Implemented once, non-virtually, in
|
||||
// src/data/series.cpp. Preconditions: begin < end, end <= size().
|
||||
Range reduce(u64 begin, u64 end) const;
|
||||
f64 sum(u64 begin, u64 end) const;
|
||||
f64 sumsq(u64 begin, u64 end) const;
|
||||
|
||||
protected:
|
||||
// Copies raw y samples for [begin, end) into `out`, which the caller sized to
|
||||
// (end - begin). Handles ring wraparound. Only ever called with small edge
|
||||
// spans by the queries above; not on a per-sample path.
|
||||
virtual void copy_raw(u64 begin, u64 end, f32* out) const = 0;
|
||||
};
|
||||
|
||||
class ArraySeries final : public Series { // own storage, uniform dx
|
||||
|
||||
Reference in new issue
Block a user