coordinate-system 7.0.0__cp313-cp313-win_amd64.whl → 7.0.2__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coordinate_system/__init__.py +102 -61
- coordinate_system/complex_geometric_physics.py +177 -178
- coordinate_system/coordinate_system.cp313-win_amd64.pyd +0 -0
- coordinate_system/curve_interpolation.py +4 -3
- coordinate_system/differential_geometry.py +81 -1
- coordinate_system/spectral_geometry.py +1169 -1185
- coordinate_system/u3_frame.py +25 -3
- coordinate_system/visualization.py +4 -3
- {coordinate_system-7.0.0.dist-info → coordinate_system-7.0.2.dist-info}/METADATA +36 -67
- coordinate_system-7.0.2.dist-info/RECORD +13 -0
- coordinate_system-7.0.0.dist-info/RECORD +0 -13
- {coordinate_system-7.0.0.dist-info → coordinate_system-7.0.2.dist-info}/LICENSE +0 -0
- {coordinate_system-7.0.0.dist-info → coordinate_system-7.0.2.dist-info}/WHEEL +0 -0
- {coordinate_system-7.0.0.dist-info → coordinate_system-7.0.2.dist-info}/top_level.txt +0 -0
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- Berry
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- FourierFrame ↔ GL(1,ℂ) = ℂ× = U(1) × ℝ⁺
|
|
24
|
-
- Phase e^{iθ} ↔ U(1) (
|
|
25
|
-
- Magnitude |Q| ↔ ℝ⁺ (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Date: 2025-12-04
|
|
29
|
-
Version: 6.0.3
|
|
30
|
-
|
|
1
|
+
"""
|
|
2
|
+
Fourier Frame Field and Hilbert Space Spectral Geometry
|
|
3
|
+
=======================================================
|
|
4
|
+
|
|
5
|
+
A FourierFrame-based spectral geometry framework on Hilbert spaces.
|
|
6
|
+
|
|
7
|
+
Core ideas:
|
|
8
|
+
- Fourier frame = FourierFrame * e^{iθ} (phase rotation / Fourier transform)
|
|
9
|
+
- Conformal transform = FourierFrame * λ, λ ∈ ℝ⁺ (scaling)
|
|
10
|
+
- Intrinsic gradient operator G_μ = d/dx^μ log FourierFrame(x)
|
|
11
|
+
- Curvature R_{μν} = [G_μ, G_ν]
|
|
12
|
+
- Berry phase γ = ∮ G_μ dx^μ (geometric phase / parallel transport)
|
|
13
|
+
- Chern number c₁ = (1/2π) ∬ R_{μν} dS (topological invariant)
|
|
14
|
+
|
|
15
|
+
Mathematical framework:
|
|
16
|
+
- Hilbert space: Fourier analysis on L²(M)
|
|
17
|
+
- Spectral theory: Laplacian eigenvalue problems
|
|
18
|
+
- Fiber bundles: FourierFrame as a local section of the frame bundle
|
|
19
|
+
- Lie groups/algebras: intrinsic gradient operator forms a Lie algebra
|
|
20
|
+
- Heat kernel expansion: extraction of geometric invariants
|
|
21
|
+
|
|
22
|
+
Group correspondence:
|
|
23
|
+
- FourierFrame ↔ GL(1,ℂ) = ℂ× = U(1) × ℝ⁺
|
|
24
|
+
- Phase e^{iθ} ↔ U(1) (unit circle)
|
|
25
|
+
- Magnitude |Q| ↔ ℝ⁺ (positive scaling)
|
|
26
|
+
|
|
27
|
+
**Authors:** Pan Guojun
|
|
28
|
+
Date: 2025-12-04
|
|
29
|
+
Version: 6.0.3
|
|
30
|
+
**DOI:** https://doi.org/10.5281/zenodo.14435613
|
|
31
|
+
"""
|
|
31
32
|
|
|
32
33
|
__version__ = '6.0.3'
|
|
33
34
|
|
|
@@ -36,22 +37,22 @@ from typing import List, Tuple, Dict, Optional, Union, Any, Callable
|
|
|
36
37
|
from dataclasses import dataclass
|
|
37
38
|
import warnings
|
|
38
39
|
|
|
39
|
-
#
|
|
40
|
+
# Import coordinate system types
|
|
40
41
|
try:
|
|
41
42
|
from .coordinate_system import coord3, vec3, quat
|
|
42
43
|
except ImportError:
|
|
43
44
|
try:
|
|
44
45
|
from coordinate_system import coord3, vec3, quat
|
|
45
46
|
except ImportError:
|
|
46
|
-
#
|
|
47
|
+
# Lazy import for standalone usage
|
|
47
48
|
coord3 = None
|
|
48
49
|
vec3 = None
|
|
49
50
|
quat = None
|
|
50
51
|
|
|
51
|
-
#
|
|
52
|
-
HBAR = 1.0 #
|
|
52
|
+
# Physical constants
|
|
53
|
+
HBAR = 1.0 # Reduced Planck constant (natural units)
|
|
53
54
|
|
|
54
|
-
# GPU
|
|
55
|
+
# GPU availability check
|
|
55
56
|
try:
|
|
56
57
|
import cupy as cp
|
|
57
58
|
import cupyx.scipy.fft as cufft
|
|
@@ -62,66 +63,60 @@ except ImportError:
|
|
|
62
63
|
cufft = None
|
|
63
64
|
|
|
64
65
|
|
|
65
|
-
# ============================================================
|
|
66
|
-
#
|
|
67
|
-
# ============================================================
|
|
68
|
-
|
|
69
|
-
class FourierFrame:
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- Q
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
base_coord: 基础坐标系 (coord3 对象),None 时使用单位标架
|
|
120
|
-
q_factor: 复标度因子 Q ∈ ℂ× = GL(1,ℂ)
|
|
121
|
-
Q = |Q| · e^{iθ}
|
|
122
|
-
- 相位 e^{iθ}: 傅里叶变换
|
|
123
|
-
- 模 |Q|: 共形缩放
|
|
124
|
-
"""
|
|
66
|
+
# ============================================================
|
|
67
|
+
# Fourier Frame Algebra
|
|
68
|
+
# ============================================================
|
|
69
|
+
|
|
70
|
+
class FourierFrame:
|
|
71
|
+
"""
|
|
72
|
+
FourierFrame: core spectral-geometry object on Hilbert space.
|
|
73
|
+
|
|
74
|
+
Mathematical structure:
|
|
75
|
+
FourierFrame ↔ GL(1,ℂ) = ℂ× = U(1) × ℝ⁺
|
|
76
|
+
|
|
77
|
+
- Q ∈ ℂ× (nonzero complex)
|
|
78
|
+
- Q = |Q| · e^{iθ}
|
|
79
|
+
- Phase e^{iθ} ∈ U(1): Fourier transform (phase rotation)
|
|
80
|
+
- Magnitude |Q| ∈ ℝ⁺: conformal scaling
|
|
81
|
+
|
|
82
|
+
Key properties:
|
|
83
|
+
- base_coord: underlying coord3 frame (optional)
|
|
84
|
+
- Q: complex scale factor encoding phase and magnitude
|
|
85
|
+
|
|
86
|
+
Key transforms:
|
|
87
|
+
- FourierFrame * e^{iθ} = Fourier transform (θ = π/2 is the standard 90°)
|
|
88
|
+
- FourierFrame * λ = conformal transform (λ ∈ ℝ⁺)
|
|
89
|
+
- FourierFrame * FourierFrame = frame composition
|
|
90
|
+
|
|
91
|
+
Hilbert-space meaning:
|
|
92
|
+
- Fourier basis on L²(M)
|
|
93
|
+
- Intrinsic gradient G_μ = d/dx^μ log Q(x)
|
|
94
|
+
- Spectrum: Δφ_n = -λ_n φ_n
|
|
95
|
+
- Heat kernel: K(x,y,t) = Σ_n e^{-λ_n t} φ_n(x) φ_n*(y)
|
|
96
|
+
|
|
97
|
+
Geometric meaning:
|
|
98
|
+
- Local section of the frame bundle F(M)
|
|
99
|
+
- G_μ describes local rotation
|
|
100
|
+
- Curvature R_{μν} = [G_μ, G_ν]
|
|
101
|
+
- Berry phase γ = ∮ G_μ dx^μ (parallel transport)
|
|
102
|
+
|
|
103
|
+
Relation to U3Frame:
|
|
104
|
+
- FourierFrame: GL(1,ℂ), one complex DOF
|
|
105
|
+
- U3Frame: U(3), nine real DOF
|
|
106
|
+
- U(3) ⊃ U(1) as global phase subgroup
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(self, base_coord=None, q_factor=1.0+0j):
|
|
110
|
+
"""
|
|
111
|
+
Initialize a FourierFrame.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
base_coord: base coordinate frame (coord3), or identity if None
|
|
115
|
+
q_factor: complex scale factor Q ∈ ℂ× = GL(1,ℂ)
|
|
116
|
+
Q = |Q| · e^{iθ}
|
|
117
|
+
- phase e^{iθ}: Fourier transform
|
|
118
|
+
- magnitude |Q|: conformal scaling
|
|
119
|
+
"""
|
|
125
120
|
if base_coord is None:
|
|
126
121
|
if coord3 is not None:
|
|
127
122
|
self.base = coord3.identity()
|
|
@@ -130,283 +125,282 @@ class FourierFrame:
|
|
|
130
125
|
else:
|
|
131
126
|
self.base = base_coord
|
|
132
127
|
|
|
133
|
-
self.Q = complex(q_factor) #
|
|
134
|
-
self.dim = 3
|
|
135
|
-
|
|
136
|
-
# --------------------
|
|
137
|
-
|
|
138
|
-
@property
|
|
139
|
-
def o(self):
|
|
140
|
-
"""
|
|
141
|
-
if self.base is None:
|
|
142
|
-
return None
|
|
143
|
-
o_base = self.base.o
|
|
144
|
-
#
|
|
145
|
-
return vec3(
|
|
146
|
-
o_base.x * self.Q.real + 1j * o_base.x * self.Q.imag,
|
|
147
|
-
o_base.y * self.Q.real + 1j * o_base.y * self.Q.imag,
|
|
148
|
-
o_base.z * self.Q.real + 1j * o_base.z * self.Q.imag
|
|
128
|
+
self.Q = complex(q_factor) # Ensure complex
|
|
129
|
+
self.dim = 3
|
|
130
|
+
|
|
131
|
+
# -------------------- Complex frame properties --------------------
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def o(self):
|
|
135
|
+
"""Complex position vector."""
|
|
136
|
+
if self.base is None:
|
|
137
|
+
return None
|
|
138
|
+
o_base = self.base.o
|
|
139
|
+
# Complex extension: real part is position, imaginary part encodes phase
|
|
140
|
+
return vec3(
|
|
141
|
+
o_base.x * self.Q.real + 1j * o_base.x * self.Q.imag,
|
|
142
|
+
o_base.y * self.Q.real + 1j * o_base.y * self.Q.imag,
|
|
143
|
+
o_base.z * self.Q.real + 1j * o_base.z * self.Q.imag
|
|
149
144
|
) if vec3 else None
|
|
150
145
|
|
|
151
|
-
@property
|
|
152
|
-
def s(self):
|
|
153
|
-
"""
|
|
154
|
-
if self.base is None:
|
|
155
|
-
return None
|
|
156
|
-
s_base = self.base.s
|
|
146
|
+
@property
|
|
147
|
+
def s(self):
|
|
148
|
+
"""Complex scale vector: s_Q = s_base · Q."""
|
|
149
|
+
if self.base is None:
|
|
150
|
+
return None
|
|
151
|
+
s_base = self.base.s
|
|
157
152
|
return vec3(
|
|
158
153
|
s_base.x * self.Q.real + 1j * s_base.x * self.Q.imag,
|
|
159
154
|
s_base.y * self.Q.real + 1j * s_base.y * self.Q.imag,
|
|
160
155
|
s_base.z * self.Q.real + 1j * s_base.z * self.Q.imag
|
|
161
156
|
) if vec3 else None
|
|
162
157
|
|
|
163
|
-
@property
|
|
164
|
-
def phase(self):
|
|
165
|
-
"""
|
|
166
|
-
return np.angle(self.Q)
|
|
167
|
-
|
|
168
|
-
@property
|
|
169
|
-
def magnitude(self):
|
|
170
|
-
"""
|
|
171
|
-
return np.abs(self.Q)
|
|
158
|
+
@property
|
|
159
|
+
def phase(self):
|
|
160
|
+
"""Phase arg(Q)."""
|
|
161
|
+
return np.angle(self.Q)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def magnitude(self):
|
|
165
|
+
"""Magnitude |Q|."""
|
|
166
|
+
return np.abs(self.Q)
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def det(self):
|
|
170
|
+
"""
|
|
171
|
+
Determinant: Det(Frame).
|
|
172
|
+
|
|
173
|
+
Used in the path-integral measure ∫ Dφ · Det[Frame] · exp(iS/ħ).
|
|
174
|
+
"""
|
|
175
|
+
if self.base is None:
|
|
176
|
+
return self.Q ** 3 # 3D frame
|
|
177
|
+
s = self.base.s
|
|
178
|
+
det_s = s.x * s.y * s.z
|
|
179
|
+
return det_s * (self.Q ** 3)
|
|
180
|
+
|
|
181
|
+
# -------------------- Frame operations --------------------
|
|
182
|
+
|
|
183
|
+
def __mul__(self, other):
|
|
184
|
+
"""
|
|
185
|
+
Frame multiplication: core transform operator.
|
|
186
|
+
|
|
187
|
+
Supports:
|
|
188
|
+
- Frame * complex: phase rotation / scaling (Fourier / conformal transform)
|
|
189
|
+
- Frame * Frame: frame composition
|
|
190
|
+
- Frame * vec3: vector transform
|
|
191
|
+
"""
|
|
192
|
+
if isinstance(other, (int, float, complex)):
|
|
193
|
+
# Scalar multiplication: Fourier / conformal transform
|
|
194
|
+
new_Q = self.Q * other
|
|
195
|
+
return FourierFrame(self.base, new_Q)
|
|
196
|
+
|
|
197
|
+
elif isinstance(other, FourierFrame):
|
|
198
|
+
# Frame composition
|
|
199
|
+
if self.base is not None and other.base is not None:
|
|
200
|
+
new_base = self.base * other.base
|
|
201
|
+
else:
|
|
202
|
+
new_base = self.base or other.base
|
|
203
|
+
new_Q = self.Q * other.Q
|
|
204
|
+
return FourierFrame(new_base, new_Q)
|
|
205
|
+
|
|
206
|
+
elif vec3 is not None and isinstance(other, vec3):
|
|
207
|
+
# Vector transform
|
|
208
|
+
return vec3(
|
|
209
|
+
other.x * self.Q.real,
|
|
210
|
+
other.y * self.Q.real,
|
|
211
|
+
other.z * self.Q.real
|
|
212
|
+
)
|
|
172
213
|
|
|
173
|
-
@property
|
|
174
|
-
def det(self):
|
|
175
|
-
"""
|
|
176
|
-
行列式: Det(Frame)
|
|
177
|
-
|
|
178
|
-
用于路径积分测度 ∫ Dφ · Det[Frame] · exp(iS/ħ)
|
|
179
|
-
"""
|
|
180
|
-
if self.base is None:
|
|
181
|
-
return self.Q ** 3 # 3维标架
|
|
182
|
-
s = self.base.s
|
|
183
|
-
det_s = s.x * s.y * s.z
|
|
184
|
-
return det_s * (self.Q ** 3)
|
|
185
|
-
|
|
186
|
-
# -------------------- 标架运算 --------------------
|
|
187
|
-
|
|
188
|
-
def __mul__(self, other):
|
|
189
|
-
"""
|
|
190
|
-
标架乘法 - 核心变换操作
|
|
191
|
-
|
|
192
|
-
支持:
|
|
193
|
-
- Frame * complex: 相位旋转/缩放 (傅里叶/共形变换)
|
|
194
|
-
- Frame * Frame: 标架复合 (路径积分复合)
|
|
195
|
-
- Frame * vec3: 向量变换
|
|
196
|
-
"""
|
|
197
|
-
if isinstance(other, (int, float, complex)):
|
|
198
|
-
# 标量乘法实现傅里叶/共形变换
|
|
199
|
-
new_Q = self.Q * other
|
|
200
|
-
return FourierFrame(self.base, new_Q)
|
|
201
|
-
|
|
202
|
-
elif isinstance(other, FourierFrame):
|
|
203
|
-
# 标架复合
|
|
204
|
-
if self.base is not None and other.base is not None:
|
|
205
|
-
new_base = self.base * other.base
|
|
206
|
-
else:
|
|
207
|
-
new_base = self.base or other.base
|
|
208
|
-
new_Q = self.Q * other.Q
|
|
209
|
-
return FourierFrame(new_base, new_Q)
|
|
210
|
-
|
|
211
|
-
elif vec3 is not None and isinstance(other, vec3):
|
|
212
|
-
# 向量变换
|
|
213
|
-
return vec3(
|
|
214
|
-
other.x * self.Q.real,
|
|
215
|
-
other.y * self.Q.real,
|
|
216
|
-
other.z * self.Q.real
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
return NotImplemented
|
|
220
|
-
|
|
221
|
-
def __rmul__(self, other):
|
|
222
|
-
"""右乘法"""
|
|
223
|
-
return self.__mul__(other)
|
|
224
|
-
|
|
225
|
-
def __truediv__(self, other):
|
|
226
|
-
"""标架除法 - 逆变换"""
|
|
227
|
-
if isinstance(other, (int, float, complex)):
|
|
228
|
-
return FourierFrame(self.base, self.Q / other)
|
|
229
|
-
elif isinstance(other, FourierFrame):
|
|
230
|
-
if self.base is not None and other.base is not None:
|
|
231
|
-
new_base = self.base / other.base
|
|
232
|
-
else:
|
|
233
|
-
new_base = self.base
|
|
234
|
-
return FourierFrame(new_base, self.Q / other.Q)
|
|
235
|
-
return NotImplemented
|
|
236
|
-
|
|
237
|
-
def __pow__(self, n):
|
|
238
|
-
"""幂运算: 对应多次变换"""
|
|
239
|
-
if isinstance(n, (int, float, complex)):
|
|
240
|
-
return FourierFrame(self.base, self.Q ** n)
|
|
241
214
|
return NotImplemented
|
|
242
215
|
|
|
243
|
-
def
|
|
244
|
-
"""
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
216
|
+
def __rmul__(self, other):
|
|
217
|
+
"""Right multiplication."""
|
|
218
|
+
return self.__mul__(other)
|
|
219
|
+
|
|
220
|
+
def __truediv__(self, other):
|
|
221
|
+
"""Frame division (inverse transform)."""
|
|
222
|
+
if isinstance(other, (int, float, complex)):
|
|
223
|
+
return FourierFrame(self.base, self.Q / other)
|
|
224
|
+
elif isinstance(other, FourierFrame):
|
|
225
|
+
if self.base is not None and other.base is not None:
|
|
226
|
+
new_base = self.base / other.base
|
|
227
|
+
else:
|
|
228
|
+
new_base = self.base
|
|
229
|
+
return FourierFrame(new_base, self.Q / other.Q)
|
|
230
|
+
return NotImplemented
|
|
231
|
+
|
|
232
|
+
def __pow__(self, n):
|
|
233
|
+
"""Power operator for repeated transforms."""
|
|
234
|
+
if isinstance(n, (int, float, complex)):
|
|
235
|
+
return FourierFrame(self.base, self.Q ** n)
|
|
236
|
+
return NotImplemented
|
|
237
|
+
|
|
238
|
+
def __eq__(self, other):
|
|
239
|
+
"""Equality check."""
|
|
240
|
+
if not isinstance(other, FourierFrame):
|
|
241
|
+
return False
|
|
242
|
+
return np.isclose(self.Q, other.Q)
|
|
248
243
|
|
|
249
244
|
def __repr__(self):
|
|
250
245
|
if self.base is not None:
|
|
251
246
|
return f"FourierFrame(Q={self.Q:.4f}, o={self.base.o})"
|
|
252
247
|
return f"FourierFrame(Q={self.Q:.4f})"
|
|
253
248
|
|
|
254
|
-
# --------------------
|
|
255
|
-
|
|
256
|
-
def fourier_transform(self, theta: float = np.pi/2) -> 'FourierFrame':
|
|
257
|
-
"""
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
Args:
|
|
261
|
-
theta:
|
|
262
|
-
|
|
263
|
-
Returns:
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
- F^4 = I (
|
|
268
|
-
- F^2 = P (
|
|
269
|
-
"""
|
|
270
|
-
ft_factor = np.exp(1j * theta)
|
|
271
|
-
return self * ft_factor
|
|
272
|
-
|
|
273
|
-
def inverse_fourier_transform(self, theta: float = np.pi/2) -> 'FourierFrame':
|
|
274
|
-
"""
|
|
275
|
-
return self.fourier_transform(-theta)
|
|
276
|
-
|
|
277
|
-
def conformal_transform(self, lambda_factor: float) -> 'FourierFrame':
|
|
278
|
-
"""
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
"""
|
|
283
|
-
return self * lambda_factor
|
|
284
|
-
|
|
285
|
-
# --------------------
|
|
286
|
-
|
|
287
|
-
def diffusion_evolution(self, t: float, kappa: float = 1.0) -> 'FourierFrame':
|
|
288
|
-
"""
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
- Δ
|
|
293
|
-
- t
|
|
294
|
-
- κ
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
FourierFrame(x, t) = e^{tκΔ} FourierFrame(x, 0)
|
|
298
|
-
= FourierFrame₀ · e^{-tκ|k|²}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
-
|
|
302
|
-
-
|
|
303
|
-
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
-
|
|
317
|
-
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
#
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
evolved_Q = self.Q * decay_factor
|
|
249
|
+
# -------------------- Fourier transform --------------------
|
|
250
|
+
|
|
251
|
+
def fourier_transform(self, theta: float = np.pi/2) -> 'FourierFrame':
|
|
252
|
+
"""
|
|
253
|
+
Fourier transform: F_θ[FourierFrame] = FourierFrame · e^{iθ}.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
theta: rotation angle, π/2 is the standard Fourier transform
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Transformed FourierFrame.
|
|
260
|
+
|
|
261
|
+
Properties:
|
|
262
|
+
- F^4 = I (four transforms return to identity)
|
|
263
|
+
- F^2 = P (parity)
|
|
264
|
+
"""
|
|
265
|
+
ft_factor = np.exp(1j * theta)
|
|
266
|
+
return self * ft_factor
|
|
267
|
+
|
|
268
|
+
def inverse_fourier_transform(self, theta: float = np.pi/2) -> 'FourierFrame':
|
|
269
|
+
"""Inverse Fourier transform: F^{-1} = F_{-θ}."""
|
|
270
|
+
return self.fourier_transform(-theta)
|
|
271
|
+
|
|
272
|
+
def conformal_transform(self, lambda_factor: float) -> 'FourierFrame':
|
|
273
|
+
"""
|
|
274
|
+
Conformal transform: FourierFrame → FourierFrame · λ, λ ∈ ℝ⁺.
|
|
275
|
+
|
|
276
|
+
Implements scaling while preserving angles.
|
|
277
|
+
"""
|
|
278
|
+
return self * lambda_factor
|
|
279
|
+
|
|
280
|
+
# -------------------- Classical geometric evolution --------------------
|
|
281
|
+
|
|
282
|
+
def diffusion_evolution(self, t: float, kappa: float = 1.0) -> 'FourierFrame':
|
|
283
|
+
"""
|
|
284
|
+
Diffusion evolution operator: e^{tΔ} FourierFrame.
|
|
285
|
+
|
|
286
|
+
This is the solution operator of the classical heat equation ∂u/∂t = κΔu:
|
|
287
|
+
- Δ is the Laplacian (geometric diffusion operator)
|
|
288
|
+
- t is time (real time, not imaginary)
|
|
289
|
+
- κ is the diffusion coefficient
|
|
290
|
+
|
|
291
|
+
Form:
|
|
292
|
+
FourierFrame(x, t) = e^{tκΔ} FourierFrame(x, 0)
|
|
293
|
+
= FourierFrame₀ · e^{-tκ|k|²} (momentum space)
|
|
294
|
+
|
|
295
|
+
Notes:
|
|
296
|
+
- Describes diffusion of geometric information over the manifold
|
|
297
|
+
- Heat kernel K(x,y,t) = (4πκt)^{-d/2} e^{-|x-y|²/(4κt)}
|
|
298
|
+
- Core tool in spectral geometry for extracting invariants
|
|
299
|
+
|
|
300
|
+
This is a classical diffusion process (not quantum imaginary-time evolution).
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
t: diffusion time (must be > 0)
|
|
304
|
+
kappa: diffusion coefficient (default 1.0)
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Diffused FourierFrame.
|
|
308
|
+
|
|
309
|
+
Limitations:
|
|
310
|
+
- Simplified: only the Q factor is diffused
|
|
311
|
+
- Full implementation requires a Laplacian on the frame field
|
|
312
|
+
- Numerical results depend on grid resolution
|
|
313
|
+
"""
|
|
314
|
+
if t < 0:
|
|
315
|
+
raise ValueError("Diffusion time t must be non-negative")
|
|
316
|
+
|
|
317
|
+
# Simplified model: decay Q factor (high-frequency suppression)
|
|
318
|
+
# Full implementation uses spectral representation of the frame field
|
|
319
|
+
decay_factor = np.exp(-kappa * t * np.abs(self.Q)**2)
|
|
320
|
+
evolved_Q = self.Q * decay_factor
|
|
327
321
|
|
|
328
322
|
return FourierFrame(self.base, evolved_Q)
|
|
329
323
|
|
|
330
324
|
@staticmethod
|
|
331
|
-
def laplacian_from_field(frame_field: List[List['FourierFrame']],
|
|
332
|
-
i: int, j: int) -> complex:
|
|
333
|
-
"""
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
Δ log FourierFrame ≈ (∇² log FourierFrame)
|
|
337
|
-
= ∂²/∂x² log Q + ∂²/∂y² log Q
|
|
338
|
-
|
|
339
|
-
Args:
|
|
340
|
-
frame_field:
|
|
341
|
-
i, j:
|
|
342
|
-
|
|
343
|
-
Returns:
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
Δf ≈ [f(i+1,j) + f(i-1,j) + f(i,j+1) + f(i,j-1) - 4f(i,j)] / h²
|
|
349
|
-
"""
|
|
350
|
-
ny, nx = len(frame_field), len(frame_field[0])
|
|
325
|
+
def laplacian_from_field(frame_field: List[List['FourierFrame']],
|
|
326
|
+
i: int, j: int) -> complex:
|
|
327
|
+
"""
|
|
328
|
+
Compute the Laplacian from a discrete frame field.
|
|
329
|
+
|
|
330
|
+
Δ log FourierFrame ≈ (∇² log FourierFrame)
|
|
331
|
+
= ∂²/∂x² log Q + ∂²/∂y² log Q
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
frame_field: discrete frame field
|
|
335
|
+
i, j: grid indices
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Laplacian result (complex).
|
|
339
|
+
|
|
340
|
+
Numerical details:
|
|
341
|
+
Use a 5-point stencil for second derivatives:
|
|
342
|
+
Δf ≈ [f(i+1,j) + f(i-1,j) + f(i,j+1) + f(i,j-1) - 4f(i,j)] / h²
|
|
343
|
+
"""
|
|
344
|
+
ny, nx = len(frame_field), len(frame_field[0])
|
|
351
345
|
|
|
352
346
|
if i <= 0 or i >= ny - 1 or j <= 0 or j >= nx - 1:
|
|
353
347
|
return 0.0 + 0j
|
|
354
348
|
|
|
355
|
-
#
|
|
356
|
-
log_Q_center = np.log(frame_field[i][j].Q)
|
|
357
|
-
|
|
358
|
-
#
|
|
359
|
-
log_Q_xp = np.log(frame_field[i][j+1].Q)
|
|
360
|
-
log_Q_xm = np.log(frame_field[i][j-1].Q)
|
|
361
|
-
log_Q_yp = np.log(frame_field[i+1][j].Q)
|
|
362
|
-
log_Q_ym = np.log(frame_field[i-1][j].Q)
|
|
363
|
-
|
|
364
|
-
#
|
|
365
|
-
laplacian = (log_Q_xp + log_Q_xm + log_Q_yp + log_Q_ym - 4*log_Q_center)
|
|
349
|
+
# Center point
|
|
350
|
+
log_Q_center = np.log(frame_field[i][j].Q)
|
|
351
|
+
|
|
352
|
+
# Four neighbors
|
|
353
|
+
log_Q_xp = np.log(frame_field[i][j+1].Q)
|
|
354
|
+
log_Q_xm = np.log(frame_field[i][j-1].Q)
|
|
355
|
+
log_Q_yp = np.log(frame_field[i+1][j].Q)
|
|
356
|
+
log_Q_ym = np.log(frame_field[i-1][j].Q)
|
|
357
|
+
|
|
358
|
+
# 5-point Laplacian stencil (assume grid spacing h=1)
|
|
359
|
+
laplacian = (log_Q_xp + log_Q_xm + log_Q_yp + log_Q_ym - 4*log_Q_center)
|
|
366
360
|
|
|
367
361
|
return laplacian
|
|
368
362
|
|
|
369
|
-
# --------------------
|
|
363
|
+
# -------------------- Spectral transform --------------------
|
|
370
364
|
|
|
371
365
|
@staticmethod
|
|
372
|
-
def spectral_transform_2d(field: np.ndarray, hbar: float = HBAR) -> np.ndarray:
|
|
373
|
-
"""
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
ψ̃(k) = ∫ e^{ikx/ħ} ψ(x) dx / √(2πħ)
|
|
378
|
-
|
|
379
|
-
Args:
|
|
380
|
-
field:
|
|
381
|
-
hbar:
|
|
382
|
-
|
|
383
|
-
Returns:
|
|
384
|
-
|
|
385
|
-
"""
|
|
386
|
-
#
|
|
387
|
-
if field.ndim >= 2:
|
|
388
|
-
axes = (0, 1)
|
|
389
|
-
else:
|
|
390
|
-
axes = None
|
|
391
|
-
|
|
392
|
-
spectrum = np.fft.fft2(field, axes=axes)
|
|
393
|
-
|
|
394
|
-
#
|
|
395
|
-
normalization = 1.0 / np.sqrt(2 * np.pi * hbar)
|
|
396
|
-
return spectrum * normalization
|
|
366
|
+
def spectral_transform_2d(field: np.ndarray, hbar: float = HBAR) -> np.ndarray:
|
|
367
|
+
"""
|
|
368
|
+
2D spectral transform: position space → momentum space.
|
|
369
|
+
|
|
370
|
+
Form:
|
|
371
|
+
ψ̃(k) = ∫ e^{ikx/ħ} ψ(x) dx / √(2πħ)
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
field: input field, shape [ny, nx, ...] or [ny, nx]
|
|
375
|
+
hbar: reduced Planck constant
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Momentum-space spectrum.
|
|
379
|
+
"""
|
|
380
|
+
# FFT axes
|
|
381
|
+
if field.ndim >= 2:
|
|
382
|
+
axes = (0, 1)
|
|
383
|
+
else:
|
|
384
|
+
axes = None
|
|
385
|
+
|
|
386
|
+
spectrum = np.fft.fft2(field, axes=axes)
|
|
387
|
+
|
|
388
|
+
# Quantum normalization
|
|
389
|
+
normalization = 1.0 / np.sqrt(2 * np.pi * hbar)
|
|
390
|
+
return spectrum * normalization
|
|
397
391
|
|
|
398
392
|
@staticmethod
|
|
399
|
-
def inverse_spectral_transform_2d(spectrum: np.ndarray, hbar: float = HBAR) -> np.ndarray:
|
|
400
|
-
"""
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
Args:
|
|
404
|
-
spectrum:
|
|
405
|
-
hbar:
|
|
406
|
-
|
|
407
|
-
Returns:
|
|
408
|
-
|
|
409
|
-
"""
|
|
393
|
+
def inverse_spectral_transform_2d(spectrum: np.ndarray, hbar: float = HBAR) -> np.ndarray:
|
|
394
|
+
"""
|
|
395
|
+
2D inverse spectral transform: momentum space → position space.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
spectrum: momentum-space spectrum
|
|
399
|
+
hbar: reduced Planck constant
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
Position-space field.
|
|
403
|
+
"""
|
|
410
404
|
if spectrum.ndim >= 2:
|
|
411
405
|
axes = (0, 1)
|
|
412
406
|
else:
|
|
@@ -416,10 +410,10 @@ class FourierFrame:
|
|
|
416
410
|
return np.fft.ifft2(spectrum * denormalization, axes=axes).real
|
|
417
411
|
|
|
418
412
|
@staticmethod
|
|
419
|
-
def spectral_transform_2d_gpu(field: np.ndarray, hbar: float = HBAR) -> np.ndarray:
|
|
420
|
-
"""GPU
|
|
421
|
-
if not GPU_AVAILABLE:
|
|
422
|
-
raise RuntimeError("CuPy
|
|
413
|
+
def spectral_transform_2d_gpu(field: np.ndarray, hbar: float = HBAR) -> np.ndarray:
|
|
414
|
+
"""GPU-accelerated 2D spectral transform."""
|
|
415
|
+
if not GPU_AVAILABLE:
|
|
416
|
+
raise RuntimeError("CuPy is unavailable; GPU acceleration not supported")
|
|
423
417
|
|
|
424
418
|
field_gpu = cp.asarray(field)
|
|
425
419
|
spectrum_gpu = cufft.fft2(field_gpu, axes=(0, 1))
|
|
@@ -428,41 +422,41 @@ class FourierFrame:
|
|
|
428
422
|
return cp.asnumpy(spectrum_gpu)
|
|
429
423
|
|
|
430
424
|
@classmethod
|
|
431
|
-
def from_coord_field(cls, coord_field: List[List], hbar: float = HBAR) -> 'FourierFrameSpectrum':
|
|
432
|
-
"""
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
Args:
|
|
438
|
-
coord_field:
|
|
439
|
-
hbar:
|
|
440
|
-
|
|
441
|
-
Returns:
|
|
442
|
-
FourierFrameSpectrum
|
|
443
|
-
"""
|
|
444
|
-
ny = len(coord_field)
|
|
445
|
-
nx = len(coord_field[0]) if ny > 0 else 0
|
|
446
|
-
|
|
447
|
-
#
|
|
448
|
-
tensor_field = np.zeros((ny, nx, 12), dtype=np.float64)
|
|
449
|
-
for i in range(ny):
|
|
450
|
-
for j in range(nx):
|
|
425
|
+
def from_coord_field(cls, coord_field: List[List], hbar: float = HBAR) -> 'FourierFrameSpectrum':
|
|
426
|
+
"""
|
|
427
|
+
Create a spectral representation from a coordinate field.
|
|
428
|
+
|
|
429
|
+
Applies spectral transforms to each component of the coordinate field.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
coord_field: 2D coordinate field list [[coord3, ...], ...]
|
|
433
|
+
hbar: reduced Planck constant
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
FourierFrameSpectrum object.
|
|
437
|
+
"""
|
|
438
|
+
ny = len(coord_field)
|
|
439
|
+
nx = len(coord_field[0]) if ny > 0 else 0
|
|
440
|
+
|
|
441
|
+
# Extract field components
|
|
442
|
+
tensor_field = np.zeros((ny, nx, 12), dtype=np.float64)
|
|
443
|
+
for i in range(ny):
|
|
444
|
+
for j in range(nx):
|
|
451
445
|
coord = coord_field[i][j]
|
|
452
446
|
tensor_field[i, j, 0:3] = [coord.o.x, coord.o.y, coord.o.z]
|
|
453
447
|
tensor_field[i, j, 3:6] = [coord.ux.x, coord.ux.y, coord.ux.z]
|
|
454
448
|
tensor_field[i, j, 6:9] = [coord.uy.x, coord.uy.y, coord.uy.z]
|
|
455
449
|
tensor_field[i, j, 9:12] = [coord.uz.x, coord.uz.y, coord.uz.z]
|
|
456
450
|
|
|
457
|
-
#
|
|
458
|
-
origin_spectrum = cls.spectral_transform_2d(tensor_field[..., 0:3], hbar)
|
|
459
|
-
ux_spectrum = cls.spectral_transform_2d(tensor_field[..., 3:6], hbar)
|
|
460
|
-
uy_spectrum = cls.spectral_transform_2d(tensor_field[..., 6:9], hbar)
|
|
461
|
-
uz_spectrum = cls.spectral_transform_2d(tensor_field[..., 9:12], hbar)
|
|
462
|
-
|
|
463
|
-
#
|
|
464
|
-
kx = 2 * np.pi * np.fft.fftfreq(nx) / hbar
|
|
465
|
-
ky = 2 * np.pi * np.fft.fftfreq(ny) / hbar
|
|
451
|
+
# Spectral transform per component
|
|
452
|
+
origin_spectrum = cls.spectral_transform_2d(tensor_field[..., 0:3], hbar)
|
|
453
|
+
ux_spectrum = cls.spectral_transform_2d(tensor_field[..., 3:6], hbar)
|
|
454
|
+
uy_spectrum = cls.spectral_transform_2d(tensor_field[..., 6:9], hbar)
|
|
455
|
+
uz_spectrum = cls.spectral_transform_2d(tensor_field[..., 9:12], hbar)
|
|
456
|
+
|
|
457
|
+
# Momentum grid
|
|
458
|
+
kx = 2 * np.pi * np.fft.fftfreq(nx) / hbar
|
|
459
|
+
ky = 2 * np.pi * np.fft.fftfreq(ny) / hbar
|
|
466
460
|
|
|
467
461
|
return FourierFrameSpectrum(
|
|
468
462
|
ux_spectrum=ux_spectrum,
|
|
@@ -475,89 +469,89 @@ class FourierFrame:
|
|
|
475
469
|
|
|
476
470
|
|
|
477
471
|
# ============================================================
|
|
478
|
-
#
|
|
472
|
+
# Intrinsic Gradient Operator
|
|
479
473
|
# ============================================================
|
|
480
474
|
|
|
481
|
-
class IntrinsicGradient:
|
|
482
|
-
"""
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
-
|
|
487
|
-
-
|
|
488
|
-
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
- δFourierFrame = G_μ FourierFrame δx^μ
|
|
492
|
-
- [G_μ, G_ν] = R_{μν}
|
|
493
|
-
- γ = ∮ G_μ dx^μ
|
|
494
|
-
- Δ = ∇² = ∂_μ ∂^μ
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
-
|
|
498
|
-
- Berry
|
|
499
|
-
-
|
|
500
|
-
"""
|
|
501
|
-
|
|
502
|
-
def __init__(self, frame_field: Union[Callable, List[List['FourierFrame']]]):
|
|
503
|
-
"""
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
Args:
|
|
507
|
-
frame_field:
|
|
508
|
-
"""
|
|
475
|
+
class IntrinsicGradient:
|
|
476
|
+
"""
|
|
477
|
+
Intrinsic gradient operator G_μ = d/dx^μ log FourierFrame(x).
|
|
478
|
+
|
|
479
|
+
Geometric meaning (classical spectral geometry):
|
|
480
|
+
- Describes the local rotation rate of the frame (logarithmic covariant derivative)
|
|
481
|
+
- Corresponds to the connection 1-form in Riemannian geometry
|
|
482
|
+
- Non-commutativity [G_μ, G_ν] yields curvature
|
|
483
|
+
|
|
484
|
+
Properties:
|
|
485
|
+
- δFourierFrame = G_μ FourierFrame δx^μ (infinitesimal transform)
|
|
486
|
+
- [G_μ, G_ν] = R_{μν} (curvature 2-form)
|
|
487
|
+
- γ = ∮ G_μ dx^μ (geometric phase / parallel transport)
|
|
488
|
+
- Δ = ∇² = ∂_μ ∂^μ (Laplacian)
|
|
489
|
+
|
|
490
|
+
Relation to quantum theory:
|
|
491
|
+
- Formally resembles a gauge connection, but here it is purely geometric
|
|
492
|
+
- Berry phase here is a classical geometric phase, not a quantum effect
|
|
493
|
+
- The framework stays within classical differential geometry
|
|
494
|
+
"""
|
|
495
|
+
|
|
496
|
+
def __init__(self, frame_field: Union[Callable, List[List['FourierFrame']]]):
|
|
497
|
+
"""
|
|
498
|
+
Initialize the intrinsic gradient operator.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
frame_field: frame field, either a function or a discrete field
|
|
502
|
+
"""
|
|
509
503
|
self.frame_field = frame_field
|
|
510
504
|
self.is_discrete = isinstance(frame_field, list)
|
|
511
505
|
|
|
512
|
-
def compute_at(self, position: Union[Tuple, int],
|
|
513
|
-
direction: int, delta: float = 1e-5) -> complex:
|
|
514
|
-
"""
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
Args:
|
|
518
|
-
position:
|
|
519
|
-
direction:
|
|
520
|
-
delta:
|
|
521
|
-
|
|
522
|
-
Returns:
|
|
523
|
-
G_
|
|
524
|
-
"""
|
|
506
|
+
def compute_at(self, position: Union[Tuple, int],
|
|
507
|
+
direction: int, delta: float = 1e-5) -> complex:
|
|
508
|
+
"""
|
|
509
|
+
Compute the intrinsic gradient at a given position and direction.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
position: coordinates (continuous) or indices (discrete)
|
|
513
|
+
direction: direction index (0=x, 1=y, 2=z)
|
|
514
|
+
delta: finite-difference step size
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Complex value of G_μ.
|
|
518
|
+
"""
|
|
525
519
|
if self.is_discrete:
|
|
526
520
|
return self._compute_discrete(position, direction)
|
|
527
521
|
else:
|
|
528
522
|
return self._compute_continuous(position, direction, delta)
|
|
529
523
|
|
|
530
|
-
def _compute_discrete(self, idx: Tuple[int, int], direction: int) -> complex:
|
|
531
|
-
"""
|
|
532
|
-
i, j = idx
|
|
533
|
-
ny, nx = len(self.frame_field), len(self.frame_field[0])
|
|
534
|
-
|
|
535
|
-
#
|
|
536
|
-
if direction == 0: # x
|
|
537
|
-
if j + 1 < nx and j - 1 >= 0:
|
|
538
|
-
frame_forward = self.frame_field[i][j + 1]
|
|
539
|
-
frame_backward = self.frame_field[i][j - 1]
|
|
540
|
-
frame_center = self.frame_field[i][j]
|
|
524
|
+
def _compute_discrete(self, idx: Tuple[int, int], direction: int) -> complex:
|
|
525
|
+
"""Intrinsic gradient for a discrete field."""
|
|
526
|
+
i, j = idx
|
|
527
|
+
ny, nx = len(self.frame_field), len(self.frame_field[0])
|
|
528
|
+
|
|
529
|
+
# Central difference
|
|
530
|
+
if direction == 0: # x direction
|
|
531
|
+
if j + 1 < nx and j - 1 >= 0:
|
|
532
|
+
frame_forward = self.frame_field[i][j + 1]
|
|
533
|
+
frame_backward = self.frame_field[i][j - 1]
|
|
534
|
+
frame_center = self.frame_field[i][j]
|
|
541
535
|
|
|
542
536
|
# G_x ≈ (log Q_{j+1} - log Q_{j-1}) / 2
|
|
543
537
|
return (np.log(frame_forward.Q) - np.log(frame_backward.Q)) / 2.0
|
|
544
538
|
|
|
545
|
-
elif direction == 1: # y
|
|
546
|
-
if i + 1 < ny and i - 1 >= 0:
|
|
547
|
-
frame_forward = self.frame_field[i + 1][j]
|
|
548
|
-
frame_backward = self.frame_field[i - 1][j]
|
|
539
|
+
elif direction == 1: # y direction
|
|
540
|
+
if i + 1 < ny and i - 1 >= 0:
|
|
541
|
+
frame_forward = self.frame_field[i + 1][j]
|
|
542
|
+
frame_backward = self.frame_field[i - 1][j]
|
|
549
543
|
|
|
550
544
|
return (np.log(frame_forward.Q) - np.log(frame_backward.Q)) / 2.0
|
|
551
545
|
|
|
552
546
|
return 0.0 + 0j
|
|
553
547
|
|
|
554
|
-
def _compute_continuous(self, pos: Tuple, direction: int, delta: float) -> complex:
|
|
555
|
-
"""
|
|
556
|
-
pos_list = list(pos)
|
|
557
|
-
|
|
558
|
-
#
|
|
559
|
-
pos_forward = pos_list.copy()
|
|
560
|
-
pos_backward = pos_list.copy()
|
|
548
|
+
def _compute_continuous(self, pos: Tuple, direction: int, delta: float) -> complex:
|
|
549
|
+
"""Intrinsic gradient for a continuous field."""
|
|
550
|
+
pos_list = list(pos)
|
|
551
|
+
|
|
552
|
+
# Forward and backward positions
|
|
553
|
+
pos_forward = pos_list.copy()
|
|
554
|
+
pos_backward = pos_list.copy()
|
|
561
555
|
pos_forward[direction] += delta
|
|
562
556
|
pos_backward[direction] -= delta
|
|
563
557
|
|
|
@@ -567,136 +561,136 @@ class IntrinsicGradient:
|
|
|
567
561
|
# G_μ = d/dx^μ log Frame
|
|
568
562
|
return (np.log(frame_forward.Q) - np.log(frame_backward.Q)) / (2 * delta)
|
|
569
563
|
|
|
570
|
-
def commutator(self, pos: Union[Tuple, int],
|
|
571
|
-
dir1: int, dir2: int, delta: float = 1e-5) -> complex:
|
|
572
|
-
"""
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
Args:
|
|
576
|
-
pos:
|
|
577
|
-
dir1, dir2:
|
|
578
|
-
delta:
|
|
579
|
-
|
|
580
|
-
Returns:
|
|
581
|
-
|
|
582
|
-
"""
|
|
583
|
-
G_mu = self.compute_at(pos, dir1, delta)
|
|
584
|
-
G_nu = self.compute_at(pos, dir2, delta)
|
|
585
|
-
|
|
586
|
-
#
|
|
587
|
-
#
|
|
588
|
-
#
|
|
589
|
-
return G_mu * G_nu - G_nu * G_mu
|
|
590
|
-
|
|
591
|
-
def laplacian(self, position: Union[Tuple[int, int], int]) -> complex:
|
|
592
|
-
"""
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
Δ log FourierFrame = ∂²/∂x² log Q + ∂²/∂y² log Q
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
Δφ_n = -λ_n φ_n
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
Args:
|
|
604
|
-
position:
|
|
605
|
-
|
|
606
|
-
Returns:
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
-
|
|
611
|
-
-
|
|
612
|
-
- "Can one hear the shape of a drum?" - Kac's
|
|
613
|
-
"""
|
|
614
|
-
if not self.is_discrete:
|
|
615
|
-
raise NotImplementedError("
|
|
616
|
-
|
|
617
|
-
if isinstance(position, int):
|
|
618
|
-
# 1D
|
|
619
|
-
return 0.0 + 0j
|
|
620
|
-
|
|
621
|
-
#
|
|
622
|
-
return FourierFrame.laplacian_from_field(self.frame_field, position[0], position[1])
|
|
564
|
+
def commutator(self, pos: Union[Tuple, int],
|
|
565
|
+
dir1: int, dir2: int, delta: float = 1e-5) -> complex:
|
|
566
|
+
"""
|
|
567
|
+
Compute the commutator [G_μ, G_ν] = curvature R_{μν}.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
pos: position
|
|
571
|
+
dir1, dir2: direction indices
|
|
572
|
+
delta: finite-difference step
|
|
573
|
+
|
|
574
|
+
Returns:
|
|
575
|
+
Curvature component R_{μν}.
|
|
576
|
+
"""
|
|
577
|
+
G_mu = self.compute_at(pos, dir1, delta)
|
|
578
|
+
G_nu = self.compute_at(pos, dir2, delta)
|
|
579
|
+
|
|
580
|
+
# Simplified: [G_μ, G_ν] ≈ G_μ G_ν - G_ν G_μ
|
|
581
|
+
# For Abelian case (scalar Q), the commutator is zero
|
|
582
|
+
# Full implementation must consider non-Abelian frame structure
|
|
583
|
+
return G_mu * G_nu - G_nu * G_mu
|
|
584
|
+
|
|
585
|
+
def laplacian(self, position: Union[Tuple[int, int], int]) -> complex:
|
|
586
|
+
"""
|
|
587
|
+
Laplacian Δ = ∇² = ∂²/∂x² + ∂²/∂y².
|
|
588
|
+
|
|
589
|
+
Applied to log FourierFrame:
|
|
590
|
+
Δ log FourierFrame = ∂²/∂x² log Q + ∂²/∂y² log Q
|
|
591
|
+
|
|
592
|
+
This is the core operator in spectral geometry, with eigenvalue problem:
|
|
593
|
+
Δφ_n = -λ_n φ_n
|
|
594
|
+
|
|
595
|
+
The spectrum encodes geometric information of the manifold.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
position: grid indices (i, j) or index
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
Laplacian result.
|
|
602
|
+
|
|
603
|
+
Background:
|
|
604
|
+
- The Laplacian is a natural differential operator on a Riemannian manifold
|
|
605
|
+
- Its spectrum {λ_n} contains geometric invariants (Weyl law, heat kernel expansion)
|
|
606
|
+
- "Can one hear the shape of a drum?" - Kac's question
|
|
607
|
+
"""
|
|
608
|
+
if not self.is_discrete:
|
|
609
|
+
raise NotImplementedError("Laplacian for continuous fields requires additional implementation")
|
|
610
|
+
|
|
611
|
+
if isinstance(position, int):
|
|
612
|
+
# 1D case (simplified)
|
|
613
|
+
return 0.0 + 0j
|
|
614
|
+
|
|
615
|
+
# Use FourierFrame static method
|
|
616
|
+
return FourierFrame.laplacian_from_field(self.frame_field, position[0], position[1])
|
|
623
617
|
|
|
624
618
|
|
|
625
619
|
# ============================================================
|
|
626
|
-
#
|
|
620
|
+
# Curvature from Frames
|
|
627
621
|
# ============================================================
|
|
628
622
|
|
|
629
|
-
class CurvatureFromFrame:
|
|
630
|
-
"""
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
- R_{μν} = [G_μ, G_ν] = ∂_μ G_ν - ∂_ν G_μ - [G_μ, G_ν]
|
|
635
|
-
-
|
|
636
|
-
-
|
|
637
|
-
"""
|
|
638
|
-
|
|
639
|
-
def __init__(self, frame_field: List[List['FourierFrame']]):
|
|
640
|
-
"""
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
Args:
|
|
644
|
-
frame_field:
|
|
645
|
-
"""
|
|
623
|
+
class CurvatureFromFrame:
|
|
624
|
+
"""
|
|
625
|
+
Compute curvature from a frame field.
|
|
626
|
+
|
|
627
|
+
Core formulas:
|
|
628
|
+
- R_{μν} = [G_μ, G_ν] = ∂_μ G_ν - ∂_ν G_μ - [G_μ, G_ν]
|
|
629
|
+
- Gaussian curvature K = -⟨[G_u, G_v] e_v, e_u⟩ / √det(g)
|
|
630
|
+
- Mean curvature H = (1/2) Tr(R)
|
|
631
|
+
"""
|
|
632
|
+
|
|
633
|
+
def __init__(self, frame_field: List[List['FourierFrame']]):
|
|
634
|
+
"""
|
|
635
|
+
Initialize curvature calculator.
|
|
636
|
+
|
|
637
|
+
Args:
|
|
638
|
+
frame_field: discrete frame field
|
|
639
|
+
"""
|
|
646
640
|
self.frame_field = frame_field
|
|
647
641
|
self.gradient_op = IntrinsicGradient(frame_field)
|
|
648
642
|
self.ny = len(frame_field)
|
|
649
643
|
self.nx = len(frame_field[0]) if self.ny > 0 else 0
|
|
650
644
|
|
|
651
|
-
def gaussian_curvature(self, i: int, j: int) -> float:
|
|
652
|
-
"""
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
K = -⟨[G_u, G_v] e_v, e_u⟩ / √det(g)
|
|
656
|
-
|
|
657
|
-
Args:
|
|
658
|
-
i, j:
|
|
659
|
-
|
|
660
|
-
Returns:
|
|
661
|
-
|
|
662
|
-
"""
|
|
663
|
-
#
|
|
664
|
-
R_xy = self.gradient_op.commutator((i, j), 0, 1)
|
|
665
|
-
|
|
666
|
-
#
|
|
667
|
-
#
|
|
668
|
-
return -R_xy.imag
|
|
669
|
-
|
|
670
|
-
def mean_curvature(self, i: int, j: int) -> float:
|
|
671
|
-
"""
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
H = (1/2) Tr(R)
|
|
675
|
-
|
|
676
|
-
Args:
|
|
677
|
-
i, j:
|
|
678
|
-
|
|
679
|
-
Returns:
|
|
680
|
-
|
|
681
|
-
"""
|
|
682
|
-
#
|
|
683
|
-
R_xx = self.gradient_op.commutator((i, j), 0, 0)
|
|
684
|
-
R_yy = self.gradient_op.commutator((i, j), 1, 1)
|
|
645
|
+
def gaussian_curvature(self, i: int, j: int) -> float:
|
|
646
|
+
"""
|
|
647
|
+
Compute Gaussian curvature.
|
|
648
|
+
|
|
649
|
+
K = -⟨[G_u, G_v] e_v, e_u⟩ / √det(g)
|
|
650
|
+
|
|
651
|
+
Args:
|
|
652
|
+
i, j: grid indices
|
|
653
|
+
|
|
654
|
+
Returns:
|
|
655
|
+
Gaussian curvature value.
|
|
656
|
+
"""
|
|
657
|
+
# Commutator [G_x, G_y]
|
|
658
|
+
R_xy = self.gradient_op.commutator((i, j), 0, 1)
|
|
659
|
+
|
|
660
|
+
# Simplified: K ≈ -Im(R_xy) for complex frames
|
|
661
|
+
# Full implementation should compute the metric tensor
|
|
662
|
+
return -R_xy.imag
|
|
663
|
+
|
|
664
|
+
def mean_curvature(self, i: int, j: int) -> float:
|
|
665
|
+
"""
|
|
666
|
+
Compute mean curvature.
|
|
667
|
+
|
|
668
|
+
H = (1/2) Tr(R)
|
|
669
|
+
|
|
670
|
+
Args:
|
|
671
|
+
i, j: grid indices
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
Mean curvature value.
|
|
675
|
+
"""
|
|
676
|
+
# Simplified implementation
|
|
677
|
+
R_xx = self.gradient_op.commutator((i, j), 0, 0)
|
|
678
|
+
R_yy = self.gradient_op.commutator((i, j), 1, 1)
|
|
685
679
|
|
|
686
680
|
return 0.5 * (R_xx.real + R_yy.real)
|
|
687
681
|
|
|
688
|
-
def riemann_curvature_tensor(self, i: int, j: int) -> np.ndarray:
|
|
689
|
-
"""
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
R_{ijkl} = -√det(g) ⟨[G_i, G_j] e_l, e_k⟩
|
|
693
|
-
|
|
694
|
-
Args:
|
|
695
|
-
i, j:
|
|
696
|
-
|
|
697
|
-
Returns:
|
|
698
|
-
|
|
699
|
-
"""
|
|
682
|
+
def riemann_curvature_tensor(self, i: int, j: int) -> np.ndarray:
|
|
683
|
+
"""
|
|
684
|
+
Compute the Riemann curvature tensor.
|
|
685
|
+
|
|
686
|
+
R_{ijkl} = -√det(g) ⟨[G_i, G_j] e_l, e_k⟩
|
|
687
|
+
|
|
688
|
+
Args:
|
|
689
|
+
i, j: grid indices
|
|
690
|
+
|
|
691
|
+
Returns:
|
|
692
|
+
Curvature tensor (2×2 matrix, simplified 2D case).
|
|
693
|
+
"""
|
|
700
694
|
R = np.zeros((2, 2), dtype=complex)
|
|
701
695
|
|
|
702
696
|
R[0, 0] = self.gradient_op.commutator((i, j), 0, 0)
|
|
@@ -707,45 +701,45 @@ class CurvatureFromFrame:
|
|
|
707
701
|
return R
|
|
708
702
|
|
|
709
703
|
|
|
710
|
-
# ============================================================
|
|
711
|
-
#
|
|
712
|
-
# ============================================================
|
|
713
|
-
|
|
714
|
-
class BerryPhase:
|
|
715
|
-
"""
|
|
716
|
-
Berry
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
γ = ∮_C G_μ dx^μ = ∮_C (∂_μ Frame · Frame^{-1}) dx^μ
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
-
|
|
723
|
-
-
|
|
724
|
-
-
|
|
725
|
-
"""
|
|
726
|
-
|
|
727
|
-
def __init__(self, gradient_operator: IntrinsicGradient):
|
|
728
|
-
"""
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
Args:
|
|
732
|
-
gradient_operator:
|
|
733
|
-
"""
|
|
734
|
-
self.gradient_op = gradient_operator
|
|
735
|
-
|
|
736
|
-
def compute_along_path(self, path: List[Tuple], closed: bool = True) -> complex:
|
|
737
|
-
"""
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
γ = ∮_C G_μ dx^μ
|
|
741
|
-
|
|
742
|
-
Args:
|
|
743
|
-
path:
|
|
744
|
-
closed:
|
|
745
|
-
|
|
746
|
-
Returns:
|
|
747
|
-
|
|
748
|
-
"""
|
|
704
|
+
# ============================================================
|
|
705
|
+
# Geometric Phase
|
|
706
|
+
# ============================================================
|
|
707
|
+
|
|
708
|
+
class BerryPhase:
|
|
709
|
+
"""
|
|
710
|
+
Berry phase computation.
|
|
711
|
+
|
|
712
|
+
Geometric phase formula:
|
|
713
|
+
γ = ∮_C G_μ dx^μ = ∮_C (∂_μ Frame · Frame^{-1}) dx^μ
|
|
714
|
+
|
|
715
|
+
Properties:
|
|
716
|
+
- Independent of path parametrization
|
|
717
|
+
- Depends only on the geometry of the path
|
|
718
|
+
- Equals surface integral of curvature (Stokes' theorem)
|
|
719
|
+
"""
|
|
720
|
+
|
|
721
|
+
def __init__(self, gradient_operator: IntrinsicGradient):
|
|
722
|
+
"""
|
|
723
|
+
Initialize the Berry phase calculator.
|
|
724
|
+
|
|
725
|
+
Args:
|
|
726
|
+
gradient_operator: intrinsic gradient operator
|
|
727
|
+
"""
|
|
728
|
+
self.gradient_op = gradient_operator
|
|
729
|
+
|
|
730
|
+
def compute_along_path(self, path: List[Tuple], closed: bool = True) -> complex:
|
|
731
|
+
"""
|
|
732
|
+
Compute Berry phase along a path.
|
|
733
|
+
|
|
734
|
+
γ = ∮_C G_μ dx^μ
|
|
735
|
+
|
|
736
|
+
Args:
|
|
737
|
+
path: list of path points [(i1, j1), (i2, j2), ...]
|
|
738
|
+
closed: whether to close the path
|
|
739
|
+
|
|
740
|
+
Returns:
|
|
741
|
+
Geometric phase (complex).
|
|
742
|
+
"""
|
|
749
743
|
if len(path) < 2:
|
|
750
744
|
return 0.0 + 0j
|
|
751
745
|
|
|
@@ -755,24 +749,24 @@ class BerryPhase:
|
|
|
755
749
|
pos_current = path[k]
|
|
756
750
|
pos_next = path[k + 1]
|
|
757
751
|
|
|
758
|
-
#
|
|
759
|
-
dx = pos_next[0] - pos_current[0]
|
|
760
|
-
dy = pos_next[1] - pos_current[1]
|
|
761
|
-
|
|
762
|
-
#
|
|
763
|
-
if abs(dx) > abs(dy):
|
|
764
|
-
direction = 0 # x
|
|
765
|
-
step = dx
|
|
766
|
-
else:
|
|
767
|
-
direction = 1 # y
|
|
768
|
-
step = dy
|
|
752
|
+
# Determine direction
|
|
753
|
+
dx = pos_next[0] - pos_current[0]
|
|
754
|
+
dy = pos_next[1] - pos_current[1]
|
|
755
|
+
|
|
756
|
+
# Compute gradient component
|
|
757
|
+
if abs(dx) > abs(dy):
|
|
758
|
+
direction = 0 # x direction
|
|
759
|
+
step = dx
|
|
760
|
+
else:
|
|
761
|
+
direction = 1 # y direction
|
|
762
|
+
step = dy
|
|
769
763
|
|
|
770
764
|
# G_μ dx^μ
|
|
771
765
|
G_mu = self.gradient_op.compute_at(pos_current, direction)
|
|
772
766
|
phase += G_mu * step
|
|
773
767
|
|
|
774
|
-
#
|
|
775
|
-
if closed and len(path) > 2:
|
|
768
|
+
# Close the path
|
|
769
|
+
if closed and len(path) > 2:
|
|
776
770
|
pos_last = path[-1]
|
|
777
771
|
pos_first = path[0]
|
|
778
772
|
|
|
@@ -792,157 +786,153 @@ class BerryPhase:
|
|
|
792
786
|
return phase
|
|
793
787
|
|
|
794
788
|
|
|
795
|
-
# ============================================================
|
|
796
|
-
#
|
|
797
|
-
# ============================================================
|
|
798
|
-
|
|
799
|
-
class ChernNumber:
|
|
800
|
-
"""
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
c₁ = (1/2π) ∬_M R_{μν} dS^{μν}
|
|
805
|
-
= (1/2π) ∬_M Tr([G_μ, G_ν]) dS^{μν}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
-
|
|
809
|
-
-
|
|
810
|
-
-
|
|
811
|
-
"""
|
|
812
|
-
|
|
813
|
-
def __init__(self, curvature_calculator: CurvatureFromFrame):
|
|
814
|
-
"""
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
Args:
|
|
818
|
-
curvature_calculator:
|
|
819
|
-
"""
|
|
820
|
-
self.curvature = curvature_calculator
|
|
821
|
-
|
|
822
|
-
def compute(self) -> float:
|
|
823
|
-
"""
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
c₁ = (1/2π) Σ_{ij} R_{xy}(i,j) ΔS
|
|
827
|
-
|
|
828
|
-
Returns:
|
|
829
|
-
|
|
830
|
-
"""
|
|
789
|
+
# ============================================================
|
|
790
|
+
# Chern Number
|
|
791
|
+
# ============================================================
|
|
792
|
+
|
|
793
|
+
class ChernNumber:
|
|
794
|
+
"""
|
|
795
|
+
First Chern number computation.
|
|
796
|
+
|
|
797
|
+
Topological invariant:
|
|
798
|
+
c₁ = (1/2π) ∬_M R_{μν} dS^{μν}
|
|
799
|
+
= (1/2π) ∬_M Tr([G_μ, G_ν]) dS^{μν}
|
|
800
|
+
|
|
801
|
+
Physical meaning:
|
|
802
|
+
- Characterizes the topology of the fiber bundle
|
|
803
|
+
- Topological invariant in the quantum Hall effect
|
|
804
|
+
- Related to Berry phase via Stokes' theorem
|
|
805
|
+
"""
|
|
806
|
+
|
|
807
|
+
def __init__(self, curvature_calculator: CurvatureFromFrame):
|
|
808
|
+
"""
|
|
809
|
+
Initialize the Chern number calculator.
|
|
810
|
+
|
|
811
|
+
Args:
|
|
812
|
+
curvature_calculator: curvature calculator
|
|
813
|
+
"""
|
|
814
|
+
self.curvature = curvature_calculator
|
|
815
|
+
|
|
816
|
+
def compute(self) -> float:
|
|
817
|
+
"""
|
|
818
|
+
Compute the first Chern number.
|
|
819
|
+
|
|
820
|
+
c₁ = (1/2π) Σ_{ij} R_{xy}(i,j) ΔS
|
|
821
|
+
|
|
822
|
+
Returns:
|
|
823
|
+
Chern number (real).
|
|
824
|
+
"""
|
|
831
825
|
total = 0.0 + 0j
|
|
832
826
|
|
|
833
827
|
ny, nx = self.curvature.ny, self.curvature.nx
|
|
834
828
|
|
|
835
829
|
for i in range(1, ny - 1):
|
|
836
830
|
for j in range(1, nx - 1):
|
|
837
|
-
#
|
|
838
|
-
R_xy = self.curvature.gradient_op.commutator((i, j), 0, 1)
|
|
839
|
-
total += R_xy
|
|
840
|
-
|
|
841
|
-
#
|
|
842
|
-
c1 = total / (2 * np.pi)
|
|
843
|
-
|
|
844
|
-
#
|
|
845
|
-
return round(c1.real)
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
# ============================================================
|
|
849
|
-
#
|
|
850
|
-
# ============================================================
|
|
851
|
-
|
|
852
|
-
class SpectralDecomposition:
|
|
853
|
-
"""
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
Δφ_n = -λ_n φ_n
|
|
859
|
-
∫ φ_m φ_n dV = δ_{mn}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
Δ = -Σ_n λ_n |φ_n⟩⟨φ_n|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
frame_field
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
self.
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
self.
|
|
904
|
-
self.
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
self.spectrum = frame_field
|
|
908
|
-
self.frame_field = None
|
|
909
|
-
self.gradient_op = None
|
|
910
|
-
self.ny, self.nx = frame_field.shape
|
|
831
|
+
# Curvature R_{xy}
|
|
832
|
+
R_xy = self.curvature.gradient_op.commutator((i, j), 0, 1)
|
|
833
|
+
total += R_xy
|
|
834
|
+
|
|
835
|
+
# Normalize
|
|
836
|
+
c1 = total / (2 * np.pi)
|
|
837
|
+
|
|
838
|
+
# Chern number should be an integer (topological invariant)
|
|
839
|
+
return round(c1.real)
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
# ============================================================
|
|
843
|
+
# Spectral Decomposition
|
|
844
|
+
# ============================================================
|
|
845
|
+
|
|
846
|
+
class SpectralDecomposition:
|
|
847
|
+
"""
|
|
848
|
+
Spectral decomposition of the Laplacian (classical spectral geometry).
|
|
849
|
+
|
|
850
|
+
Framework:
|
|
851
|
+
Laplacian eigenvalue problem:
|
|
852
|
+
Δφ_n = -λ_n φ_n
|
|
853
|
+
∫ φ_m φ_n dV = δ_{mn}
|
|
854
|
+
|
|
855
|
+
Spectral decomposition theorem:
|
|
856
|
+
Δ = -Σ_n λ_n |φ_n⟩⟨φ_n|
|
|
857
|
+
|
|
858
|
+
FourierFrame as eigenstate basis:
|
|
859
|
+
FourierFrame(x) = Σ_n c_n φ_n(x) FourierFrame_n
|
|
860
|
+
|
|
861
|
+
Where:
|
|
862
|
+
- φ_n(x) are scalar Laplacian eigenfunctions
|
|
863
|
+
- FourierFrame_n are frame eigenstates
|
|
864
|
+
- c_n = ⟨φ_n | FourierFrame⟩ are expansion coefficients
|
|
865
|
+
|
|
866
|
+
Geometric meaning:
|
|
867
|
+
- {λ_n} encodes geometry (ShapeDNA)
|
|
868
|
+
- low-frequency modes (small λ) capture large-scale features
|
|
869
|
+
- high-frequency modes (large λ) capture local detail
|
|
870
|
+
|
|
871
|
+
Weyl asymptotics:
|
|
872
|
+
N(λ) ~ (ω_d / (2π)^d) Vol(M) λ^{d/2}
|
|
873
|
+
|
|
874
|
+
Applications:
|
|
875
|
+
- ShapeDNA: spectral signature for shape recognition
|
|
876
|
+
- Spectral distance: geometric distance between manifolds
|
|
877
|
+
- Multiscale analysis: geometry in different frequency bands
|
|
878
|
+
- Heat kernel trace: Tr(e^{tΔ}) = Σ_n e^{-tλ_n}
|
|
879
|
+
"""
|
|
880
|
+
|
|
881
|
+
def __init__(self, frame_field: Union[List[List['FourierFrame']], 'FourierFrameSpectrum']):
|
|
882
|
+
"""
|
|
883
|
+
Initialize spectral decomposition.
|
|
884
|
+
|
|
885
|
+
Args:
|
|
886
|
+
frame_field: FourierFrame field or its spectral representation
|
|
887
|
+
"""
|
|
888
|
+
if isinstance(frame_field, list):
|
|
889
|
+
# Discrete frame field
|
|
890
|
+
self.frame_field = frame_field
|
|
891
|
+
self.gradient_op = IntrinsicGradient(frame_field)
|
|
892
|
+
self.ny = len(frame_field)
|
|
893
|
+
self.nx = len(frame_field[0]) if self.ny > 0 else 0
|
|
894
|
+
self.spectrum = None
|
|
895
|
+
else:
|
|
896
|
+
# Spectral representation
|
|
897
|
+
self.spectrum = frame_field
|
|
898
|
+
self.frame_field = None
|
|
899
|
+
self.gradient_op = None
|
|
900
|
+
self.ny, self.nx = frame_field.shape
|
|
911
901
|
|
|
912
902
|
self._eigenvalues = None
|
|
913
903
|
self._eigenvectors = None
|
|
914
904
|
|
|
915
|
-
def compute_eigenspectrum(self) -> Tuple[np.ndarray, Optional[np.ndarray]]:
|
|
916
|
-
"""
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
Returns:
|
|
920
|
-
(eigenvalues, eigenvectors)
|
|
921
|
-
- eigenvalues: λ_n
|
|
922
|
-
- eigenvectors:
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
"""
|
|
905
|
+
def compute_eigenspectrum(self) -> Tuple[np.ndarray, Optional[np.ndarray]]:
|
|
906
|
+
"""
|
|
907
|
+
Compute the Laplacian eigenspectrum.
|
|
908
|
+
|
|
909
|
+
Returns:
|
|
910
|
+
(eigenvalues, eigenvectors)
|
|
911
|
+
- eigenvalues: λ_n array (descending)
|
|
912
|
+
- eigenvectors: eigenfunctions φ_n (may be None in simplified mode)
|
|
913
|
+
|
|
914
|
+
Numerical notes:
|
|
915
|
+
- Spectral representation: eigenvalues correspond to k² = |k|²
|
|
916
|
+
- Discrete field: requires Laplacian matrix and numerical solve
|
|
917
|
+
"""
|
|
928
918
|
if self._eigenvalues is not None:
|
|
929
919
|
return self._eigenvalues, self._eigenvectors
|
|
930
920
|
|
|
931
|
-
if self.spectrum is not None:
|
|
932
|
-
#
|
|
933
|
-
kx, ky = self.spectrum.momentum_grid
|
|
934
|
-
k2 = kx[:, None]**2 + ky[None, :]**2
|
|
935
|
-
|
|
936
|
-
#
|
|
937
|
-
eigenvalues = np.sort(k2.flatten())[::-1]
|
|
921
|
+
if self.spectrum is not None:
|
|
922
|
+
# Spectral representation: eigenvalues = k²
|
|
923
|
+
kx, ky = self.spectrum.momentum_grid
|
|
924
|
+
k2 = kx[:, None]**2 + ky[None, :]**2
|
|
925
|
+
|
|
926
|
+
# Flatten and sort (descending)
|
|
927
|
+
eigenvalues = np.sort(k2.flatten())[::-1]
|
|
938
928
|
|
|
939
929
|
self._eigenvalues = eigenvalues
|
|
940
930
|
self._eigenvectors = None
|
|
941
931
|
|
|
942
|
-
elif self.frame_field is not None:
|
|
943
|
-
#
|
|
944
|
-
#
|
|
945
|
-
eigenvalues_list = []
|
|
932
|
+
elif self.frame_field is not None:
|
|
933
|
+
# Discrete field: approximate eigenvalues
|
|
934
|
+
# Full implementation needs Laplacian matrix and numerical solve
|
|
935
|
+
eigenvalues_list = []
|
|
946
936
|
|
|
947
937
|
for i in range(1, self.ny - 1):
|
|
948
938
|
for j in range(1, self.nx - 1):
|
|
@@ -955,52 +945,52 @@ class SpectralDecomposition:
|
|
|
955
945
|
|
|
956
946
|
return self._eigenvalues, self._eigenvectors
|
|
957
947
|
|
|
958
|
-
def expand_frame_in_eigenbasis(self, frame: 'FourierFrame',
|
|
959
|
-
n_modes: int = 10) -> np.ndarray:
|
|
960
|
-
"""
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
FourierFrame = Σ_n c_n FourierFrame_n
|
|
964
|
-
|
|
965
|
-
Args:
|
|
966
|
-
frame:
|
|
967
|
-
n_modes:
|
|
968
|
-
|
|
969
|
-
Returns:
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
c_n = ∫ φ_n*(x) FourierFrame(x) dV
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
"""
|
|
977
|
-
eigenvalues, _ = self.compute_eigenspectrum()
|
|
978
|
-
|
|
979
|
-
#
|
|
980
|
-
coefficients = np.zeros(n_modes, dtype=complex)
|
|
981
|
-
q_value = frame.Q
|
|
982
|
-
|
|
983
|
-
for n in range(min(n_modes, len(eigenvalues))):
|
|
984
|
-
#
|
|
985
|
-
if eigenvalues[n] > 0:
|
|
986
|
-
coefficients[n] = q_value / np.sqrt(eigenvalues[n])
|
|
948
|
+
def expand_frame_in_eigenbasis(self, frame: 'FourierFrame',
|
|
949
|
+
n_modes: int = 10) -> np.ndarray:
|
|
950
|
+
"""
|
|
951
|
+
Expand FourierFrame in the eigenbasis.
|
|
952
|
+
|
|
953
|
+
FourierFrame = Σ_n c_n FourierFrame_n
|
|
954
|
+
|
|
955
|
+
Args:
|
|
956
|
+
frame: FourierFrame to expand
|
|
957
|
+
n_modes: number of modes
|
|
958
|
+
|
|
959
|
+
Returns:
|
|
960
|
+
Expansion coefficients c_n.
|
|
961
|
+
|
|
962
|
+
Notes:
|
|
963
|
+
c_n = ∫ φ_n*(x) FourierFrame(x) dV
|
|
964
|
+
|
|
965
|
+
Simplified implementation uses Fourier coefficients as an approximation.
|
|
966
|
+
"""
|
|
967
|
+
eigenvalues, _ = self.compute_eigenspectrum()
|
|
968
|
+
|
|
969
|
+
# Simplified: use FourierFrame Q factor
|
|
970
|
+
coefficients = np.zeros(n_modes, dtype=complex)
|
|
971
|
+
q_value = frame.Q
|
|
972
|
+
|
|
973
|
+
for n in range(min(n_modes, len(eigenvalues))):
|
|
974
|
+
# Simplified projection: c_n ∝ Q / √λ_n
|
|
975
|
+
if eigenvalues[n] > 0:
|
|
976
|
+
coefficients[n] = q_value / np.sqrt(eigenvalues[n])
|
|
987
977
|
|
|
988
978
|
return coefficients
|
|
989
979
|
|
|
990
|
-
def reconstruct_from_modes(self, coefficients: np.ndarray,
|
|
991
|
-
base_frame: 'FourierFrame') -> 'FourierFrame':
|
|
992
|
-
"""
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
FourierFrame = Σ_n c_n FourierFrame_n
|
|
996
|
-
|
|
997
|
-
Args:
|
|
998
|
-
coefficients:
|
|
999
|
-
base_frame:
|
|
1000
|
-
|
|
1001
|
-
Returns:
|
|
1002
|
-
|
|
1003
|
-
"""
|
|
980
|
+
def reconstruct_from_modes(self, coefficients: np.ndarray,
|
|
981
|
+
base_frame: 'FourierFrame') -> 'FourierFrame':
|
|
982
|
+
"""
|
|
983
|
+
Reconstruct FourierFrame from eigenmodes.
|
|
984
|
+
|
|
985
|
+
FourierFrame = Σ_n c_n FourierFrame_n
|
|
986
|
+
|
|
987
|
+
Args:
|
|
988
|
+
coefficients: expansion coefficients
|
|
989
|
+
base_frame: base frame
|
|
990
|
+
|
|
991
|
+
Returns:
|
|
992
|
+
Reconstructed FourierFrame.
|
|
993
|
+
"""
|
|
1004
994
|
eigenvalues, _ = self.compute_eigenspectrum()
|
|
1005
995
|
|
|
1006
996
|
reconstructed_Q = 0.0 + 0j
|
|
@@ -1010,166 +1000,161 @@ class SpectralDecomposition:
|
|
|
1010
1000
|
|
|
1011
1001
|
return FourierFrame(base_frame.base, reconstructed_Q)
|
|
1012
1002
|
|
|
1013
|
-
def weyl_counting(self, lambda_threshold: float) -> int:
|
|
1014
|
-
"""
|
|
1015
|
-
Weyl
|
|
1016
|
-
|
|
1017
|
-
N(λ) = #{n : λ_n < λ}
|
|
1018
|
-
|
|
1019
|
-
Weyl
|
|
1020
|
-
N(λ) ~ (ω_d / (2π)^d) Vol(M) λ^{d/2} (λ → ∞)
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
Args:
|
|
1025
|
-
lambda_threshold:
|
|
1026
|
-
|
|
1027
|
-
Returns:
|
|
1028
|
-
N(λ) -
|
|
1029
|
-
"""
|
|
1003
|
+
def weyl_counting(self, lambda_threshold: float) -> int:
|
|
1004
|
+
"""
|
|
1005
|
+
Weyl asymptotic counting function.
|
|
1006
|
+
|
|
1007
|
+
N(λ) = #{n : λ_n < λ} (number of eigenvalues below λ)
|
|
1008
|
+
|
|
1009
|
+
Weyl law:
|
|
1010
|
+
N(λ) ~ (ω_d / (2π)^d) Vol(M) λ^{d/2} (λ → ∞)
|
|
1011
|
+
|
|
1012
|
+
For 2D manifolds: N(λ) ~ (1/4π) Area(M) λ
|
|
1013
|
+
|
|
1014
|
+
Args:
|
|
1015
|
+
lambda_threshold: eigenvalue threshold λ
|
|
1016
|
+
|
|
1017
|
+
Returns:
|
|
1018
|
+
N(λ) - eigenvalue count
|
|
1019
|
+
"""
|
|
1030
1020
|
eigenvalues, _ = self.compute_eigenspectrum()
|
|
1031
1021
|
return int(np.sum(eigenvalues < lambda_threshold))
|
|
1032
1022
|
|
|
1033
|
-
def shape_dna(self, n_modes: int = 50) -> np.ndarray:
|
|
1034
|
-
"""
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
"Can one hear the shape of a drum?" - Mark Kac (1966)
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
Args:
|
|
1047
|
-
n_modes:
|
|
1048
|
-
|
|
1049
|
-
Returns:
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
-
|
|
1054
|
-
-
|
|
1055
|
-
-
|
|
1056
|
-
"""
|
|
1023
|
+
def shape_dna(self, n_modes: int = 50) -> np.ndarray:
|
|
1024
|
+
"""
|
|
1025
|
+
ShapeDNA: spectral signature of a manifold.
|
|
1026
|
+
|
|
1027
|
+
The first n eigenvalues {λ_1, λ_2, ..., λ_n} encode geometric shape
|
|
1028
|
+
(up to isospectral non-isometries).
|
|
1029
|
+
|
|
1030
|
+
Classic question in spectral geometry:
|
|
1031
|
+
"Can one hear the shape of a drum?" - Mark Kac (1966)
|
|
1032
|
+
|
|
1033
|
+
For 2D manifolds, spectra often distinguish shapes, with known counterexamples
|
|
1034
|
+
(Gordon-Webb-Wolpert, 1992).
|
|
1035
|
+
|
|
1036
|
+
Args:
|
|
1037
|
+
n_modes: number of modes (default 50)
|
|
1038
|
+
|
|
1039
|
+
Returns:
|
|
1040
|
+
Spectral signature of the first n eigenvalues.
|
|
1041
|
+
|
|
1042
|
+
Applications:
|
|
1043
|
+
- Shape recognition and retrieval
|
|
1044
|
+
- Geometric similarity metrics
|
|
1045
|
+
- Topological invariant extraction
|
|
1046
|
+
"""
|
|
1057
1047
|
eigenvalues, _ = self.compute_eigenspectrum()
|
|
1058
1048
|
return eigenvalues[:n_modes]
|
|
1059
1049
|
|
|
1060
1050
|
|
|
1061
|
-
# ============================================================
|
|
1062
|
-
#
|
|
1063
|
-
# ============================================================
|
|
1064
|
-
|
|
1065
|
-
class HeatKernel:
|
|
1066
|
-
"""
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
∂u/∂t = Δu
|
|
1071
|
-
K(x, y, t)
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
K(x, y, t) = Σ_n e^{-λ_n t} φ_n(x) φ_n(y)
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
Tr(e^{tΔ}) ~ (4πt)^{-d/2} Σ_k a_k t^k
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
- a₀ = Vol(M)
|
|
1083
|
-
- a₁ ∝ ∫ R dV
|
|
1084
|
-
- a₂ ∝ ∫ (R² +
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
-
|
|
1098
|
-
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
frame_field
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
self.
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
self.
|
|
1120
|
-
self.
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
evolved_field = []
|
|
1153
|
-
for i in range(self.ny):
|
|
1154
|
-
row = []
|
|
1155
|
-
for j in range(self.nx):
|
|
1156
|
-
evolved_frame = self.frame_field[i][j].diffusion_evolution(t, kappa)
|
|
1051
|
+
# ============================================================
|
|
1052
|
+
# Heat Kernel
|
|
1053
|
+
# ============================================================
|
|
1054
|
+
|
|
1055
|
+
class HeatKernel:
|
|
1056
|
+
"""
|
|
1057
|
+
Heat kernel: fundamental solution of the heat equation (classical spectral geometry).
|
|
1058
|
+
|
|
1059
|
+
Definition:
|
|
1060
|
+
∂u/∂t = Δu (heat equation)
|
|
1061
|
+
K(x, y, t) satisfies ∂K/∂t = Δ_x K, with K(x,y,0) = δ(x-y)
|
|
1062
|
+
|
|
1063
|
+
Spectral expansion:
|
|
1064
|
+
K(x, y, t) = Σ_n e^{-λ_n t} φ_n(x) φ_n(y)
|
|
1065
|
+
|
|
1066
|
+
where {λ_n, φ_n} are eigenpairs of the Laplacian Δφ_n = -λ_n φ_n.
|
|
1067
|
+
|
|
1068
|
+
Heat trace asymptotics (Minakshisundaram-Pleijel):
|
|
1069
|
+
Tr(e^{tΔ}) ~ (4πt)^{-d/2} Σ_k a_k t^k
|
|
1070
|
+
|
|
1071
|
+
Heat kernel coefficients {a_k} encode geometric invariants:
|
|
1072
|
+
- a₀ = Vol(M) (volume)
|
|
1073
|
+
- a₁ ∝ ∫ R dV (curvature integral)
|
|
1074
|
+
- a₂ ∝ ∫ (R² + other invariants) dV
|
|
1075
|
+
|
|
1076
|
+
FourierFrame basis:
|
|
1077
|
+
FourierFrame(x, t) = ∫ K(x, y, t) FourierFrame(y, 0) dy
|
|
1078
|
+
= e^{tΔ} FourierFrame(x, 0)
|
|
1079
|
+
|
|
1080
|
+
Relation to quantum theory:
|
|
1081
|
+
- Formally analogous to imaginary-time propagators (Wick rotation)
|
|
1082
|
+
- Here it is purely geometric diffusion, not quantum evolution
|
|
1083
|
+
- No state vectors or operator expectations
|
|
1084
|
+
- Suitable for classical numerical computation
|
|
1085
|
+
|
|
1086
|
+
Applications:
|
|
1087
|
+
- Shape recognition (ShapeDNA)
|
|
1088
|
+
- Geometric invariant extraction
|
|
1089
|
+
- Multiscale geometric analysis
|
|
1090
|
+
- Spectral distance between manifolds
|
|
1091
|
+
"""
|
|
1092
|
+
|
|
1093
|
+
def __init__(self, frame_field: Union[List[List['FourierFrame']], 'FourierFrameSpectrum']):
|
|
1094
|
+
"""
|
|
1095
|
+
Initialize the heat kernel.
|
|
1096
|
+
|
|
1097
|
+
Args:
|
|
1098
|
+
frame_field: discrete FourierFrame field or its spectral representation
|
|
1099
|
+
"""
|
|
1100
|
+
if isinstance(frame_field, list):
|
|
1101
|
+
# Discrete frame field
|
|
1102
|
+
self.frame_field = frame_field
|
|
1103
|
+
self.gradient_op = IntrinsicGradient(frame_field)
|
|
1104
|
+
self.ny = len(frame_field)
|
|
1105
|
+
self.nx = len(frame_field[0]) if self.ny > 0 else 0
|
|
1106
|
+
self.spectrum = None
|
|
1107
|
+
else:
|
|
1108
|
+
# Spectral representation
|
|
1109
|
+
self.spectrum = frame_field
|
|
1110
|
+
self.frame_field = None
|
|
1111
|
+
self.gradient_op = None
|
|
1112
|
+
self.ny, self.nx = frame_field.shape
|
|
1113
|
+
|
|
1114
|
+
def evolution_operator(self, t: float, kappa: float = 1.0) -> Union[np.ndarray, List[List['FourierFrame']]]:
|
|
1115
|
+
"""
|
|
1116
|
+
Heat kernel evolution operator: e^{tκΔ} FourierFrame.
|
|
1117
|
+
|
|
1118
|
+
Computes the diffused frame field at time t.
|
|
1119
|
+
|
|
1120
|
+
Args:
|
|
1121
|
+
t: diffusion time (>0)
|
|
1122
|
+
kappa: diffusion coefficient (default 1.0)
|
|
1123
|
+
|
|
1124
|
+
Returns:
|
|
1125
|
+
Evolved frame field.
|
|
1126
|
+
|
|
1127
|
+
In frequency domain:
|
|
1128
|
+
FourierFrame(k, t) = e^{-κt|k|²} FourierFrame(k, 0)
|
|
1129
|
+
|
|
1130
|
+
This suppresses high frequencies (low-pass filtering), i.e., spatial smoothing.
|
|
1131
|
+
"""
|
|
1132
|
+
if t < 0:
|
|
1133
|
+
raise ValueError("Diffusion time must be non-negative")
|
|
1134
|
+
|
|
1135
|
+
if self.frame_field is not None:
|
|
1136
|
+
# Discrete field: pointwise evolution
|
|
1137
|
+
evolved_field = []
|
|
1138
|
+
for i in range(self.ny):
|
|
1139
|
+
row = []
|
|
1140
|
+
for j in range(self.nx):
|
|
1141
|
+
evolved_frame = self.frame_field[i][j].diffusion_evolution(t, kappa)
|
|
1157
1142
|
row.append(evolved_frame)
|
|
1158
1143
|
evolved_field.append(row)
|
|
1159
1144
|
return evolved_field
|
|
1160
1145
|
|
|
1161
|
-
elif self.spectrum is not None:
|
|
1162
|
-
#
|
|
1163
|
-
kx, ky = self.spectrum.momentum_grid
|
|
1164
|
-
k2 = kx[:, None]**2 + ky[None, :]**2
|
|
1165
|
-
|
|
1166
|
-
# e^{-κt k²}
|
|
1167
|
-
decay = np.exp(-kappa * t * k2)
|
|
1168
|
-
|
|
1169
|
-
#
|
|
1170
|
-
evolved_spectrum = FourierFrameSpectrum(
|
|
1171
|
-
ux_spectrum=self.spectrum.ux_spectrum * decay[..., None],
|
|
1172
|
-
uy_spectrum=self.spectrum.uy_spectrum * decay[..., None],
|
|
1146
|
+
elif self.spectrum is not None:
|
|
1147
|
+
# Spectral representation: frequency-domain decay
|
|
1148
|
+
kx, ky = self.spectrum.momentum_grid
|
|
1149
|
+
k2 = kx[:, None]**2 + ky[None, :]**2
|
|
1150
|
+
|
|
1151
|
+
# e^{-κt k²} decay factor
|
|
1152
|
+
decay = np.exp(-kappa * t * k2)
|
|
1153
|
+
|
|
1154
|
+
# Apply to each component
|
|
1155
|
+
evolved_spectrum = FourierFrameSpectrum(
|
|
1156
|
+
ux_spectrum=self.spectrum.ux_spectrum * decay[..., None],
|
|
1157
|
+
uy_spectrum=self.spectrum.uy_spectrum * decay[..., None],
|
|
1173
1158
|
uz_spectrum=self.spectrum.uz_spectrum * decay[..., None],
|
|
1174
1159
|
origin_spectrum=self.spectrum.origin_spectrum * decay[..., None],
|
|
1175
1160
|
momentum_grid=self.spectrum.momentum_grid,
|
|
@@ -1177,73 +1162,73 @@ class HeatKernel:
|
|
|
1177
1162
|
)
|
|
1178
1163
|
return evolved_spectrum
|
|
1179
1164
|
|
|
1180
|
-
else:
|
|
1181
|
-
raise ValueError("
|
|
1182
|
-
|
|
1183
|
-
def trace(self, t: float, kappa: float = 1.0) -> float:
|
|
1184
|
-
"""
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
Args:
|
|
1190
|
-
t:
|
|
1191
|
-
kappa:
|
|
1192
|
-
|
|
1193
|
-
Returns:
|
|
1194
|
-
|
|
1195
|
-
"""
|
|
1196
|
-
if self.spectrum is not None:
|
|
1197
|
-
#
|
|
1198
|
-
density = self.spectrum.spectral_density()
|
|
1199
|
-
kx, ky = self.spectrum.momentum_grid
|
|
1200
|
-
k2 = kx[:, None]**2 + ky[None, :]**2
|
|
1201
|
-
|
|
1202
|
-
# Tr(e^{-κt Δ}) = Σ e^{-κt k²}
|
|
1203
|
-
trace_val = np.sum(np.exp(-kappa * t * k2))
|
|
1204
|
-
return float(trace_val)
|
|
1205
|
-
|
|
1206
|
-
elif self.frame_field is not None:
|
|
1207
|
-
#
|
|
1208
|
-
total = 0.0
|
|
1209
|
-
for i in range(self.ny):
|
|
1210
|
-
for j in range(self.nx):
|
|
1211
|
-
laplacian = self.gradient_op.laplacian((i, j))
|
|
1212
|
-
# e^{-t λ} ≈ 1 - tλ
|
|
1213
|
-
total += np.exp(-kappa * t * abs(laplacian))
|
|
1214
|
-
return total
|
|
1165
|
+
else:
|
|
1166
|
+
raise ValueError("Provide a frame field or spectrum")
|
|
1167
|
+
|
|
1168
|
+
def trace(self, t: float, kappa: float = 1.0) -> float:
|
|
1169
|
+
"""
|
|
1170
|
+
Heat trace: Tr(e^{tκΔ}) = Σ_n e^{-κt λ_n}.
|
|
1171
|
+
|
|
1172
|
+
This aggregates spectral information of the manifold.
|
|
1173
|
+
|
|
1174
|
+
Args:
|
|
1175
|
+
t: diffusion time
|
|
1176
|
+
kappa: diffusion coefficient
|
|
1177
|
+
|
|
1178
|
+
Returns:
|
|
1179
|
+
Heat trace value.
|
|
1180
|
+
"""
|
|
1181
|
+
if self.spectrum is not None:
|
|
1182
|
+
# Use spectral representation
|
|
1183
|
+
density = self.spectrum.spectral_density()
|
|
1184
|
+
kx, ky = self.spectrum.momentum_grid
|
|
1185
|
+
k2 = kx[:, None]**2 + ky[None, :]**2
|
|
1186
|
+
|
|
1187
|
+
# Tr(e^{-κt Δ}) = Σ e^{-κt k²}
|
|
1188
|
+
trace_val = np.sum(np.exp(-kappa * t * k2))
|
|
1189
|
+
return float(trace_val)
|
|
1190
|
+
|
|
1191
|
+
elif self.frame_field is not None:
|
|
1192
|
+
# Simplified estimate: use frame-field "energy"
|
|
1193
|
+
total = 0.0
|
|
1194
|
+
for i in range(self.ny):
|
|
1195
|
+
for j in range(self.nx):
|
|
1196
|
+
laplacian = self.gradient_op.laplacian((i, j))
|
|
1197
|
+
# e^{-t λ} ≈ 1 - tλ (small-t approximation)
|
|
1198
|
+
total += np.exp(-kappa * t * abs(laplacian))
|
|
1199
|
+
return total
|
|
1215
1200
|
|
|
1216
1201
|
return 0.0
|
|
1217
1202
|
|
|
1218
|
-
def asymptotic_expansion(self, t: float, kappa: float = 1.0, order: int = 2) -> float:
|
|
1219
|
-
"""
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
Tr(e^{tκΔ}) ~ (4πκt)^{-d/2} [a₀ + a₁(κt) + a₂(κt)² + ...]
|
|
1223
|
-
|
|
1224
|
-
Args:
|
|
1225
|
-
t:
|
|
1226
|
-
kappa:
|
|
1227
|
-
order:
|
|
1228
|
-
|
|
1229
|
-
Returns:
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
- a₀ = Vol(M) -
|
|
1234
|
-
- a₁ ∝ ∫ R dV -
|
|
1235
|
-
- a₂ ∝ ∫ (R² - |Ric|² + ...) dV -
|
|
1236
|
-
"""
|
|
1237
|
-
d = 2 #
|
|
1238
|
-
prefactor = (4 * np.pi * kappa * t) ** (-d / 2)
|
|
1239
|
-
|
|
1240
|
-
#
|
|
1241
|
-
a0 = float(self.ny * self.nx) #
|
|
1242
|
-
|
|
1243
|
-
# a₁
|
|
1244
|
-
if self.gradient_op is not None:
|
|
1245
|
-
curvature_sum = 0.0
|
|
1246
|
-
for i in range(1, self.ny - 1):
|
|
1203
|
+
def asymptotic_expansion(self, t: float, kappa: float = 1.0, order: int = 2) -> float:
|
|
1204
|
+
"""
|
|
1205
|
+
Asymptotic expansion of heat trace (Minakshisundaram-Pleijel).
|
|
1206
|
+
|
|
1207
|
+
Tr(e^{tκΔ}) ~ (4πκt)^{-d/2} [a₀ + a₁(κt) + a₂(κt)² + ...]
|
|
1208
|
+
|
|
1209
|
+
Args:
|
|
1210
|
+
t: diffusion time (small-t asymptotic)
|
|
1211
|
+
kappa: diffusion coefficient
|
|
1212
|
+
order: expansion order (default 2)
|
|
1213
|
+
|
|
1214
|
+
Returns:
|
|
1215
|
+
Asymptotic estimate.
|
|
1216
|
+
|
|
1217
|
+
Geometric meaning:
|
|
1218
|
+
- a₀ = Vol(M) - manifold volume
|
|
1219
|
+
- a₁ ∝ ∫ R dV - scalar curvature integral
|
|
1220
|
+
- a₂ ∝ ∫ (R² - |Ric|² + ...) dV - higher-order invariants
|
|
1221
|
+
"""
|
|
1222
|
+
d = 2 # 2D manifold
|
|
1223
|
+
prefactor = (4 * np.pi * kappa * t) ** (-d / 2)
|
|
1224
|
+
|
|
1225
|
+
# Estimate heat kernel coefficients
|
|
1226
|
+
a0 = float(self.ny * self.nx) # volume (grid points)
|
|
1227
|
+
|
|
1228
|
+
# a₁ needs curvature information
|
|
1229
|
+
if self.gradient_op is not None:
|
|
1230
|
+
curvature_sum = 0.0
|
|
1231
|
+
for i in range(1, self.ny - 1):
|
|
1247
1232
|
for j in range(1, self.nx - 1):
|
|
1248
1233
|
R_xy = self.gradient_op.commutator((i, j), 0, 1)
|
|
1249
1234
|
curvature_sum += abs(R_xy)
|
|
@@ -1251,10 +1236,10 @@ class HeatKernel:
|
|
|
1251
1236
|
else:
|
|
1252
1237
|
a1 = 0.0
|
|
1253
1238
|
|
|
1254
|
-
a2 = 0.0 #
|
|
1255
|
-
|
|
1256
|
-
#
|
|
1257
|
-
expansion = a0
|
|
1239
|
+
a2 = 0.0 # Simplified
|
|
1240
|
+
|
|
1241
|
+
# Asymptotic series
|
|
1242
|
+
expansion = a0
|
|
1258
1243
|
if order >= 1:
|
|
1259
1244
|
expansion += a1 * (kappa * t)
|
|
1260
1245
|
if order >= 2:
|
|
@@ -1263,53 +1248,53 @@ class HeatKernel:
|
|
|
1263
1248
|
return prefactor * expansion
|
|
1264
1249
|
|
|
1265
1250
|
|
|
1266
|
-
# ============================================================
|
|
1267
|
-
#
|
|
1268
|
-
# ============================================================
|
|
1269
|
-
|
|
1270
|
-
class FrequencyProjection:
|
|
1271
|
-
"""
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
ω_n = √|κ_n| · sign(κ_n)
|
|
1275
|
-
P_Ω = Σ_{n: ω_n ∈ Ω} |Frame_n⟩⟨Frame_n|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
-
|
|
1279
|
-
-
|
|
1280
|
-
-
|
|
1281
|
-
"""
|
|
1282
|
-
|
|
1283
|
-
def __init__(self, spectral_decomposition: SpectralDecomposition):
|
|
1284
|
-
"""
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
Args:
|
|
1288
|
-
spectral_decomposition:
|
|
1289
|
-
"""
|
|
1290
|
-
self.spectral = spectral_decomposition
|
|
1291
|
-
|
|
1292
|
-
def compute_frequencies(self) -> np.ndarray:
|
|
1293
|
-
"""
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
Returns:
|
|
1297
|
-
|
|
1298
|
-
"""
|
|
1251
|
+
# ============================================================
|
|
1252
|
+
# Frequency Projection
|
|
1253
|
+
# ============================================================
|
|
1254
|
+
|
|
1255
|
+
class FrequencyProjection:
|
|
1256
|
+
"""
|
|
1257
|
+
Geometric frequency projection operator.
|
|
1258
|
+
|
|
1259
|
+
ω_n = √|κ_n| · sign(κ_n)
|
|
1260
|
+
P_Ω = Σ_{n: ω_n ∈ Ω} |Frame_n⟩⟨Frame_n|
|
|
1261
|
+
|
|
1262
|
+
Applications:
|
|
1263
|
+
- Band filtering
|
|
1264
|
+
- Multiscale analysis
|
|
1265
|
+
- Frequency-domain wavefunctions
|
|
1266
|
+
"""
|
|
1267
|
+
|
|
1268
|
+
def __init__(self, spectral_decomposition: SpectralDecomposition):
|
|
1269
|
+
"""
|
|
1270
|
+
Initialize frequency projection.
|
|
1271
|
+
|
|
1272
|
+
Args:
|
|
1273
|
+
spectral_decomposition: spectral decomposition
|
|
1274
|
+
"""
|
|
1275
|
+
self.spectral = spectral_decomposition
|
|
1276
|
+
|
|
1277
|
+
def compute_frequencies(self) -> np.ndarray:
|
|
1278
|
+
"""
|
|
1279
|
+
Compute geometric frequencies: ω_n = √|κ_n| · sign(κ_n).
|
|
1280
|
+
|
|
1281
|
+
Returns:
|
|
1282
|
+
Frequency array.
|
|
1283
|
+
"""
|
|
1299
1284
|
eigenvalues, _ = self.spectral.compute_eigenspectrum()
|
|
1300
1285
|
frequencies = np.sqrt(np.abs(eigenvalues)) * np.sign(eigenvalues)
|
|
1301
1286
|
return frequencies
|
|
1302
1287
|
|
|
1303
|
-
def project_to_band(self, omega_min: float, omega_max: float) -> 'FrequencyBandState':
|
|
1304
|
-
"""
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
Args:
|
|
1308
|
-
omega_min, omega_max:
|
|
1309
|
-
|
|
1310
|
-
Returns:
|
|
1311
|
-
|
|
1312
|
-
"""
|
|
1288
|
+
def project_to_band(self, omega_min: float, omega_max: float) -> 'FrequencyBandState':
|
|
1289
|
+
"""
|
|
1290
|
+
Project onto band [ω_min, ω_max].
|
|
1291
|
+
|
|
1292
|
+
Args:
|
|
1293
|
+
omega_min, omega_max: band limits
|
|
1294
|
+
|
|
1295
|
+
Returns:
|
|
1296
|
+
Band state.
|
|
1297
|
+
"""
|
|
1313
1298
|
frequencies = self.compute_frequencies()
|
|
1314
1299
|
mask = (frequencies >= omega_min) & (frequencies <= omega_max)
|
|
1315
1300
|
|
|
@@ -1322,91 +1307,90 @@ class FrequencyProjection:
|
|
|
1322
1307
|
)
|
|
1323
1308
|
|
|
1324
1309
|
|
|
1325
|
-
@dataclass
|
|
1326
|
-
class FrequencyBandState:
|
|
1327
|
-
"""
|
|
1310
|
+
@dataclass
|
|
1311
|
+
class FrequencyBandState:
|
|
1312
|
+
"""Band-limited wavefunction."""
|
|
1328
1313
|
frequency_range: Tuple[float, float]
|
|
1329
1314
|
mode_indices: np.ndarray
|
|
1330
1315
|
projection_operator: FrequencyProjection
|
|
1331
1316
|
|
|
1332
|
-
def wavefunction(self, amplitudes: np.ndarray, phases: np.ndarray) -> complex:
|
|
1333
|
-
"""
|
|
1334
|
-
Ψ_Ω = Σ_{n ∈ Ω} a_n Frame_n e^{iθ_n}
|
|
1335
|
-
|
|
1336
|
-
Args:
|
|
1337
|
-
amplitudes:
|
|
1338
|
-
phases:
|
|
1339
|
-
|
|
1340
|
-
Returns:
|
|
1341
|
-
|
|
1342
|
-
"""
|
|
1317
|
+
def wavefunction(self, amplitudes: np.ndarray, phases: np.ndarray) -> complex:
|
|
1318
|
+
"""
|
|
1319
|
+
Ψ_Ω = Σ_{n ∈ Ω} a_n Frame_n e^{iθ_n}
|
|
1320
|
+
|
|
1321
|
+
Args:
|
|
1322
|
+
amplitudes: amplitude array
|
|
1323
|
+
phases: phase array
|
|
1324
|
+
|
|
1325
|
+
Returns:
|
|
1326
|
+
Wavefunction value.
|
|
1327
|
+
"""
|
|
1343
1328
|
psi = 0.0 + 0j
|
|
1344
1329
|
for idx, amp, phase in zip(self.mode_indices, amplitudes, phases):
|
|
1345
1330
|
psi += amp * np.exp(1j * phase)
|
|
1346
1331
|
return psi
|
|
1347
1332
|
|
|
1348
1333
|
|
|
1349
|
-
# ============================================================
|
|
1350
|
-
#
|
|
1351
|
-
#
|
|
1352
|
-
# ============================================================
|
|
1334
|
+
# ============================================================
|
|
1335
|
+
# Spectral data structures
|
|
1336
|
+
# ============================================================
|
|
1353
1337
|
|
|
1354
1338
|
@dataclass
|
|
1355
|
-
class FourierFrameSpectrum:
|
|
1356
|
-
"""
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
"""
|
|
1361
|
-
ux_spectrum: np.ndarray # x
|
|
1362
|
-
uy_spectrum: np.ndarray # y
|
|
1363
|
-
uz_spectrum: np.ndarray # z
|
|
1364
|
-
origin_spectrum: np.ndarray #
|
|
1365
|
-
momentum_grid: Tuple[np.ndarray, np.ndarray] # (kx, ky)
|
|
1366
|
-
hbar: float = HBAR
|
|
1367
|
-
|
|
1368
|
-
def __post_init__(self):
|
|
1369
|
-
"""
|
|
1370
|
-
shapes = [
|
|
1371
|
-
self.ux_spectrum.shape,
|
|
1372
|
-
self.uy_spectrum.shape,
|
|
1373
|
-
self.uz_spectrum.shape,
|
|
1374
|
-
self.origin_spectrum.shape
|
|
1375
|
-
]
|
|
1376
|
-
if not all(s == shapes[0] for s in shapes):
|
|
1377
|
-
raise ValueError("
|
|
1339
|
+
class FourierFrameSpectrum:
|
|
1340
|
+
"""
|
|
1341
|
+
FourierFrame spectrum: coordinate field in momentum space.
|
|
1342
|
+
|
|
1343
|
+
Stores Fourier spectra of each coordinate component.
|
|
1344
|
+
"""
|
|
1345
|
+
ux_spectrum: np.ndarray # x-axis basis spectrum
|
|
1346
|
+
uy_spectrum: np.ndarray # y-axis basis spectrum
|
|
1347
|
+
uz_spectrum: np.ndarray # z-axis basis spectrum
|
|
1348
|
+
origin_spectrum: np.ndarray # origin position spectrum
|
|
1349
|
+
momentum_grid: Tuple[np.ndarray, np.ndarray] # (kx, ky)
|
|
1350
|
+
hbar: float = HBAR
|
|
1351
|
+
|
|
1352
|
+
def __post_init__(self):
|
|
1353
|
+
"""Validate dimension consistency."""
|
|
1354
|
+
shapes = [
|
|
1355
|
+
self.ux_spectrum.shape,
|
|
1356
|
+
self.uy_spectrum.shape,
|
|
1357
|
+
self.uz_spectrum.shape,
|
|
1358
|
+
self.origin_spectrum.shape
|
|
1359
|
+
]
|
|
1360
|
+
if not all(s == shapes[0] for s in shapes):
|
|
1361
|
+
raise ValueError("All spectral components must have the same shape")
|
|
1378
1362
|
|
|
1379
1363
|
@property
|
|
1380
|
-
def shape(self) -> Tuple[int, int]:
|
|
1381
|
-
"""
|
|
1382
|
-
return self.ux_spectrum.shape[:2]
|
|
1364
|
+
def shape(self) -> Tuple[int, int]:
|
|
1365
|
+
"""Spatial shape of the spectrum."""
|
|
1366
|
+
return self.ux_spectrum.shape[:2]
|
|
1383
1367
|
|
|
1384
|
-
def total_energy(self) -> float:
|
|
1385
|
-
"""
|
|
1386
|
-
return float(
|
|
1368
|
+
def total_energy(self) -> float:
|
|
1369
|
+
"""Total energy E = ∫ |ψ̃(k)|² dk."""
|
|
1370
|
+
return float(
|
|
1387
1371
|
np.sum(np.abs(self.ux_spectrum)**2) +
|
|
1388
1372
|
np.sum(np.abs(self.uy_spectrum)**2) +
|
|
1389
1373
|
np.sum(np.abs(self.uz_spectrum)**2) +
|
|
1390
1374
|
np.sum(np.abs(self.origin_spectrum)**2)
|
|
1391
1375
|
)
|
|
1392
1376
|
|
|
1393
|
-
def spectral_density(self) -> np.ndarray:
|
|
1394
|
-
"""
|
|
1395
|
-
density = (
|
|
1396
|
-
np.abs(self.ux_spectrum)**2 +
|
|
1397
|
-
np.abs(self.uy_spectrum)**2 +
|
|
1398
|
-
np.abs(self.uz_spectrum)**2 +
|
|
1399
|
-
np.abs(self.origin_spectrum)**2
|
|
1400
|
-
)
|
|
1401
|
-
return np.mean(density, axis=-1) if density.ndim > 2 else density
|
|
1402
|
-
|
|
1403
|
-
def radial_average(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
1404
|
-
"""
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
Returns:
|
|
1408
|
-
(k_bins, radial_spectrum)
|
|
1409
|
-
"""
|
|
1377
|
+
def spectral_density(self) -> np.ndarray:
|
|
1378
|
+
"""Spectral density ρ(k) = Σ_μ |ψ̃_μ(k)|²."""
|
|
1379
|
+
density = (
|
|
1380
|
+
np.abs(self.ux_spectrum)**2 +
|
|
1381
|
+
np.abs(self.uy_spectrum)**2 +
|
|
1382
|
+
np.abs(self.uz_spectrum)**2 +
|
|
1383
|
+
np.abs(self.origin_spectrum)**2
|
|
1384
|
+
)
|
|
1385
|
+
return np.mean(density, axis=-1) if density.ndim > 2 else density
|
|
1386
|
+
|
|
1387
|
+
def radial_average(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
1388
|
+
"""
|
|
1389
|
+
Radial average spectrum (ShapeDNA).
|
|
1390
|
+
|
|
1391
|
+
Returns:
|
|
1392
|
+
(k_bins, radial_spectrum)
|
|
1393
|
+
"""
|
|
1410
1394
|
kx, ky = self.momentum_grid
|
|
1411
1395
|
k_mag = np.sqrt(kx[:, None]**2 + ky[None, :]**2)
|
|
1412
1396
|
|
|
@@ -1423,13 +1407,13 @@ class FourierFrameSpectrum:
|
|
|
1423
1407
|
|
|
1424
1408
|
return k_bins, radial_avg
|
|
1425
1409
|
|
|
1426
|
-
def to_coord_field(self) -> List[List]:
|
|
1427
|
-
"""
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
Returns:
|
|
1431
|
-
|
|
1432
|
-
"""
|
|
1410
|
+
def to_coord_field(self) -> List[List]:
|
|
1411
|
+
"""
|
|
1412
|
+
Inverse transform: spectrum → coordinate field.
|
|
1413
|
+
|
|
1414
|
+
Returns:
|
|
1415
|
+
2D coordinate field list.
|
|
1416
|
+
"""
|
|
1433
1417
|
ny, nx = self.shape
|
|
1434
1418
|
|
|
1435
1419
|
origin_field = Frame.inverse_spectral_transform_2d(self.origin_spectrum, self.hbar)
|
|
@@ -1454,82 +1438,82 @@ class FourierFrameSpectrum:
|
|
|
1454
1438
|
return coord_field
|
|
1455
1439
|
|
|
1456
1440
|
|
|
1457
|
-
# ============================================================
|
|
1458
|
-
#
|
|
1459
|
-
# ============================================================
|
|
1460
|
-
|
|
1461
|
-
def spectral_transform(coord_field: List[List],
|
|
1462
|
-
hbar: float = HBAR,
|
|
1463
|
-
use_gpu: bool = False) -> FourierFrameSpectrum:
|
|
1464
|
-
"""
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
Args:
|
|
1468
|
-
coord_field:
|
|
1469
|
-
hbar:
|
|
1470
|
-
use_gpu:
|
|
1471
|
-
|
|
1472
|
-
Returns:
|
|
1473
|
-
FourierFrameSpectrum
|
|
1474
|
-
"""
|
|
1441
|
+
# ============================================================
|
|
1442
|
+
# Convenience functions
|
|
1443
|
+
# ============================================================
|
|
1444
|
+
|
|
1445
|
+
def spectral_transform(coord_field: List[List],
|
|
1446
|
+
hbar: float = HBAR,
|
|
1447
|
+
use_gpu: bool = False) -> FourierFrameSpectrum:
|
|
1448
|
+
"""
|
|
1449
|
+
Coordinate field spectral transform.
|
|
1450
|
+
|
|
1451
|
+
Args:
|
|
1452
|
+
coord_field: 2D coordinate field
|
|
1453
|
+
hbar: reduced Planck constant
|
|
1454
|
+
use_gpu: whether to use GPU acceleration
|
|
1455
|
+
|
|
1456
|
+
Returns:
|
|
1457
|
+
FourierFrameSpectrum object.
|
|
1458
|
+
"""
|
|
1475
1459
|
return FourierFrame.from_coord_field(coord_field, hbar)
|
|
1476
1460
|
|
|
1477
1461
|
|
|
1478
|
-
def inverse_spectral_transform(spectrum: FourierFrameSpectrum) -> List[List]:
|
|
1479
|
-
"""
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
Args:
|
|
1483
|
-
spectrum: FourierFrameSpectrum
|
|
1484
|
-
|
|
1485
|
-
Returns:
|
|
1486
|
-
|
|
1487
|
-
"""
|
|
1462
|
+
def inverse_spectral_transform(spectrum: FourierFrameSpectrum) -> List[List]:
|
|
1463
|
+
"""
|
|
1464
|
+
Inverse spectral transform.
|
|
1465
|
+
|
|
1466
|
+
Args:
|
|
1467
|
+
spectrum: FourierFrameSpectrum object
|
|
1468
|
+
|
|
1469
|
+
Returns:
|
|
1470
|
+
Reconstructed coordinate field.
|
|
1471
|
+
"""
|
|
1488
1472
|
return spectrum.to_coord_field()
|
|
1489
1473
|
|
|
1490
1474
|
|
|
1491
|
-
# ============================================================
|
|
1492
|
-
#
|
|
1493
|
-
# ============================================================
|
|
1475
|
+
# ============================================================
|
|
1476
|
+
# Demonstration
|
|
1477
|
+
# ============================================================
|
|
1494
1478
|
|
|
1495
|
-
def demonstrate():
|
|
1496
|
-
"""
|
|
1479
|
+
def demonstrate():
|
|
1480
|
+
"""Demonstrate FourierFrame algebra and spectral geometry."""
|
|
1497
1481
|
print("=" * 70)
|
|
1498
|
-
print("
|
|
1482
|
+
print("FourierFrame Field & Spectral Geometry")
|
|
1499
1483
|
print("=" * 70)
|
|
1500
1484
|
|
|
1501
|
-
# 1.
|
|
1485
|
+
# 1. Create base FourierFrame
|
|
1502
1486
|
if coord3 is not None:
|
|
1503
1487
|
base_frame = coord3.from_position(vec3(1, 0, 0))
|
|
1504
1488
|
frame = FourierFrame(base_frame, q_factor=1.0+0.5j)
|
|
1505
1489
|
else:
|
|
1506
1490
|
frame = FourierFrame(q_factor=1.0+0.5j)
|
|
1507
1491
|
|
|
1508
|
-
print(f"\n1.
|
|
1509
|
-
print(f"
|
|
1510
|
-
print(f"
|
|
1511
|
-
print(f"
|
|
1492
|
+
print(f"\n1. Base FourierFrame: {frame}")
|
|
1493
|
+
print(f" Phase: {frame.phase:.4f} rad")
|
|
1494
|
+
print(f" Magnitude: {frame.magnitude:.4f}")
|
|
1495
|
+
print(f" Determinant: {frame.det:.4f}")
|
|
1512
1496
|
|
|
1513
|
-
# 2.
|
|
1514
|
-
print(
|
|
1497
|
+
# 2. Fourier transform
|
|
1498
|
+
print("\n2. Fourier transform:")
|
|
1515
1499
|
ft = frame.fourier_transform()
|
|
1516
1500
|
print(f" F[FourierFrame] = {ft}")
|
|
1517
1501
|
print(f" F^4[FourierFrame] ≈ FourierFrame: {frame.fourier_transform(2*np.pi)}")
|
|
1518
1502
|
|
|
1519
|
-
# 3.
|
|
1520
|
-
print(
|
|
1503
|
+
# 3. Conformal transform
|
|
1504
|
+
print("\n3. Conformal transform:")
|
|
1521
1505
|
conf = frame.conformal_transform(2.0)
|
|
1522
1506
|
print(f" λ=2: {conf}")
|
|
1523
1507
|
|
|
1524
|
-
# 4.
|
|
1525
|
-
print(
|
|
1508
|
+
# 4. Frame composition
|
|
1509
|
+
print("\n4. Frame composition:")
|
|
1526
1510
|
frame2 = FourierFrame(q_factor=0.5+0.5j)
|
|
1527
1511
|
composed = frame * frame2
|
|
1528
1512
|
print(f" FourierFrame1 * FourierFrame2 = {composed}")
|
|
1529
1513
|
|
|
1530
|
-
# 5.
|
|
1531
|
-
print(
|
|
1532
|
-
#
|
|
1514
|
+
# 5. Intrinsic gradient operator
|
|
1515
|
+
print("\n5. Intrinsic gradient operator:")
|
|
1516
|
+
# Create a simple frame field
|
|
1533
1517
|
frame_field = [[FourierFrame(q_factor=1.0 + 0.1j*(i+j)) for j in range(5)] for i in range(5)]
|
|
1534
1518
|
grad_op = IntrinsicGradient(frame_field)
|
|
1535
1519
|
G_x = grad_op.compute_at((2, 2), 0)
|
|
@@ -1537,48 +1521,48 @@ def demonstrate():
|
|
|
1537
1521
|
print(f" G_x(2,2) = {G_x:.4f}")
|
|
1538
1522
|
print(f" G_y(2,2) = {G_y:.4f}")
|
|
1539
1523
|
|
|
1540
|
-
# 6.
|
|
1541
|
-
print(
|
|
1524
|
+
# 6. Curvature computation
|
|
1525
|
+
print("\n6. Curvature computation:")
|
|
1542
1526
|
curvature_calc = CurvatureFromFrame(frame_field)
|
|
1543
1527
|
K = curvature_calc.gaussian_curvature(2, 2)
|
|
1544
1528
|
H = curvature_calc.mean_curvature(2, 2)
|
|
1545
|
-
print(f"
|
|
1546
|
-
print(f"
|
|
1529
|
+
print(f" Gaussian curvature K = {K:.6f}")
|
|
1530
|
+
print(f" Mean curvature H = {H:.6f}")
|
|
1547
1531
|
|
|
1548
|
-
# 7. Berry
|
|
1549
|
-
print(
|
|
1532
|
+
# 7. Berry phase
|
|
1533
|
+
print("\n7. Berry phase:")
|
|
1550
1534
|
berry = BerryPhase(grad_op)
|
|
1551
1535
|
path = [(1, 1), (1, 3), (3, 3), (3, 1), (1, 1)]
|
|
1552
1536
|
gamma = berry.compute_along_path(path, closed=True)
|
|
1553
1537
|
print(f" γ = ∮ G_μ dx^μ = {gamma:.4f}")
|
|
1554
1538
|
|
|
1555
|
-
# 8.
|
|
1556
|
-
print(
|
|
1539
|
+
# 8. Chern number
|
|
1540
|
+
print("\n8. Chern number:")
|
|
1557
1541
|
chern = ChernNumber(curvature_calc)
|
|
1558
1542
|
c1 = chern.compute()
|
|
1559
|
-
print(f"
|
|
1543
|
+
print(f" First Chern number c₁ = {c1}")
|
|
1560
1544
|
|
|
1561
1545
|
print("\n" + "=" * 70)
|
|
1562
|
-
print("
|
|
1563
|
-
print("
|
|
1564
|
-
print("
|
|
1565
|
-
print("
|
|
1566
|
-
print("
|
|
1567
|
-
print("
|
|
1568
|
-
print("
|
|
1546
|
+
print("Core formula summary:")
|
|
1547
|
+
print(" - G_μ = d/dx^μ log FourierFrame(x) [intrinsic gradient]")
|
|
1548
|
+
print(" - R_{μν} = [G_μ, G_ν] [curvature]")
|
|
1549
|
+
print(" - FourierFrame * e^{iθ} = Fourier transform")
|
|
1550
|
+
print(" - FourierFrame * λ = conformal transform")
|
|
1551
|
+
print(" - γ = ∮ G_μ dx^μ [Berry phase]")
|
|
1552
|
+
print(" - c₁ = (1/2π) ∬ R_{μν} dS [Chern number]")
|
|
1569
1553
|
print("=" * 70)
|
|
1570
1554
|
|
|
1571
1555
|
|
|
1572
|
-
# ============================================================
|
|
1573
|
-
#
|
|
1574
|
-
# ============================================================
|
|
1556
|
+
# ============================================================
|
|
1557
|
+
# Exports
|
|
1558
|
+
# ============================================================
|
|
1575
1559
|
|
|
1576
1560
|
__all__ = [
|
|
1577
|
-
#
|
|
1561
|
+
# Core classes
|
|
1578
1562
|
'FourierFrame',
|
|
1579
1563
|
'FourierFrameSpectrum',
|
|
1580
1564
|
|
|
1581
|
-
#
|
|
1565
|
+
# Spectral geometry core
|
|
1582
1566
|
'IntrinsicGradient',
|
|
1583
1567
|
'CurvatureFromFrame',
|
|
1584
1568
|
'BerryPhase',
|
|
@@ -1588,11 +1572,11 @@ __all__ = [
|
|
|
1588
1572
|
'FrequencyProjection',
|
|
1589
1573
|
'FrequencyBandState',
|
|
1590
1574
|
|
|
1591
|
-
#
|
|
1575
|
+
# Convenience functions
|
|
1592
1576
|
'spectral_transform',
|
|
1593
1577
|
'inverse_spectral_transform',
|
|
1594
1578
|
|
|
1595
|
-
#
|
|
1579
|
+
# Constants
|
|
1596
1580
|
'HBAR',
|
|
1597
1581
|
'GPU_AVAILABLE',
|
|
1598
1582
|
]
|