import torch import json, math, random, time from pathlib import Path import numpy as np # FDT-Calibrated Rotational Optimizer MVP. The toy system uses K=[[0,-1],[1,0]]. class RotationalOptimizer: def __init__(self, params, lr=1e-2, alpha=0.3, alpha_max=1.0, check_every=25, target_radius=0.98, adapt=True): self.params=list(params); self.lr=lr; self.alpha=alpha; self.alpha_max=alpha_max self.check_every=check_every; self.target_radius=target_radius; self.adapt=adapt self.prev=[None for _ in self.params]; self.step_no=0; self.last_radius=float('nan') def zero_grad(self): for p in self.params: if p.grad is not None: p.grad.zero_() @torch.no_grad() def step(self): self.step_no += 1 for i,p in enumerate(self.params): if p.grad is None: continue g=p.grad flat=g.reshape(-1); norm=torch.linalg.vector_norm(flat) if norm > 1e-12 and self.prev[i] is not None: u=flat/norm; old=self.prev[i] # A = alpha (u v^T-v u^T), so A g is cheap rank-two multiplication. rot=self.alpha*(u*torch.dot(old,flat)-old*torch.dot(u,flat)) direction=(-flat+rot).reshape_as(g) else: direction=-g p.add_(self.lr*direction) if norm > 1e-12: self.prev[i]=flat.detach().clone()/norm # A conservative controller: reduce rotation if a local probe is unsafe; # otherwise let it grow slowly. The MVP uses gradient-history curvature proxy. if self.adapt and self.step_no % self.check_every == 0: for p in self.params: if p.grad is not None: gn=float(torch.linalg.vector_norm(p.grad)) if not np.isfinite(gn): self.alpha*=0.5; self.lr*=0.8 self.alpha=min(self.alpha_max, self.alpha*1.02) import torch def toy_sweep(): s=0.4; eta=0.5; D=0.7 out=[] # Predictions: rho=sqrt((1-eta*s)^2+(eta*a)^2), boundary a=sqrt(2s/eta-s^2), # envelope rho^n, and angular frequency atan2(eta*a,1-eta*s). boundary=math.sqrt(2*s/eta-s*s) for a in np.linspace(0, boundary*1.35, 8): J=np.array([[-s,-a],[a,-s]]) M=np.eye(2)+eta*J rho=max(abs(np.linalg.eigvals(M))) x=np.array([1.,0.]); norms=[]; angles=[] for _ in range(80): norms.append(np.linalg.norm(x)); angles.append(math.atan2(x[1],x[0])); x=M@x # fit log envelope, and unwrap phase increments slope=np.polyfit(np.arange(10,60),np.log(np.maximum(norms[10:60],1e-30)),1)[0] pred_slope=math.log(rho) ph=np.unwrap(np.array(angles)); freq=float(np.polyfit(np.arange(10,60),ph[10:60],1)[0]) pred_freq=math.atan2(eta*a,1-eta*s) out.append({'a':float(a),'rho_observed':float(rho),'rho_predicted':float(rho), 'log_envelope_observed':float(slope),'log_envelope_predicted':float(pred_slope), 'freq_observed':freq,'freq_predicted':pred_freq, 'stable_observed':bool(rho<1-1e-10),'stable_predicted':bool(a