fluxprune keeps a subset of a series' points, read as straight lines between them: the first and last points (to stitch blocks), the minimum and maximum, then whichever points lower the time-weighted RMSE of the lines most, until a point no longer buys enough. prune(times, values) works with its defaults; three settings change what it keeps:
error_per_point (θ): the exchange rate between error and points, and the one to choose first: the RMSE relative to the block's range you would accept at some number of points, divided by that number. The default is 0.1% at a tenth of the input's points.max_points: a hard cap.max_error: the largest error of any scan, in the signal's units. It only adds points.The first two sections show them on each signal; the rest is how it works and how it was chosen.
Data: 19 synthetic signals × 5 days of 1 s scans, cut into 1 h storage blocks of 3,600 points (tslab/prune/signals.py; the studies of ranking and stopping rules use the first 12). Relative errors are relative to each block's range (max − min). Regenerate with uv run python make_prune_report.py in experimental/.
error_per_point (θ) is an exchange rate: prune adds points in order of how much they lower the time-weighted RMSE and stops when a point no longer buys θ of it, the RMSE relative to the block's range and read above the block's own white noise (the noise floor). Choose it first, as the RMSE you would accept at some number of points divided by that number: θ = Y / X reads "Y at X points". X is not a target: a block that needs fewer stops sooner, and a harder one goes on at a worse error. The default is 0.1% of the range at a tenth of the input's points, θ = 0.001 / (n / 10): 2.8·10⁻⁶ for an hour of 1 s scans. max_points is a hard cap on top; it drops the rmse pass's least significant points. Three settings:
One hour of eight signals (day 0, 09:00; the pyrometer's busiest hour, of which minutes 10-30 are shown); each panel gives the points kept in the hour, whether the cap bound, the RMSE over the hour's range and the largest error of any scan, in the signal's units.
All 19 signals (described below), 1 h blocks of all 5 days: mean points per hour (the most in one block) and the share of blocks where the cap bound; below, the mean RMSE over the block's range · the largest error of any block, in the signal's units. The default keeps 162 points per hour on average (median 77), and 22% of the blocks want more than 360: the noisy and fast-moving signals, which the cap holds at 360 for a larger RMSE. The coarse θ keeps 24 on average and lets the RMSE grow to several per cent where a signal is busy, which mostly costs the largest error: max_error bounds that.
| signal | default | default, max_points 360 | coarse |
|---|---|---|---|
| temperature | 452 (max 568) 0.9% · 0.236 | 360 (max 360) · capped 100% 1% · 0.255 | 33 (max 48) 3.2% · 0.797 |
| flow | 419 (max 484) 5.7% · 5.16 | 360 (max 360) · capped 99% 6.1% · 5.17 | 76 (max 97) 9.4% · 9.84 |
| tank-level | 61 (max 140) 0.035% · 0.12 | 61 (max 140) 0.035% · 0.12 | 5 (max 7) 0.077% · 1.52 |
| valve | 6 (max 17) 3.4e-14% · 6.88e-13 | 6 (max 17) 3.4e-14% · 6.88e-13 | 5 (max 12) 0.079% · 7.4 |
| ph-float32 | 518 (max 567) 0.85% · 0.043 | 360 (max 360) · capped 100% 1% · 0.0544 | 36 (max 47) 3.4% · 0.167 |
| motor-current | 22 (max 172) 8% · 6.53 | 22 (max 172) 8% · 6.53 | 12 (max 32) 8.1% · 6.71 |
| totalizer | 5 (max 66) 0.001% · 0.124 | 5 (max 66) 0.001% · 0.124 | 2 (max 4) 0.0061% · 35.7 |
| batch | 67 (max 160) 0.088% · 0.3 | 67 (max 160) 0.088% · 0.3 | 8 (max 11) 0.43% · 1.89 |
| vibration | 78 (max 216) 5.5% · 0.189 | 78 (max 216) 5.5% · 0.189 | 19 (max 34) 5.9% · 0.654 |
| compressor | 67 (max 194) 0.34% · 0.752 | 67 (max 194) 0.34% · 0.752 | 19 (max 23) 1.5% · 16.8 |
| white-noise | 18 (max 171) 12% · 6.43 | 18 (max 171) 12% · 6.43 | 12 (max 47) 12% · 6.43 |
| sine | 164 (max 164) 0.035% · 0.00163 | 164 (max 164) 0.035% · 0.00163 | 22 (max 22) 1.6% · 0.0829 |
| furnace | 51 (max 76) 2.6% · 0.572 | 51 (max 76) 2.6% · 0.572 | 21 (max 30) 3.2% · 0.957 |
| furnace-dropout | 45 (max 76) 2.1% · 0.702 | 45 (max 76) 2.1% · 0.702 | 18 (max 30) 2.6% · 6.62 |
| grid-frequency | 584 (max 635) 1.8% · 0.00661 | 360 (max 360) · capped 100% 2.4% · 0.00967 | 60 (max 73) 6.1% · 0.0379 |
| ambient-C | 83 (max 103) 2.3% · 0.3 | 83 (max 103) 2.3% · 0.3 | 27 (max 38) 3.4% · 0.414 |
| ambient-K | 83 (max 102) 2.3% · 0.3 | 83 (max 102) 2.3% · 0.3 | 27 (max 38) 3.4% · 0.488 |
| level-quiet | 107 (max 335) 9.1% · 0.0352 | 107 (max 335) 9.1% · 0.0352 | 20 (max 73) 9.4% · 0.119 |
| pyrometer | 190 (max 364) 2.7% · 28 | 190 (max 360) · capped 1% 2.7% · 28 | 34 (max 57) 6.1% · 810 |
| all | 162 (median 77) · capped 0% 3% (median 1.1%) | 134 (median 77) · capped 22% 3.1% (median 1.2%) | 24 (median 21) · capped 0% 4.3% (median 3.2%) |
The pyrometer against its noise-free curve: points per hour in hours with spikes and in quiet ones, points on a spike per hour, how far the kept lines' top is below each spike's (°C, mean and worst), and the RMSE against the curve under the noise (°C). A time-weighted RMSE counts a narrow spike as cheap (a 600 °C spike 4 s wide adds about 30 °C to an hour's RMSE), so the coarse θ cuts its tops; the default keeps them.
| setting | points/h, busy | points/h, quiet | on spikes/h | peak miss | worst | RMSE, busy | RMSE, quiet |
|---|---|---|---|---|---|---|---|
| default | 236 | 22 | 145 | 1.7 | 7 | 1.6 | 0.60 |
| default, max_points 360 | 236 | 22 | 145 | 1.7 | 7 | 1.6 | 0.60 |
| coarse | 41 | 11 | 29 | 239.5 | 809 | 39.5 | 0.56 |
max_error bounds the largest distance of any scan from the kept lines, in the signal's own units. It only adds points: after the rmse pass, every line that strays further is split (the max_error pass). It covers what an RMSE averages away: a narrow spike, a glitch, a quiet block whose range is its own dither. Choose it against the sensor's noise σ (Pruned.noise estimates it): lines through noisy scans can't stay closer than a few σ without keeping most of them. On white noise at θ = 0.0014, max_error = 1σ keeps 1681, 2σ keeps 381, 3σ keeps 34, 5σ keeps 12 points per hour of 3,600; below σ prune warns. With max_points as well, the cap goes to max_error first and the rmse pass gets the rest; if max_error can't be met within the cap it is relaxed to about the tightest bound the cap allows (max_error_met false). Four settings on top of the coarse θ, with a max_error per signal (columns σ, loose and fine below):
All signals, 1 h blocks of all days, as above; σ is the median noise estimate (0 where the times have no cadence or the signal no noise). A loose max_error adds points only where the coarse lines missed something: the pyrometer's spikes, the furnace's dropouts, the grid frequency's swings. A fine one, at about 2σ, is expensive wherever there is noise: hundreds of points per hour that mostly trace it (temperature 511, white noise 381; the pyrometer's quiet hours, below, go from about ten to over a hundred). The cap holds them at 360 by relaxing the bound, in the worst block to 1.05× the one asked (white-noise) to 1.42× (grid-frequency). At 3σ or more the same signals need far fewer.
| signal | σ | loose | fine | coarse | coarse, loose max_error | coarse, fine max_error | coarse, fine max_error, max_points 360 |
|---|---|---|---|---|---|---|---|
| temperature | 0.042 | 0.5 | 0.1 | 33 (max 48) 3.2% · 0.797 | 34 (max 48) 3.2% · 0.5 | 511 (max 570) 1.1% · 0.1 | 358 (max 360) · capped 100% 1.3% · 0.12 |
| flow | 1 | 5 | 2 | 76 (max 97) 9.4% · 9.84 | 82 (max 102) 9.5% · 5 | 686 (max 740) 5.8% · 2 | 358 (max 360) · capped 100% 7.7% · 2.71 |
| tank-level | 0.018 | 1 | 0.1 | 5 (max 7) 0.077% · 1.52 | 5 (max 8) 0.075% · 0.997 | 7 (max 13) 0.052% · 0.1 | 7 (max 13) 0.052% · 0.1 |
| valve | 0 | 2 | 0.5 | 5 (max 12) 0.079% · 7.4 | 5 (max 15) 0.07% · 1.98 | 6 (max 18) 9.6e-05% · 0.5 | 6 (max 18) 9.6e-05% · 0.5 |
| ph-float32 | 0.0078 | 0.1 | 0.02 | 36 (max 47) 3.4% · 0.167 | 38 (max 48) 3.4% · 0.1 | 513 (max 550) 1.1% · 0.02 | 359 (max 360) · capped 100% 1.3% · 0.0252 |
| motor-current | 1 | 5 | 2 | 12 (max 32) 8.1% · 6.71 | 12 (max 32) 8.1% · 5 | 381 (max 446) 7.9% · 2 | 355 (max 360) · capped 79% 8% · 2.11 |
| totalizer | 1e-11 | 5 | 1 | 2 (max 4) 0.0061% · 35.7 | 3 (max 4) 0.0032% · 5 | 3 (max 4) 0.0015% · 0.994 | 3 (max 4) 0.0015% · 0.994 |
| batch | 0.048 | 1 | 0.1 | 8 (max 11) 0.43% · 1.89 | 9 (max 13) 0.42% · 0.999 | 391 (max 458) 0.088% · 0.1 | 355 (max 360) · capped 88% 0.089% · 0.105 |
| vibration | 0.029 | 0.2 | 0.06 | 19 (max 34) 5.9% · 0.654 | 19 (max 34) 5.9% · 0.199 | 369 (max 440) 5.6% · 0.06 | 351 (max 360) · capped 60% 5.7% · 0.0635 |
| compressor | 0.1 | 2 | 0.25 | 19 (max 23) 1.5% · 16.8 | 21 (max 27) 1.3% · 2 | 148 (max 188) 0.37% · 0.25 | 148 (max 188) 0.37% · 0.25 |
| white-noise | 1 | 5 | 2 | 12 (max 47) 12% · 6.43 | 12 (max 47) 12% · 4.99 | 381 (max 459) 12% · 2 | 355 (max 360) · capped 78% 12% · 2.1 |
| sine | 1.2e-05 | 0.05 | 0.005 | 22 (max 22) 1.6% · 0.0829 | 29 (max 31) 1.5% · 0.05 | 82 (max 84) 0.16% · 0.005 | 82 (max 84) 0.16% · 0.005 |
| furnace | 0.12 | 1 | 0.25 | 21 (max 30) 3.2% · 0.957 | 21 (max 30) 3.2% · 0.957 | 213 (max 264) 2.6% · 0.25 | 213 (max 264) 2.6% · 0.25 |
| furnace-dropout | 0.12 | 1 | 0.25 | 18 (max 30) 2.6% · 6.62 | 20 (max 30) 2.6% · 1 | 211 (max 264) 2.1% · 0.25 | 211 (max 264) 2.1% · 0.25 |
| grid-frequency | 0.0012 | 0.01 | 0.003 | 60 (max 73) 6.1% · 0.0379 | 82 (max 96) 5.9% · 0.01 | 640 (max 685) 2.1% · 0.003 | 355 (max 360) · capped 100% 3.1% · 0.00425 |
| ambient-C | 0.061 | 0.5 | 0.15 | 27 (max 38) 3.4% · 0.414 | 27 (max 38) 3.4% · 0.414 | 105 (max 129) 2.7% · 0.15 | 105 (max 129) 2.7% · 0.15 |
| ambient-K | 0.061 | 0.5 | 0.15 | 27 (max 38) 3.4% · 0.488 | 27 (max 38) 3.4% · 0.488 | 105 (max 131) 2.7% · 0.15 | 105 (max 131) 2.7% · 0.15 |
| level-quiet | 0.0061 | 0.05 | 0.015 | 20 (max 73) 9.4% · 0.119 | 20 (max 73) 9.4% · 0.0496 | 197 (max 275) 9.1% · 0.015 | 197 (max 275) 9.1% · 0.015 |
| pyrometer | 2.1 | 50 | 5 | 34 (max 57) 6.1% · 810 | 85 (max 159) 4.4% · 50 | 300 (max 486) 2.9% · 5 | 270 (max 360) · capped 47% 3% · 7 |
| all | 24 (median 21) · capped 0% 4.3% (median 3.2%) | 30 (median 22) · capped 0% 4.2% (median 3.1%) | 282 (median 223) · capped 0% 3.1% (median 1.3%) | 226 (median 223) · capped 40% 3.3% (median 1.6%) |
The pyrometer, as above.
| setting | points/h, busy | points/h, quiet | on spikes/h | peak miss | worst | RMSE, busy | RMSE, quiet |
|---|---|---|---|---|---|---|---|
| coarse | 41 | 11 | 29 | 239.5 | 809 | 39.5 | 0.56 |
| coarse, loose max_error | 105 | 11 | 79 | 1.7 | 7 | 19.8 | 0.56 |
| coarse, fine max_error | 350 | 119 | 185 | 1.7 | 7 | 1.5 | 1.22 |
| coarse, fine max_error, max_points 360 | 311 | 119 | 174 | 1.7 | 7 | 1.6 | 1.22 |
The first eighteen are the corpus of the ranking and stopping-rule studies; the six after them are for what errors are relative to (small variations on a large offset), and the pyrometer for the noise floor.
| signal | what it is |
|---|---|
| temperature | Daily cycle + slow drift (τ 30 min) + noise σ 0.03 °C, 0.01 °C resolution |
| flow | Setpoint steps (6/day) through a 60 s lag + process noise (σ 1.5, τ 5 s) + measurement noise σ 0.8, 0.1 resolution: noise above CompDev |
| tank-level | Fill and drain ramps (0.01–0.05 %/s) between holds + noise σ 0.02 %, 0.01 % resolution |
| valve | Position commands in 5 % steps (30/day), 2 %/s travel, no noise, 0.1 % resolution |
| ph-float32 | pH 7 + slow drift (τ 10 min) + noise σ 0.004, stored as float32 (not decimal) |
| motor-current | Load steps (24/day) through a 10 s lag + noise σ 1 A, 0.01 A resolution: CompDev far below the noise, so most scans are archived |
| totalizer | Flow totalizer: integral of a stepped flow, 0.1 resolution (piecewise straight) |
| batch | Batch reactor: heat ramp, soak, cool, ~2 h cycles + noise σ 0.05 |
| vibration | Vibration RMS: slow drift + rare bursts (seconds to minutes) + noise |
| compressor | Compressor power: on/off cycles (5-15 min) with start-up overshoot + noise |
| white-noise | White noise only, σ 1 |
| sine | One 20 min sine, nothing else (smooth: error falls as k⁻² with no floor) |
| furnace | Furnace temperature, °F: setpoints 2100-2130 with ramps, control cycling ±0.3-1.5 °F, 0.1 °F steps |
| furnace-dropout | The furnace with 6 thermocouple dropouts a day (reads 0 for 2-20 s) |
| grid-frequency | Grid frequency, Hz: 60 ± ~10 mHz, 4 generator trips a day (−40 to −150 mHz), 1 mHz steps |
| ambient-C | Outdoor temperature, °C: −3 to 9 over the day, weather drift, 0.1 °C steps (crosses zero) |
| ambient-K | The same ambient temperature in kelvin (+273.15) |
| level-quiet | Tank level, %: held at 62.4 (±1 step of 0.01 dither) but for 3 fill/draw cycles a day |
| pyrometer | Pyrometer at a furnace's exit conveyor, °C: a 45-55 floor with noise σ 2, and hot items passing every 1.5-5 min in production runs of 1-4 h with 0.5-3 h quiet between: spikes of 500-900 above the floor, a / (b + t²) with a half-width of 1.5-2.5 s, 0.1 °C steps |
Lines through noisy samples leave about the noise's σ against them however many points are kept, until nearly all are, so the rmse pass on a quiet, noisy block keeps buying points that trace the noise: its range is a few σ, and relative to that the noise looks like signal. With the noise floor (the default) it stops on √(e² − σ²) instead of e, an estimate of the error against the signal under the noise. σ is estimated in one O(n) pass: each scan's distance from the chord of its two neighbours, divided by √(1 + w² + (1 − w)²) (w its position between them in time), median, × 1.4826 (fluxprune/_noise.py). Nothing is smoothed and the ranking is the same; only where it stops moves. Per signal, 1 h blocks of all days: the median σ over the range, and at θ = 0.0015 (the old default) and at the default (θ = 0.001 / (n / 10): 2.8e-6 for 3,600 scans) the points per hour without → with the floor and, below, the mean RMSE over the range (against the scans).
| signal | σ / range | θ = 0.0015 | default |
|---|---|---|---|
| temperature | 1.1% | 32 → 32 3.2% → 3.3% | 1462 → 452 0.38% → 0.9% |
| flow | 7.9% | 84 → 72 9.2% → 9.5% | 2566 → 419 0.7% → 5.7% |
| tank-level | 0.033% | 5 → 5 0.082% → 0.082% | 151 → 61 0.033% → 0.035% |
| valve | 0% | 4 → 4 0.06% → 0.06% | 5 → 5 2.3e-14% → 2.3e-14% |
| ph-float32 | 0.97% | 35 → 35 3.5% → 3.5% | 1439 → 518 0.38% → 0.85% |
| motor-current | 9.4% | 67 → 12 7.6% → 8.1% | 2705 → 22 0.74% → 8% |
| totalizer | 2.2e-13% | 2 → 2 0.0054% → 0.0054% | 5 → 5 0.00083% → 0.00089% |
| batch | 0.093% | 8 → 8 0.45% → 0.45% | 327 → 67 0.074% → 0.088% |
| vibration | 8% | 47 → 18 5.6% → 6% | 2289 → 78 0.62% → 5.5% |
| compressor | 0.41% | 19 → 18 1.5% → 1.5% | 871 → 67 0.21% → 0.34% |
| white-noise | 14% | 95 → 11 11% → 12% | 2941 → 18 0.81% → 12% |
| sine | 0.00059% | 22 → 22 1.6% → 1.6% | 164 → 164 0.035% → 0.035% |
| furnace | 3.3% | 28 → 20 2.9% → 3.2% | 2017 → 51 0.54% → 2.6% |
| furnace-dropout | 3% | 24 → 18 2.3% → 2.6% | 1596 → 45 0.43% → 2.1% |
| grid-frequency | 2.2% | 58 → 57 6.2% → 6.2% | 1905 → 584 0.51% → 1.8% |
| ambient-C | 2.6% | 30 → 26 3.3% → 3.5% | 1706 → 83 0.45% → 2.3% |
| ambient-K | 2.6% | 30 → 26 3.2% → 3.5% | 1704 → 83 0.44% → 2.3% |
| level-quiet | 15% | 77 → 19 9.3% → 9.4% | 1869 → 107 0.5% → 9.1% |
| pyrometer | 0.25% | 51 → 33 6% → 6.2% | 1152 → 190 0.3% → 2.7% |
Every noise estimate assumes the times were chosen independently of the values; an archive's weren't (swinging door or a deadband keeps a point because of its value), and on a smooth tag its points pass tests of white noise as well as raw scans do, with σ read at about the CompDev. So the floor runs only on a cadence: at least 90% of the intervals one scan (fluxcode's own test, fluxcode._noise.cadence). The tags' swinging-door archives in 1 h blocks of all days: points per hour in, the share of blocks with a floor, and the default prune's points per hour · RMSE over the range against the 1 s scans, as is and with noise_floor=False, and the archive's own RMSE (the tank level and the valve archive 2-5 points per hour: nothing to prune).
| tag | archived/h | blocks with a floor | default | noise_floor=False | the archive |
|---|---|---|---|---|---|
| temperature | 325 | 0% | 197 · 1.8% | 197 · 1.8% | 1.7% |
| flow | 1362 | 0% | 1215 · 4.6% | 1215 · 4.6% | 4.5% |
| ph-float32 | 340 | 0% | 213 · 1.9% | 213 · 1.9% | 1.8% |
| motor-current | 2957 | 0% | 2533 · 1.3% | 2533 · 1.3% | 0.71% |
Against the signal under the noise instead: on white noise (σ = 1, 120 hours) the kept lines' RMSE from zero is 0.27σ on average with the floor and 1.00σ without; in the worst hour 0.48σ and 1.03σ (with few points left, the lines from the required minimum and maximum lean further).
Points per hour (day 0) without → with the floor as θ falls. Without it a lower θ buys noise, so the count follows the noise; with it the noise-dominated signals level off at what their content needs, and only signals with structure at finer scales keep more.
| signal | θ = 0.0015 | θ = 0.00015 | θ = 1.5e-05 | θ = 1.5e-06 |
|---|---|---|---|---|
| temperature | 32 → 32 | 136 → 123 | 591 → 365 | 1841 → 456 |
| flow | 86 → 75 | 455 → 289 | 1584 → 414 | 2866 → 422 |
| motor-current | 74 → 11 | 518 → 13 | 1791 → 14 | 2986 → 14 |
| vibration | 48 → 17 | 345 → 45 | 1343 → 64 | 2529 → 73 |
| white-noise | 96 → 11 | 657 → 15 | 2070 → 17 | 3142 → 17 |
| sine | 22 → 22 | 42 → 42 | 91 → 91 | 191 → 191 |
| level-quiet | 72 → 22 | 465 → 87 | 1360 → 112 | 1866 → 142 |
| pyrometer | 55 → 32 | 251 → 71 | 749 → 132 | 1604 → 182 |
| mean of the 12 | 36 → 20 | 197 → 60 | 702 → 122 | 1449 → 164 |
The case that asked for it: a pyrometer at a furnace's exit conveyor reads its noisy floor most of the time and a narrow spike (a / (b + t²), about 10 s wide) as each hot item passes, 1,264 spikes in 5 days, with quiet hours between production runs (tslab/prune/signals.py, pyrometer). Against its noise-free curve: points per hour in hours with spikes and in quiet ones, points on a spike (3σ above the floor) per hour, how far the kept lines' top is below each spike's (°C, mean and worst), and the RMSE against the curve under the noise (°C) in busy and quiet hours. The floor takes the quiet hours from tracing noise to a handful of points; it changes nothing on the spikes, which a time-weighted RMSE counts as cheap: a 600 °C spike 4 s wide adds only about 30 °C to an hour's RMSE. A lower θ (the default) or a max_error of a few σ keeps them.
| setting | points/h, busy | points/h, quiet | on spikes/h | peak miss | worst | RMSE, busy | RMSE, quiet |
|---|---|---|---|---|---|---|---|
| θ = 0.0015, no floor | 39 | 94 | 28 | 253.1 | 809 | 40.7 | 0.66 |
| θ = 0.0015 | 39 | 11 | 28 | 253.2 | 809 | 40.7 | 0.56 |
| default (θ = 2.8e-6 at 1 h) | 236 | 22 | 145 | 1.7 | 7 | 1.6 | 0.60 |
| default, no floor | 660 | 2933 | 181 | 1.7 | 7 | 1.6 | 2.00 |
| θ = 0.0015, max_error = 20 | 162 | 11 | 120 | 1.7 | 7 | 8.2 | 0.56 |
The studies behind the defaults: how good the ranking is, which stopping rule, what errors are relative to, how max_error is met, refit, archives as input, and speed. Some use rules and settings that are only experiments (tslab/prune/rules.py).
Every method picks the same number of points per 1 h block (day 0, every 4th hour, so 6 blocks per signal); cells are the geometric mean of the block's RMSE over fluxprune's (refined). fluxprune and top-down keep the required points within k; swinging door's CompDev is searched for k points (the nearest count at or above k); LTTB and min/max buckets are the plotting downsamplers; Visvalingam-Whyatt (cartography's line simplification) is bottom-up by triangle area, and + refine adds fluxprune's two refinement passes to its points.
| method | temperature | flow | tank-level | valve | ph-float32 | motor-current | totalizer | batch | vibration | compressor | white-noise | sine | all (RMSE) | all (max error) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| fluxprune | 1.00 | 1.00 | 1.00 | exact | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| bottom-up (nested) | 1.36 | 1.33 | 1.16 | exact | 1.33 | 1.29 | 1.36 | 1.29 | 1.11 | 1.11 | 1.11 | 1.23 | 1.24 | 1.14 |
| top-down | 1.24 | 0.98 | 1.23 | exact | 1.33 | 1.30 | 0.97 | 2.11 | 1.06 | 1.49 | 1.05 | 2.87 | 1.35 | 1.21 |
| Visvalingam-Whyatt | 1.64 | 1.87 | 1.80 | exact | 1.53 | 2.22 | 1.63 | 1.46 | 1.74 | 1.44 | 2.23 | 1.11 | 1.67 | 1.39 |
| Visvalingam-Whyatt + refine | 1.04 | 1.09 | 1.04 | exact | 1.08 | 1.27 | 0.98 | 0.96 | 1.08 | 1.06 | 1.21 | 0.99 | 1.07 | 1.08 |
| swinging door | 1.78 | 1.60 | 1.37 | exact | 1.76 | 1.47 | 1.21 | 1.48 | 1.42 | 1.50 | 1.24 | 1.42 | 1.47 | 1.05 |
| LTTB | 1.72 | 1.93 | 2.28 | ∞ | 1.68 | 2.60 | 1.58 | 2.77 | 2.42 | 2.79 | 2.38 | 1.18 | 2.06 | 1.53 |
| min/max buckets | 1.83 | 2.38 | 32.05 | ∞ | 1.88 | 3.51 | 6.67 | 17.23 | 2.59 | 2.31 | 3.01 | 1.43 | 3.81 | 2.67 |
| uniform | 1.63 | 1.25 | 22.94 | ∞ | 1.61 | 1.91 | 5.47 | 7.58 | 1.36 | 1.75 | 1.47 | 1.68 | 2.56 | 2.35 |
| method | temperature | flow | tank-level | valve | ph-float32 | motor-current | totalizer | batch | vibration | compressor | white-noise | sine | all (RMSE) | all (max error) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| fluxprune | 1.00 | 1.00 | 1.00 | exact | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| bottom-up (nested) | 1.31 | 1.30 | 1.22 | exact | 1.32 | 1.22 | 1.29 | 1.22 | 1.15 | 1.14 | 1.23 | 1.21 | 1.24 | 1.24 |
| top-down | 1.39 | 1.04 | 0.99 | exact | 1.45 | 1.03 | 0.95 | 1.11 | 1.02 | 31.75 | 1.00 | 1.78 | 1.57 | 1.55 |
| Visvalingam-Whyatt | 1.57 | 1.68 | 1.86 | exact | 1.59 | 1.98 | 1.54 | 1.82 | 1.63 | 2.25 | 1.91 | 1.65 | 1.76 | 1.59 |
| Visvalingam-Whyatt + refine | 1.02 | 1.02 | 1.01 | exact | 1.03 | 1.04 | 0.98 | 1.08 | 1.00 | 1.37 | 1.01 | 1.03 | 1.05 | 1.14 |
| swinging door | 1.68 | 1.50 | 1.28 | exact | 1.74 | 1.28 | 1.08 | 1.53 | 1.35 | 1.28 | 1.27 | 1.27 | 1.38 | 1.13 |
| LTTB | 1.88 | 1.89 | 2.09 | ∞ | 1.80 | 2.23 | 1.46 | 2.37 | 2.38 | 34.82 | 2.07 | 1.30 | 2.51 | 2.22 |
| min/max buckets | 1.93 | 2.26 | 8.20 | ∞ | 1.89 | 2.82 | 3.07 | 8.30 | 2.54 | 40.64 | 2.59 | 4.72 | 4.16 | 3.79 |
| uniform | 1.68 | 1.39 | 4.20 | ∞ | 1.75 | 1.56 | 2.00 | 4.61 | 1.40 | 36.50 | 1.40 | 1.55 | 2.54 | 2.90 |
| method | temperature | flow | tank-level | valve | ph-float32 | motor-current | totalizer | batch | vibration | compressor | white-noise | sine | all (RMSE) | all (max error) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| fluxprune | 1.00 | 1.00 | 1.00 | exact | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| bottom-up (nested) | 1.32 | 1.31 | 1.27 | exact | 1.31 | 1.25 | 1.20 | 1.26 | 1.18 | 1.23 | 1.26 | 1.13 | 1.25 | 1.18 |
| top-down | 1.58 | 1.17 | 1.01 | exact | 1.46 | 1.01 | 0.93 | 1.01 | 1.02 | 14.22 | 1.01 | 1.16 | 1.42 | 1.50 |
| Visvalingam-Whyatt | 1.54 | 1.65 | 1.74 | exact | 1.54 | 1.75 | 1.44 | 1.70 | 1.56 | 1.72 | 1.75 | 1.30 | 1.60 | 1.47 |
| Visvalingam-Whyatt + refine | 1.03 | 1.05 | 1.02 | exact | 1.03 | 1.02 | 0.99 | 1.01 | 1.01 | 1.04 | 1.01 | 1.11 | 1.03 | 1.08 |
| swinging door | 1.63 | 1.43 | 1.27 | exact | 1.68 | 1.29 | 1.07 | 1.35 | 1.30 | 1.30 | 1.27 | 1.06 | 1.32 | 1.01 |
| LTTB | 1.83 | 1.84 | 1.87 | ∞ | 1.80 | 1.88 | 1.38 | 1.91 | 2.12 | 22.28 | 1.90 | 1.23 | 2.23 | 2.15 |
| min/max buckets | 1.89 | 2.13 | 2.39 | ∞ | 1.97 | 2.47 | 1.36 | 2.41 | 2.38 | 22.35 | 2.36 | 4.63 | 2.85 | 2.83 |
| uniform | 1.78 | 1.51 | 1.72 | ∞ | 1.80 | 1.51 | 1.64 | 1.61 | 1.42 | 24.83 | 1.49 | 1.28 | 2.02 | 2.32 |
On the same whole blocks (3,600 points), the optimal choice of k points that include the required ones (dynamic programming, O(n²k): about 2 s a block, an oracle, not a method); each greedy method's RMSE over the optimum's, geometric mean.
| method | k = 12 | k = 36 | k = 108 |
|---|---|---|---|
| fluxprune | 1.05 (worst 1.25) | 1.04 (worst 1.38) | 1.06 (worst 1.40) |
| bottom-up (nested) | 1.30 (worst 1.63) | 1.29 (worst 1.60) | 1.32 (worst 1.53) |
| top-down | 1.41 (worst 4.08) | 1.64 (worst 66.18) | 1.50 (worst 74.09) |
What a rule sees: the relative RMSE of keeping k points (the ranking, before refinement), one hour of each signal. Smooth signals fall as k⁻² (sine), random walks as k⁻½ (temperature, pH), and white noise hardly at all until most points are kept; a step or ramp signal falls off a cliff and then sits on its noise. The dashed line is 0.5% for scale.
Each rule's one parameter is set so that all 1,440 blocks together keep 36 points per hour on average (1% of the scans), except knee, which can't be calibrated (its total is what its α gives). Cells are the mean points kept per hour and, below, the geometric mean RMSE of the blocks. The corpus columns are the mean points per block, the mean and median block RMSE, and the most points one block got.
| rule | what it does | parameter | temperature | flow | tank-level | valve | ph-float32 | motor-current | totalizer | batch | vibration | compressor | white-noise | sine | points/h | mean | median | max k |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | K points per hour | K = 36 | 36 3% | 36 9.7% | 36 0.033% | 36 2.3e-14% | 36 3.4% | 36 6.9% | 36 0.0004% | 36 0.085% | 36 3.7% | 36 0.36% | 36 11% | 36 0.61% | 36.0 | 3.6% | 0.62% | 36 |
| tolerance | fewest points within RMSE ε | ε = 0.131 | 7 9.2% | 92 9.8% | 4 2.6% | 4 4.7e-05% | 8 9.5% | 88 10% | 2 0.009% | 5 3.7% | 7 8.7% | 14 9.4% | 194 11% | 8 7.6% | 36.0 | 7.5% | 8.9% | 432 |
| balance | √(K·k_ε), K = the budget per hour: the geometric mean of the two | ε = 0.105 | 18 4.4% | 75 8.8% | 12 0.035% | 11 3.2e-14% | 19 5% | 71 6.8% | 8 0.00051% | 13 0.15% | 23 4% | 23 0.6% | 141 11% | 17 2.8% | 36.0 | 3.9% | 2.9% | 165 |
| marginal | minimise e² + λk | λ = 2.87e-05 | 45 2.6% | 216 6.9% | 5 0.072% | 4 2e-06% | 52 2.8% | 15 7% | 2 0.00093% | 8 0.43% | 21 4.3% | 21 0.71% | 15 12% | 27 1% | 36.0 | 3.4% | 1.1% | 361 |
| marginal (RMSE) | minimise e + λk: the least mean RMSE for the total | λ = 0.000203 | 71 2.1% | 140 7.7% | 6 0.05% | 5 6.5e-14% | 78 2.3% | 14 7.1% | 2 0.00069% | 14 0.13% | 21 3.9% | 28 0.41% | 13 12% | 39 0.52% | 36.0 | 3.3% | 0.54% | 277 |
| amortized | first k with √(e² − σ²) / (points added) < θ / hours, σ the block's noise (the default) | θ = 0.000454 | 65 2.2% | 161 7.3% | 5 0.054% | 4 7.5e-09% | 71 2.4% | 15 7.1% | 2 0.00073% | 11 0.22% | 33 3.9% | 21 0.69% | 14 12% | 29 0.95% | 36.0 | 3.3% | 0.95% | 200 |
| amortized, no floor | first k with e / (points added) < θ / hours | θ = 0.00145 | 33 3.1% | 86 8.5% | 5 0.064% | 4 2.1e-06% | 36 3.4% | 69 6.8% | 2 0.00093% | 8 0.41% | 49 4% | 19 1.5% | 99 11% | 22 1.6% | 36.0 | 3.6% | 1.7% | 112 |
| tolerance+noise | tolerance, ε raised to the noise σ | ε = 0.0968 | 10 7% | 223 7.6% | 4 1.8% | 4 2.2e-05% | 12 7.1% | 47 8.8% | 2 0.0047% | 5 2.7% | 24 7% | 15 6.2% | 78 11% | 8 7.4% | 36.0 | 6% | 7% | 383 |
| balance+noise | balance, ε raised to the noise σ | ε = 0.039 | 39 2.9% | 112 8% | 12 0.035% | 12 2.2e-14% | 43 3.1% | 49 6.8% | 9 0.00051% | 14 0.13% | 43 3.8% | 25 0.49% | 49 11% | 25 1.2% | 36.0 | 3.4% | 1.3% | 132 |
| knee | first k where 2k points don't improve e by 2^α (α = 0.25, not calibrated) | α = 0.25 | 3061 1.3e-12% | 8 12% | 8 0.039% | 5 2.3e-14% | 2882 1.3e-13% | 9 7.6% | 3 0.00063% | 19 0.094% | 11 4.5% | 28 0.44% | 8 13% | 3600 5.7e-18% | 803.4 | 3.7% | 0.083% | 3,600 |
Each rule's points per block against the tolerance rule's at the same budget (the tolerance rule follows complexity linearly by definition), every block of the corpus, and the slope of a log-log fit: 0 is a fixed count, 1 the tolerance, ½ the plain geometric mean. All slopes: count 0.00, tolerance 1.00, balance 0.61, marginal 0.47, marginal (RMSE) 0.36, amortized 0.37, amortized, no floor 0.70, tolerance+noise 0.82, balance+noise 0.38.
The same calibration at other averages: mean / median block RMSE and, below, the most points one block got.
| rule | 12/h | 36/h | 108/h | 324/h |
|---|---|---|---|---|
| count | 5.7% / 5.4% max k 12 | 3.6% / 0.62% max k 36 | 3.1% / 0.33% max k 108 | 2.5% / 0.3% max k 324 |
| tolerance | 8.5% / 10% max k 196 | 7.5% / 8.9% max k 432 | 5.7% / 7% max k 864 | 3% / 3.7% max k 1,700 |
| balance | 6.2% / 7% max k 61 | 3.9% / 2.9% max k 165 | 2.9% / 0.6% max k 402 | 1.9% / 0.34% max k 921 |
| marginal | 4.3% / 1.9% max k 47 | 3.4% / 1.1% max k 361 | 2.9% / 1% max k 1,066 | 1.9% / 0.57% max k 1,773 |
| marginal (RMSE) | 4.2% / 1.4% max k 33 | 3.3% / 0.54% max k 277 | 2.7% / 0.41% max k 1,250 | 1.8% / 0.4% max k 1,953 |
| amortized | 4.7% / 3.9% max k 38 | 3.3% / 0.95% max k 200 | 2.8% / 0.39% max k 473 | 2.8% / 0.37% max k 1,654 |
| amortized, no floor | 5.1% / 5.2% max k 29 | 3.6% / 1.7% max k 112 | 2.8% / 0.77% max k 402 | 1.9% / 0.35% max k 1,171 |
| tolerance+noise | 8.4% / 10% max k 161 | 6% / 7% max k 383 | 3% / 1.1% max k 531 | 2.7% / 0.36% max k 1,698 |
| balance+noise | 6.1% / 7% max k 48 | 3.4% / 1.3% max k 132 | 2.9% / 0.34% max k 249 | 2.6% / 0.32% max k 742 |
A rule that adapts to the data, not to how it was stored, should keep about as many points of an hour sampled every 10 s as of the same hour sampled every second (ratio 1 when the signal is captured at both rates; noise can't be), and about as many in one 12 h block as in twelve 1 h blocks (count and balance scale K with the block's hours). Median over day 0's hours of k at 10 s over k at 1 s:
| rule | temperature | flow | tank-level | valve | ph-float32 | motor-current | totalizer | batch | vibration | compressor | white-noise | sine |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| tolerance | 1.00 | 0.46 | 1.00 | 1.00 | 1.00 | 0.64 | 1.00 | 1.00 | 1.00 | 1.00 | 0.30 | 1.25 |
| balance | 1.00 | 0.62 | 1.00 | 1.00 | 1.00 | 0.42 | 1.00 | 0.96 | 1.00 | 1.00 | 0.39 | 1.18 |
| marginal | 0.94 | 0.78 | 1.00 | 1.00 | 0.98 | 13.20 | 1.00 | 1.00 | 4.66 | 0.98 | 17.17 | 0.89 |
| marginal (RMSE) | 1.18 | 1.62 | 1.00 | 1.00 | 1.07 | 23.85 | 1.00 | 1.00 | 9.30 | 0.91 | 25.25 | 1.10 |
| amortized | 0.69 | 0.14 | 1.00 | 1.00 | 0.66 | 1.34 | 1.00 | 1.00 | 0.85 | 0.96 | 1.41 | 1.00 |
| amortized, no floor | 0.94 | 0.82 | 1.00 | 1.00 | 0.94 | 0.83 | 1.00 | 0.95 | 0.91 | 0.98 | 0.80 | 0.91 |
| tolerance+noise | 1.00 | 0.10 | 1.00 | 1.00 | 1.00 | 0.38 | 1.00 | 1.00 | 1.00 | 1.00 | 0.22 | 1.22 |
| balance+noise | 0.91 | 0.25 | 1.00 | 1.00 | 0.88 | 0.50 | 1.00 | 1.00 | 0.69 | 1.00 | 0.46 | 0.96 |
| knee | 0.10 | 1.07 | 1.00 | 1.00 | 0.10 | 1.04 | 1.00 | 1.11 | 0.97 | 11.21 | 1.00 | 0.10 |
k of a 12 h block over the sum of k of its twelve 1 h blocks (day 0, mean of the two halves):
| rule | temperature | flow | tank-level | valve | ph-float32 | motor-current | totalizer | batch | vibration | compressor | white-noise | sine |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| tolerance | 0.09 | 0.00 | 0.29 | 0.23 | 0.15 | 0.01 | 0.12 | 0.42 | 0.07 | 0.79 | 0.00 | 0.78 |
| balance | 0.31 | 0.06 | 0.56 | 0.54 | 0.47 | 0.06 | 0.36 | 0.68 | 0.14 | 0.91 | 0.75 | 0.93 |
| marginal | 0.10 | 0.01 | 0.43 | 0.45 | 0.18 | 0.17 | 0.17 | 0.44 | 0.05 | 0.73 | 0.09 | 0.67 |
| marginal (RMSE) | 0.07 | 0.01 | 0.40 | 0.46 | 0.12 | 0.19 | 0.23 | 0.38 | 0.12 | 0.64 | 0.09 | 0.62 |
| amortized | 0.51 | 0.33 | 0.43 | 0.49 | 0.67 | 0.21 | 0.24 | 0.68 | 0.21 | 0.84 | 0.06 | 0.91 |
| amortized, no floor | 0.47 | 0.26 | 0.44 | 0.51 | 0.63 | 0.28 | 0.25 | 0.59 | 0.08 | 0.83 | 0.83 | 0.92 |
| tolerance+noise | 0.10 | 0.00 | 0.30 | 0.27 | 0.26 | 0.01 | 0.11 | 0.45 | 0.02 | 0.82 | 0.48 | 0.90 |
| balance+noise | 0.36 | 0.22 | 0.63 | 0.64 | 0.54 | 0.15 | 0.35 | 0.68 | 0.11 | 0.92 | 0.56 | 0.97 |
| knee | 1.27 | 0.13 | 0.42 | 0.55 | 0.00 | 0.24 | 0.25 | 0.37 | 0.45 | 0.02 | 0.09 | 0.00 |
Errors so far are relative to each block's range, so a block's precision follows its own range whatever the units or the offset: an hour of furnace that moves 4 °F around 2,115 °F gets the same relative precision as an hour of a valve that moves 0-100%. The alternatives are the block's largest absolute value, the geometric mean of the two, and a known span per series (here each signal's range over all its days, as a historian's per-tag span would give, but which a store that knows nothing of the series lacks). The ranking doesn't depend on the scale, only the rules' thresholds do. Six more signals where it matters: furnace, furnace-dropout, grid-frequency, ambient-C, ambient-K, level-quiet.
Each rule and scale calibrated to 36 points per hour over all 2,160 blocks of the 18 signals (5 days each). Cells are the mean points kept per hour and, below, the mean RMSE relative to the signal's span: a measure that doesn't depend on the units, the offset or the block, so the scales can be compared (the furnace with dropouts is measured off its dropouts). The last row is the least mean RMSE over span possible for the total, which needs the span.
| rule | errors relative to | temperature | flow | tank-level | valve | ph-float32 | motor-current | totalizer | batch | vibration | compressor | white-noise | sine | furnace | furnace-dropout | grid-frequency | ambient-C | ambient-K | level-quiet | mean | worst signal | max k |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| tolerance | range | 7 2% | 79 1.9% | 4 3.3% | 4 1.4% | 8 5.1% | 63 3.7% | 2 0.05% | 5 4.7% | 7 1.3% | 13 9.8% | 127 8.6% | 8 7.6% | 10 1.2% | 9 32% | 22 3.6% | 6 1.4% | 6 1.4% | 270 0.56% | 5% | 32% | 469 |
| tolerance | √(range·max|x|) | 4 4% | 4 5% | 4 4.7% | 3 2.1% | 4 12% | 4 7.5% | 2 0.07% | 4 7.3% | 4 1.7% | 13 11% | 568 6.9% | 8 7.6% | 4 3.8% | 4 46% | 4 11% | 5 2% | 4 2.6% | 4 2% | 7.6% | 46% | 793 |
| tolerance | max|x| | 4 4% | 4 5.2% | 3 5.8% | 3 4.2% | 4 12% | 4 9.2% | 2 0.072% | 4 14% | 4 2.8% | 10 16% | 573 6.8% | 8 7.6% | 4 3.8% | 4 67% | 4 11% | 4 2.4% | 4 2.6% | 4 2.1% | 9.7% | 67% | 813 |
| tolerance | span (known) | 4 4% | 4 4.5% | 4 3.3% | 4 1.8% | 6 5.9% | 5 6.1% | 2 0.084% | 5 3.1% | 4 1.7% | 16 4.9% | 558 6.9% | 10 6.5% | 4 3.4% | 5 3.4% | 5 6.6% | 4 2.6% | 4 2.6% | 4 0.91% | 3.8% | 6.9% | 616 |
| balance | range | 18 0.93% | 74 1.5% | 12 0.022% | 11 0.00028% | 19 2.6% | 70 2.2% | 8 1.3e-05% | 13 0.13% | 22 0.31% | 23 0.6% | 138 8.4% | 17 2.8% | 19 0.4% | 18 0.56% | 36 2.6% | 16 0.6% | 16 0.6% | 115 0.027% | 1.4% | 8.4% | 175 |
| balance | √(range·max|x|) | 14 1.1% | 58 1.6% | 12 0.022% | 12 9.6e-15% | 17 2.8% | 78 2.2% | 8 1.3e-05% | 14 0.11% | 21 0.3% | 25 0.49% | 276 7.9% | 27 1.1% | 12 0.71% | 13 0.75% | 12 3.9% | 25 0.5% | 12 0.73% | 12 0.028% | 1.3% | 7.9% | 285 |
| balance | max|x| | 12 1.2% | 23 1.7% | 13 0.021% | 12 9.7e-15% | 14 3.1% | 49 2.2% | 8 1.3e-05% | 15 0.1% | 16 0.31% | 26 0.46% | 319 7.7% | 34 0.7% | 12 0.71% | 13 0.73% | 12 3.9% | 46 0.43% | 12 0.73% | 12 0.028% | 1.3% | 7.7% | 325 |
| balance | span (known) | 16 1% | 19 1.7% | 12 0.022% | 12 9.6e-15% | 34 1.8% | 82 2.2% | 8 1.3e-05% | 15 0.1% | 13 0.32% | 25 0.48% | 269 7.9% | 28 0.99% | 15 0.47% | 16 0.5% | 48 2.2% | 13 0.7% | 13 0.69% | 12 0.028% | 1.2% | 7.9% | 272 |
| amortized | range | 55 0.49% | 136 1.3% | 5 0.036% | 4 0.017% | 60 1.3% | 14 2.3% | 2 4.9e-05% | 10 0.21% | 28 0.31% | 21 0.76% | 13 9.2% | 26 1.1% | 29 0.32% | 25 1.1% | 100 1.5% | 41 0.4% | 41 0.4% | 37 0.032% | 1.2% | 9.2% | 169 |
| amortized | √(range·max|x|) | 59 0.47% | 176 1.2% | 6 0.029% | 5 0.0059% | 76 1.2% | 17 2.3% | 2 2.5e-05% | 13 0.11% | 39 0.29% | 27 0.43% | 17 9.2% | 51 0.33% | 16 0.42% | 14 0.95% | 29 2.7% | 64 0.34% | 26 0.47% | 10 0.031% | 1.1% | 9.2% | 214 |
| amortized | max|x| | 50 0.51% | 180 1.2% | 7 0.025% | 5 0.0015% | 74 1.2% | 19 2.3% | 3 2.5e-05% | 16 0.081% | 45 0.28% | 32 0.36% | 18 9.2% | 79 0.14% | 9 1.2% | 10 1.3% | 8 4.7% | 74 0.33% | 14 0.66% | 6 0.035% | 1.3% | 9.2% | 224 |
| amortized | span (known) | 52 0.5% | 109 1.4% | 6 0.031% | 5 0.0087% | 99 1% | 16 2.3% | 2 0.00053% | 13 0.11% | 15 0.31% | 27 0.43% | 17 9.2% | 45 0.43% | 23 0.34% | 23 0.39% | 126 1.3% | 32 0.43% | 32 0.43% | 6 0.034% | 1% | 9.2% | 147 |
| least mean RMSE | span (known) | 56 0.48% | 27 1.7% | 6 0.028% | 5 1e-14% | 113 0.95% | 14 2.3% | 2 9.7e-05% | 17 0.077% | 14 0.32% | 32 0.36% | 20 8.9% | 68 0.18% | 24 0.33% | 25 0.37% | 150 1.2% | 34 0.42% | 35 0.42% | 6 0.033% | 1% | 8.9% | 471 |
Block length: k of a 12 h block over the sum of k of its twelve 1 h blocks (all days), with θ / hours for the amortized rule. A ratio below 1 is partly right: twelve blocks keep twelve sets of required points, and a longer block can spend its points where they matter most.
| rule | errors relative to | temperature | flow | tank-level | valve | ph-float32 | motor-current | totalizer | batch | vibration | compressor | white-noise | sine | furnace | furnace-dropout | grid-frequency | ambient-C | ambient-K | level-quiet | geometric mean |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| amortized | range | 0.42 | 0.36 | 0.44 | 0.56 | 0.69 | 0.19 | 0.25 | 0.65 | 0.20 | 0.84 | 0.06 | 0.90 | 0.49 | 0.06 | 0.56 | 0.34 | 0.34 | 0.03 | 0.30 |
| amortized | √(range·max|x|) | 0.62 | 0.60 | 0.40 | 0.55 | 0.81 | 0.15 | 0.26 | 0.70 | 0.31 | 0.84 | 0.04 | 0.96 | 0.66 | 0.24 | 0.66 | 0.57 | 0.52 | 0.11 | 0.40 |
| amortized | max|x| | 0.89 | 0.88 | 0.44 | 0.59 | 0.94 | 0.14 | 0.25 | 0.73 | 0.48 | 0.81 | 0.04 | 0.98 | 0.65 | 0.67 | 0.51 | 0.85 | 0.76 | 0.20 | 0.49 |
| amortized | span (known) | 0.94 | 0.96 | 0.42 | 0.56 | 0.97 | 0.17 | 0.26 | 0.73 | 0.73 | 0.84 | 0.05 | 0.94 | 0.85 | 0.87 | 0.98 | 0.90 | 0.90 | 0.20 | 0.55 |
| least mean RMSE | span (known) | 0.18 | 0.07 | 0.38 | 0.52 | 0.19 | 0.23 | 0.26 | 0.44 | 0.24 | 0.63 | 0.05 | 0.36 | 0.49 | 0.50 | 0.06 | 0.23 | 0.22 | 0.16 | 0.23 |
The rmse pass at θ = 0.0015 (the old default: coarse, so that the guards do the work) alone, then with max_error set to an engineer's value per signal (the historian tags' CompDev, otherwise about what matters), then with a safety cap of max_points = 360 per hour (10% of the scans) as well. The max_error pass splits each line between the rmse pass's points that strays further, by farthest reach (below). Swinging door at CompDev = max_error / 2 has the same guarantee: its doors pivot about the archived point, so at CompDev = max_error (last column) its line strays up to twice that. Cells: mean points per hour · the largest error of any block over max_error · mean RMSE over the signal's span; below, the share of blocks the cap bound (rmse_met or max_error_met false).
| signal | max_error | rmse pass | max_error | max_error, max_points 360 | swinging door, CompDev = max_error / 2 | swinging door, CompDev = max_error |
|---|---|---|---|---|---|---|
| temperature | 0.1 | 32 · 8 · 0.66% | 509 · 1 · 0.22% | 358 · 1.2 · 0.26% capped 100% | 1136 · 0.95 · 0.15% | 342 · 1.9 · 0.31% |
| flow | 1 | 72 · 9.8 · 1.5% | 1742 · 1 · 0.37% | 358 · 2.7 · 1.2% capped 100% | 2201 · 0.93 · 0.24% | 1363 · 1.9 · 0.6% |
| tank-level | 0.2 | 5 · 9 · 0.053% | 5 · 1 · 0.037% | 5 · 1 · 0.037% | 4 · 0.99 · 0.07% | 4 · 2 · 0.18% |
| valve | 0.5 | 4 · 20 · 0.029% | 5 · 1 · 5.7e-05% | 5 · 1 · 5.7e-05% | 5 · 1.4e-12 · 1e-14% | 4 · 2 · 0.018% |
| ph-float32 | 0.02 | 35 · 8.3 · 1.8% | 512 · 1 · 0.55% | 359 · 1.3 · 0.67% capped 100% | 1054 · 0.95 · 0.38% | 357 · 1.9 · 0.82% |
| motor-current | 0.2 | 12 · 34 · 2.3% | 3150 · 1 · 0.091% | 357 · 11 · 2.3% capped 100% | 3261 · 0.88 · 0.06% | 2958 · 1.8 · 0.16% |
| totalizer | 1 | 2 · 36 · 0.00022% | 2 · 0.99 · 2.6e-05% | 2 · 0.99 · 2.6e-05% | 2 · 0.98 · 6.1e-05% | 2 · 2 · 0.00018% |
| batch | 0.2 | 8 · 9.5 · 0.36% | 23 · 1 · 0.094% | 23 · 1 · 0.094% | 228 · 0.98 · 0.075% | 13 · 2 · 0.15% |
| vibration | 0.1 | 18 · 6.5 · 0.35% | 36 · 1 · 0.41% | 36 · 1 · 0.41% | 422 · 0.99 · 0.29% | 15 · 2 · 0.44% |
| compressor | 0.5 | 18 · 34 · 1.5% | 28 · 1 · 0.54% | 28 · 1 · 0.54% | 87 · 0.97 · 0.42% | 23 · 1.6 · 0.61% |
| white-noise | 1 | 11 · 6.4 · 9.2% | 1681 · 1 · 3.8% | 357 · 2.1 · 9.1% capped 100% | 2223 · 0.94 · 2.4% | 1333 · 1.9 · 6% |
| sine | 0.01 | 22 · 8.3 · 1.6% | 61 · 1 · 0.33% | 61 · 1 · 0.33% | 62 · 0.83 · 0.26% | 44 · 1.6 · 0.52% |
| furnace | 1 | 20 · 0.96 · 0.37% | 20 · 0.96 · 0.37% | 20 · 0.96 · 0.37% | 15 · 0.93 · 0.84% | 9 · 1.9 · 2.1% |
| furnace-dropout | 1 | 18 · 6.6 · 1.2% | 20 · 1 · 0.6% | 20 · 1 · 0.6% | 16 · 0.93 · 0.86% | 9 · 1.9 · 2.1% |
| grid-frequency | 0.005 | 57 · 7.6 · 2% | 282 · 1 · 1.2% | 282 · 1 · 1.2% | 597 · 0.97 · 0.85% | 178 · 1.9 · 1.8% |
| ambient-C | 0.2 | 26 · 2.1 · 0.48% | 49 · 1 · 0.45% | 49 · 1 · 0.45% | 148 · 0.99 · 0.37% | 26 · 1.9 · 0.73% |
| ambient-K | 0.2 | 26 · 2.4 · 0.48% | 48 · 1 · 0.45% | 48 · 1 · 0.45% | 111 · 0.99 · 0.39% | 27 · 1.9 · 0.72% |
| level-quiet | 0.1 | 19 · 1.2 · 0.035% | 19 · 0.98 · 0.035% | 19 · 0.98 · 0.035% | 2 · 0.99 · 0.082% | 2 · 2 · 0.15% |
| all | 23 · 36 · 1.3% | 455 · 1 · 0.53% | 133 · 11 · 0.99% | 643 · 0.99 · 0.43% | 373 · 2 · 0.97% |
How max_error is met. Each line between the rmse pass's points that strays further than max_error is split from its left end. Every scan after an anchor bounds the slope of a line from the anchor that passes within max_error of it, from below and above. Swinging door's rule ends the line at the first scan its chord can't reach. Farthest reach, fluxprune's, scans on while the bounds still overlap (some line could still pass every scan so far), notes the last scan whose own chord satisfies them, and when they cross makes that scan the next anchor: with noise, a scan after one the chord can't reach is often reachable again. Fewest points is the exact minimum (dynamic programming over every feasible chord). Cells: points per hour · RMSE over the span; the rmse pass's points the same in each column; below, the max_error pass's time per 1 h block.
| signal | swinging door's rule | farthest reach (fluxprune) | fewest points (exact) |
|---|---|---|---|
| temperature | 766 · 0.21% | 509 · 0.22% | 424 · 0.22% |
| flow | 1926 · 0.34% | 1742 · 0.37% | 1693 · 0.37% |
| tank-level | 5 · 0.037% | 5 · 0.037% | 5 · 0.037% |
| valve | 6 · 8.2e-05% | 6 · 8.2e-05% | 6 · 0.0034% |
| ph-float32 | 728 · 0.53% | 512 · 0.55% | 436 · 0.56% |
| motor-current | 3162 · 0.09% | 3150 · 0.091% | 3149 · 0.091% |
| totalizer | 3 · 2.9e-05% | 3 · 2.9e-05% | 3 · 2.8e-05% |
| batch | 73 · 0.088% | 23 · 0.094% | 20 · 0.091% |
| vibration | 176 · 0.35% | 36 · 0.41% | 31 · 0.41% |
| compressor | 33 · 0.5% | 28 · 0.54% | 28 · 0.54% |
| white-noise | 1914 · 3.5% | 1681 · 3.8% | 1609 · 3.9% |
| sine | 62 · 0.33% | 61 · 0.33% | 60 · 0.33% |
| furnace | 20 · 0.37% | 20 · 0.37% | 20 · 0.37% |
| furnace-dropout | 20 · 0.58% | 20 · 0.6% | 19 · 0.6% |
| grid-frequency | 379 · 1.1% | 282 · 1.2% | 245 · 1.2% |
| ambient-C | 69 · 0.42% | 49 · 0.45% | 45 · 0.44% |
| ambient-K | 65 · 0.43% | 48 · 0.45% | 44 · 0.45% |
| level-quiet | 19 · 0.035% | 19 · 0.035% | 19 · 0.035% |
| all | 536 · 0.51% 10 µs per block, at most 24 µs | 466 · 0.54% 13 µs per block, at most 45 µs | 447 · 0.55% 489 µs per block, at most 8,378 µs |
prune(refit=True) keeps the same times but replaces the values with those whose lines fit the input best in time-weighted L2 (a tridiagonal system over the kept points, O(n)), the first, last, minimum and maximum points (and the points around gaps) keeping their samples. Per signal, over 1 h blocks of all days with any error left: the geometric mean of the RMSE and the largest error with refit over without, at the rmse pass's points; and with max_error = the signal's guard value, the share of blocks where it still holds after the refit (it isn't checked again).
| signal | RMSE, refit / samples | largest error, refit / samples | max_error still holds |
|---|---|---|---|
| temperature | 0.99 | 1.00 | 0% |
| flow | 0.99 | 0.99 | 0% |
| tank-level | 0.98 | 1.00 | 99.2% |
| valve | 0.99 | 1.00 | 100% |
| ph-float32 | 0.99 | 0.99 | 0% |
| motor-current | 0.99 | 1.00 | 87.5% |
| totalizer | 0.96 | 0.98 | 100% |
| batch | 0.76 | 0.90 | 1.67% |
| vibration | 0.97 | 0.98 | 0% |
| compressor | 0.58 | 0.83 | 15% |
| white-noise | 1.00 | 1.00 | 0% |
| sine | 0.54 | 0.82 | 100% |
| furnace | 0.99 | 0.99 | 100% |
| furnace-dropout | 0.95 | 0.98 | 80.8% |
| grid-frequency | 0.99 | 0.99 | 0% |
| ambient-C | 0.97 | 0.98 | 0% |
| ambient-K | 0.97 | 0.99 | 0% |
| level-quiet | 0.98 | 0.97 | 100% |
| all | 0.91 | 0.96 | 41.3% |
The same rules with the same parameters on what each tag's swinging-door historian archived (tslab/common/historian.py: exception + swinging-door filters at the tag's CompDev), in 1 h blocks: points per hour in and kept (share of the archive), and below, the geometric-mean RMSE against the archive's own straight lines (relative to the block's range).
| tag | archived/h | count | tolerance | balance | tolerance+noise | balance+noise |
|---|---|---|---|---|---|---|
| temperature | 325 | 36 (11%) 2.8% | 7 (2.1%) 9.3% | 18 (5.4%) 4.4% | 9 (2.9%) 7.1% | 33 (10%) 3% |
| flow | 1362 | 36 (2.6%) 9.7% | 80 (5.9%) 10% | 71 (5.2%) 9% | 15 (1.1%) 12% | 24 (1.8%) 10% |
| tank-level | 2 | 2 (1e+02%) 1.5e-15% | 2 (97%) 2.2e-09% | 2 (98%) 3.6e-11% | 2 (99%) 2.9e-12% | 2 (1e+02%) 1.5e-15% |
| valve | 2 | 2 (1e+02%) 4.4e-16% | 2 (88%) 0.0029% | 2 (93%) 2e-05% | 2 (90%) 2.3e-05% | 2 (96%) 3e-08% |
| ph-float32 | 340 | 36 (11%) 3.3% | 8 (2.3%) 9.6% | 19 (5.6%) 5% | 11 (3.3%) 7.2% | 36 (11%) 3.3% |
| motor-current | 2957 | 36 (1.2%) 6.9% | 86 (2.9%) 10% | 71 (2.4%) 6.9% | 7 (0.25%) 10% | 17 (0.58%) 7% |
Apple M3, one thread, best of 3, 1 s scans. rank is the O(n log n) pass; prune adds picking k (O(n)) and the two O(n) refinement passes, shown as the time it adds to rank; max_error (1% of the range) adds the max_error pass on top of that, and refit the least-squares values, both also against rank. bench/prune.py has the same per call for more signals.
| signal | n | rank | per point | prune (rmse pass) | prune, max_error | prune, refit |
|---|---|---|---|---|---|---|
| temperature | 3,600 | 0.4 ms | 114 ns | +0.1 ms (+28%) | +0.2 ms (+44%) | +0.1 ms (+33%) |
| temperature | 43,200 | 7.0 ms | 161 ns | +1.6 ms (+23%) | +2.0 ms (+29%) | +2.0 ms (+29%) |
| temperature | 86,400 | 15.1 ms | 174 ns | +3.3 ms (+22%) | +4.0 ms (+26%) | +4.1 ms (+28%) |
| temperature | 1,000,000 | 323.8 ms | 324 ns | +38.0 ms (+12%) | +45.3 ms (+14%) | +44.6 ms (+14%) |
| white-noise | 3,600 | 0.4 ms | 111 ns | +0.1 ms (+26%) | +0.2 ms (+50%) | +0.1 ms (+28%) |
| white-noise | 43,200 | 7.3 ms | 169 ns | +1.5 ms (+20%) | +2.8 ms (+38%) | +1.7 ms (+23%) |
| white-noise | 86,400 | 15.5 ms | 179 ns | +2.6 ms (+17%) | +5.2 ms (+34%) | +3.1 ms (+20%) |
| white-noise | 1,000,000 | 328.8 ms | 329 ns | +25.1 ms (+8%) | +56.8 ms (+17%) | +31.7 ms (+10%) |
tslab/prune/rules.py, noise), regular or irregular times.error_per_point in the API.max_error = 10σ (162, the same peaks), not by the floor. At the default θ the floor is what
makes the low rate usable: without it the noise-dominated signals keep most of their scans (white noise 2,941 of 3,600
per hour, motor current 2,705, the pyrometer's quiet hours 2,933), with it 18, 22 and 22. Its costs: correlated noise reads partly as
signal at the sampling rate (flow, τ 5 s: at 10 s scans the same noise reads as noise and keeps 0.14× as many points;
temperature and pH 0.66-0.69×), so it is less stable under a change of sampling rate than the rule without it; and on
pure noise the lines from the required minimum and maximum lean on the few points left (the worst of 120 hours of
white noise 0.48σ from the zero under it, against 0.38σ without the floor; 0.26σ against 0.33σ on average). Every noise
estimate assumes the times were chosen independently of the values, which an archive's weren't: on the tags'
swinging-door archives the estimate read about the CompDev (temperature 3× its scans' noise) and passed tests of
white noise (residuals near zero, their lag-1 correlation) as well as the scans, and the floor doubled the error
against the scans (temperature 3.3% of the range against 1.9%). No test on the values tells them apart, so the floor
runs only on a cadence (at least 90% of the intervals one scan, fluxcode's own test): the archives get none, and keep
what they kept without it. Scoring the
ranking against a smoothed copy of the scans was tried and dropped: a line through noisy samples is no closer to a
smooth curve, so it keeps as many points on noise, needs a window, and shaves narrow peaks.max_error guard (next).refit=True): least-squares values at the same kept times, the required points pinned to
their samples, lower the RMSE by about a tenth overall, almost all of it on smooth or step-like signals (sine,
compressor, batch: 0.5-0.8×) where a chord cuts a curve's corner; on noisy ones by 1-3%. It costs one O(n) tridiagonal
solve. The values are then no longer readings, and it doesn't mix with max_error: a least-squares line through noise
moves away from the scans that set the bound, so max_error still holds in only 41% of the blocks (none on the noisy
signals, all on the smooth or quiet ones). It stays an option to measure on real data. Choosing the times and the values together (a free-knot fit, or Imai and Iri's 1986 fewest-segments algorithm
for max_error with corners off the samples) could gain more and wasn't tried.The API. prune(t, x) returns the kept times and values, the RMSE and largest error they leave,
and rmse_met, max_error_met and max_points_met. The rmse pass is the default
(error_per_point: an exchange rate, best chosen as the RMSE one would accept at a number of points
divided by that number, θ = Y / X; the error relative to each block's range and read above its white noise,
noise_floor=True). Its default is 0.1% at a tenth of the input's points, θ = 0.001 / (n / 10)
(2.8·10⁻⁶ for 1 h of 1 s scans; ~4% of the scans on this corpus, the median block 2%): scaled by the length, it keeps
about the same density in long blocks, but fewer points per hour at a slower sampling rate. Two optional guards bound it:
max_points, a hard cap, and max_error, the largest error of any scan in value units, which
also takes care of quiet blocks, glitches and the block length for a configured signal. θ = 0 with
max_points keeps the best max_points; θ = ∞ with max_error runs the max_error
pass alone. A run of non-finite values keeps its first point and the finite points around it (a forward hold), past
max_points if need be. The other rules on this page (count, tolerance, balance, ε raised to the noise,
marginal, knee) are experiments in tslab/prune/rules.py.