File size: 1,611 Bytes
fc329a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
"""Scale heterogeneity with mild tail/covariance drift."""
import numpy as np

from .base import DGPSample
from .pure_scale import PureScaleDGP
from ..utils.simplex import aitchison_dist, entropy, ilr, ilr_inv


class TailDriftDGP(PureScaleDGP):
    """Pure-scale DGP with entropy-dependent anisotropic ILR noise.

    This auxiliary regime is not part of the main table, but it is useful for
    stress-testing local normalization when the residual shape changes as well
    as the scalar residual scale.
    """

    def __init__(
        self,
        K: int = 3,
        sigma_min: float = 0.1,
        c: float = 0.5,
        d_x: int = 2,
        drift_strength: float = 0.5,
    ):
        super().__init__(K=K, sigma_min=sigma_min, c=c, d_x=d_x)
        self.drift_strength = drift_strength

    def sample(self, n: int, rng: np.random.Generator) -> DGPSample:
        self._init_weights(rng)
        X = rng.standard_normal((n, self.d_x))
        mu = self._mu(X)
        sigma = self._sigma(mu)

        Z_mu = ilr(mu)
        H = entropy(mu) / np.log(self.K)
        eps = rng.standard_normal((n, self.K - 1))
        if self.K > 2:
            # Low-entropy predictions get a mild directional covariance tilt.
            eps[:, 0] *= 1.0 + self.drift_strength * (1.0 - H)
            eps[:, 1:] *= 1.0 - 0.5 * self.drift_strength * (1.0 - H[:, None])
        else:
            eps[:, 0] *= 1.0 + self.drift_strength * (1.0 - H)
        Y = ilr_inv(Z_mu + sigma[:, None] * eps, K=self.K)
        U = mu
        R = aitchison_dist(Y, U)
        return DGPSample(X=X, Y=Y, U=U, R=R, sigma_true=sigma)