import json, math import numpy as np from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import RBF, WhiteKernel, ConstantKernel SEED = 2690 T = 50 RMAX = 1.7 R_TRUE = math.sqrt((1.0 - 0.8) / 0.12) # Radial discrete dynamics: x+ = (a + b ||x||^2)x. # For V=||x||^2, m(r)=V(x)-V(x+) = r^2[1-(a+b r^2)^2]. def rho(r, a=0.8, b=0.12): return a + b * r * r def dynamics(x, a=0.8, b=0.12): r = np.linalg.norm(x, axis=-1, keepdims=True) return rho(r, a, b) * x def V(x): return np.sum(np.asarray(x) ** 2, axis=-1) def margin(x, a=0.8, b=0.12): x = np.asarray(x) return V(x) - V(dynamics(x, a, b)) def rollout_y(x, a=0.8, b=0.12, T=T): z = np.asarray(x, dtype=float).copy() vals = [] for _ in range(T): zn = dynamics(z[None, :], a, b)[0] vals.append(float(V(z[None, :])[0] - V(zn[None, :])[0])) z = zn if not np.isfinite(z).all() or np.linalg.norm(z) > 1e6: break return float(np.min(vals)) def make_candidates(n=18000, rmax=RMAX, seed=SEED): g = np.random.default_rng(seed) theta = g.uniform(0, 2*np.pi, n) r = np.sqrt(g.uniform(0, rmax*rmax, n)) return np.c_[r*np.cos(theta), r*np.sin(theta)] CAND = make_candidates() def fit_gp(X, y): kernel = ConstantKernel(1.0, (1e-3, 10.0))*RBF(.35, (.03, 2.0)) + WhiteKernel(.002, (1e-6, .1)) gp = GaussianProcessRegressor(kernel=kernel, normalize_y=True, optimizer=None, random_state=SEED) gp.fit(X, y) return gp def run_policy(policy, budget=32, seed=SEED): g = np.random.default_rng(seed) radii = np.array([.15,.30,.45,.60,.75,.90,1.05,1.20]) angles = np.linspace(0, 2*np.pi, len(radii), endpoint=False) + .13 X = np.c_[radii*np.cos(angles), radii*np.sin(angles)] y = np.array([rollout_y(x) for x in X]) queried = list(X) for _ in range(budget): if policy == 'uniform': idx = g.integers(len(CAND)) elif policy == 'random_shell': # emulate a shell search with random points from the outer candidate pool idx = g.integers(len(CAND)) else: gp = fit_gp(X, y) mu, sd = gp.predict(CAND, return_std=True) tau = .035 acq = sd * np.maximum(tau - np.abs(mu), 0.0) # If the strict tolerance has no positive candidate, use uncertainty, # preserving the intended boundary-seeking behavior. idx = int(np.argmax(acq if np.max(acq) > 0 else sd)) x = CAND[idx] X = np.vstack([X, x]); y = np.append(y, rollout_y(x)) queried.append(x) q = np.asarray(queried) qmargin = np.abs(margin(q)) # first queried point within a radial tolerance of the analytic frontier near = np.where(np.abs(np.linalg.norm(q,axis=1)-R_TRUE) < .06)[0] return {'X':X, 'y':y, 'first_frontier': int(near[0]) if len(near) else budget+8, 'boundary_fraction': float(np.mean(qmargin < .02)), 'mean_frontier_distance': float(np.mean(np.abs(np.linalg.norm(q,axis=1)-R_TRUE))), 'queries': len(q)} def sweep_frontier(): # Prediction 1: instability threshold is rho(r)=1, hence r*=sqrt((1-a)/b). rows=[] for a,b in [(0.6,.08),(.7,.10),(.8,.12),(.9,.20)]: rs=np.linspace(.02,2.6,26000) vals=margin(np.c_[rs,np.zeros_like(rs)],a,b) ix=np.where(vals <= 0)[0] observed=float(rs[ix[0]]) if len(ix) else float('nan') predicted=math.sqrt((1-a)/b) rows.append({'a':a,'b':b,'predicted_r':predicted,'observed_r':observed,'abs_error':abs(observed-predicted)}) return rows def sweep_scaling(): # Prediction 2: at fixed radius, margin scales linearly with b; exact ratio check. r=.95; a=.8; bs=np.array([.04,.08,.12,.16,.20]) ms=np.array([margin(np.array([[r,0.]]),a,b)[0] for b in bs]) coef=np.polyfit(bs,ms,1) return {'b_values':bs.tolist(),'margins':ms.tolist(),'linear_r2':float(1-np.sum((ms-np.polyval(coef,bs))**2)/np.sum((ms-ms.mean())**2)),'slope':float(coef[0])} def main(): # Core identity verification: closed form equals V(x)-V(f(x)). g=np.random.default_rng(SEED) X=g.uniform(-1.5,1.5,(1000,2)); r=np.linalg.norm(X,axis=1) closed=r*r*(1-(.8+.12*r*r)**2) exact=margin(X) identity_err=float(np.max(np.abs(closed-exact))) frontier=sweep_frontier(); scaling=sweep_scaling() policies={p:run_policy(p,32,SEED+17) for p in ['uniform','random_shell','gp_boundary']} summary={ 'analytic_frontier':R_TRUE, 'identity_max_abs_error':identity_err, 'frontier_sweep':frontier, 'margin_scaling':scaling, 'policies':{k:{'first_frontier_query':v['first_frontier'],'boundary_fraction':v['boundary_fraction'],'queries':v['queries'],'mean_frontier_distance':v['mean_frontier_distance']} for k,v in policies.items()} } with open('results.json','w') as f: json.dump(summary,f,indent=2) print(json.dumps(summary,indent=2)) if __name__ == '__main__': main()