Floquet-Stabilized Periodic Training Dynamics / floquet_mvp.py
Beats tuned baseline
1import json
2from pathlib import Path
3import numpy as np
4from scipy.linalg import expm, eigvals
5
6OUT = Path('floquet_results.json')
7M1 = np.array([[-5.83182011, 6.67253161], [4.98376025, -2.86979823]])
8M2 = np.array([[4.15640208, -3.67581620], [5.62849205, -1.08915620]])
9I = np.eye(2)
10
11def edge(A):
12 return float(np.max(np.real(eigvals(A))))
13
14def gamma(M1, M2, damping, period):
15 h = period / 2.0
16 P = expm((-damping*I + M2)*h) @ expm((-damping*I + M1)*h)
17 return float(np.log(np.max(np.abs(eigvals(P)))) / period)
18
19def avg_edge(d):
20 return .5*(edge(M1-d*I) + edge(M2-d*I))
21
22def crossing(xs, ys):
23 for x0,x1,y0,y1 in zip(xs[:-1],xs[1:],ys[:-1],ys[1:]):
24 if y0*y1 <= 0:
25 return float(x0-y0*(x1-x0)/(y1-y0))
26 return None
27
28def main():
29 e1,e2=edge(M1),edge(M2)
30 comm=float(np.linalg.norm(M2@M1-M1@M2))
31 periods=[.01,.03,.1,.2,.5,1.,2.,5.,10.,20.,50.,100.]
32 # P1: scalar damping shifts the exact Floquet exponent by -d.
33 shifts=[]
34 for T in periods:
35 g0,g2=gamma(M1,M2,0,T),gamma(M1,M2,2,T)
36 shifts.append({'period':T,'measured_shift':g0-g2,'predicted_shift':2.,
37 'abs_error':abs(g0-g2-2.)})
38 # P2: at long periods the boundary approaches the mean instantaneous edge.
39 ds=np.linspace(0,3,1201)
40 adiabatic=.5*(e1+e2)
41 boundaries=[]
42 for T in periods:
43 ys=np.array([gamma(M1,M2,d,T) for d in ds])
44 boundaries.append({'period':T,'floquet_boundary':crossing(ds,ys),
45 'adiabatic_boundary':adiabatic})
46 # P3: noncommuting finite-period correction can stabilize even when the
47 # phasewise rightmost eigenvalues and constant-mean system are unstable.
48 mean_edge=edge((M1+M2)/2)
49 correction=[]
50 for T in periods:
51 g=gamma(M1,M2,0,T)
52 correction.append({'period':T,'frequency':2*np.pi/T,'floquet_gamma':g,
53 'constant_mean_gamma':mean_edge,'correction':g-mean_edge})
54 d=1.4; T=1.0
55 rescue={'damping':d,'period':T,'phase_edges':[e1-d,e2-d],
56 'floquet_gamma':gamma(M1,M2,d,T),'adiabatic_estimate':avg_edge(d),
57 'constant_mean_baseline':mean_edge-d}
58 rescue['instantaneous_edges_positive']=min(rescue['phase_edges'])>0
59 rescue['floquet_stable']=rescue['floquet_gamma']<0
60 rescue['baseline_unstable']=rescue['constant_mean_baseline']>0
61 # Linearized-training proxy: norm after repeated periods, same mean B and d.
62 x0=np.array([1.,-1.]); n=20
63 h=T/2
64 P=expm((-d*I+M2)*h)@expm((-d*I+M1)*h)
65 periodic=np.linalg.matrix_power(P,n)@x0
66 baseline=expm((-d*I+(M1+M2)/2)*T*n)@x0
67 proxy={'periods':n,'initial_norm':float(np.linalg.norm(x0)),
68 'constant_mean_final_norm':float(np.linalg.norm(baseline)),
69 'periodic_final_norm':float(np.linalg.norm(periodic))}
70 result={'matrices':{'M1':M1.tolist(),'M2':M2.tolist(),'edge_M1':e1,
71 'edge_M2':e2,'commutator_norm':comm},
72 'predictions':{'P1':'gamma(d)=gamma(0)-d exactly',
73 'P2':'slow-period boundary approaches average instantaneous edge',
74 'P3':'noncommuting finite-period forcing can stabilize beyond mean-matrix dynamics'},
75 'damping_shift':shifts,'boundaries':boundaries,'mean_matrix_edge':mean_edge,
76 'correction':correction,'rescue':rescue,'proxy_comparison':proxy}
77 OUT.write_text(json.dumps(result,indent=2)); print(json.dumps(result,indent=2))
78if __name__=='__main__': main()