Prolate Energy-Preserving Bottleneck / prolate_bottleneck.py

Failed on benchmark

Raw ⬇ ZIP
 1import math
 2import numpy as np
 3
 4
 5def concentration_matrix(length: int, bandwidth: float) -> np.ndarray:
 6    """Finite discrete time-frequency concentration matrix."""
 7    if length < 1 or not 0.0 < bandwidth < 0.5:
 8        raise ValueError("length >= 1 and bandwidth in (0, .5) required")
 9    i = np.arange(length)
10    d = i[:, None] - i[None, :]
11    C = np.empty((length, length), dtype=np.float64)
12    off = d != 0
13    C[off] = np.sin(2.0 * np.pi * bandwidth * d[off]) / (np.pi * d[off])
14    C[~off] = 2.0 * bandwidth
15    return (C + C.T) * 0.5
16
17
18def asymptotic_rank(length: int, bandwidth: float, delta: float) -> float:
19    """Paper-inspired rank estimate with c=2*bandwidth*length."""
20    if not 0.0 < delta < 0.5:
21        raise ValueError("delta must be in (0, .5)")
22    c = 2.0 * bandwidth * length
23    log_odds = math.log((1.0 - delta) / delta)
24    return c + log_odds / math.pi**2 * math.log(
25        max(4.0 * math.pi**2 * c / log_odds, 1.000001))
26
27
28def fourier_basis(length: int, rank: int) -> np.ndarray:
29    """Real orthonormal low-frequency Fourier basis (cos/sin pairs)."""
30    if not 1 <= rank <= length:
31        raise ValueError("rank must be between 1 and length")
32    n = np.arange(length)
33    cols = [np.ones(length) / np.sqrt(length)]
34    k = 1
35    while len(cols) < rank:
36        cols.append(np.sqrt(2.0 / length) * np.cos(2 * np.pi * k * n / length))
37        if len(cols) < rank:
38            cols.append(np.sqrt(2.0 / length) * np.sin(2 * np.pi * k * n / length))
39        k += 1
40    return np.stack(cols[:rank], axis=1)
41
42
43class ProlateBottleneck:
44    """Fixed orthogonal DPSS projection along axis 1 of [B,T,D] arrays."""
45    def __init__(self, length: int, bandwidth: float, delta: float = 0.1,
46                 rank: int | None = None):
47        C = concentration_matrix(length, bandwidth)
48        vals, vecs = np.linalg.eigh(C)
49        order = np.argsort(vals)[::-1]
50        self.eigenvalues = vals[order]
51        self.full_basis = vecs[:, order]
52        proposed = math.ceil(asymptotic_rank(length, bandwidth, delta))
53        self.empirical_rank = int(np.sum(self.eigenvalues > delta))
54        chosen = proposed if rank is None else rank
55        self.rank = int(np.clip(chosen, 1, length))
56        self.basis = self.full_basis[:, :self.rank]
57
58    def encode(self, x: np.ndarray) -> np.ndarray:
59        if x.ndim != 3 or x.shape[1] != self.basis.shape[0]:
60            raise ValueError("x must have shape [batch, matching_length, channels]")
61        return np.einsum("btd,tr->brd", x, self.basis)
62
63    def decode(self, z: np.ndarray) -> np.ndarray:
64        if z.ndim != 3 or z.shape[1] != self.rank:
65            raise ValueError("z must have shape [batch, rank, channels]")
66        return np.einsum("brd,tr->btd", z, self.basis)
67
68    def reconstruct(self, x: np.ndarray) -> np.ndarray:
69        return self.decode(self.encode(x))
70
71    @property
72    def attention_work_fraction(self) -> float:
73        return (self.rank / self.basis.shape[0]) ** 2