measure: top/bottom/amplitude read the flat levels, not the extremes
A bench scope separates Vtop/Vbase from Vmax/Vmin because a band-limited edge overshoots and rings: the mock's `square` at 30 harmonics reaches ±1.179 where its plateau is ±1. On the rippled square the old readings were a property of the edges rather than of the waveform -- `top` 0.986496, `amplitude` 1.179350 -- and `amplitude` reported the overshoot. `top`, `bottom` and `amplitude` are now the flat levels and their delta. A flat level is a density question -- the plateau holds most of the samples and the ringing a few percent -- so `measure_scan_buckets()` folds a 64-bin histogram of the whole buckets' midpoints while it has the list in hand (one observation per bucket, each bin carrying its buckets' exact sum and finite count), and `measure_flat_levels()` takes, either side of the split, the densest bin holding at least half the densest one's buckets -- the outermost of those, which is what makes a waveform with no plateau read its peak -- and the exact mean of the samples of the buckets within 5 % of the window's peak-to-peak of it. 5 % is deliberately under the ~9 %-of-swing Gibbs overshoot. `amplitude` is `top - bottom` (a scope's Vamplitude), not the half-swing it was. Measured, fast mock, 2 s window, harmonics 0 -> 30: top 1.000000 -> 0.999983, bottom -1.000000 -> -0.999983, amplitude 2.000000 -> 1.999966, while `peak to peak` keeps the extremes (2.000000 -> 2.358701). On the 1 Hz sine top is 0.957988 (it was 0.636620, the half-wave mean; a sine has no plateau, so the densest band is at its peak) with sd 0.0014 over a scrolling window, and `peak to peak` is still exactly 2. No raw pass and no new fetch: the histogram is the same fetched bucket list read a second time, gated on a pane that holds one of the three types. `raw` is unchanged (0.1% clean, 2.1% with the --noise 0.1 interferer, against 0.1%/2.1% before) for 0.44 -> 0.50 ms and 0.52 -> 0.65 ms a pass. A window the pyramid cannot summarize -- an hf-filtered walk, or one shorter than a bucket -- keeps the old readings: the mean of each side of the split, and the window's peak to peak for amplitude. Docs: the flat-level invariant (and the two 2/pi assertions it replaces), the probe readings, the manual's measurements page and the README.
This commit is contained in:
1 parent
afd2a6cdc1
commit
e83ad86b8f
10 files changed
+495
-80
No files matched your search
+26
-3
@@ -118,15 +118,24 @@ void measure_dialog_sync(MeasureDialog* d) {
|
||||
"show on plot (ruler at the value, marker where it is)");
|
||||
break;
|
||||
case MeasureType::PeakToPeak:
|
||||
case MeasureType::Amplitude:
|
||||
gtk_label_set_text(GTK_LABEL(d->threshold_label), "");
|
||||
gtk_label_set_text(GTK_LABEL(d->side_label), "");
|
||||
gtk_check_button_set_label(GTK_CHECK_BUTTON(d->show),
|
||||
"show on plot (rulers at both extremes)");
|
||||
break;
|
||||
case MeasureType::Amplitude:
|
||||
gtk_label_set_text(GTK_LABEL(d->threshold_label), "");
|
||||
gtk_label_set_text(GTK_LABEL(d->side_label), "");
|
||||
// Amplitude is the delta between the two *flat* levels, so its rulers are those two and not
|
||||
// the extremes a peak-to-peak draws: the pair of rulers is the reading.
|
||||
gtk_check_button_set_label(GTK_CHECK_BUTTON(d->show),
|
||||
"show on plot (rulers at both flat levels)");
|
||||
break;
|
||||
case MeasureType::Top:
|
||||
case MeasureType::Bottom:
|
||||
gtk_label_set_text(GTK_LABEL(d->threshold_label), "split % of the range");
|
||||
// The split picks the *side*: the level is the densest band either above or below it, so any
|
||||
// split between the two plateaus reads the same -- it is not a mean over the side any more.
|
||||
gtk_label_set_text(GTK_LABEL(d->threshold_label), "split % of the range (picks the side)");
|
||||
gtk_check_button_set_label(GTK_CHECK_BUTTON(d->show), "show on plot (ruler at the level)");
|
||||
break;
|
||||
case MeasureType::PulseDuration:
|
||||
@@ -320,6 +329,12 @@ void update_measures(App& app, bool force, bool accumulate) {
|
||||
}
|
||||
want_window[i] = static_cast<int>(wi);
|
||||
App::MeasureWindow& w = app.measure_samples[wi];
|
||||
// The three types whose value is a flat level: pass one folds the bucket histogram for this
|
||||
// window only if one of them is in the pane.
|
||||
if (m.type == MeasureType::Top || m.type == MeasureType::Bottom ||
|
||||
m.type == MeasureType::Amplitude) {
|
||||
w.flat_wanted = true;
|
||||
}
|
||||
double pct = 0.0;
|
||||
if (!measure_level_pct(m.type, m.options, pct)) continue;
|
||||
// A type that does not read the band must not split the accumulator over it: its sums split at the bare
|
||||
@@ -348,6 +363,12 @@ void update_measures(App& app, bool force, bool accumulate) {
|
||||
// for the samples produced since this frame's `acquire_all()`, and for those straddling
|
||||
// buckets; a window shorter than a bucket, or a signal noisy enough to straddle everywhere,
|
||||
// degrades back to the O(window) cost and nothing else changes.
|
||||
//
|
||||
// The flat-level types (`top`/`bottom`/`amplitude`) are the one thing that reads a *third* time
|
||||
// -- and it is the same fetched bucket list, not a third fetch: pass one's bucket walk folds its
|
||||
// own histogram while it has the buckets in hand (see `measure_scan_buckets`). It is O(buckets)
|
||||
// and it reads no sample, so `raw` does not move; a pane with none of those three types does
|
||||
// not pay for it at all.
|
||||
constexpr std::size_t kChunk = 8192;
|
||||
std::vector<MeasureBucket>& buckets = app.measure_bucket_scratch;
|
||||
for (App::MeasureWindow& w : app.measure_samples) {
|
||||
@@ -385,7 +406,9 @@ void update_measures(App& app, bool force, bool accumulate) {
|
||||
|
||||
const auto scan = [&](const MeasureChunk& c) { measure_scan(c, w.stats, w.scan_filter); };
|
||||
raw(0, bulk_from, scan);
|
||||
if (!buckets.empty()) measure_scan_buckets(buckets.data(), buckets.size(), w.stats);
|
||||
if (!buckets.empty()) {
|
||||
measure_scan_buckets(buckets.data(), buckets.size(), w.stats, w.flat_wanted);
|
||||
}
|
||||
raw(bulk_to, n, scan);
|
||||
// A bucket named the 64 samples that hold an extreme, not the sample. One scan of that bucket
|
||||
// pins it -- two buckets at most, whatever the window's size.
|
||||
|
||||
Reference in new issue
Block a user