/ concept-collection / timeseries-compressibility
Sign in
concept-collection / timeseries-compressibility
timeseries-compressibility / docs / mc-true-rate.md
19.4 KBPreviewCodeBlameHistoryRaw

Monte Carlo estimation of the true entropy rate of a quantized filtered Gaussian process#

Purpose. This document specifies, in enough detail for independent implementation and testing (Python + numpy/scipy is the intended target), a Monte Carlo method for estimating the exact entropy rate R of the integer process produced by the pipeline below — the quantity that the closed-form formula in the companion app (timeseries-compressibility) only approximates. The estimator is consistent (converges to the true R as particle count, window length, and sample count grow), all of its systematic errors push in a known direction (upward), and it admits a two-sided sandwich so convergence can be certified rather than assumed.

No code from the app needs to be reused; everything required is specified here. Reference values measured with the app are given in §7 for cross-checking.

Implementation status. A reference implementation exists at scripts/true_rate.py (--selftest runs §6's cheap tests; the UI shows the ready-to-run command for its current parameters). It validates on none (matches the closed form), first-difference, moving-average, and windowed-sinc kernels up to ~21 taps. For longer windowed-sinc kernels the innovation std falls to a few hundredths of a step and the plain sequential filter hits the §5.3 genealogical collapse — the script detects this and aborts rather than reporting garbage. Extending to that regime needs lookahead/twisted proposals (Guarniero, Johansen & Lee, "The iterated auxiliary particle filter", JASA 2017) or exact-HMC rejuvenation for truncated Gaussians (Pakman & Paninski 2014); single-site Gibbs rejuvenation is not a viable substitute here because the near-deterministic dynamics make its conditionals microscopically narrow. Early healthy-regime results: the closed-form R overestimates the MC truth by ≈ 0.07–0.10 bits/sample at σ = 5 for MA(8) and the 21-tap bandpass — consistent with §1.3's error analysis.


1. The system under study#

1.1 Generating model#

xnN(0,σ2) i.i.d.,y=hx,zn=round(yn+dn)x_n \sim \mathcal{N}(0,\sigma^2)\ \text{i.i.d.}, \qquad y = h * x, \qquad z_n = \operatorname{round}(y_n + d_n)

The object of interest is the entropy rate of the integer process zz:

R  =  limkH ⁣(z0z1,,zk)[bits/sample]R \;=\; \lim_{k\to\infty} H\!\left(z_0 \mid z_{-1},\dots,z_{-k}\right) \qquad \text{[bits/sample]}

This is the true lossless compression limit for zz, the number the app's LPC + ANS bars try to approach from above.

1.2 Kernel designs (must match to compare numbers)#

All comparisons in §7 use the bandpass kernel below; implement at least none and bandpass.

1.3 The approximate formula being tested (for comparison only)#

The app's closed-form estimate, to be reimplemented for the comparison plots:

Sz(f)=S(f)+σq2,σe=201/2log2Sz(f)df,Rapprox=HΔ(σe)S_z(f) = S(f) + \sigma_q^2, \qquad \sigma_e = 2^{\int_0^{1/2} \log_2 S_z(f)\,df}, \qquad R_{\text{approx}} = H_\Delta(\sigma_e)

with σq2=1/12\sigma_q^2 = 1/12 (no dither) or 1/61/6 (dither), the integral by midpoint rule (8192 points suffices), and HΔ(s)H_\Delta(s) the exact entropy of round(N(0,s2))\operatorname{round}(\mathcal N(0,s^2)) on the unit lattice: HΔ(s)=zpzlog2pzH_\Delta(s) = -\sum_z p_z \log_2 p_z, pz=Φ(z+1/2s)Φ(z1/2s)p_z = \Phi(\tfrac{z+1/2}{s}) - \Phi(\tfrac{z-1/2}{s}).

Known inexactness (details in the analysis that motivated this work):


2. Why Monte Carlo can be exact here#

Three facts combine:

  1. The law of zz is fully known, so conditional probabilities can be computed, not estimated from frequencies. For a window z1:mz_{1:m}, the event probability (no dither) is a Gaussian rectangle probability:

    P(z1:m)=Pr ⁣[yt[zt12,zt+12) tm]P(z_{1:m}) = \Pr\!\left[\, y_t \in [z_t - \tfrac12,\, z_t + \tfrac12) \ \forall t \le m \,\right]

    — an mm-dimensional box integral of N(0,Σy)\mathcal N(0, \Sigma_y) with the banded covariance from ryr_y. (With dither the box edges are shifted by the dtd_t, which are handled by sampling; §4.4.)

  2. Predictive decomposition. log2P(z1:m)=tlog2P(ztz1:t1)-\log_2 P(z_{1:m}) = \sum_t -\log_2 P(z_t \mid z_{1:t-1}), and by Shannon–McMillan–Breiman, 1mlog2P(z1:m)R-\tfrac1m \log_2 P(z_{1:m}) \to R almost surely along a single simulated realization. Equivalently, averaging the conditional surprisal log2P(z0zk:1)-\log_2 P(z_0 \mid z_{-k:-1}) over independent draws estimates hk=H(z0zk:1)h_k = H(z_0 \mid z_{-k:-1}), and hkRh_k \downarrow R monotonically.

  3. Monotone error structure. Every systematic error is upward:

    • finite window: hkRh_k \ge R for every kk (conditioning reduces entropy);
    • inner estimation: an unbiased P^\hat P gives E[log2P^]log2P\mathbb E[-\log_2 \hat P] \ge -\log_2 P by Jensen, with bias Var(P^)/(2P2ln2)\approx \operatorname{Var}(\hat P) / (2 P^2 \ln 2), vanishing as the particle count grows.

    So the estimate converges to R from above, and the only downward fluctuation is CLT noise, which gets an error bar.

For a two-sided certificate, use the hidden-Markov sandwich (Cover & Thomas, Elements of Information Theory, §4.5): zz is a function of the Markov state st=(xtL+1,,xt)s_t = (x_{t-L+1},\dots,x_t) (plus dtd_t), so

H ⁣(z0zk:1,sk1)    R    H ⁣(z0zk:1)H\!\left(z_0 \mid z_{-k:-1},\, s_{-k-1}\right) \;\le\; R \;\le\; H\!\left(z_0 \mid z_{-k:-1}\right)

with the lower bound increasing in kk and the gap I(sk1;z0zk:1)0I(s_{-k-1}; z_0 \mid z_{-k:-1}) \to 0. In simulation the true state at k1-k-1 is known (we generated it), so the lower bound is estimable by the same machinery (§4.5).


3. The estimator in one paragraph#

Simulate one long realization z1:Tz_{1:T} from the pipeline. Run a sequential Monte Carlo over the latent Gaussian path constrained to the observed boxes: each particle is a sampled path yiy^i consistent with z1:tz_{1:t} so far; at each step the exact one-step predictive probability of the observed box, given the particle's path, is a Gaussian Φ\Phi-difference (because yty1:t1y_t \mid y_{1:t-1} is Gaussian with linear-prediction mean and fixed innovation variance); the mean of those Φ\Phi-differences across particles is an unbiased estimate of P(ztz1:t1)P(z_t \mid z_{1:t-1}); the particle is extended by sampling yty_t from the corresponding truncated Gaussian (this "optimal one-step proposal" is exactly Genz's separation-of-variables construction for box probabilities, run sequentially with resampling). Average log2P^(zt)-\log_2 \hat P(z_t \mid \cdot) over tt after a burn-in; repeat over independent replicates for error bars. That average estimates R.


