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