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
@@ -218,19 +218,32 @@ Two lines of the self-check output are assertions in disguise:
|
||||
pane_h=H switch_on=X reset_sensitive=Y`
|
||||
(stderr, `UDPSCOPE_PROBE_MEASURES_MS`) — adds one measurement of every type on the first
|
||||
plottable signal and lets the tick compute them, so what it prints is what a user reads.
|
||||
On the test stream's 1 Hz ±1 sine the values must be: average ≈ 0, `top` ≈ 0.64 and
|
||||
`bottom` ≈ -0.64 (the mean of each half of a sine), max/min ≈ ±1, `pulse duration` ≈ 0.5 s
|
||||
with `duty` ≈ 0.5, `period` ≈ 1 s and `frequency` ≈ 1 Hz. It needs **`UDPSCOPE_ENABLE_ALL_MS`
|
||||
ahead of it** or it prints `no acquired signal to measure` and the dump reads `n=0`.
|
||||
On the test stream's 1 Hz ±1 sine the values must be: average ≈ 0, `top` ≈ 0.96 and
|
||||
`bottom` ≈ -0.96, `amplitude` ≈ 1.92, max/min ≈ ±1, `peak to peak` **exactly 2**,
|
||||
`pulse duration` ≈ 0.5 s
|
||||
with `duty` ≈ 0.5, `period` ≈ 1 s and `frequency` ≈ 1 Hz. **`top` is no longer 2/π**, and that
|
||||
is the point of it: it is the *flat* level of the upper side, read from the density of the
|
||||
window rather than from a mean over a side, and a sine has no plateau — so the densest band
|
||||
sits at its peak and the reading is near it, not the 0.636620 a mean over a half would give.
|
||||
`amplitude` is that pair's delta (`top - bottom`), which is why it reads about 1.92 rather than
|
||||
the half-swing it used to: a bench scope's `Vamplitude` is `Vtop - Vbase`. The measured
|
||||
figures, and the reason they are what they are, are in
|
||||
[invariants-measurements.md](invariants-measurements.md) — on the mock's `square` the
|
||||
separation is the visible one (`top`/`bottom` `0.999983`/`-0.999983`, `amplitude` `1.999966`,
|
||||
while `peak to peak` keeps the edges' ±1.179 overshoot at `2.358701`). It needs
|
||||
**`UDPSCOPE_ENABLE_ALL_MS` ahead of it** or it prints `no acquired signal to measure` and the
|
||||
dump reads `n=0`.
|
||||
**Run it against `--noise 0.1 --freq 1` as well, and the three crossing types must still read
|
||||
those figures** (measured: `period=1.000100 frequency=0.999900 pulse duration=0.504100`): the
|
||||
hysteresis band is the only thing holding them there, a single level reads a 500 Hz interferer as
|
||||
the signal, and the default `--noise 0` mock cannot see it. The band costs `raw`, not accuracy --
|
||||
the clean fast mock reads `raw=0.1%` at 1.41 ms a recompute and the noisy one `raw=2.0%` at 1.96
|
||||
ms, so read `measure cost:` on **both**. `marks=` lists each mark with its kind, and the **mix is the point**:
|
||||
`level:V` alone for average/top/bottom, `level:V,time:T` for min/max (a ruler at the value
|
||||
`level:V` alone for average/top/bottom; **two** level marks for `peak to peak` (the window's
|
||||
extremes) and for `amplitude` (the two *flat* levels it subtracts -- a pair of rulers, because
|
||||
the span between them *is* the measurement); `level:V,time:T` for min/max (a ruler at the value
|
||||
*and* a marker at the first sample that reached it — two level marks at both extremes was
|
||||
the bug), `time:A,time:B` for pulse/period/frequency. `text=` is the pane's own string with
|
||||
the bug); `time:A,time:B` for pulse/period/frequency. `text=` is the pane's own string with
|
||||
its unit, so `frequency` must read `Hz` (not the signal's unit), `period`/`pulse duration`
|
||||
seconds, and the rest the channel's unit. `switch_on` and `reset_sensitive` are the panel's *frame* read back: the switch widget against
|
||||
`measure_stats_on`, which the code drives, and the Reset button that follows it. Either disagreeing with
|
||||
@@ -1000,18 +1013,26 @@ pane's recompute count since start) and one `| n=… mean=… sd=… lo=… hi=
|
||||
measurement. Phase 1 adds `dn=` per group.
|
||||
|
||||
Measured readings (fast mock, 1 Hz sine, with
|
||||
`UDPSCOPE_ENABLE_ALL_MS=800 UDPSCOPE_PROBE_MEASURE_MS=1500 UDPSCOPE_PROBE_STATS_MS=2500`):
|
||||
`UDPSCOPE_ENABLE_ALL_MS=800 UDPSCOPE_PROBE_MEASURE_MS=1500 UDPSCOPE_PROBE_STATS_MS=2500`, the
|
||||
four measurements the measure probe adds -- average, top, peak to peak, amplitude):
|
||||
|
||||
```
|
||||
phase=0 on=1 passes=36 | n=30 mean=-0.0370313 sd=0.0953509 lo=-0.246367 hi=0.105167 | n=30 mean=0.633511 … | n=30 mean=1.99567 … hi=2 | n=30 mean=0.997834 … hi=1
|
||||
phase=1 … dn=0 (on all four measurements)
|
||||
phase=2 … n=1 mean=-4.84467e-14 | n=1 mean=0.63662 | n=1 mean=2 | n=1 mean=1
|
||||
phase=0 on=1 passes=43 | n=28 mean=0.0826556 sd=0.085427 lo=-0.0314722 hi=0.208469 | n=28 mean=0.957905 sd=0.00142833 lo=0.951967 hi=0.962348 | n=28 mean=2 sd=0 lo=2 hi=2 | n=28 mean=1.91652 sd=0.00300419 lo=1.90993 hi=1.92432
|
||||
phase=1 … dn=1 (on all four measurements)
|
||||
phase=2 … n=0 mean=0 sd=0 lo=0 hi=0 (all four)
|
||||
```
|
||||
|
||||
- `phase=0` — the running count. **`n` must be in the low tens**, not thousands: a count in the
|
||||
thousands means the accumulator is fed per sample, not per recompute. `mean=0.633511` (≈ 2/π)
|
||||
for the `top` measurement on a unit sine is the check that the threshold types are correct, not
|
||||
merely plausible. `sd` must be 0 exactly when `n < 2`, and `lo`/`hi` must bracket `mean`.
|
||||
thousands means the accumulator is fed per sample, not per recompute. `mean=2 sd=0 lo=2 hi=2` for
|
||||
the `peak to peak` measurement on a unit sine is the assertion on the shared pass: the extremes of
|
||||
a sine are exactly ±1 whatever the window does, so anything else is the scan, not the reading.
|
||||
The two flat-level types are the other half and their figure is the **`sd`**, not the mean: `top`
|
||||
reads `mean=0.957905 sd=0.00142833` and `amplitude` `mean=1.91652 sd=0.0030` over windows that
|
||||
scroll under them, and a `top` that wanders by tenths is a level that follows the window's
|
||||
alignment instead of a *value* band (see the flat-level invariant in
|
||||
[invariants-measurements.md](invariants-measurements.md); the mean itself is window-dependent on a
|
||||
sine, which has no plateau -- the densest band sits at its peak and moves with how much of the
|
||||
peak a window holds). `sd` must be 0 exactly when `n < 2`, and `lo`/`hi` must bracket `mean`.
|
||||
- `phase=1` — after a two-second pause. **`dn` must be 0 or 1**: a pinned window recomputes once
|
||||
when it settles and then never again. A `dn` near 120 is the second defect — accumulating per
|
||||
frame, which adds a hundred-odd observations of the identical window and collapses `sd` towards
|
||||
|
||||
Reference in new issue
Block a user