4. Algorithm specification#

4.1 Precomputation#

  1. Build the kernel hh (§1.2) and the autocovariance ry(0),,ry(kmax)r_y(0),\dots,r_y(k_{\max}) (zero beyond lag L1L-1).

  2. Run Levinson–Durbin on ryr_y up to order kmaxk_{\max} (suggest kmax=4Lk_{\max} = 4L, capped at ~1024). Store, for each order pkmaxp \le k_{\max} — or just for the terminal order — the prediction coefficients a1(p),,ap(p)a^{(p)}_1,\dots,a^{(p)}_p and innovation variance vpv_p. After pLp \gtrsim L these are effectively converged (vpv_p \to the Szegő value for SS without any noise floor; e.g. for the default bandpass at σ = 5, v0.0019v_\infty \approx 0.0019, i.e. innovation std ≈ 0.044). Guard against underflow of vpv_p for extreme stopbands (floor at ~1e-30 and warn).

    For tkmaxt \le k_{\max} use the order-(t1)(t-1) coefficients; past that, the terminal ones (steady state).

4.2 Per-step recursion (undithered)#

State: particle paths y1:t1iy^i_{1:t-1}, i=1..Ni = 1..N (only the last kmaxk_{\max} values are needed), all with equal weight after resampling.

For t=1,2,,Tt = 1, 2, \dots, T, with observed integer ztz_t:

  1. Prediction per particle: μi=j=1paj(p)ytji\mu^i = \sum_{j=1}^{p} a^{(p)}_j\, y^i_{t-j}, innovation std s=vps = \sqrt{v_p}, where p=min(t1,kmax)p = \min(t-1, k_{\max}).
  2. Standardized box edges: αi=(zt12μi)/s\alpha^i = (z_t - \tfrac12 - \mu^i)/s, βi=(zt+12μi)/s\beta^i = (z_t + \tfrac12 - \mu^i)/s.
  3. Incremental weight wi=Φ(βi)Φ(αi)w^i = \Phi(\beta^i) - \Phi(\alpha^i) (log-space; see §5.1).
  4. Predictive estimate p^t=1Niwi\hat p_t = \tfrac1N \sum_i w^i. Record log2p^t-\log_2 \hat p_t. If p^t=0\hat p_t = 0 (all particles incompatible), the run has degenerated — restart the replicate with larger N; do not clamp.
  5. Resample particles with probabilities wi\propto w^i (systematic resampling; adaptive — only when ESS<N/2\mathrm{ESS} < N/2 — is fine, but then p^t\hat p_t must use the standard normalized-weight form p^t=iWt1iwti\hat p_t = \sum_i W^i_{t-1} w^i_t with carried weights WW).
  6. Extend each surviving particle: yti=μi+sΦ1 ⁣(Φ(αi)+Ui(Φ(βi)Φ(αi)))y^i_t = \mu^i + s\,\Phi^{-1}\!\big(\Phi(\alpha^i) + U^i\,(\Phi(\beta^i)-\Phi(\alpha^i))\big), UiU(0,1)U^i \sim \mathcal U(0,1) — the truncated-Gaussian draw (§5.2).

