/ concept-collection / timeseries-compressibility
Sign in
concept-collection / timeseries-compressibility
timeseries-compressibility / src / App.tsx
257 lines · 8.9 KBCodeBlameHistory
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
17interface CompressionState {
18 results: CodecResult[]
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 19 bounds: BoundResult[]
21 computing: boolean
22 error: string | null
25/** The nine codec sizes, measured in a worker on a debounced parameter set. */
e411dffMake LPC order and compression block size controlsJeremy Magland 26function useCompression(
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: [],
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,
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,
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])
82 return state
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 ]
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(' ')
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)
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)
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?
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">
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 }}
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. */}
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 181 <span className="label">reference rate R</span>
43704d2Clear the analytic rate: R now comes from the timeseries-entropy estimatorJeremy Magland 183<small>bits/sample</small>
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>
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}
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).
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)}
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} />
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.
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} />
253 <MethodNote />
255 </div>
256 )
moveopenescclose