36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 1import { useEffect, useMemo, useRef, useState } from 'react'
95929e6Monte-Carlo ground-truth script for R, with the command printed in the UIJeremy Magland 2import CopyableCommand from './components/CopyableCommand'
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 3import Controls from './components/Controls'
4import FilterViz from './components/FilterViz'
5import ScrollingView from './components/ScrollingView'
6import CompressionChart from './components/CompressionChart'
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 7import MethodNote from './components/MethodNote'
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 8import { useEntropyRate } from './components/useEntropyRate'
fec2284Show analytic entropy-rate prediction alongside the Monte-Carlo estimateJeremy Magland 9import { predictEntropyRate } from './entropy'
fb1a12fSnap every parameter slider to a ladder of round valuesJeremy Magland 10import { DEFAULT_SPEC, clampSpec, designKernel, kernelNorm } from './model/filters'
e411dffMake LPC order and compression block size controlsJeremy Magland 11import { LATENT_SEED } from './model/latent'
12import { DEFAULT_LPC_ORDER, LPC_ORDERS } from './compress/codecs'
4ec0133Code against the prediction, not the integer residualJeremy Magland 13import type { CodecResult } from './compress/codecs'
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 14import type { CompressRequest, CompressResponse } from './worker/compressWorker'
e411dffMake LPC order and compression block size controlsJeremy Magland 16const BLOCK_SIZES = [10000, 20000, 50000, 100000, 200000, 500000, 1000000]
17const DEFAULT_BLOCK_SIZE = 100000
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 18
19interface CompressionState {
20 results: CodecResult[]
21 empiricalStd: number
22 computing: boolean
23 error: string | null
24}
4ec0133Code against the prediction, not the integer residualJeremy Magland 26/** The ten codec sizes, measured in a worker on a debounced parameter set. */
28 kernel: Float64Array,
29 sigma: number,
30 lpcOrder: number,
31 blockSize: number,
32): CompressionState {
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 33 const [state, setState] = useState<CompressionState>({
34 results: [],
35 empiricalStd: 0,
36 computing: true,
37 error: null,
38 })
39 const workerRef = useRef<Worker | null>(null)
40 const idRef = useRef(0)
42 useEffect(() => {
43 const worker = new Worker(new URL('./worker/compressWorker.ts', import.meta.url), {
44 type: 'module',
45 })
46 worker.onmessage = (e: MessageEvent<CompressResponse>) => {
47 if (e.data.id !== idRef.current) return
48 setState({
49 results: e.data.error ? [] : e.data.results,
50 empiricalStd: e.data.empiricalStd,
51 computing: false,
52 error: e.data.error ?? null,
53 })
54 }
55 workerRef.current = worker
56 return () => {
57 worker.terminate()
58 workerRef.current = null
59 }
60 }, [])
62 useEffect(() => {
63 setState(s => ({ ...s, computing: true }))
64 const id = ++idRef.current
65 const timer = setTimeout(() => {
66 const request: CompressRequest = {
67 id,
68 kernel,
69 sigma,
71 lpcOrder,
72 // The same seed the signal view draws from, so the block really is
73 // the data on screen.
74 seed: LATENT_SEED,
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 75 }
76 workerRef.current?.postMessage(request)
77 }, 250)
78 return () => clearTimeout(timer)
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 79 }, [kernel, sigma, lpcOrder, blockSize])
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 80
81 return state
82}
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 84/**
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 85 * The terminal command that estimates the entropy rate R at the current
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 86 * settings, using the unbiased Monte-Carlo estimator from the companion
87 * timeseries-entropy package.
88 */
89function mcCommand(sigma: number, spec: ReturnType<typeof clampSpec>, rate: number): string {
90 const parts = [
91 'uvx --from git+https://github.com/concept-collection/timeseries-entropy',
92 'timeseries-entropy',
93 `--sigma ${sigma}`,
94 ]
95929e6Monte-Carlo ground-truth script for R, with the command printed in the UIJeremy Magland 95 switch (spec.family) {
96 case 'none':
97 parts.push('--filter none')
98 break
99 case 'movingAverage':
100 parts.push('--filter moving-average', `--width ${spec.width}`)
101 break
102 case 'lowpass':
103 parts.push('--filter lowpass', `--high ${spec.highHz}`, `--taps ${spec.taps}`, `--rate ${rate}`)
104 break
105 case 'bandpass':
106 parts.push(
107 '--filter bandpass',
108 `--low ${spec.lowHz}`,
109 `--high ${spec.highHz}`,
110 `--taps ${spec.taps}`,
111 `--rate ${rate}`,
112 )
113 break
114 case 'firstDifference':
115 parts.push('--filter first-difference')
116 break
117 }
118 return parts.join(' ')
119}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 121export default function App() {
122 const [sigma, setSigma] = useState(5)
123 const [sampleRateHz, setSampleRateHz] = useState(30000)
124 const [spec, setSpec] = useState(DEFAULT_SPEC)
e411dffMake LPC order and compression block size controlsJeremy Magland 125 const [lpcOrder, setLpcOrder] = useState(DEFAULT_LPC_ORDER)
126 const [blockSize, setBlockSize] = useState(DEFAULT_BLOCK_SIZE)
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 127
128 const kernel = useMemo(() => designKernel(spec, sampleRateHz), [spec, sampleRateHz])
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 129 const sigmaY = useMemo(() => sigma * kernelNorm(kernel), [kernel, sigma])
130 const compression = useCompression(kernel, sigma, lpcOrder, blockSize)
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 131 const entropyRate = useEntropyRate(kernel, sigma)
fec2284Show analytic entropy-rate prediction alongside the Monte-Carlo estimateJeremy Magland 132 const theoryBits = useMemo(() => predictEntropyRate(kernel, sigma), [kernel, sigma])
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 133
134 return (
135 <div className="app">
136 <header className="app-header">
137 <h1>Time-series compressibility</h1>
138 <p>
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 139 Gaussian noise → FIR filter → round to integers. How well can the integer stream be
140 losslessly compressed, and how close do practical codecs get to the entropy rate of
141 the process?
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 142 </p>
143 </header>
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 145 {/* The controls stay pinned so the parameters and the ratios they move
146 are always on screen together, whatever is scrolled to. */}
147 <section className="card control-bar">
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 148 <Controls
149 sigma={sigma}
150 setSigma={setSigma}
151 sampleRateHz={sampleRateHz}
fb1a12fSnap every parameter slider to a ladder of round valuesJeremy Magland 152 setSampleRateHz={rate => {
153 // Band edges are absolute, so a new rate can push them past
154 // Nyquist; re-snap the spec so sliders and kernel stay in step.
155 setSampleRateHz(rate)
156 setSpec(s => clampSpec(s, rate))
157 }}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 158 spec={spec}
159 setSpec={setSpec}
160 />
161 </section>
163 <section className="card">
164 <h2>Compression</h2>
fec2284Show analytic entropy-rate prediction alongside the Monte-Carlo estimateJeremy Magland 165 {/* Two readouts of the same number, each keyed to its chart line by a
166 sample of that line's own stroke. */}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 167 <div className="stat-row">
168 <div className="stat">
fec2284Show analytic entropy-rate prediction alongside the Monte-Carlo estimateJeremy Magland 169 <span className="label">
170 <span className="line-swatch" style={{ borderTop: '2px dotted var(--theory)' }} />
171 entropy rate R — analytic theory
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 172 </span>
173 <span className="value">
fec2284Show analytic entropy-rate prediction alongside the Monte-Carlo estimateJeremy Magland 174 {theoryBits.toFixed(2)} <small>bits/sample</small>
175 </span>
176 <span className="stat-sub">
177 {theoryBits > 0 ? `best possible ratio ${(16 / theoryBits).toFixed(2)}×` : '—'}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 178 </span>
179 </div>
180 <div className="stat">
fec2284Show analytic entropy-rate prediction alongside the Monte-Carlo estimateJeremy Magland 181 <span className="label">
182 <span className="line-swatch" style={{ borderTop: '2px dashed var(--ink-2)' }} />
183 entropy rate R — Monte-Carlo ground truth
184 </span>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 185 <span className="value">
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 186 {entropyRate.mean !== null ? entropyRate.mean.toFixed(2) : '—'}
187 {entropyRate.se !== null && <small> ± {entropyRate.se.toFixed(2)}</small>}{' '}
189 </span>
fec2284Show analytic entropy-rate prediction alongside the Monte-Carlo estimateJeremy Magland 190 <span className="stat-sub">
191 {entropyRate.mean !== null && entropyRate.mean > 0
192 ? `best possible ratio ${(16 / entropyRate.mean).toFixed(2)}×`
193 : 'run the estimate to check the theory'}
194 </span>
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 195 {/* The estimate lives with its readout: start, watch it refine,
196 stop; a model change resets it. */}
197 <span className="stat-action">
198 <button onClick={entropyRate.running ? entropyRate.stop : entropyRate.start}>
199 {entropyRate.running ? 'stop' : entropyRate.perPast.length > 0 ? 'refine' : 'estimate'}
200 </button>
201 {/* Each independent past contributes one unbiased estimate;
202 the readout is their average, so that is the word used. */}
203 <span className="estimate-status">
204 {entropyRate.running
205 ? `${entropyRate.perPast.length} estimates · ${entropyRate.progress ?? 'starting…'}`
206 : entropyRate.perPast.length > 0
207 ? `${entropyRate.perPast.length} estimates · M = ${entropyRate.past}`
208 : `M = ${entropyRate.past}`}
209 </span>
210 </span>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 212 </div>
e411dffMake LPC order and compression block size controlsJeremy Magland 213 {/* Settings of the measurement, not of the model — so they live with
214 the chart they change rather than in the model bar. */}
215 <div className="measure-row">
216 <label>
217 LPC order
218 <select value={lpcOrder} onChange={e => setLpcOrder(Number(e.target.value))}>
219 {LPC_ORDERS.map(o => (
220 <option key={o} value={o}>
221 {o}
222 </option>
223 ))}
224 </select>
225 </label>
226 <label>
227 block size
228 <select value={blockSize} onChange={e => setBlockSize(Number(e.target.value))}>
229 {BLOCK_SIZES.map(n => (
230 <option key={n} value={n}>
231 {n.toLocaleString()} samples
232 </option>
233 ))}
234 </select>
235 </label>
236 </div>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 237 {compression.error ? (
238 <p className="card-note">Compression failed: {compression.error}</p>
239 ) : (
240 <CompressionChart
241 results={compression.results}
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 242 rateBits={entropyRate.mean}
fec2284Show analytic entropy-rate prediction alongside the Monte-Carlo estimateJeremy Magland 243 rateSe={entropyRate.se}
244 theoryBits={theoryBits}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 245 computing={compression.computing}
246 />
247 )}
248 <p className="card-note">
e411dffMake LPC order and compression block size controlsJeremy Magland 249 Measured on a {blockSize.toLocaleString()}-sample block of the same latent data the
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 250 signal view shows; sizes include everything a decoder needs (ANS symbol table, LPC
fec2284Show analytic entropy-rate prediction alongside the Monte-Carlo estimateJeremy Magland 251 coefficients). Baseline is raw int16 (16 bits/sample). The two reference lines mark
252 the entropy rate R of the process — the one limit no lossless method whatsoever can
253 beat: dotted for the analytic theory, dashed for the Monte-Carlo ground truth, shaded
254 by its standard error (see the method section at the bottom).
4ec0133Code against the prediction, not the integer residualJeremy Magland 255 What separates the methods is the model each one codes against: ANS uses the histogram
256 of whatever stream it is given, so a better prefilter is the only way it improves,
257 while the conditional-Gaussian coder codes each sample against a prediction and can
258 therefore approach R. The strip under the chart scores each coder against its own
259 model, which is a question about the coder rather than about the model.
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 260 </p>
95929e6Monte-Carlo ground-truth script for R, with the command printed in the UIJeremy Magland 261 <CopyableCommand
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 262 label="cross-check R from the command line:"
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 263 command={mcCommand(sigma, spec, sampleRateHz)}
95929e6Monte-Carlo ground-truth script for R, with the command printed in the UIJeremy Magland 264 />
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 265 </section>
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 267 <section className="card">
268 <h2>Quantized signal z</h2>
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 269 <ScrollingView kernel={kernel} sigma={sigma} sigmaY={sigmaY} />
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 270 <p className="card-note">
271 A window of samples from the model, drawn from a fixed latent noise sequence — changing
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 272 σ or the filter transforms the same underlying data, so the trace morphs rather than
273 resampling. Press play to advance through the sequence.
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 274 </p>
275 </section>
277 <section className="card">
278 <h2>Filter</h2>
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 279 <FilterViz kernel={kernel} sampleRateHz={sampleRateHz} />
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 280 </section>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 282 <section className="card">
6b485dfCall R the entropy rate, and put the estimate button under its readoutJeremy Magland 283 <h2>The entropy rate</h2>
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 284 <MethodNote />
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 285 </section>
286 </div>
287 )
288}