grapheneaffiliates commited on
Commit
780ccb5
Β·
verified Β·
1 Parent(s): 04cf4b7

Upload experiments/e8_theta_h4_decomposition.py with huggingface_hub

Browse files
experiments/e8_theta_h4_decomposition.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ E8 Theta Series Decomposition Under H4 Projection
4
+
5
+ The E8 theta function Theta_E8(q) = 1 + 240q + 2160q^2 + 6720q^3 + ...
6
+ counts lattice vectors at each squared norm (using norm^2/2 as index).
7
+
8
+ Under E8 = H4 + H4', each vector v decomposes as v = v_H4 + v_H4'.
9
+ Its norm splits: |v|^2 = |v_H4|^2 + |v_H4'|^2.
10
+
11
+ But H4 norms live in Q(sqrt(5)), not in Q. So the decomposition
12
+ produces a TWO-VARIABLE theta series over the golden ring Z[phi].
13
+
14
+ This connects E8 modular forms to HILBERT MODULAR FORMS of Q(sqrt(5)).
15
+ The explicit decomposition may reveal structure nobody has written down.
16
+
17
+ Strategy: enumerate E8 lattice vectors at norms 2,4,6,8,10,
18
+ project each to H4+H4', record the (norm_H4, norm_H4') pairs
19
+ in exact Q(sqrt(5)) arithmetic.
20
+ """
21
+
22
+ import os
23
+ import time
24
+ import json
25
+ from collections import Counter
26
+ from itertools import product as cartprod
27
+
28
+ os.environ["OMP_NUM_THREADS"] = "2"
29
+
30
+ print("=" * 65)
31
+ print(" E8 THETA SERIES: H4 DECOMPOSITION")
32
+ print(" Exact Q(sqrt(5)) arithmetic")
33
+ print("=" * 65)
34
+
35
+
36
+ # ── Q(sqrt(5)) arithmetic ────────────────────────────────────────
37
+ # Represent a + b*sqrt(5) as (a, b) with a, b rational (we use
38
+ # integers * 4 to avoid fractions from the 2x-scaled E8 roots).
39
+
40
+ class QSqrt5:
41
+ """Exact element of Q(sqrt(5)): a + b*sqrt(5), stored as (a, b)
42
+ with integer numerators (denominator tracked separately)."""
43
+ __slots__ = ('a', 'b')
44
+
45
+ def __init__(self, a=0, b=0):
46
+ self.a = a # rational part (integer)
47
+ self.b = b # sqrt(5) coefficient (integer)
48
+
49
+ def __add__(self, other):
50
+ return QSqrt5(self.a + other.a, self.b + other.b)
51
+
52
+ def __eq__(self, other):
53
+ return self.a == other.a and self.b == other.b
54
+
55
+ def __hash__(self):
56
+ return hash((self.a, self.b))
57
+
58
+ def __repr__(self):
59
+ if self.b == 0:
60
+ return f"{self.a}"
61
+ if self.a == 0:
62
+ return f"{self.b}*sqrt5"
63
+ return f"({self.a}+{self.b}*sqrt5)"
64
+
65
+ def approx(self):
66
+ return self.a + self.b * 2.2360679774997896 # sqrt(5)
67
+
68
+
69
+ # ── E8 lattice vector enumeration ─────────────────────────────────
70
+
71
+ def enumerate_e8_shell(norm_sq):
72
+ """Enumerate all E8 lattice vectors with given squared norm.
73
+
74
+ E8 lattice (D8+ form): vectors are either
75
+ Type A: all integers, even sum
76
+ Type B: all half-integers, even sum
77
+ Squared norm = sum of squares.
78
+
79
+ We use 2x-scaling: actual coordinates * 2 -> all integers.
80
+ Scaled squared norm = 4 * actual squared norm.
81
+ So norm_sq=2 (actual) -> scaled_norm_sq=8.
82
+ """
83
+ target = norm_sq * 4 # scaled
84
+ vectors = []
85
+
86
+ # Type A: coordinates are even integers (0, +-2, +-4, ...)
87
+ # Sum must be even (always true since all even)
88
+ # Sum of squares = target
89
+ _enumerate_type_a([], 8, target, vectors)
90
+
91
+ # Type B: coordinates are odd integers (+-1, +-3, +-5, ...)
92
+ # Sum must be even
93
+ _enumerate_type_b([], 8, target, vectors)
94
+
95
+ return vectors
96
+
97
+
98
+ def _enumerate_type_a(partial, remaining, target, results):
99
+ """Enumerate 8D vectors with even integer coords, sum of squares = target."""
100
+ if remaining == 0:
101
+ if target == 0:
102
+ # Check even sum (always true for even integers, but verify)
103
+ s = sum(partial)
104
+ if s % 4 == 0: # in 2x scaling, "even sum" means sum divisible by 4
105
+ results.append(tuple(partial))
106
+ return
107
+
108
+ # Maximum absolute value for remaining coordinates
109
+ max_val = int(target ** 0.5)
110
+ # Only even values
111
+ for v in range(0, max_val + 1, 2):
112
+ v_sq = v * v
113
+ if v_sq > target:
114
+ break
115
+ if v == 0:
116
+ _enumerate_type_a(partial + [0], remaining - 1, target, results)
117
+ else:
118
+ for sign in [v, -v]:
119
+ _enumerate_type_a(partial + [sign], remaining - 1, target - v_sq, results)
120
+
121
+
122
+ def _enumerate_type_b(partial, remaining, target, results):
123
+ """Enumerate 8D vectors with odd integer coords, sum of squares = target."""
124
+ if remaining == 0:
125
+ if target == 0:
126
+ s = sum(partial)
127
+ if s % 4 == 0: # even sum condition in 2x scaling
128
+ results.append(tuple(partial))
129
+ return
130
+
131
+ max_val = int(target ** 0.5)
132
+ # Only odd values
133
+ for v in range(1, max_val + 1, 2):
134
+ v_sq = v * v
135
+ if v_sq > target:
136
+ break
137
+ for sign in [v, -v]:
138
+ _enumerate_type_b(partial + [sign], remaining - 1, target - v_sq, results)
139
+
140
+
141
+ # ── H4 projection with exact Q(sqrt(5)) arithmetic ───────────────
142
+
143
+ def project_exact(vec):
144
+ """Project 8D vector to H4 using exact Q(sqrt(5)) arithmetic.
145
+
146
+ Projection: (x1,...,x8) -> (x1 + phi*x5, ..., x4 + phi*x8)
147
+ where phi = (1+sqrt(5))/2.
148
+
149
+ In our 2x-scaled coords, xi are integers.
150
+ Result: 4 components, each of form (a + b*sqrt(5)) where
151
+ a = 2*xi + xj (integer, from 2*xi + (1/2)*2*xj = 2*xi + xj)
152
+ Wait, let me be careful.
153
+
154
+ phi = (1+sqrt(5))/2
155
+ Component i: vec[i] + phi * vec[i+4]
156
+ = vec[i] + (1+sqrt(5))/2 * vec[i+4]
157
+ = vec[i] + vec[i+4]/2 + vec[i+4]*sqrt(5)/2
158
+
159
+ In 2x-scaled: actual vec[i] = scaled[i]/2
160
+ So: scaled[i]/2 + phi * scaled[i+4]/2
161
+ = scaled[i]/2 + (1+sqrt(5))/2 * scaled[i+4]/2
162
+ = (scaled[i] + scaled[i+4])/4 + scaled[i+4]*sqrt(5)/4
163
+
164
+ To stay in integers, multiply everything by 4:
165
+ 4 * component_i = (scaled[i] + scaled[i+4]) + scaled[i+4]*sqrt(5)
166
+
167
+ So norm in Q(sqrt(5)):
168
+ |4*comp_i|^2 = (a + b*sqrt(5))^2 = a^2 + 5b^2 + 2ab*sqrt(5)
169
+ Total 4^2 * |proj|^2 = sum_i (a_i^2 + 5*b_i^2) + sqrt(5)*sum_i(2*a_i*b_i)
170
+ """
171
+ components = []
172
+ for i in range(4):
173
+ a = vec[i] + vec[i + 4] # rational part * 4
174
+ b = vec[i + 4] # sqrt(5) part * 4
175
+ components.append((a, b))
176
+
177
+ # Compute |proj|^2 * 16 in Q(sqrt(5))
178
+ rat_part = 0
179
+ sqrt5_part = 0
180
+ for a, b in components:
181
+ rat_part += a * a + 5 * b * b
182
+ sqrt5_part += 2 * a * b
183
+
184
+ return QSqrt5(rat_part, sqrt5_part), components
185
+
186
+
187
+ def project_conjugate_exact(vec):
188
+ """Project to H4' using phi_bar = (1-sqrt(5))/2.
189
+
190
+ 4 * component_i = (scaled[i] + scaled[i+4]) - scaled[i+4]*sqrt(5)
191
+ -> just negate the sqrt(5) coefficient.
192
+ """
193
+ rat_part = 0
194
+ sqrt5_part = 0
195
+ for i in range(4):
196
+ a = vec[i] + vec[i + 4]
197
+ b = -vec[i + 4] # negated!
198
+ rat_part += a * a + 5 * b * b
199
+ sqrt5_part += 2 * a * b
200
+
201
+ return QSqrt5(rat_part, sqrt5_part)
202
+
203
+
204
+ # ── Main computation ──────────────────────────────────────────────
205
+
206
+ # Known theta series coefficients: vectors at norm^2 = 2n
207
+ # a(n) for E8: 1, 240, 2160, 6720, 17520, 30240, 60480, 82560, ...
208
+ expected_counts = {
209
+ 1: 240,
210
+ 2: 2160,
211
+ 3: 6720,
212
+ 4: 17520,
213
+ 5: 30240,
214
+ }
215
+
216
+ all_decompositions = {}
217
+
218
+ for shell in range(1, 6):
219
+ norm_sq = 2 * shell
220
+
221
+ print(f"\n--- Shell {shell}: norm^2 = {norm_sq} ---")
222
+ t0 = time.time()
223
+ vectors = enumerate_e8_shell(norm_sq)
224
+ elapsed = time.time() - t0
225
+ print(f" Found {len(vectors)} vectors ({elapsed:.1f}s)")
226
+
227
+ if shell in expected_counts:
228
+ expected = expected_counts[shell]
229
+ status = "OK" if len(vectors) == expected else f"MISMATCH (expected {expected})"
230
+ print(f" Expected: {expected} -> {status}")
231
+
232
+ if len(vectors) == 0:
233
+ continue
234
+
235
+ # Project each vector and record (norm_H4, norm_H4') in Q(sqrt(5))
236
+ decomp = Counter() # (norm_h4, norm_h4_bar) -> count
237
+ for v in vectors:
238
+ nh4, _ = project_exact(v)
239
+ nh4bar = project_conjugate_exact(v)
240
+ decomp[(nh4, nh4bar)] += 1
241
+
242
+ print(f" Distinct (norm_H4, norm_H4') pairs: {len(decomp)}")
243
+ print(f" Decomposition:")
244
+ for (nh4, nh4bar), count in sorted(decomp.items(),
245
+ key=lambda x: x[0][0].approx()):
246
+ # The two norms should sum to the original norm * 16
247
+ total = QSqrt5(nh4.a + nh4bar.a, nh4.b + nh4bar.b)
248
+ print(f" H4={nh4!r:>20s} H4'={nh4bar!r:>20s} "
249
+ f"sum={total!r:>15s} count={count:>5d}")
250
+
251
+ all_decompositions[shell] = {
252
+ str((str(k[0]), str(k[1]))): v
253
+ for k, v in decomp.items()
254
+ }
255
+
256
+ # Safety: don't run too long
257
+ if time.time() - t0 > 300: # 5 min max per shell
258
+ print(" Time limit reached, stopping")
259
+ break
260
+
261
+
262
+ # ── Analysis: look for patterns ───────────────────────────────────
263
+
264
+ print(f"\n\n" + "=" * 65)
265
+ print(f" PATTERN ANALYSIS")
266
+ print(f"=" * 65)
267
+
268
+ print(f"""
269
+ The norm decomposition n = n_H4 + n_H4' lives in Q(sqrt(5)).
270
+ If the counts at each (n_H4, n_H4') pair follow a pattern
271
+ related to Hilbert modular forms of Q(sqrt(5)), that would
272
+ connect E8 theta series to the arithmetic of the golden field.
273
+
274
+ Key question: do the counts factorize? I.e., is
275
+ #{'{'}v : |v_H4|^2=a, |v_H4'|^2=b{'}'} = f(a) * g(b)
276
+ for some functions f, g? If yes, Theta_E8 = Theta_H4 * Theta_H4'
277
+ as Hilbert modular forms. If no, there's a non-trivial mixing term.
278
+ """)
279
+
280
+ # Save results
281
+ out_path = os.path.join(os.path.dirname(__file__), "e8_theta_decomp.json")
282
+ with open(out_path, "w") as f:
283
+ json.dump(all_decompositions, f, indent=2)
284
+ print(f"Results saved to {out_path}")