RG Pyramid Flow Matching / rg_pyramid_toy.py
Failed on benchmark
1import json
2import math
3from pathlib import Path
4import numpy as np
5
6# Toy RG velocity: a translation-invariant field whose real-space influence
7# decays as exp(-Lambda * distance). This is the minimal kernel implied by
8# quasi-local RG flow and lets us measure truncation error exactly.
9SEED = 2708
10L = 256
11LAMBDAS = np.array([0.10, 0.16, 0.25, 0.40, 0.64, 1.00])
12EPSILONS = np.array([0.20, 0.10, 0.05, 0.02, 0.01])
13
14
15def periodic_kernel(lam, n=L):
16 d = np.minimum(np.arange(n), n - np.arange(n)).astype(float)
17 k = np.exp(-lam * d)
18 # Normalize so the zero mode has unit gain; normalization cancels in the
19 # relative truncation error but makes the operator easy to interpret.
20 return k / k.sum()
21
22
23def truncate(k, radius):
24 n = len(k)
25 d = np.minimum(np.arange(n), n - np.arange(n))
26 out = np.where(d <= radius, k, 0.0)
27 # Preserve the zero-mode response, as a local approximation would normally
28 # absorb the omitted mass into its learned bias/gain.
29 return out / out.sum()
30
31
32def relative_operator_error(k, radius):
33 """RMS error over all Fourier modes, normalized by exact operator power."""
34 exact = np.fft.rfft(k)
35 approx = np.fft.rfft(truncate(k, radius))
36 return float(np.linalg.norm(approx - exact) / np.linalg.norm(exact))
37
38
39def required_radius(lam, eps):
40 k = periodic_kernel(lam)
41 for r in range(L // 2):
42 if relative_operator_error(k, r) <= eps:
43 return r
44 return L // 2
45
46
47def verify_field_error(lam, radius, trials=64):
48 rng = np.random.default_rng(SEED + int(1000 * lam) + radius)
49 k = periodic_kernel(lam)
50 kt = truncate(k, radius)
51 errs = []
52 for _ in range(trials):
53 x = rng.normal(size=L)
54 y = np.fft.irfft(np.fft.rfft(k) * np.fft.rfft(x), n=L)
55 yt = np.fft.irfft(np.fft.rfft(kt) * np.fft.rfft(x), n=L)
56 errs.append(np.linalg.norm(y - yt) / np.linalg.norm(y))
57 return float(np.mean(errs))
58
59
60def linear_fit(x, y):
61 a, b = np.polyfit(x, y, 1)
62 pred = a * x + b
63 r2 = 1.0 - np.sum((y - pred) ** 2) / max(np.sum((y - y.mean()) ** 2), 1e-12)
64 return float(a), float(b), float(r2)
65
66
67def pyramid_radius_check():
68 # Lambda_s = pi/a_s. The claimed cell-radius scaling is
69 # R_s/a_s = c [log L + log(1/eps)] / pi, independent of s.
70 eps = 0.02
71 c = 1.0
72 rows = []
73 for s in range(6):
74 a = 2 ** s
75 lam = math.pi / a
76 R = c * (math.log(L) + math.log(1 / eps)) / lam
77 r = math.ceil(R / a)
78 rows.append({'level': s, 'spacing': a, 'Lambda': lam,
79 'physical_R': R, 'cell_radius': r})
80 return rows
81
82
83def main():
84 table = []
85 for lam in LAMBDAS:
86 for eps in EPSILONS:
87 r = required_radius(lam, eps)
88 table.append({'Lambda': float(lam), 'epsilon': float(eps),
89 'required_R': r,
90 'measured_error': relative_operator_error(periodic_kernel(lam), r),
91 'field_error': verify_field_error(lam, r)})
92
93 # Prediction 1: at fixed epsilon, R*Lambda is approximately constant.
94 fixed = [q for q in table if q['epsilon'] == 0.02]
95 inv_lam = np.array([1 / q['Lambda'] for q in fixed])
96 radii = np.array([q['required_R'] for q in fixed])
97 slope, intercept, r2 = linear_fit(inv_lam, radii)
98 products = [q['Lambda'] * q['required_R'] for q in fixed]
99
100 # Prediction 2: at fixed Lambda, R grows linearly with log(1/epsilon).
101 lam0 = 0.25
102 tolrows = [q for q in table if q['Lambda'] == lam0]
103 xlog = np.array([math.log(1 / q['epsilon']) for q in tolrows])
104 yR = np.array([q['required_R'] for q in tolrows])
105 slope_log, intercept_log, r2_log = linear_fit(xlog, yR)
106
107 # Prediction 3 / baseline: a fixed radius has increasingly bad error as the
108 # RG length grows (Lambda decreases), while radius selected by the bound
109 # stays below tolerance.
110 fixed_radius = 8
111 baseline = []
112 for lam in LAMBDAS:
113 err = relative_operator_error(periodic_kernel(lam), fixed_radius)
114 rg_r = required_radius(lam, 0.02)
115 rg_err = relative_operator_error(periodic_kernel(lam), rg_r)
116 baseline.append({'Lambda': float(lam), 'fixed_radius': fixed_radius,
117 'fixed_radius_error': err, 'rg_radius': rg_r,
118 'rg_error': rg_err})
119
120 result = {
121 'setup': {'L': L, 'kernel': 'normalized exp(-Lambda * periodic_distance)',
122 'seed': SEED},
123 'radius_sweep': table,
124 'predictions': {
125 'inverse_cutoff': {
126 'statement': 'R is affine in 1/Lambda at fixed epsilon',
127 'fit_R_vs_1_over_Lambda': {'slope': slope, 'intercept': intercept, 'R2': r2},
128 'Lambda_times_R': products,
129 'mean': float(np.mean(products)), 'cv': float(np.std(products) / np.mean(products))},
130 'log_tolerance': {
131 'statement': 'R is affine in log(1/epsilon) at fixed Lambda',
132 'Lambda': lam0,
133 'fit_R_vs_log_inverse_epsilon': {'slope': slope_log, 'intercept': intercept_log, 'R2': r2_log}},
134 'rescaled_grid': {
135 'statement': 'R/a is approximately constant when Lambda=pi/a',
136 'levels': pyramid_radius_check()}
137 },
138 'baseline_comparison': baseline
139 }
140 Path('results.json').write_text(json.dumps(result, indent=2))
141 print(json.dumps(result, indent=2))
142
143
144if __name__ == '__main__':
145 main()