simplexuq-code / src /dgp /pure_scale.py
anonymous0523ly's picture
Initial anonymous code release
fc329a3 verified
Raw
History Blame Contribute Delete
2.13 kB
"""DGP 1: Pure scale heterogeneity in ILR space.
Y = ilr⁻¹(ilr(μ(X)) + σ(μ(X))·ε), ε ~ N(0, I)
σ(u) = σ_min + c·(1 - H(u)/log K)
f̂(X) = μ(X)
"""
import numpy as np
from .base import BaseDGP, DGPSample
from ..utils.simplex import ilr, ilr_inv, entropy, aitchison_dist
def _softmax(z: np.ndarray) -> np.ndarray:
z = z - z.max(axis=-1, keepdims=True)
e = np.exp(z)
return e / e.sum(axis=-1, keepdims=True)
class PureScaleDGP(BaseDGP):
"""Pure scale heterogeneity: residual distribution is N(0, σ²(u)I) in ILR space.
Args:
K: number of simplex components
sigma_min: minimum scale
c: scale range (σ_max = σ_min + c)
d_x: dimension of covariate X
"""
def __init__(self, K: int = 3, sigma_min: float = 0.1, c: float = 0.5,
d_x: int = 2):
self.K = K
self.sigma_min = sigma_min
self.c = c
self.d_x = d_x
# Fixed linear map X -> logits, generated once
self._W = None
def _init_weights(self, rng: np.random.Generator):
"""Initialize fixed linear map for μ(X)."""
if self._W is None:
self._W = rng.standard_normal((self.d_x, self.K)) * 0.8
def _mu(self, X: np.ndarray) -> np.ndarray:
"""Map X -> simplex via softmax of linear functions."""
logits = X @ self._W
return _softmax(logits)
def _sigma(self, u: np.ndarray) -> np.ndarray:
"""Scale function: low entropy -> high scale."""
H = entropy(u)
return self.sigma_min + self.c * (1.0 - H / np.log(self.K))
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)
# Generate Y in ILR space
Z_mu = ilr(mu)
eps = rng.standard_normal((n, self.K - 1))
Z_y = Z_mu + sigma[:, None] * eps
Y = ilr_inv(Z_y, K=self.K)
# Predictor = oracle mean
U = mu
R = aitchison_dist(Y, U)
return DGPSample(X=X, Y=Y, U=U, R=R, sigma_true=sigma)