Quotient-Fibre Mixing Network / experiment.py
Failed on benchmark
1import json, math, random
2from pathlib import Path
3import numpy as np
4
5SEED = 1595
6np.random.seed(SEED)
7random.seed(SEED)
8
9
10def fit_log_slope(values, start=5, stop=None):
11 v = np.maximum(np.asarray(values, float), 1e-30)
12 stop = len(v) if stop is None else stop
13 x = np.arange(start, stop)
14 return float(np.polyfit(x, np.log(v[start:stop]), 1)[0])
15
16
17def trajectory(aq, af, c=0.7, n=80, z0=1.0, y0=0.0):
18 z = np.zeros(n); y = np.zeros(n)
19 z[0], y[0] = z0, y0
20 for t in range(n - 1):
21 z[t + 1] = aq * z[t]
22 y[t + 1] = af * y[t] + c * z[t]
23 return z, y
24
25
26def max_rate_sweep():
27 rows = []
28 pairs = [(0.98,.30),(.70,.90),(.55,.55),(.90,.40),(.40,.90),(.80,.60)]
29 for aq, af in pairs:
30 z, y = trajectory(aq, af)
31 norm = np.sqrt(z*z + y*y)
32 observed = math.exp(fit_log_slope(norm, 15, 55))
33 predicted = max(aq, af)
34 rows.append(dict(aq=aq, af=af, predicted=predicted, observed=observed,
35 relative_error=abs(observed-predicted)/predicted))
36 return rows
37
38
39def slow_branch_invariance():
40 rows = []
41 aq = .92
42 for af in [.10,.30,.60,.80]:
43 z, y = trajectory(aq, af)
44 observed = math.exp(fit_log_slope(np.sqrt(z*z+y*y), 15, 55))
45 rows.append(dict(aq=aq, af=af, predicted=aq, observed=observed,
46 relative_error=abs(observed-aq)/aq))
47 return rows
48
49
50def stability_sweep():
51 # For this triangular linear transition, eigenvalues are aq and af.
52 # Thus the asymptotic norm slope is log(max(|aq|,|af|)); its sign changes at 1.
53 rows = []
54 for aq, af in [(0.95,.5),(.99,.99),(1.0,.5),(1.01,.5),(.5,1.01),(.5,.98)]:
55 z, y = trajectory(aq, af, n=300)
56 norm = np.sqrt(z*z+y*y)
57 slope = fit_log_slope(norm, 100, 250)
58 predicted_rate = max(abs(aq), abs(af))
59 predicted_stable = predicted_rate < 1
60 observed_rate = math.exp(slope)
61 observed_stable = slope < -1e-5
62 rows.append(dict(aq=aq, af=af, predicted_rate=predicted_rate,
63 observed_rate=observed_rate, predicted_stable=predicted_stable,
64 observed_stable=observed_stable,
65 slope=slope))
66 return rows
67
68
69def learning_comparison():
70 # Fixed linear system identification: split model has independently constrained rates;
71 # monolithic matched-size model has unconstrained 2x2 transition.
72 rng = np.random.default_rng(SEED)
73 T, N = 25, 1600
74 X = rng.normal(size=(N,T,1))
75 Z = np.zeros((N,T)); Y = np.zeros((N,T))
76 for i in range(N):
77 for t in range(T-1):
78 Z[i,t+1] = .96*Z[i,t] + .08*X[i,t,0] + .03*rng.normal()
79 Y[i,t+1] = .42*Y[i,t] + .65*Z[i,t] + .08*X[i,t,0] + .03*rng.normal()
80 H = np.stack([Z,Y], axis=-1)
81 train = np.arange(1200); test = np.arange(1200,N)
82 # Least-squares one-step fit with states supplied, then multi-step free rollout.
83 def mse(a,b): return float(np.mean((a-b)**2))
84 inp = np.concatenate([H[train,:-1], X[train,:-1]], axis=-1).reshape(-1,3)
85 target = H[train,1:].reshape(-1,2)
86 W = np.linalg.lstsq(inp, target, rcond=None)[0]
87 # split: z uses z,x; y uses y,z,x, preserving triangular structure
88 wz = np.linalg.lstsq(inp[:,[0,2]], target[:,0], rcond=None)[0]
89 wy = np.linalg.lstsq(inp[:,[1,0,2]], target[:,1], rcond=None)[0]
90 one_m = mse(inp @ W, target)
91 one_s = mse(np.column_stack([inp[:,[0,2]] @ wz, inp[:,[1,0,2]] @ wy]), target)
92 def rollout(kind, idx):
93 out=[]
94 for i in idx:
95 h = H[i,0].copy(); pred=[]
96 for t in range(T-1):
97 q = X[i,t,0]
98 if kind=='mono': h = np.array([h[0],h[1],q]) @ W
99 else: h = np.array([np.array([h[0],q]) @ wz, np.array([h[1],h[0],q]) @ wy])
100 pred.append(h.copy())
101 out.append(pred)
102 return np.asarray(out)
103 pm, ps = rollout('mono', test), rollout('split', test)
104 truth = H[test,1:]
105 return dict(monolithic_one_step=one_m, split_one_step=one_s,
106 monolithic_rollout=mse(pm,truth), split_rollout=mse(ps,truth),
107 n_test=len(test))
108
109
110def main():
111 result = dict(seed=SEED,
112 prediction_1_max_rate=max_rate_sweep(),
113 prediction_2_slow_branch_invariance=slow_branch_invariance(),
114 prediction_3_stability_boundary=stability_sweep(),
115 learning_comparison=learning_comparison())
116 Path('results.json').write_text(json.dumps(result, indent=2))
117 print(json.dumps(result, indent=2))
118
119if __name__ == '__main__':
120 main()