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
@@ -232,18 +232,47 @@ measurement.
|
||||
| type | what it is | options | shown on the plot as |
|
||||
|---|---|---|---|
|
||||
| `average` | the mean of the visible samples | — | a ruler at the mean |
|
||||
| `top` / `bottom` | the high / low level: the mean of the samples above / below the split | split % | a ruler at the level |
|
||||
| `top` / `bottom` | the high / low **flat** level: the densest band of the window on that side of the split | split % (which side) | a ruler at the level |
|
||||
| `max` / `min` | the extremes | — | a ruler at the value **and** a marker where it happened |
|
||||
| `peak to peak` | the swing, `max - min` — the extremes, ripple and overshoot included | — | a ruler at each extreme |
|
||||
| `amplitude` | the two flat levels' delta, `top - bottom` | — | a ruler at each flat level |
|
||||
| `pulse duration` | the mean width of the pulse-side runs | split %, which side is the pulse | markers at the pulse's two edges |
|
||||
| `period` / `frequency` | the spacing of the threshold crossings / its reciprocal, in Hz | — | markers at two edges of a cycle |
|
||||
|
||||
**`top`/`bottom`/`amplitude` read a *flat* level, and that is a different question from the
|
||||
extremes.** A band-limited edge does not step, it overshoots and rings: the mock's `square` at 30
|
||||
harmonics reaches **±1.179** where its plateau is ±1, so anything that reads the window's extremes
|
||||
reports the ringing — `amplitude` used to read `1.179350` there and `top` `0.986496`, both of them
|
||||
a property of the edges rather than of the waveform. A flat level is a *density* question instead:
|
||||
the plateau holds most of the samples and the ringing a few percent. Pass one folds a 64-bin
|
||||
histogram of the whole buckets' midpoints while it has them in hand, and the level is, either side
|
||||
of the split, the densest bin that still holds half the densest one's buckets — **the outermost of
|
||||
those**, which is what makes a waveform with no plateau at all (a sine, a ramp) read its *peak*
|
||||
rather than an arbitrary mid-range bin — with the exact mean of the samples of the buckets within
|
||||
**5 % of the window's peak-to-peak** of it. Five per cent is deliberately under the ~9 %-of-swing
|
||||
Gibbs overshoot, so the ringing falls outside the band and a plateau's own ripple stays inside.
|
||||
`amplitude` is then that pair's delta, which is a bench scope's `Vamplitude` (`Vtop - Vbase`) and
|
||||
**not** the half-swing it used to be. Measured on the mock, harmonics `0` → `30`: `top`
|
||||
`1.000000` → `0.999983`, `bottom` `-1.000000` → `-0.999983`, `amplitude` `2.000000` → `1.999966`,
|
||||
while `peak to peak` goes `2.000000` → `2.358701`. On the probe's 1 Hz ±1 sine `top` reads
|
||||
`0.957988` — it used to be `0.636620`, the half-wave mean, and a sine's flat level is near its
|
||||
peak instead — with `sd 0.0014` across recomputes, and `amplitude` `1.915983`; `peak to peak` is
|
||||
still exactly `2`. The histogram is the *same* fetched bucket list read a second time and never a
|
||||
sample, so `measure cost:`'s `raw` does not move; a pane with none of the three types does not pay
|
||||
for it at all. Where the pyramid cannot summarize the window — an `hf`-filtered walk, or one
|
||||
shorter than a bucket — there is no density estimate, and the types fall back to the old
|
||||
behaviour: the mean of each side of the split, and `max - min` for `amplitude`.
|
||||
|
||||
`period` and `frequency` deliberately take **no options**: the engine counts *rising*
|
||||
crossings of the *mid* level, one edge per cycle, and a split or a side to choose from could
|
||||
only make that count wrong — a split near either extreme leaves the hysteresis band below no room
|
||||
to reject noise with, and counting both edges halves the period.
|
||||
|
||||
The split is a share of the **window's own** min..max, not volts: a measurement is about this
|
||||
signal in this window, and a level in volts would have to be typed for every signal.
|
||||
signal in this window, and a level in volts would have to be typed for every signal. For
|
||||
`top`/`bottom` it says **which side** the level is read from, so any split between the two
|
||||
plateaus reads the same number — it is no longer a mean *over* the side, which is the reading a
|
||||
ringing edge used to drag.
|
||||
|
||||
Two more options ride on it, both at their old behaviour by default:
|
||||
|
||||
@@ -283,9 +312,9 @@ and to white noise at σ=0.05; at 5 % of peak-to-peak it already fails at 0.2.
|
||||
Three things about it are deliberate, and each was wrong first.
|
||||
|
||||
- **The band moves the edge decision only.** The side the `sum`/`count` split uses stays the bare
|
||||
level, because that split *is* `top` and `bottom` — the mean of the samples above and below the
|
||||
threshold — and a band there changes a documented exact reading. `top` on a unit sine must still
|
||||
be `0.636620`, 2/π.
|
||||
level, and nothing that band touches is the flat level: a band there would change what a crossing
|
||||
counts as an edge for no gain to anything, and the split's two means are now only the fallback
|
||||
`top`/`bottom` take on a window with no bucket histogram.
|
||||
- **The band is clamped to stay inside the data**, to half the room between the level and the
|
||||
nearer extreme. `pulse duration` lets the user put the split near an extreme (a 3.3 V logic
|
||||
signal with ringing wants it nearer the top), and a fixed tenth of the span there puts the upper
|
||||
@@ -353,7 +382,8 @@ Both walks go through it:
|
||||
and one raw scan of that bucket alone then pins it to a sample — two buckets at most, whatever
|
||||
the window's size. The two stamps the scan needed are read at the end rather than at every
|
||||
chunk that moved an extreme, since a stamp is a ring read and an extreme moves many times.
|
||||
- **Pass two** (the threshold types: `top`, `bottom`, `pulse duration`, `period`, `frequency`)
|
||||
- **Pass two** (the crossing types — `pulse duration`, `period`, `frequency` — and, as a fallback
|
||||
for a window with no histogram, `top`/`bottom`)
|
||||
folds a bucket's sums whenever it lies *strictly* on one side of its level — then every sample
|
||||
in it falls in the same bin and none is *at* the level, so the fold is exact. It then asks
|
||||
separately whether the bucket *enters* a side of the hysteresis band: one wholly past a threshold
|
||||
@@ -364,11 +394,14 @@ Both walks go through it:
|
||||
The raw path is still there and still exact, for the window's partial edge buckets, for the
|
||||
samples produced since this frame's `acquire_all()` (bounded by one frame of production), and
|
||||
for those opened buckets. Nothing is decimated or strided: `peak to peak` still reads
|
||||
exactly `2` on a unit sine and `top` exactly `0.636620` — 2/π, the half-wave mean, which is the
|
||||
check that a threshold type is not merely plausible. A window shorter than a bucket, or a signal
|
||||
exactly `2` on a unit sine, and the shared pass that carries it is untouched — `top` has no
|
||||
*exact* reading to protect any more, because it is a flat level read from a density rather than
|
||||
the half-wave mean it used to be (`0.636620` on a unit sine; it is `0.957988` now, near the peak,
|
||||
because a sine has no plateau). A window shorter than a bucket, or a signal
|
||||
noisy enough to cross a threshold everywhere, degrades back towards the O(window) cost and nothing
|
||||
else changes. The dump's `measure cost:` line reports `covered`, `buckets` and `raw` for exactly
|
||||
this: `raw` near `covered` means the pyramid bought nothing.
|
||||
this: `raw` near `covered` means the pyramid bought nothing — and `raw` is unchanged by the flat
|
||||
levels, which read the *same* bucket list a second time and never a sample.
|
||||
|
||||
### What the math channels cost
|
||||
|
||||
@@ -1528,11 +1561,15 @@ a relative tolerance (clamping to *exactly* `window_s_` can leave the group's sp
|
||||
and a clamp that fires every frame is a re-frame loop — the dump counts it as `clamp fires:`), and
|
||||
the sample-window walk it does is unchanged.
|
||||
|
||||
**Amplitude and peak to peak** are the two new types: `max - min`, and half of it. Deliberately
|
||||
`(max - min) / 2` rather than `max(|value|)` — the latter is a distance from zero, which for a
|
||||
signal riding on an offset is a different and less useful number. Both come out of the shared
|
||||
pass, so they are free, and both read exactly what a unit sine should: `amplitude=1`,
|
||||
`peak to peak=2`.
|
||||
**Amplitude and peak to peak** were the two types added then: `max - min`, and half of it.
|
||||
Deliberately `(max - min) / 2` rather than `max(|value|)` — the latter is a distance from zero,
|
||||
which for a signal riding on an offset is a different and less useful number. Both came out of the
|
||||
shared pass, so they were free, and both read exactly what a unit sine should: `amplitude=1`,
|
||||
`peak to peak=2`. **`amplitude` no longer means that.** It is `top - bottom`, the delta between the
|
||||
two *flat* levels — a bench scope's `Vamplitude` — and it is the extremes that stay with
|
||||
`peak to peak`, which is why it reads `2.000000` on the rippled square where the swing is
|
||||
`2.358701`: a ringing edge's overshoot belongs to the swing and not to the amplitude (see
|
||||
*Measurements*).
|
||||
|
||||
The second is subtler and needs a saturated ring. A `ChannelBuffer` sample is a (value,
|
||||
stamp) pair split across two rings of equal capacity, and the published window is pinned
|
||||
|
||||
Reference in new issue
Block a user