4.3 Assembling the estimate#

4.4 Dither#

With dither, the box for yty_t is shifted by the (unknown to the decoder) dtd_t: yt[zt12dt,zt+12dt)y_t \in [z_t - \tfrac12 - d_t,\, z_t + \tfrac12 - d_t). Extend each particle with its own dtiU[12,12)d^i_t \sim \mathcal U[-\tfrac12,\tfrac12) drawn before step 2, and use the shifted edges. Everything else is unchanged; p^t\hat p_t remains unbiased (the dither prior is part of the proposal). Optionally, integrate dtd_t analytically in the weight — the integral of a Φ\Phi-difference over a unit shift is a difference of G(t)=tΦ(t)+φ(t)G(t) = t\Phi(t) + \varphi(t) terms — and then sample (dt,yt)(d_t, y_t) jointly; lower variance, more code. Start with the sampled version.

Note: when generating the observed z1:Tz_{1:T} for a dithered run, the generator also draws dtd_t; those true dither values are not given to the estimator (non-subtractive dither).

Estimate H(z0zk:1,sk1)H(z_0 \mid z_{-k:-1}, s_{-k-1}): for each of many independent windows, (a) simulate truth and keep the exact latent inputs up to time k1-k-1; (b) condition the Gaussian law of yk:0y_{-k:0} on that known state — its conditional mean is the deterministic tail response j>t+khjxtj\sum_{j > t+k} h_j x_{t-j} and its conditional covariance is that of the truncated kernel (only the taps applied to inputs after k1-k-1); (c) run the same SOV/SMC over the k+1k{+}1 constrained steps and record the last step's log2p^-\log_2 \hat p. Average over windows. Plot lower and upper curves against kk; the closing gap certifies memory-length convergence independently of any modeling argument.


5. Numerical hazards (all known, all manageable)#

5.1 Φ-differences in the tails#

When both edges are far in one tail, Φ(β)Φ(α)\Phi(\beta)-\Phi(\alpha) underflows in the naive form. Compute in log space: use scipy.special.log_ndtr; for α,β>0\alpha, \beta > 0 switch to the upper tail, log(Φ(α)Φ(β))\log(\Phi(-\alpha) - \Phi(-\beta)), and combine as log_ndtr(hi) + log1p(-exp(log_ndtr(lo) - log_ndtr(hi))). These low-probability steps are precisely the ones that dominate the surprisal, so they must not be clamped to zero.

5.2 Truncated-normal sampling#

The inverse-CDF trick in §4.2(6) loses precision when the interval sits beyond ~6σ. Use scipy.stats.truncnorm (which handles tails), or Robert's exponential-rejection sampler for one-sided extreme tails. A particle sitting exactly on an edge after sampling is harmless.

5.3 Weight degeneracy and path collapse#

