Compactified Burst Controller / burst_controller_experiment.py
Mechanism confirmed, baseline not beaten
1import json, math
2import numpy as np
3
4# Homogeneous cubic field: F_x=alpha*x^3, F_y=(alpha+beta)*x^2*y.
5# At u*=e1: a=alpha, G=0, and the tangent Jacobian eigenvalue is beta.
6def field(z, alpha, beta):
7 x, y = z
8 return np.array([alpha*x**3, (alpha+beta)*x*x*y], dtype=float)
9
10def leading_on_unit(u, alpha, beta):
11 return field(u, alpha, beta)
12
13def controller(z, alpha, beta, rc=2.0, delta=.15, k=8.0, qtol=.20):
14 r = np.linalg.norm(z)
15 if r == 0: return field(z, alpha, beta), False
16 u = z/r
17 v = leading_on_unit(u, alpha, beta)
18 a = float(u @ v)
19 q = np.linalg.norm(v-a*u)
20 # smooth radial damping gated by radius and the near-equilibrium test
21 kap = k * np.logaddexp(0., (r-rc)/delta)
22 active = (r > rc and q < qtol and a > 0)
23 return field(z, alpha, beta) - (kap*u if active else 0.), active
24
25def rk4_step(z, dt, fun):
26 k1=fun(z); k2=fun(z+dt*k1/2); k3=fun(z+dt*k2/2); k4=fun(z+dt*k3)
27 return z + dt*(k1+2*k2+2*k3+k4)/6
28
29def integrate(z0, T, dt, fun, rcap=1e5):
30 z=np.array(z0,dtype=float); maxr=np.linalg.norm(z); interventions=0; first=None
31 n=int(math.ceil(T/dt)); actual=0.
32 for i in range(n):
33 h=min(dt,T-actual)
34 if h<=0: break
35 znew=rk4_step(z,h,fun)
36 actual += h; z=znew
37 r=np.linalg.norm(z); maxr=max(maxr,r)
38 if getattr(fun,'last_active',False): interventions += 1; first=first if first is not None else actual
39 if not np.all(np.isfinite(z)) or r>rcap:
40 return {'z':z,'r':r,'maxr':maxr,'t':actual,'failed':True,'interventions':interventions,'first':first}
41 return {'z':z,'r':np.linalg.norm(z),'maxr':maxr,'t':actual,'failed':False,'interventions':interventions,'first':first}
42
43def make_control_fun(alpha,beta):
44 state={'active':False}
45 def f(z):
46 v, act=controller(z,alpha,beta)
47 state['active']=act
48 f.last_active=act
49 return v
50 f.last_active=False
51 return f
52
53def math_check():
54 # Exact infinity-atlas quantities at e1, checked by finite differences.
55 alpha,beta=1.7,.63; u=np.array([1.,0.]); v=leading_on_unit(u,alpha,beta)
56 a=float(u@v); eps=1e-6
57 def G(th):
58 uu=np.array([math.cos(th),math.sin(th)])
59 vv=leading_on_unit(uu,alpha,beta)
60 return vv-(uu@vv)*uu
61 num=(G(eps)[1]-G(-eps)[1])/(2*eps)
62 return {'a_at_u_star':a,'predicted_alpha':alpha,'tangent_eigenvalue_fd':num,'predicted_beta':beta,
63 'relative_errors':[abs(a-alpha)/alpha,abs(num-beta)/beta]}
64
65def radial_scaling():
66 # Along exact e1, blow-up time is t*=1/(2 alpha r0^2). Measure when r reaches R.
67 r0=.8; R=20.; dt=5e-5
68 rows=[]
69 for alpha in [.5, .8, 1.2, 1.8]:
70 target=(1/r0**2-1/R**2)/(2*alpha)
71 T=target + .05
72 out=integrate([r0,0],T,dt,lambda z:field(z,alpha,0),rcap=R)
73 rows.append({'alpha':alpha,'predicted_time_to_R':target,'observed_time':out['t'],'relative_error':abs(out['t']-target)/target})
74 return rows
75
76def angular_scaling():
77 # For small angle, d log(theta)/d tau = beta. tau=int r^2 dt, and use a short interval.
78 r0=.35; theta0=1e-4; T=.25; dt=1e-5
79 rows=[]
80 for beta in [-.8,.2,.5,1.0,1.5]:
81 alpha=0.4
82 z0=[r0*math.cos(theta0),r0*math.sin(theta0)]
83 out=integrate(z0,T,dt,lambda z:field(z,alpha,beta))
84 th0=math.atan2(z0[1],z0[0]); th=math.atan2(out['z'][1],out['z'][0])
85 tau=(r0**2)*T # leading approximation, adequate at small T
86 measured=math.log(abs(th/th0))/tau
87 rows.append({'beta':beta,'predicted_slope':beta,'observed_slope':measured,'relative_error':abs(measured-beta)/max(abs(beta),1e-12)})
88 return rows
89
90def controller_sweep():
91 # Perturbed near-equilibrium direction: vanilla bursts, controller damps radial growth.
92 rows=[]; r0=.8; T=.8; dt=2e-5; beta=.5; theta=1e-3
93 for alpha in [.8,1.2,1.6,2.0]:
94 z0=[r0*math.cos(theta),r0*math.sin(theta)]
95 vanilla=integrate(z0,T,dt,lambda z:field(z,alpha,beta),rcap=1e4)
96 cf=make_control_fun(alpha,beta)
97 controlled=integrate(z0,T,dt,cf,rcap=1e4)
98 rows.append({'alpha':alpha,'vanilla_max_r':vanilla['maxr'],'controlled_max_r':controlled['maxr'],
99 'vanilla_failed':vanilla['failed'],'controlled_failed':controlled['failed'],
100 'controller_interventions':controlled['interventions'],'first_intervention':controlled['first']})
101 return rows
102
103def onset_boundary():
104 # Along e1, reaching rc by T requires alpha >= (1/r0^2-1/rc^2)/(2T).
105 r0=.8; rc=2.; T=.8; dt=5e-5
106 predicted=(1/r0**2-1/rc**2)/(2*T)
107 rows=[]
108 for alpha in [.6,.8,.9,1.0,1.2]:
109 out=integrate([r0,0],T,dt,lambda z:field(z,alpha,.5),rcap=1e4)
110 rows.append({'alpha':alpha,'reached_rc':bool(out['maxr']>rc),'max_r':out['maxr']})
111 return {'predicted_alpha_boundary':predicted,'sweep':rows}
112
113def main():
114 result={'math_check':math_check(),'radial_scaling':radial_scaling(),
115 'angular_scaling':angular_scaling(),'onset_boundary':onset_boundary(),'controller_sweep':controller_sweep()}
116 print(json.dumps(result,indent=2))
117
118if __name__=='__main__': main()