Efradeca's picture
Upload folder using huggingface_hub
237a3b2 verified
Raw
History Blame Contribute Delete
3.75 kB
"""Out-of-distribution (covariate-shift) split from void-geometry statistics (Gate D).
No new FEM is needed: we re-use the existing 2000 samples but split them by a per-sample
geometric statistic of the void boundary radii ``rr`` (shape ``(n, 42)``). The model trains on
the lower percentile (typical voids) and is tested on the upper percentile (large/extreme voids
it never saw) — a genuine covariate shift in geometry.
Split rule (documented exactly, master plan §2.6):
stat_i = STAT(rr_i) # default STAT = max radius ("void size")
threshold = quantile(stat, train_frac) # default train_frac = 0.80
in-distribution (train+id-test): stat_i <= threshold (lower ~80%)
out-of-distribution (ood-test) : stat_i > threshold (upper ~20%)
The in-distribution pool is further split into train / id-test by ``id_test_frac`` (held-out,
seeded) so in-dist and OOD errors are both measured on data unseen during training.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict
import numpy as np
import torch
STATS = {
"max": lambda rr: rr.max(axis=1), # largest radius -> void size (primary)
"mean": lambda rr: rr.mean(axis=1),
"std": lambda rr: rr.std(axis=1), # radius spread -> "lobiness"
"range": lambda rr: rr.max(axis=1) - rr.min(axis=1),
}
def geometry_stat(rr: torch.Tensor, kind: str = "max") -> np.ndarray:
if kind not in STATS:
raise ValueError(f"unknown stat {kind!r}; choose from {list(STATS)}")
return STATS[kind](rr.detach().cpu().numpy())
@dataclass
class OODSplit:
train_idx: np.ndarray # in-distribution, used for training
id_test_idx: np.ndarray # in-distribution, held out for testing
ood_test_idx: np.ndarray # out-of-distribution (extreme geometry), held out
threshold: float
stat_kind: str
info: Dict
def make_ood_split(
rr: torch.Tensor,
stat_kind: str = "max",
train_frac: float = 0.80,
id_test_frac: float = 0.20,
seed: int = 0,
) -> OODSplit:
"""Build a covariate-shift split from ``rr`` statistics. Deterministic given ``seed``."""
stat = geometry_stat(rr, stat_kind) # (n,)
n = stat.shape[0]
threshold = float(np.quantile(stat, train_frac))
in_dist = np.where(stat <= threshold)[0]
ood = np.where(stat > threshold)[0]
# A strict `> quantile` split yields an empty OOD group if the stat ties at its max
# (degenerate distribution). Guard so `.min()/.max()` below cannot crash (audit bug 2);
# real continuous `rr` data is non-degenerate (n_ood ~ 400 at train_frac=0.8).
if ood.size == 0:
raise ValueError(
f"OOD split is empty: stat ({stat_kind}) has no values above the "
f"{train_frac:.0%} quantile (likely tied at the maximum). Lower train_frac "
f"or choose a different stat."
)
rng = np.random.default_rng(seed)
perm = rng.permutation(in_dist)
n_id_test = int(round(len(in_dist) * id_test_frac))
id_test_idx = np.sort(perm[:n_id_test])
train_idx = np.sort(perm[n_id_test:])
info = {
"n_total": int(n),
"n_train": int(train_idx.size),
"n_id_test": int(id_test_idx.size),
"n_ood_test": int(ood.size),
"threshold": threshold,
"stat_kind": stat_kind,
"stat_train_max": float(stat[train_idx].max()) if train_idx.size else float("nan"),
"stat_ood_min": float(stat[ood].min()),
"stat_ood_max": float(stat[ood].max()),
}
return OODSplit(
train_idx=train_idx,
id_test_idx=id_test_idx,
ood_test_idx=np.sort(ood),
threshold=threshold,
stat_kind=stat_kind,
info=info,
)