The innovation std ss can be much smaller than the box width (deep stopbands, no dither). Then most particles get wi1w^i \approx 1 and the rest 0\approx 0: weights are Bernoulli-like with success probability 2R\approx 2^{-R} per step. This is workable — the truncated draw in step 6 spans the box, i.e. many innovation-σ's, so resampled duplicates re-diversify quickly — but it costs particles. If ESS collapses persistently:

5.4 Sanity invariants to assert in code#


6. Test plan#

Ordered from cheap-and-exact to expensive-and-comparative.

  1. i.i.d. exactness (primary). none kernel. True R=HΔ(σ)R = H_\Delta(\sigma) in closed form. Require agreement within 3 SE at σ ∈ {0.5, 1, 5}, and that the reimplemented RapproxR_{\text{approx}} reproduces its known overestimates (§7 table).
  2. Marginal check. h0h_0 (no conditioning) equals the marginal quantized entropy for any kernel: compare a short-window run against the 1-D formula with s=ry(0)s = \sqrt{r_y(0)} (undithered).
  3. Brute force, small windows. For MA(1)-like kernels (h=[1,a]h = [1, a], σ ≤ 2), compute hkh_k for k3k \le 3 exactly: enumerate integer tuples within ±8σ_y and evaluate box probabilities with scipy.stats.multivariate_normal.cdf (Genz), then hk=Hk+1Hkh_k = H_{k+1} - H_k. The SMC at the same kk (fresh independent windows, fixed kk) must match within error bars.
  4. Monotonicity. h^k\hat h_k non-increasing in kk (within noise); estimate decreasing and plateauing in particle count N; sandwich gap (if implemented) shrinking in kk.
  5. Dead zone. none, σ = 0.1: R^0\hat R \approx 0 (truth ~1e-5), vs Rapprox=0.576R_{\text{approx}} = 0.576. With dither, σ → 0: truth → 0 as well (uniform ±½ dither alone never flips the integer), vs Rapprox0.56R_{\text{approx}} \approx 0.56.
  6. Achievability cross-check. For the default bandpass, σ = 5: R^\hat R must be ≤ the achievable rates in §7 (LPC residual entropy ≈ 1.947) plus noise, and plausibly close to Rapprox=1.940R_{\text{approx}} = 1.940. This is the headline number this whole exercise exists to pin down.
  7. Sweep deliverable. σ ∈ {0.5, 1, 2, 5, 10, 20, 50, 100} × {dither on, off} for the default bandpass: plot R^\hat R with error bars against RapproxR_{\text{approx}}, plus the i.i.d. sweep where truth is closed-form. Deliver the numbers as CSV alongside the plot.

Suggested starting parameters: N = 4096 particles, T = 4000 steps, burn-in B = 1000, J = 16 replicates, kmax=512k_{\max} = 512 for the 101-tap kernel. Rough cost: O(N·k_max) per step ⇒ ~10⁹–10¹⁰ flops per replicate — seconds to a minute in vectorized numpy (vectorize across particles; the prediction is a matrix–vector product against the shared coefficient vector).

7. Reference values (default bandpass = 300–6000 Hz @ 30 kHz, 101 taps)#

Closed-form / app-measured values for validating reimplementations. "Exact" rows are analytic; others were measured with the app's codecs on a 100k-sample block (seed-dependent in the third decimal).

Quantity Value Status
HΔ(s)H_\Delta(s) at s = 0.1 / 0.3 / 1 / 5 / 50 0.00001 / 0.55042 / 2.10483 / 4.37142 / 7.69098 exact
True R, none, σ = 5 / 0.5 / 0.1 4.37142 / 1.24174 / ≈1e-5 exact (=HΔ(σ)=H_\Delta(\sigma))
RapproxR_{\text{approx}}, none, σ = 0.1 / 1 / 5 / 100 0.57611 / 2.15829 / 4.37382 / 8.69096 exact given §1.3
RapproxR_{\text{approx}}, bandpass, σ = 0.5 / 5 / 20 / 100 0.8620 / 1.9397 / 2.7282 / 3.7166 exact given §1.3
RapproxR_{\text{approx}}, bandpass + dither, σ = 5 2.2111 exact given §1.3
h2\lVert h \rVert_2, default bandpass 0.60216 exact given §1.2
Order-0 entropy of LPC(32) / LPC(128) residual, bandpass σ = 5 1.965 / 1.947 measured (achievable ⇒ upper bounds on R)
LPC(32)+ANS achieved, bandpass σ = 5 2.002–2.011 measured
Pure high-res rate (no floor), bandpass σ = 5 −2.4604 exact; demonstrates why the floor exists

8. References#

moveopenescclose