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'
3f857e3Estimate the reference rate R in the browserJeremy Magland 8import { useReferenceRate } from './components/useReferenceRate'
fb1a12fSnap every parameter slider to a ladder of round valuesJeremy Magland 9import { DEFAULT_SPEC, clampSpec, designKernel, kernelNorm } from './model/filters'
e411dffMake LPC order and compression block size controlsJeremy Magland 10import { LATENT_SEED } from './model/latent'
11import { DEFAULT_LPC_ORDER, LPC_ORDERS } from './compress/codecs'
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 12import type { BoundResult, CodecResult } from './compress/codecs'
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 13import type { CompressRequest, CompressResponse } from './worker/compressWorker'
e411dffMake LPC order and compression block size controlsJeremy Magland 15const BLOCK_SIZES = [10000, 20000, 50000, 100000, 200000, 500000, 1000000]
16const DEFAULT_BLOCK_SIZE = 100000
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 17
18interface CompressionState {
19 results: CodecResult[]
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 20 bounds: BoundResult[]
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 21 empiricalStd: number
22 computing: boolean
23 error: string | null
24}
26/** The nine 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: [],
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 36 empiricalStd: 0,
37 computing: true,
38 error: null,
39 })
40 const workerRef = useRef<Worker | null>(null)
41 const idRef = useRef(0)
43 useEffect(() => {
44 const worker = new Worker(new URL('./worker/compressWorker.ts', import.meta.url), {
45 type: 'module',
46 })
47 worker.onmessage = (e: MessageEvent<CompressResponse>) => {
48 if (e.data.id !== idRef.current) return
49 setState({
50 results: e.data.error ? [] : e.data.results,
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 51 bounds: e.data.error ? [] : e.data.bounds,
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 52 empiricalStd: e.data.empiricalStd,
53 computing: false,
54 error: e.data.error ?? null,
55 })
56 }
57 workerRef.current = worker
58 return () => {
59 worker.terminate()
60 workerRef.current = null
61 }
62 }, [])
64 useEffect(() => {
65 setState(s => ({ ...s, computing: true }))
66 const id = ++idRef.current
67 const timer = setTimeout(() => {
68 const request: CompressRequest = {
69 id,
70 kernel,
71 sigma,
73 lpcOrder,
74 // The same seed the signal view draws from, so the block really is
75 // the data on screen.
76 seed: LATENT_SEED,
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 77 }
78 workerRef.current?.postMessage(request)
79 }, 250)
80 return () => clearTimeout(timer)
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 81 }, [kernel, sigma, lpcOrder, blockSize])
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 82
83 return state
84}
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 86/**
87 * The terminal command that estimates the reference rate R at the current
88 * settings, using the unbiased Monte-Carlo estimator from the companion
89 * timeseries-entropy package.
90 */
91function mcCommand(sigma: number, spec: ReturnType<typeof clampSpec>, rate: number): string {
92 const parts = [
93 'uvx --from git+https://github.com/concept-collection/timeseries-entropy',
94 'timeseries-entropy',
95 `--sigma ${sigma}`,
96 ]
95929e6Monte-Carlo ground-truth script for R, with the command printed in the UIJeremy Magland 97 switch (spec.family) {
98 case 'none':
99 parts.push('--filter none')
100 break
101 case 'movingAverage':
102 parts.push('--filter moving-average', `--width ${spec.width}`)
103 break
104 case 'lowpass':
105 parts.push('--filter lowpass', `--high ${spec.highHz}`, `--taps ${spec.taps}`, `--rate ${rate}`)
106 break
107 case 'bandpass':
108 parts.push(
109 '--filter bandpass',
110 `--low ${spec.lowHz}`,
111 `--high ${spec.highHz}`,
112 `--taps ${spec.taps}`,
113 `--rate ${rate}`,
114 )
115 break
116 case 'firstDifference':
117 parts.push('--filter first-difference')
118 break
119 }
120 return parts.join(' ')
121}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 123export default function App() {
124 const [sigma, setSigma] = useState(5)
125 const [sampleRateHz, setSampleRateHz] = useState(30000)
126 const [spec, setSpec] = useState(DEFAULT_SPEC)
e411dffMake LPC order and compression block size controlsJeremy Magland 127 const [lpcOrder, setLpcOrder] = useState(DEFAULT_LPC_ORDER)
128 const [blockSize, setBlockSize] = useState(DEFAULT_BLOCK_SIZE)
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 129
130 const kernel = useMemo(() => designKernel(spec, sampleRateHz), [spec, sampleRateHz])
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 131 const sigmaY = useMemo(() => sigma * kernelNorm(kernel), [kernel, sigma])
132 const compression = useCompression(kernel, sigma, lpcOrder, blockSize)
3f857e3Estimate the reference rate R in the browserJeremy Magland 133 const refRate = useReferenceRate(kernel, sigma)
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 134
135 return (
136 <div className="app">
137 <header className="app-header">
138 <h1>Time-series compressibility</h1>
139 <p>
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 140 Gaussian noise → FIR filter → round to integers. How well can the integer stream be
141 losslessly compressed, and how close do practical codecs get to the entropy rate of
142 the process?
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 143 </p>
144 </header>
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 146 {/* The controls stay pinned so the parameters and the ratios they move
147 are always on screen together, whatever is scrolled to. */}
148 <section className="card control-bar">
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 149 <Controls
150 sigma={sigma}
151 setSigma={setSigma}
152 sampleRateHz={sampleRateHz}
fb1a12fSnap every parameter slider to a ladder of round valuesJeremy Magland 153 setSampleRateHz={rate => {
154 // Band edges are absolute, so a new rate can push them past
155 // Nyquist; re-snap the spec so sliders and kernel stay in step.
156 setSampleRateHz(rate)
157 setSpec(s => clampSpec(s, rate))
158 }}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 159 spec={spec}
160 setSpec={setSpec}
161 />
162 </section>
164 <section className="card">
165 <h2>Compression</h2>
166 <div className="stat-row">
167 <div className="stat">
168 <span className="label">predicted std of z</span>
169 <span className="value">
170 {sigmaY.toFixed(2)} <small>steps</small>
171 </span>
172 </div>
173 <div className="stat">
174 <span className="label">measured std of z</span>
175 <span className="value">
176 {compression.results.length > 0 ? compression.empiricalStd.toFixed(2) : '…'}{' '}
177 <small>steps</small>
178 </span>
179 </div>
180 <div className="stat">
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 181 <span className="label">reference rate R</span>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 182 <span className="value">
3f857e3Estimate the reference rate R in the browserJeremy Magland 183 {refRate.mean !== null ? refRate.mean.toFixed(2) : '—'}
184 {refRate.se !== null && <small> ± {refRate.se.toFixed(2)}</small>}{' '}
185 <small>bits/sample</small>
186 </span>
187 </div>
188 <div className="stat">
189 <span className="label">implied best ratio</span>
190 <span className="value">
191 {refRate.mean !== null && refRate.mean > 0 ? `${(16 / refRate.mean).toFixed(2)}×` : '—'}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 192 </span>
193 </div>
194 </div>
e411dffMake LPC order and compression block size controlsJeremy Magland 195 {/* Settings of the measurement, not of the model — so they live with
196 the chart they change rather than in the model bar. */}
197 <div className="measure-row">
198 <label>
199 LPC order
200 <select value={lpcOrder} onChange={e => setLpcOrder(Number(e.target.value))}>
201 {LPC_ORDERS.map(o => (
202 <option key={o} value={o}>
203 {o}
204 </option>
205 ))}
206 </select>
207 </label>
208 <label>
209 block size
210 <select value={blockSize} onChange={e => setBlockSize(Number(e.target.value))}>
211 {BLOCK_SIZES.map(n => (
212 <option key={n} value={n}>
213 {n.toLocaleString()} samples
214 </option>
215 ))}
216 </select>
217 </label>
218 </div>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 219 {compression.error ? (
220 <p className="card-note">Compression failed: {compression.error}</p>
221 ) : (
222 <CompressionChart
223 results={compression.results}
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 224 bounds={compression.bounds}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 226 computing={compression.computing}
227 />
228 )}
229 <p className="card-note">
e411dffMake LPC order and compression block size controlsJeremy Magland 230 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 231 signal view shows; sizes include everything a decoder needs (ANS symbol table, LPC
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 232 coefficients). Baseline is raw int16 (16 bits/sample). The hollow bar in each group is
233 that group's entropy limit — the order-0 entropy of the stream being coded, which no
234 per-sample entropy coder can beat and ANS falls short of by its symbol table plus its
3f857e3Estimate the reference rate R in the browserJeremy Magland 235 own arithmetic loss. The dashed line, once estimated, is the reference rate R — the
236 entropy rate of the process itself, the limit no lossless method whatsoever can beat
237 (see the method section at the bottom).
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 238 </p>
3f857e3Estimate the reference rate R in the browserJeremy Magland 239 <div className="estimate-row">
240 <button onClick={refRate.running ? refRate.stop : refRate.start}>
241 {refRate.running ? 'stop' : refRate.perPast.length > 0 ? 'refine R further' : 'estimate R in this browser'}
242 </button>
243 <span className="estimate-status">
244 {refRate.running
245 ? `${refRate.perPast.length} independent pasts averaged, M = ${refRate.past}` +
246 (refRate.progress ? ` · ${refRate.progress}` : '')
247 : refRate.perPast.length > 0
248 ? `${refRate.perPast.length} independent pasts averaged, M = ${refRate.past}`
249 : `unbiased Monte-Carlo conditioning on M = ${refRate.past} past samples; refines until stopped`}
250 </span>
251 </div>
95929e6Monte-Carlo ground-truth script for R, with the command printed in the UIJeremy Magland 252 <CopyableCommand
3f857e3Estimate the reference rate R in the browserJeremy Magland 253 label="or cross-check R from the command line:"
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 254 command={mcCommand(sigma, spec, sampleRateHz)}
95929e6Monte-Carlo ground-truth script for R, with the command printed in the UIJeremy Magland 255 />
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 256 </section>
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 258 <section className="card">
259 <h2>Quantized signal z</h2>
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 260 <ScrollingView kernel={kernel} sigma={sigma} sigmaY={sigmaY} />
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 261 <p className="card-note">
262 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 263 σ or the filter transforms the same underlying data, so the trace morphs rather than
264 resampling. Press play to advance through the sequence.
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 265 </p>
266 </section>
268 <section className="card">
269 <h2>Filter</h2>
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 270 <FilterViz kernel={kernel} sampleRateHz={sampleRateHz} />
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 271 </section>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 273 <section className="card">
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 274 <h2>The reference rate</h2>
275 <MethodNote />
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 276 </section>
277 </div>
278 )
279}