1import math
3import numpy as np
4import pytest
6from timeseries_entropy import kernels
7from timeseries_entropy.theory import (
8 gauss_uniform_entropy, log_spectrum_mean, predict_entropy_rate,
9 sigma_infinity)
12def test_sigma_infinity_closed_forms():
13 # MA(W): all tap-polynomial zeros on the unit circle -> sigma / W.
14 assert sigma_infinity(kernels.moving_average(8), 4.0) == pytest.approx(0.5)
15 # first difference: 1 - w, zero at w = 1 -> sigma.
16 assert sigma_infinity(kernels.first_difference(), 8.0) == pytest.approx(8.0)
17 assert sigma_infinity(kernels.identity(), 2.0) == pytest.approx(2.0)
20def test_log_spectrum_mean_matches_szego_when_floor_negligible():
21 # With a floor far below the spectrum minimum, the FFT integral must
22 # agree with the exact roots-based geometric mean.
23 h = np.array([1.0, -0.5]) # min |H|^2 = 0.25, floor 1e-12 negligible
24 gm = math.exp(log_spectrum_mean(h, 1.0, floor=1e-12))
25 assert gm == pytest.approx(sigma_infinity(h, 1.0) ** 2, rel=1e-6)
28def test_gauss_uniform_entropy_limits():
29 # Large s: h(N + U) -> 1/2 log2(2 pi e (s^2 + 1/12)) (Gaussian limit).
30 for s in [4.0, 16.0]:
31 expect = 0.5 * math.log2(2 * math.pi * math.e * (s * s + 1 / 12))
32 assert gauss_uniform_entropy(s) == pytest.approx(expect, abs=2e-3)
33 # Small s: -> 0 linearly, never negative.
34 assert gauss_uniform_entropy(0.0) == 0.0
35 assert 0 < gauss_uniform_entropy(0.01) < 0.05
38def test_gauss_uniform_entropy_is_offset_averaged_quantized_entropy():
39 # G(s) = E_c H(round(c + N(0, s^2))), c ~ U(0, 1) (dither identity).
40 s = 0.7
41 cs = (np.arange(200) + 0.5) / 200
42 zs = np.arange(-12, 13)
43 edges = (zs[None, :] - 0.5 - cs[:, None]) / s
44 from scipy.special import ndtr
45 p = np.diff(ndtr(np.concatenate([edges, edges[:, -1:] + 1 / s], axis=1)))
46 p = np.clip(p, 1e-300, None)
47 mean_h = float((-p * np.log2(p)).sum(axis=1).mean())
48 assert gauss_uniform_entropy(s) == pytest.approx(mean_h, abs=1e-3)
51def test_predict_identity_kernel_high_resolution():
52 # For h = [1], z is iid round(N(0, sigma^2)); at sigma >> 1 both
53 # predictions must approach the exact marginal entropy.
54 sigma = 16.0
55 zmax = int(8 * sigma + 4)
56 z = np.arange(-zmax, zmax + 1)
57 from scipy.special import ndtr
58 p = ndtr((z + 0.5) / sigma) - ndtr((z - 0.5) / sigma)
59 p = p[p > 0]
60 exact = float(-(p * np.log2(p)).sum())
61 pred = predict_entropy_rate(kernels.identity(), sigma)
62 assert pred['corrected'] == pytest.approx(exact, abs=2e-3)
63 assert pred['highres'] == pytest.approx(exact, abs=2e-3)
66def test_corrected_prediction_stays_finite_at_spectral_zeros():
67 pred = predict_entropy_rate(kernels.windowed_sinc_lowpass(0.1, 101), 8.0)
68 assert pred['highres'] < 0 # Szego diverges toward -inf
69 assert 0 < pred['corrected'] < 16 # corrected saturates sensibly