import json import numpy as np from scipy.special import roots_hermitenorm, ndtr from scipy.optimize import brentq SEED = 1729 LOG3 = np.log(3.0) def coth(s): s = np.asarray(s, float) out = np.empty_like(s) small = np.abs(s) < 1e-5 out[small] = 1.0/s[small] + s[small]/3.0 - s[small]**3/45.0 out[small & (s == 0)] = np.inf out[~small] = 1.0/np.tanh(s[~small]) return out def A(s): s = np.asarray(s, float) if s.ndim == 0: if abs(float(s)) < 1e-5: return 1.0 + float(s)**2/3.0 return float(s)/np.tanh(float(s)) out = np.ones_like(s, dtype=float) nz = s != 0 out[nz] = s[nz]*coth(s[nz]) return out class MasterCurve: # F is standard normal radial fluctuation, a calibrated law for Z/W. def __init__(self, n_w=18000, gh_order=32, seed=SEED): rng = np.random.default_rng(seed) self.w = rng.normal(size=(n_w, 3)) x, wt = roots_hermitenorm(gh_order) self.z = x self.wt = wt / np.sqrt(2*np.pi) def entropy(self, lam): lam = float(lam) # q_i = E_z phi(z) prod_j Phi(-(z+lambda(wj-wi))) q = np.zeros((len(self.w), 3)) for i in range(3): delta = self.w - self.w[:, i:i+1] # shape: triples, quadrature points, competitors arg = self.z[None, :, None] + lam*delta[:, None, :] surv = ndtr(-arg) surv[:, :, i] = 1.0 q[:, i] = np.sum(self.wt[None, :] * np.prod(surv, axis=2), axis=1) q = np.clip(q, 1e-14, 1.0) return float(np.mean(-np.sum(q*np.log(q), axis=1))) def table(self, grid): return np.array([self.entropy(x) for x in grid]) def invert(self, h, grid, vals): # Inversion only on a verified monotone interval; entropy decreases here. order = np.argsort(vals)[::-1] hh = np.asarray(vals)[order] gg = np.asarray(grid)[order] h = float(np.clip(h, hh[-1], hh[0])) return float(np.interp(h, hh[::-1], gg[::-1])) def simulate_entropy(lam, n=120000, seed=SEED+1): # This is the direct conditional entropy functional estimated by MC. mc = MasterCurve(n_w=n, gh_order=24, seed=seed) return mc.entropy(lam) def solve_s(lam_hat, d, tau): base = np.sqrt(d)*tau if base <= 0: return np.inf if lam_hat <= base: return 0.0 return float(brentq(lambda s: base*A(s)-lam_hat, 0.0, max(2.0, lam_hat/base*2.0+1))) def run(): # Core math: curve, its validated local monotonicity, and exact amplification. grid = np.linspace(0, 5, 26) curve = MasterCurve(n_w=16000, gh_order=32) hs = curve.table(grid) diffs = np.diff(hs) monotone_fraction = float(np.mean(diffs < 0)) # Compare predicted lambda_H with directly generated scores at several curvatures. d, tau, mu = 32, 0.12, 2.0 rows = [] for s in [0.0, 0.5, 1.0, 2.0, 3.0]: predicted = np.sqrt(d)*tau*A(s) observed = simulate_entropy(predicted, n=30000, seed=SEED+int(10*s+1)) inferred = curve.invert(observed, grid, hs) recovered_s = solve_s(inferred, d, tau) rows.append(dict(s_true=s, lambda_pred=predicted, entropy=observed, lambda_recovered=inferred, s_recovered=recovered_s)) # Controller experiment: entropy observations are noisy finite-sample measurements # around the master curve, then curvature is updated by a slow EMA. rng = np.random.default_rng(SEED+99) controller = MasterCurve(n_w=20000, gh_order=32, seed=SEED+33) # use dense calibration for inversion and stay in validated interval cgrid = np.linspace(0, 5, 51); ch = controller.table(cgrid) target_s = 1.5; target_lambda = np.sqrt(d)*tau*A(target_s) target_h = controller.entropy(target_lambda) starts = [0.15, 1.0, 4.0] control = [] for k0 in starts: k = k0 for step in range(20): # standard error is deliberately representative of a small triplet batch hobs = target_h + rng.normal(0, 0.012) lamhat = controller.invert(hobs, cgrid, ch) shat = solve_s(lamhat, d, tau) khat = shat/mu k = 0.8*k + 0.2*khat control.append(dict(initial_kappa=k0, final_kappa=k, target_kappa=target_s/mu, abs_error=abs(k-target_s/mu))) # Fixed-curvature controls are their initial values; report mean errors. fixed_error = float(np.mean([abs(x-target_s/mu) for x in starts])) adaptive_error = float(np.mean([x['abs_error'] for x in control])) result = { 'seed': SEED, 'dimension': d, 'tau': tau, 'mu_R': mu, 'grid_entropy': [{'lambda':float(x), 'H':float(y)} for x,y in zip(grid,hs)], 'monotone_fraction': monotone_fraction, 'curvature_recovery': rows, 'controller': control, 'fixed_initial_mean_abs_kappa_error': fixed_error, 'adaptive_final_mean_abs_kappa_error': adaptive_error, 'claim_check': {'A_non_decreasing': bool(np.all(np.diff(A(grid)) >= -1e-12)), 'entropy_decreasing_fraction': monotone_fraction, 'entropy_range': [float(hs.min()), float(hs.max())]} } with open('results.json','w') as f: json.dump(result,f,indent=2) print(json.dumps({'monotone_fraction':monotone_fraction, 'entropy_at_lambda_0':float(hs[0]), 'entropy_at_lambda_5':float(hs[-1]), 'fixed_mean_abs_kappa_error':fixed_error, 'adaptive_mean_abs_kappa_error':adaptive_error, 'controller':control}, indent=2)) if __name__ == '__main__': run() # Separate robustness probe: angular scores with unequal variances violate the isotropic # master-curve assumption. We estimate the conditional entropy directly and invert # using the isotropic calibration table, without changing the fitted curve. def anisotropic_entropy(lam, scales=(1.0, 2.0, 0.5), n_w=12000, gh_order=24, seed=SEED+777): rng = np.random.default_rng(seed) w = rng.normal(size=(n_w, 3)) x, wt = roots_hermitenorm(gh_order) wt = wt / np.sqrt(2*np.pi) q = np.zeros((n_w, 3)) for i in range(3): delta = w - w[:, i:i+1] arg = x[None, :, None] + lam*delta[:, None, :] * np.asarray(scales)[None, None, :] surv = ndtr(-arg) surv[:, :, i] = 1.0 q[:, i] = np.sum(wt[None, :] * np.prod(surv, axis=2), axis=1) q = np.clip(q, 1e-14, 1.0) return float(np.mean(-np.sum(q*np.log(q), axis=1))) if __name__ == '__main__': # The main experiment has already run above; append anisotropy metrics to JSON. with open('results.json') as f: result = json.load(f) grid = np.linspace(0, 5, 51) iso = MasterCurve(n_w=20000, gh_order=32, seed=SEED+33) vals = iso.table(grid) anis = [] for lam in [0.5, 1.5, 3.0]: h = anisotropic_entropy(lam) inv = iso.invert(h, grid, vals) anis.append({'lambda_true': lam, 'entropy_anisotropic': h, 'isotropic_inferred_lambda': inv, 'absolute_lambda_error': abs(inv-lam)}) result['anisotropy_probe'] = anis with open('results.json','w') as f: json.dump(result,f,indent=2) print(json.dumps({'anisotropy_probe': anis}, indent=2))