| """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: |
| |
| 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) |
|
|