import itertools import json import math import random from pathlib import Path import numpy as np import torch SEED = 466 np.random.seed(SEED) random.seed(SEED) torch.manual_seed(SEED) def subset_scores(A, m): """Return every m-row subset and its least singular value.""" subsets = list(itertools.combinations(range(A.shape[0]), m)) scores = np.array([ np.linalg.svd(A[list(T), :], compute_uv=False)[-1] for T in subsets ], dtype=np.float64) return subsets, scores def gaussian_scaling_check(): """Exhaustively check the theorem's log M / m behavior at gamma=2.""" gamma = 2.0 h = gamma * math.log(gamma) - (gamma - 1) * math.log(gamma - 1) theory = -h rows = [] for m in range(2, 9): vals = [] for rep in range(10): rng = np.random.default_rng(SEED + 1000 * m + rep) A = rng.standard_normal((2 * m, m)) _, scores = subset_scores(A, m) vals.append(math.log(max(float(scores.min()), 1e-300)) / m) rows.append({"m": m, "mean": float(np.mean(vals)), "std": float(np.std(vals)), "theory": theory}) rng = np.random.default_rng(SEED + 999) A = rng.standard_normal((12, 6)) _, scores = subset_scores(A, 6) return {"gamma": gamma, "theory_limit": theory, "scaling": rows, "directional": {"min": float(scores.min()), "median": float(np.median(scores)), "min_over_median": float(scores.min() / np.median(scores))}} def train(mask_mode, W, X, y, masks, steps=500, lr=0.04): """Train a small downstream network on fixed channel subsets.""" device = "cuda" if torch.cuda.is_available() else "cpu" try: torch.manual_seed(SEED) model = torch.nn.Sequential(torch.nn.Linear(int(masks[0].numel()), 32), torch.nn.Tanh(), torch.nn.Linear(32, 1)).to(device) opt = torch.optim.Adam(model.parameters(), lr=lr) Xt, yt = X.to(device), y.to(device) Wt = W.to(device) losses = [] for step in range(steps): if mask_mode == "random": # deterministic pseudo-random schedule, with the same pool size j = (step * 7919 + 104729) % len(masks) else: # Scores are computed from the fixed readout matrix. The # adversary chooses the lowest-score candidate each step. j = 0 # masks are preordered by increasing score; cycle through the # hardest few to avoid training on one pathological subset only. j = (step // 25) % max(1, min(5, len(masks))) idx = masks[j].to(device) # Select surviving measurements and project to a common m-vector. xb = Xt[:, idx] pred = model(xb) loss = torch.nn.functional.mse_loss(pred, yt) opt.zero_grad(); loss.backward(); opt.step() losses.append(float(loss.detach().cpu())) return model, losses except Exception: # CPU fallback is required for shared/fragile CUDA environments. model = torch.nn.Sequential(torch.nn.Linear(int(masks[0].numel()), 32), torch.nn.Tanh(), torch.nn.Linear(32, 1)) opt = torch.optim.Adam(model.parameters(), lr=lr) losses = [] for step in range(steps): j = step % len(masks) if mask_mode == "random" else (step // 25) % min(5, len(masks)) pred = model(X[:, masks[j]]) loss = torch.nn.functional.mse_loss(pred, y) opt.zero_grad(); loss.backward(); opt.step(); losses.append(float(loss)) return model, losses def regression_check(): """Compare random masks with low-condition-score masks at equal steps.""" rng = np.random.default_rng(SEED + 77) n, N, m, latent = 1200, 12, 6, 6 Z = rng.standard_normal((n, latent)).astype(np.float32) W_np = rng.standard_normal((N, latent)).astype(np.float32) X_np = Z @ W_np.T + 0.05 * rng.standard_normal((n, N)).astype(np.float32) y_np = (Z[:, :1] - 0.6 * Z[:, 1:2] + 0.2 * Z[:, 2:3]).astype(np.float32) _, scores = subset_scores(W_np, m) all_subsets = list(itertools.combinations(range(N), m)) order = np.argsort(scores) ordered = [all_subsets[i] for i in order] # Both methods have the same 20-mask training budget. Random uses a # deterministic spread of the full pool; adversarial uses the 20 lowest scores. random_subsets = [all_subsets[i] for i in np.linspace(0, len(all_subsets)-1, 20, dtype=int)] masks = [torch.tensor(t, dtype=torch.long) for t in random_subsets] hard_masks = [torch.tensor(t, dtype=torch.long) for t in ordered[:20]] X, y, W = torch.tensor(X_np), torch.tensor(y_np), torch.tensor(W_np) rmodel, rloss = train("random", W, X, y, masks) hmodel, hloss = train("hard", W, X, y, hard_masks) eval_device = next(rmodel.parameters()).device Xe, ye = X.to(eval_device), y.to(eval_device) # Identical exhaustive evaluation: this is the relevant robustness test. with torch.no_grad(): random_eval = [float(torch.nn.functional.mse_loss(rmodel(Xe[:, torch.tensor(t, device=eval_device)]), ye).cpu()) for t in all_subsets] hard_eval = [float(torch.nn.functional.mse_loss(hmodel(Xe[:, torch.tensor(t, device=eval_device)]), ye).cpu()) for t in all_subsets] def summary(a): a = np.asarray(a) return {"mean_all": float(a.mean()), "worst_1pct": float(np.quantile(a, .99)), "worst": float(a.max()), "best": float(a.min())} return {"random_final_train_loss": rloss[-1], "hard_final_train_loss": hloss[-1], "random_eval": summary(random_eval), "hard_eval": summary(hard_eval), "random_low_score20_mean": float(np.mean([random_eval[i] for i in order[:20]])), "hard_low_score20_mean": float(np.mean([hard_eval[i] for i in order[:20]])), "hardest_score": float(scores[order[0]]), "median_score": float(np.median(scores)), "score_ratio": float(scores[order[0]] / np.median(scores)), "random_curve": rloss, "hard_curve": hloss} def main(): out = {"seed": SEED, "gaussian": gaussian_scaling_check(), "regression": regression_check()} Path("results.json").write_text(json.dumps(out, indent=2)) print(json.dumps({"gaussian": out["gaussian"], "regression_summary": {k: v for k, v in out["regression"].items() if "curve" not in k}}, indent=2)) if __name__ == "__main__": main()