concept-collection / timeseries-compressibility
timeseries-compressibility / src / components / useEntropyRate.ts
90 lines · 2.7 KBBlameHistoryRaw
1import { useEffect, useRef, useState } from 'react'
2import { defaultPast } from '../entropy'
3import type { EntropyRequest, EntropyUpdate } from '../worker/entropyWorker'
5/** Fixed base seed: resumed runs continue the same per-past seed sequence,
6 * so a stop/start pair reproduces an uninterrupted run exactly. */
7const BASE_SEED = 20260731
9export interface EntropyRate {
10 /** One unbiased estimate per independent past, in completion order. */
11 perPast: number[]
12 mean: number | null
13 se: number | null
14 running: boolean
15 /** "rep 5/8" within the current past while computing. */
16 progress: string | null
17 /** Conditioning window M. */
18 past: number
19 start: () => void
20 stop: () => void
23/**
24 * The in-browser entropy-rate estimate: a worker refines it (one
25 * independent past at a time) until stopped, and any change to the model
26 * invalidates both the values and a run in flight.
27 */
28export function useEntropyRate(kernel: Float64Array, sigma: number): EntropyRate {
29 const [perPast, setPerPast] = useState<number[]>([])
30 const [running, setRunning] = useState(false)
31 const [progress, setProgress] = useState<string | null>(null)
32 const workerRef = useRef<Worker | null>(null)
33 const perPastRef = useRef<number[]>([])
34 const past = defaultPast(kernel.length)
36 useEffect(() => {
37 workerRef.current?.terminate()
38 workerRef.current = null
39 perPastRef.current = []
40 setPerPast([])
41 setRunning(false)
42 setProgress(null)
43 }, [kernel, sigma])
45 useEffect(() => () => workerRef.current?.terminate(), [])
47 const start = () => {
48 if (workerRef.current) return
49 const worker = new Worker(new URL('../worker/entropyWorker.ts', import.meta.url), {
50 type: 'module',
51 })
52 worker.onmessage = (e: MessageEvent<EntropyUpdate>) => {
53 const u = e.data
54 if (u.type === 'past') {
55 perPastRef.current = [...perPastRef.current, u.value]
56 setPerPast(perPastRef.current)
57 } else {
58 setProgress(`rep ${u.repsDone}/${u.reps}`)
59 }
60 }
61 workerRef.current = worker
62 const request: EntropyRequest = {
63 kernel,
64 sigma,
65 past,
66 seed: BASE_SEED,
67 startPast: perPastRef.current.length,
68 }
69 worker.postMessage(request)
70 setRunning(true)
71 setProgress(null)
72 }
74 const stop = () => {
75 workerRef.current?.terminate()
76 workerRef.current = null
77 setRunning(false)
78 setProgress(null)
79 }
81 const n = perPast.length
82 const mean = n > 0 ? perPast.reduce((a, b) => a + b, 0) / n : null
83 let se: number | null = null
84 if (mean !== null && n > 1) {
85 const v = perPast.reduce((a, b) => a + (b - mean) * (b - mean), 0) / (n - 1)
86 se = Math.sqrt(v / n)
87 }
89 return { perPast, mean, se, running, progress, past, start, stop }