galaga-matrix 1.7.0__tar.gz
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.
- galaga_matrix-1.7.0/.gitignore +21 -0
- galaga_matrix-1.7.0/High-priority correctness issues.md +652 -0
- galaga_matrix-1.7.0/PKG-INFO +8 -0
- galaga_matrix-1.7.0/README.md +93 -0
- galaga_matrix-1.7.0/docs/adrs/001-two-representation-modes.md +49 -0
- galaga_matrix-1.7.0/docs/adrs/002-compact-basis-periodicity-recursion.md +57 -0
- galaga_matrix-1.7.0/docs/adrs/003-double-algebras-lossy-compact.md +73 -0
- galaga_matrix-1.7.0/docs/adrs/004-matrix-repr-rendering-wrapper.md +51 -0
- galaga_matrix-1.7.0/docs/adrs/005-strict-inverses-and-spinor-conventions.md +81 -0
- galaga_matrix-1.7.0/docs/adrs/README.md +11 -0
- galaga_matrix-1.7.0/docs/cl13-vs-cl31-spinor-columns.md +366 -0
- galaga_matrix-1.7.0/docs/dirac-and-weyl-spinor-bases.md +796 -0
- galaga_matrix-1.7.0/docs/double-algebras.md +456 -0
- galaga_matrix-1.7.0/docs/edge-cases.md +103 -0
- galaga_matrix-1.7.0/docs/qm-bases-and-sta-translation.md +729 -0
- galaga_matrix-1.7.0/docs/spinor-conversions.md +316 -0
- galaga_matrix-1.7.0/galaga_matrix/__init__.py +30 -0
- galaga_matrix-1.7.0/galaga_matrix/matrix.py +929 -0
- galaga_matrix-1.7.0/galaga_matrix/repr.py +152 -0
- galaga_matrix-1.7.0/pyproject.toml +20 -0
- galaga_matrix-1.7.0/tests/test_matrix.py +1128 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
.coverage
|
|
12
|
+
.DS_Store
|
|
13
|
+
__marimo__/
|
|
14
|
+
# Vim
|
|
15
|
+
*.swp
|
|
16
|
+
*.swo
|
|
17
|
+
*~
|
|
18
|
+
test.py
|
|
19
|
+
.kiro/
|
|
20
|
+
.session.jsonl
|
|
21
|
+
.spacebot/
|
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
This is a copy of a shared ChatGPT conversation
|
|
2
|
+
|
|
3
|
+
Report conversation
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Uploaded a file
|
|
8
|
+
|
|
9
|
+
I wrote: Review this spec for my Geometric Algebra library, and it's matrix conversion package. Mathematically correctness is critical
|
|
10
|
+
|
|
11
|
+
Chat GPT replied:
|
|
12
|
+
|
|
13
|
+
I’ll treat this as a correctness review rather than a wording pass: I’m going to check the classification, dimension counts, idempotent/ideal logic, and the worked physics examples against standard Clifford algebra facts.
|
|
14
|
+
|
|
15
|
+
First significant finding: the general “dimension ≥ dimension” test is not sufficient by itself. You also need the restricted map from the even subalgebra into the chosen projected column(s) to be injective; otherwise the dimensions can match but the convention can still alias elements.
|
|
16
|
+
|
|
17
|
+
Thought for 55s
|
|
18
|
+
|
|
19
|
+
I’d **not ship this spec as-is**. The implementation idea is salvageable, but there are several mathematically serious issues, especially around **primitive idempotents, Cl(1,3), quaternionic packaging, normalization, and chirality**.
|
|
20
|
+
|
|
21
|
+
## High-priority correctness issues
|
|
22
|
+
|
|
23
|
+
### 1. “Dimension ≥ even algebra dimension” is not a sufficient roundtrip test
|
|
24
|
+
|
|
25
|
+
The spec says faithful roundtrip requires:
|
|
26
|
+
$$
|
|
27
|
+
\dim_\mathbb{R}(\text{spinor}) \ge \dim_\mathbb{R}(\mathrm{Cl}^0(p,q))
|
|
28
|
+
$$
|
|
29
|
+
and then uses that as the gate for whether `from_spinor_matrix(to_spinor_matrix(mv)) == mv` should work. That is only a **necessary** condition, not sufficient. You also need the concrete linear map
|
|
30
|
+
$$
|
|
31
|
+
\mathrm{Cl}^0(p,q) \to \text{chosen projected column space}
|
|
32
|
+
$$
|
|
33
|
+
to be **injective** for the chosen representation, idempotent, and extracted column(s). The spec later assumes the system is full-rank “where roundtrip is possible,” but that must be tested directly by rank, not inferred from dimension.
|
|
34
|
+
|
|
35
|
+
Recommended fix: define `_spinor_roundtrip_possible` as a **computed rank check** over the even blade columns:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
rank(A) == number_of_even_blades
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Dimension tables can remain explanatory, but not authoritative.
|
|
42
|
+
|
|
43
|
+
------
|
|
44
|
+
|
|
45
|
+
### 2. The idempotent story is inconsistent for Cl(1,3)
|
|
46
|
+
|
|
47
|
+
The spec says a primitive idempotent gives a minimal left ideal and “one nonzero column,” then for Cl(1,3) chooses:
|
|
48
|
+
$$
|
|
49
|
+
p = \frac12(1 + \gamma^0) = \operatorname{diag}(1,1,0,0)
|
|
50
|
+
$$
|
|
51
|
+
That is **not primitive as a complex 4×4 projector**. It has complex rank 2, not rank 1. Multiplying by it gives a matrix with **two nonzero columns**, not one. The spec notices this by saying “columns 0,1 as a pair,” but the algorithm returns only `projected[:, c:c+1]`, i.e. one column.
|
|
52
|
+
|
|
53
|
+
So there are two different constructions mixed together:
|
|
54
|
+
|
|
55
|
+
1. **Minimal-left-ideal construction**: choose a primitive idempotent and keep the resulting ideal element.
|
|
56
|
+
2. **Reference-column construction**: choose a reference column vector $u$, then map $M(\psi)u$.
|
|
57
|
+
|
|
58
|
+
For Cl(3,0), these collapse nicely because $p=\operatorname{diag}(1,0)$ is rank 1. For Cl(1,3), your chosen $p$ is not doing the same thing.
|
|
59
|
+
|
|
60
|
+
Recommended fix: rewrite the algorithm around a **reference spinor vector** rather than pretending the Cl(1,3) projector is primitive:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Choose a reference column u.
|
|
64
|
+
Map even multivector ψ to M(ψ) u.
|
|
65
|
+
Recover ψ by solving the real linear system over even blades.
|
|
66
|
+
Require rank(A) == dim Cl⁰.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then Cl(1,3) identity mapping to $(1,0,0,0)^T$ is clean: you are using $u=e_0$, not extracting a minimal ideal from $p=\frac12(1+\gamma^0)$.
|
|
70
|
+
|
|
71
|
+
------
|
|
72
|
+
|
|
73
|
+
### 3. The Cl(1,3) complex matrix description is potentially misleading
|
|
74
|
+
|
|
75
|
+
The spec says Cl(1,3) is $M(2,\mathbb H)$, but then talks about a compact 4×4 complex Dirac-matrix representation. That is okay **only if you are clear** that the 4×4 complex representation is a faithful **real** representation of $M(2,\mathbb H)$, not the full complex matrix algebra $M(4,\mathbb C)$. The image is a constrained real subalgebra inside $M(4,\mathbb C)$, not arbitrary complex 4×4 matrices.
|
|
76
|
+
|
|
77
|
+
Recommended wording:
|
|
78
|
+
|
|
79
|
+
> For quaternionic cases, the implementation uses the standard embedding $M(k,\mathbb H) \hookrightarrow M(2k,\mathbb C)$. This is faithful as a real algebra but its image is a proper real subalgebra of $M(2k,\mathbb C)$.
|
|
80
|
+
|
|
81
|
+
------
|
|
82
|
+
|
|
83
|
+
### 4. The Cl(3,0) Pauli signs are inconsistent
|
|
84
|
+
|
|
85
|
+
The worked Cl(3,0) section has a sign/basis-order problem.
|
|
86
|
+
|
|
87
|
+
It says:
|
|
88
|
+
$$
|
|
89
|
+
e_{12} \mapsto \sigma_1\sigma_2 = i\sigma_3
|
|
90
|
+
$$
|
|
91
|
+
and
|
|
92
|
+
$$
|
|
93
|
+
e_{23} \mapsto i\sigma_1
|
|
94
|
+
$$
|
|
95
|
+
Those are consistent with $e_i \mapsto \sigma_i$. But then it says:
|
|
96
|
+
$$
|
|
97
|
+
e_{31} \mapsto -i\sigma_2
|
|
98
|
+
$$
|
|
99
|
+
That is **not** consistent with $e_{31}=e_3e_1$, because:
|
|
100
|
+
$$
|
|
101
|
+
\sigma_3\sigma_1 = i\sigma_2
|
|
102
|
+
$$
|
|
103
|
+
while
|
|
104
|
+
$$
|
|
105
|
+
\sigma_1\sigma_3 = -i\sigma_2
|
|
106
|
+
$$
|
|
107
|
+
So either the blade is really $e_{13}$, or the sign is wrong. This affects the inverse formula and the Bloch sphere formula.
|
|
108
|
+
|
|
109
|
+
Recommended fix: choose one convention and state it explicitly. For canonical ascending blade order, use $e_{13}$, not $e_{31}$, or adjust the signs.
|
|
110
|
+
|
|
111
|
+
For example, with $e_{31}=e_3e_1$:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
e12 ↦ iσ3
|
|
115
|
+
e23 ↦ iσ1
|
|
116
|
+
e31 ↦ iσ2
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Then the extracted column for $e_{31}$ is $(0,-1)^T$, not $(0,1)^T$.
|
|
120
|
+
|
|
121
|
+
------
|
|
122
|
+
|
|
123
|
+
### 5. The normalization table is false for Lorentzian spinors
|
|
124
|
+
|
|
125
|
+
This is probably the most important physics correctness issue.
|
|
126
|
+
|
|
127
|
+
The spec says:
|
|
128
|
+
$$
|
|
129
|
+
\psi\tilde\psi = 1 \quad \Longleftrightarrow \quad \langle \psi|\psi\rangle = 1
|
|
130
|
+
$$
|
|
131
|
+
and claims a normalized rotor always maps to a unit-normalized column spinor.
|
|
132
|
+
|
|
133
|
+
That is true for **Euclidean Pauli rotations** under the usual Hermitian norm. It is **not true in Cl(1,3)** for Lorentz boosts. Lorentz rotor matrices are not unitary under the ordinary positive-definite Hermitian inner product. A boost rotor satisfies:
|
|
134
|
+
$$
|
|
135
|
+
R\tilde R = 1
|
|
136
|
+
$$
|
|
137
|
+
but the ordinary column norm $\psi^\dagger\psi$ generally changes under boosts. The invariant Dirac bilinear uses the **Dirac adjoint**, not the Hilbert-space norm:
|
|
138
|
+
$$
|
|
139
|
+
\bar\psi \psi = \psi^\dagger \gamma^0 \psi
|
|
140
|
+
$$
|
|
141
|
+
Recommended fix: split the normalization section by signature:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
For Cl(3,0) Pauli spinors, rotor normalization corresponds to unit Hermitian norm for the projected reference state.
|
|
145
|
+
|
|
146
|
+
For Cl(1,3), R\tilde R = 1 corresponds to Lorentz-group normalization, not ordinary Hermitian unit norm. The relevant invariant bilinears use the Dirac adjoint or GA scalar bilinears.
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
------
|
|
150
|
+
|
|
151
|
+
### 6. The quaternionic “two components = Weyl chiralities” claim is wrong as stated
|
|
152
|
+
|
|
153
|
+
The spec says the two quaternion components of a Dirac spinor correspond to the two Weyl chiralities, and that boosts mix those chiralities.
|
|
154
|
+
|
|
155
|
+
That is not right.
|
|
156
|
+
|
|
157
|
+
A Dirac spinor can be written as two Weyl spinors in the **chiral/Weyl basis**:
|
|
158
|
+
$$
|
|
159
|
+
\psi =
|
|
160
|
+
\begin{pmatrix}
|
|
161
|
+
\psi_L \\
|
|
162
|
+
\psi_R
|
|
163
|
+
\end{pmatrix}
|
|
164
|
+
$$
|
|
165
|
+
But your earlier Cl(1,3) section chooses a Dirac-style projector $p=\frac12(1+\gamma^0)$, where the upper/lower split is not automatically the left/right chirality split. More importantly, proper Lorentz transformations do **not** mix left and right Weyl representations. Rotations and boosts act separately on $\psi_L$ and $\psi_R$, with related but inequivalent $SL(2,\mathbb C)$ actions. Mass terms, parity, or basis changes can mix interpretations, but a pure Lorentz boost does not “couple the two chiralities.”
|
|
166
|
+
|
|
167
|
+
Recommended fix: remove that physical interpretation unless you explicitly choose the chiral basis and define the quaternion packaging accordingly. Safer wording:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
A complex Dirac spinor can be packaged as two quaternions, but the physical interpretation of those two quaternionic components depends on the chosen gamma-matrix basis and reference spinor. They should not be identified with Weyl chiralities unless the representation and basis are chosen to make that identification explicit.
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
------
|
|
174
|
+
|
|
175
|
+
## Medium-priority issues
|
|
176
|
+
|
|
177
|
+
### 7. “Physically, Cl(1,3) is the standard choice…” is too strong
|
|
178
|
+
|
|
179
|
+
The spec says Cl(1,3) is the standard choice for the Dirac equation precisely because its spinor representation has the right dimension.
|
|
180
|
+
|
|
181
|
+
That is misleading. Physics often works with the **complexified** Clifford algebra, where the real-signature distinction largely disappears:
|
|
182
|
+
$$
|
|
183
|
+
\mathrm{Cl}_{1,3}(\mathbb R)\otimes \mathbb C
|
|
184
|
+
\cong
|
|
185
|
+
\mathrm{Cl}_{3,1}(\mathbb R)\otimes \mathbb C
|
|
186
|
+
\cong
|
|
187
|
+
M(4,\mathbb C)
|
|
188
|
+
$$
|
|
189
|
+
The real algebra distinction matters for Majorana/quaternionic structures and for GA formulations, but saying Cl(1,3) is “the standard choice precisely because…” overstates the case.
|
|
190
|
+
|
|
191
|
+
Recommended fix:
|
|
192
|
+
|
|
193
|
+
> In this library’s real-algebra convention, Cl(1,3) has a quaternionic structure that lets an even multivector be encoded in an 8-real-dimensional spinor column. Cl(3,1), using its minimal real column representation, does not provide a faithful roundtrip by this particular column-extraction method.
|
|
194
|
+
|
|
195
|
+
------
|
|
196
|
+
|
|
197
|
+
### 8. “Minimal left ideal = matrices with only one nonzero column” only works cleanly for $M(k,F)$ over $F$
|
|
198
|
+
|
|
199
|
+
This statement is fine for $M(k,\mathbb R)$, $M(k,\mathbb C)$, or $M(k,\mathbb H)$ when viewed over the same scalar division ring. But once you embed quaternionic matrices into complex matrices, a quaternionic rank-1 column corresponds to **two complex columns**.
|
|
200
|
+
|
|
201
|
+
Recommended fix: add a note:
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
For quaternionic algebras represented via complex 2×2 blocks, a rank-1 quaternionic projector appears as a rank-2 complex projector. Therefore “one nonzero column” means one quaternionic column, not one complex column.
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
------
|
|
208
|
+
|
|
209
|
+
### 9. The API name `to_spinor_matrix` may obscure what is returned
|
|
210
|
+
|
|
211
|
+
It returns a **column vector**, not a matrix. That may be okay if the package already has naming conventions, but mathematically `to_spinor_column` or `to_column_spinor` would be clearer.
|
|
212
|
+
|
|
213
|
+
------
|
|
214
|
+
|
|
215
|
+
## What I would change structurally
|
|
216
|
+
|
|
217
|
+
I’d rewrite the central algorithm like this:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
Given an even multivector ψ ∈ Cl⁰(p,q):
|
|
221
|
+
|
|
222
|
+
1. Build a faithful real matrix representation ρ: Cl(p,q) → Mat_N(K),
|
|
223
|
+
implemented over real or complex NumPy arrays.
|
|
224
|
+
|
|
225
|
+
2. Choose a fixed reference column u.
|
|
226
|
+
|
|
227
|
+
3. Define:
|
|
228
|
+
spinor(ψ) = ρ(ψ) u
|
|
229
|
+
|
|
230
|
+
4. Precompute the real basis matrix A whose columns are spinor(E_i)
|
|
231
|
+
for each even basis blade E_i.
|
|
232
|
+
|
|
233
|
+
5. The conversion is faithful iff rank_R(A) = dim_R Cl⁰(p,q).
|
|
234
|
+
|
|
235
|
+
6. Forward conversion is multiplication by A.
|
|
236
|
+
|
|
237
|
+
7. Inverse conversion solves A c = spinor.
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
That avoids the ambiguity around primitive idempotents, rank-2 complex projectors, and whether you are extracting one column or a minimal ideal.
|
|
241
|
+
|
|
242
|
+
## Verdict
|
|
243
|
+
|
|
244
|
+
The broad idea is good:
|
|
245
|
+
|
|
246
|
+
> represent an even GA spinor by its action on a fixed reference spinor column, then reconstruct by solving over the even blade basis.
|
|
247
|
+
|
|
248
|
+
But the spec currently presents that as a minimal-left-ideal/idempotent construction, and that framing breaks down in the Cl(1,3) quaternionic/complex-embedded case.
|
|
249
|
+
|
|
250
|
+
The must-fix items are:
|
|
251
|
+
|
|
252
|
+
1. Replace the dimension-only roundtrip test with an actual rank test.
|
|
253
|
+
2. Reframe Cl(1,3) around a reference column, not a primitive idempotent.
|
|
254
|
+
3. Fix the Cl(3,0) $e_{31}$ sign convention.
|
|
255
|
+
4. Remove or heavily qualify the normalization claim.
|
|
256
|
+
5. Remove the “quaternion components = Weyl chiralities” claim unless you explicitly switch to a chiral basis and prove the mapping.
|
|
257
|
+
6. Clarify quaternionic complex embedding: one quaternionic column = two complex rows/columns depending on representation, not a naive single complex column.
|
|
258
|
+
|
|
259
|
+
Uploaded a file
|
|
260
|
+
|
|
261
|
+
I wrote: Review this code to implement
|
|
262
|
+
|
|
263
|
+
Chat GPT replied:
|
|
264
|
+
|
|
265
|
+
I’ll review this against the spec issues from the previous pass: not just “does it run,” but whether the implementation preserves the intended Clifford/spinor mapping without hidden dimension or convention errors.
|
|
266
|
+
|
|
267
|
+
A second concrete issue: the code still implements the old dimension-only roundtrip predicate. That means it can declare a spinor conversion valid without checking the actual rank of the even-blade-to-column map.
|
|
268
|
+
|
|
269
|
+
Thought for 1m 58s
|
|
270
|
+
|
|
271
|
+
I’d **not merge this yet**. The core full-matrix machinery is mostly reasonable, but the spinor/quaternion parts still encode the mathematical mistakes from the spec, and there is one extra serious implementation issue: the **Cl(1,3) Dirac matrices are not in the quaternionic block convention that `to_quaternion_matrix()` assumes**.
|
|
272
|
+
|
|
273
|
+
I couldn’t run end-to-end tests because `galaga` is not installed in the sandbox, but I did static review and rank-checked the matrix construction by stubbing the minimal `Algebra` surface.
|
|
274
|
+
|
|
275
|
+
## Biggest issues
|
|
276
|
+
|
|
277
|
+
### 1. `_spinor_roundtrip_possible()` is still dimension-only
|
|
278
|
+
|
|
279
|
+
Lines 524–555 decide roundtrippability from the ABS type and real dimension count. That was the main issue in the spec: dimension is necessary but not sufficient.
|
|
280
|
+
|
|
281
|
+
You should make this a **rank check over the actual chosen spinor map**:
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
def _even_indices(dim: int) -> np.ndarray:
|
|
285
|
+
return np.array([i for i in range(dim) if i.bit_count() % 2 == 0])
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _spinor_reference_vector(alg: Algebra) -> np.ndarray:
|
|
289
|
+
gammas = compact_basis(alg)
|
|
290
|
+
k = gammas[0].shape[0]
|
|
291
|
+
p = _choose_idempotent(gammas, k)
|
|
292
|
+
|
|
293
|
+
# Use the first nonzero column of p as the reference spinor.
|
|
294
|
+
# This works for diagonal and non-diagonal projectors.
|
|
295
|
+
norms = np.linalg.norm(p, axis=0)
|
|
296
|
+
col = int(np.argmax(norms))
|
|
297
|
+
if norms[col] < 1e-14:
|
|
298
|
+
u = np.zeros((k, 1), dtype=complex)
|
|
299
|
+
u[0, 0] = 1.0
|
|
300
|
+
return u
|
|
301
|
+
|
|
302
|
+
return p[:, col : col + 1]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _spinor_system_matrix(alg: Algebra) -> tuple[np.ndarray, np.ndarray]:
|
|
306
|
+
blade_mats = _build_compact_blade_matrices(alg)
|
|
307
|
+
even_indices = _even_indices(alg.dim)
|
|
308
|
+
u = _spinor_reference_vector(alg)
|
|
309
|
+
|
|
310
|
+
cols = (blade_mats[even_indices] @ u)[:, :, 0] # (n_even, k)
|
|
311
|
+
A_complex = cols.T # (k, n_even)
|
|
312
|
+
A_real = np.vstack([A_complex.real, A_complex.imag])
|
|
313
|
+
return A_real, even_indices
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _spinor_roundtrip_possible(alg: Algebra) -> bool:
|
|
317
|
+
try:
|
|
318
|
+
A, even_indices = _spinor_system_matrix(alg)
|
|
319
|
+
except NotImplementedError:
|
|
320
|
+
return False
|
|
321
|
+
|
|
322
|
+
return np.linalg.matrix_rank(A, tol=1e-10) == len(even_indices)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Then `from_spinor_matrix()` should use the same `A` and `even_indices`, not rebuild a slightly different column-selection path.
|
|
326
|
+
|
|
327
|
+
------
|
|
328
|
+
|
|
329
|
+
### 2. `to_spinor_matrix()` should use a reference vector, not “extract first diagonal column”
|
|
330
|
+
|
|
331
|
+
Lines 608–614 do:
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
projected = mat @ p
|
|
335
|
+
for col in range(k):
|
|
336
|
+
if np.abs(p[col, col]) > 0.5:
|
|
337
|
+
return projected[:, col : col + 1]
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
This works for Cl(3,0) and happens to work for Cl(1,3), but it is conceptually brittle. For a non-diagonal idempotent, the diagonal can be `0.5`, so the `> 0.5` test misses it. Then the fallback chooses the first nonzero projected column depending on the **input multivector**, which is a bad property: the reference spinor convention should not depend on the value being converted.
|
|
341
|
+
|
|
342
|
+
Use:
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
def to_spinor_matrix(mv: Multivector) -> np.ndarray:
|
|
346
|
+
from galaga import is_even
|
|
347
|
+
|
|
348
|
+
if not is_even(mv):
|
|
349
|
+
raise ValueError(
|
|
350
|
+
"to_spinor_matrix requires an even-grade multivector. "
|
|
351
|
+
"Use galaga.even_grades(mv) to project to the even part first."
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if not _spinor_roundtrip_possible(mv.algebra):
|
|
355
|
+
p = sum(1 for s in mv.algebra.signature if s > 0)
|
|
356
|
+
q = sum(1 for s in mv.algebra.signature if s < 0)
|
|
357
|
+
etype, _ = _classify(p, q)
|
|
358
|
+
raise TypeError(
|
|
359
|
+
f"Cl({p},{q}) (type '{etype}') does not support faithful spinor roundtrip "
|
|
360
|
+
f"with the selected compact representation and reference spinor. "
|
|
361
|
+
f"Use to_matrix(mv, mode='compact') for the full matrix representation."
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return _to_compact(mv) @ _spinor_reference_vector(mv.algebra)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
And `from_spinor_matrix()` becomes:
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
def from_spinor_matrix(alg: Algebra, spinor: np.ndarray) -> Multivector:
|
|
371
|
+
A, even_indices = _spinor_system_matrix(alg)
|
|
372
|
+
|
|
373
|
+
if np.linalg.matrix_rank(A, tol=1e-10) != len(even_indices):
|
|
374
|
+
p = sum(1 for s in alg.signature if s > 0)
|
|
375
|
+
q = sum(1 for s in alg.signature if s < 0)
|
|
376
|
+
etype, _ = _classify(p, q)
|
|
377
|
+
raise TypeError(
|
|
378
|
+
f"Cl({p},{q}) (type '{etype}') does not support faithful spinor roundtrip "
|
|
379
|
+
f"with the selected compact representation and reference spinor."
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
k = A.shape[0] // 2
|
|
383
|
+
|
|
384
|
+
spinor = np.asarray(spinor, dtype=complex)
|
|
385
|
+
if spinor.shape == (k,):
|
|
386
|
+
spinor = spinor.reshape(k, 1)
|
|
387
|
+
if spinor.shape != (k, 1):
|
|
388
|
+
raise ValueError(f"Expected ({k}, 1) spinor, got {spinor.shape}")
|
|
389
|
+
|
|
390
|
+
b = np.concatenate([spinor[:, 0].real, spinor[:, 0].imag])
|
|
391
|
+
coeffs_even, residuals, rank, _ = np.linalg.lstsq(A, b, rcond=None)
|
|
392
|
+
|
|
393
|
+
if rank != len(even_indices):
|
|
394
|
+
raise TypeError("Selected spinor map is rank-deficient.")
|
|
395
|
+
|
|
396
|
+
data = np.zeros(alg.dim)
|
|
397
|
+
data[even_indices] = coeffs_even
|
|
398
|
+
return Multivector(alg, data)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
This also removes duplicated column-selection logic.
|
|
402
|
+
|
|
403
|
+
------
|
|
404
|
+
|
|
405
|
+
### 3. `to_quaternion_matrix()` is wrong for your Cl(1,3) Dirac basis
|
|
406
|
+
|
|
407
|
+
This is the most serious new code-level issue.
|
|
408
|
+
|
|
409
|
+
Your quaternion embedding convention is:
|
|
410
|
+
|
|
411
|
+
```
|
|
412
|
+
q = a + bi + cj + dk
|
|
413
|
+
→ [[a+bi, c+di],
|
|
414
|
+
[-c+di, a-bi]]
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
That is lines 152–159 and 483–494.
|
|
418
|
+
|
|
419
|
+
But your Cl(1,3) Dirac matrices in lines 210–216 use blocks like:
|
|
420
|
+
|
|
421
|
+
```
|
|
422
|
+
[[0, σᵢ],
|
|
423
|
+
[-σᵢ, 0]]
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
The Pauli matrix `σ1 = [[0,1],[1,0]]` is **not** a quaternion block under your embedding, because a quaternion block with top-right `+1` would have bottom-left `-1`, not `+1`.
|
|
427
|
+
|
|
428
|
+
So this function:
|
|
429
|
+
|
|
430
|
+
```
|
|
431
|
+
block = cmat[2*i:2*i+2, 2*j:2*j+2]
|
|
432
|
+
row.append(_extract_quat(block))
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
silently reads non-quaternionic 2×2 complex blocks as if they were quaternions. That gives mathematically false quaternion matrices for Cl(1,3).
|
|
436
|
+
|
|
437
|
+
You have two viable options.
|
|
438
|
+
|
|
439
|
+
Option A: keep standard Dirac matrices, but disable quaternion-matrix extraction for them:
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
def to_quaternion_matrix(mv: Multivector) -> list[list[Quat]]:
|
|
443
|
+
p = sum(1 for s in mv.algebra.signature if s > 0)
|
|
444
|
+
q = sum(1 for s in mv.algebra.signature if s < 0)
|
|
445
|
+
etype, qdim = _classify(p, q)
|
|
446
|
+
|
|
447
|
+
if etype != "quaternion":
|
|
448
|
+
raise TypeError(
|
|
449
|
+
f"Cl({p},{q}) is type '{etype}', not a single quaternionic matrix algebra."
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Important: this requires the compact representation to be in quaternionic
|
|
453
|
+
# 2×2 block form. The standard Dirac basis used for Cl(1,3) is not.
|
|
454
|
+
if (p, q) == (1, 3):
|
|
455
|
+
raise TypeError(
|
|
456
|
+
"to_quaternion_matrix is not valid for the current Cl(1,3) Dirac basis; "
|
|
457
|
+
"use a quaternionic-block gamma basis or to_matrix(..., mode='compact')."
|
|
458
|
+
)
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Option B: introduce a separate **quaternionic-block Cl(1,3) basis** and use that for quaternion matrix/spinor APIs. For example, over $M(2,\mathbb H)$:
|
|
462
|
+
$$
|
|
463
|
+
\gamma^0 =
|
|
464
|
+
\begin{pmatrix}
|
|
465
|
+
1 & 0 \\
|
|
466
|
+
0 & -1
|
|
467
|
+
\end{pmatrix},
|
|
468
|
+
\quad
|
|
469
|
+
\gamma^i =
|
|
470
|
+
\begin{pmatrix}
|
|
471
|
+
0 & u_i \\
|
|
472
|
+
u_i & 0
|
|
473
|
+
\end{pmatrix}
|
|
474
|
+
$$
|
|
475
|
+
where $u_i \in \{i,j,k\}$. Since $u_i^2=-1$, the spatial gammas square to $-1$, and they anticommute correctly.
|
|
476
|
+
|
|
477
|
+
Then embed each quaternion block using your `_quat_to_complex()` function.
|
|
478
|
+
|
|
479
|
+
------
|
|
480
|
+
|
|
481
|
+
### 4. `to_spinor_quaternion()` has the same representation-convention problem
|
|
482
|
+
|
|
483
|
+
Lines 760–768 pair consecutive complex entries and interpret them as quaternions. That is only meaningful if the complex representation is arranged as a quaternionic module in the same block convention.
|
|
484
|
+
|
|
485
|
+
For Cl(0,2), that is okay. For Cl(1,3) using your current Dirac matrices, it is at best a packaging of four complex entries into two `Quat` objects, not a mathematically natural quaternionic spinor compatible with your `M(2,H)` matrix extraction.
|
|
486
|
+
|
|
487
|
+
I’d either rename it to make the weaker meaning explicit:
|
|
488
|
+
|
|
489
|
+
```
|
|
490
|
+
to_spinor_quaternion_packed()
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
or require a quaternionic-block representation before exposing it as a real quaternionic spinor.
|
|
494
|
+
|
|
495
|
+
------
|
|
496
|
+
|
|
497
|
+
### 5. Quaternionic double algebras accidentally pass the “quaternionic” check
|
|
498
|
+
|
|
499
|
+
Lines 754 and 825 check:
|
|
500
|
+
|
|
501
|
+
```
|
|
502
|
+
if "quaternion" not in etype:
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
That allows `"quaternion+quaternion"`.
|
|
506
|
+
|
|
507
|
+
But the docstring says only $(q-p)\bmod 8 \in \{2,4\}$. For $(q-p)\bmod 8=3$, the algebra is $A \oplus A$, and your compact representation chooses one summand. That can be okay for some even-subalgebra spinor maps, but it is not the same claim.
|
|
508
|
+
|
|
509
|
+
Use:
|
|
510
|
+
|
|
511
|
+
```
|
|
512
|
+
if etype != "quaternion":
|
|
513
|
+
raise TypeError(...)
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
unless you deliberately want a `one_summand=True` mode.
|
|
517
|
+
|
|
518
|
+
------
|
|
519
|
+
|
|
520
|
+
### 6. `_from_compact()` should check rank and residuals
|
|
521
|
+
|
|
522
|
+
Lines 417–435 recover coefficients using least squares, but for double algebras or invalid matrices this can silently return a “best fit” multivector. That is dangerous for a math library.
|
|
523
|
+
|
|
524
|
+
Add rank/residual validation:
|
|
525
|
+
|
|
526
|
+
```
|
|
527
|
+
coeffs, residuals, rank, _ = np.linalg.lstsq(A, b, rcond=None)
|
|
528
|
+
|
|
529
|
+
if rank < dim:
|
|
530
|
+
raise TypeError(
|
|
531
|
+
"Compact representation is not injective for this algebra; "
|
|
532
|
+
"cannot recover a unique multivector."
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
err = np.linalg.norm(A @ coeffs - b)
|
|
536
|
+
if err > 1e-9:
|
|
537
|
+
raise ValueError(
|
|
538
|
+
"Matrix is not in the image of this Clifford representation."
|
|
539
|
+
)
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
Or expose a `strict: bool = True` flag.
|
|
543
|
+
|
|
544
|
+
------
|
|
545
|
+
|
|
546
|
+
### 7. `compact_basis()` says “minimal-dimension faithful” but double algebras are not faithful
|
|
547
|
+
|
|
548
|
+
Lines 358–362 say:
|
|
549
|
+
|
|
550
|
+
> minimal-dimension faithful matrix representation
|
|
551
|
+
|
|
552
|
+
But the module docstring already admits double algebras use one summand and may not roundtrip. The function docstring should say:
|
|
553
|
+
|
|
554
|
+
```
|
|
555
|
+
Return compact gamma matrices for a low-dimensional representation.
|
|
556
|
+
|
|
557
|
+
For simple Clifford algebras this is faithful. For double algebras
|
|
558
|
+
(s = 3 or 7) this returns one irreducible summand and is not faithful
|
|
559
|
+
on the full algebra.
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Otherwise callers will trust `from_matrix(..., mode="compact")` when they shouldn’t.
|
|
563
|
+
|
|
564
|
+
------
|
|
565
|
+
|
|
566
|
+
### 8. Cl(0,0) edge case breaks
|
|
567
|
+
|
|
568
|
+
`_general_compact_basis(0,0)` returns `[]`, then `_build_compact_blade_matrices()` does:
|
|
569
|
+
|
|
570
|
+
```
|
|
571
|
+
k = gammas[0].shape[0]
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
So scalar algebra compact mode crashes. Either reject it explicitly or handle it:
|
|
575
|
+
|
|
576
|
+
```
|
|
577
|
+
if alg.n == 0:
|
|
578
|
+
return np.ones((1, 1, 1), dtype=complex)
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
------
|
|
582
|
+
|
|
583
|
+
## What looks good
|
|
584
|
+
|
|
585
|
+
The left-regular representation is straightforward and mathematically sound: first column recovery in `_from_left_regular()` is valid because left multiplication by $M$ sends $1$ to $M$.
|
|
586
|
+
|
|
587
|
+
The blade construction in `_build_compact_blade_matrices()` is also conventionally correct assuming your algebra’s bitmask blade order is ascending basis-vector order. It fixes the earlier `e31` sign ambiguity by always building canonical bitmask blades as ascending products.
|
|
588
|
+
|
|
589
|
+
The rank results I checked for the current selected-column map were encouraging for the headline cases:
|
|
590
|
+
|
|
591
|
+
```
|
|
592
|
+
Cl(3,0): rank 4 / even dim 4
|
|
593
|
+
Cl(1,3): rank 8 / even dim 8
|
|
594
|
+
Cl(0,2): rank 2 / even dim 2
|
|
595
|
+
Cl(4,0): rank 8 / even dim 8
|
|
596
|
+
Cl(3,1): rank 4 / even dim 8
|
|
597
|
+
Cl(2,2): rank 4 / even dim 8
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
So the **column-spinor roundtrip idea works for the important cases**, but the code should prove it by rank, not assert it by classification.
|
|
601
|
+
|
|
602
|
+
## Recommended merge gate
|
|
603
|
+
|
|
604
|
+
Before shipping, I’d add tests like these:
|
|
605
|
+
|
|
606
|
+
```
|
|
607
|
+
@pytest.mark.parametrize("signature", [
|
|
608
|
+
[1, 1, 1], # Cl(3,0)
|
|
609
|
+
[1, -1, -1, -1], # Cl(1,3)
|
|
610
|
+
[-1, -1], # Cl(0,2)
|
|
611
|
+
[1, 1, 1, 1], # Cl(4,0)
|
|
612
|
+
])
|
|
613
|
+
def test_even_spinor_roundtrip(signature):
|
|
614
|
+
alg = Algebra(signature)
|
|
615
|
+
rng = np.random.default_rng(0)
|
|
616
|
+
|
|
617
|
+
data = np.zeros(alg.dim)
|
|
618
|
+
for i in range(alg.dim):
|
|
619
|
+
if i.bit_count() % 2 == 0:
|
|
620
|
+
data[i] = rng.normal()
|
|
621
|
+
|
|
622
|
+
mv = Multivector(alg, data)
|
|
623
|
+
out = from_spinor_matrix(alg, to_spinor_matrix(mv))
|
|
624
|
+
|
|
625
|
+
assert np.allclose(out.data, mv.data, atol=1e-9)
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
And negative tests:
|
|
629
|
+
|
|
630
|
+
```
|
|
631
|
+
@pytest.mark.parametrize("signature", [
|
|
632
|
+
[1, 1, 1, -1], # Cl(3,1)
|
|
633
|
+
[1, 1, -1, -1], # Cl(2,2)
|
|
634
|
+
[1, 1, 1, 1, -1], # Cl(4,1)
|
|
635
|
+
])
|
|
636
|
+
def test_non_roundtrippable_spinor_rejected(signature):
|
|
637
|
+
alg = Algebra(signature)
|
|
638
|
+
assert not _spinor_roundtrip_possible(alg)
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
## Bottom line
|
|
642
|
+
|
|
643
|
+
The implementation is close for `to_spinor_matrix()` / `from_spinor_matrix()` if you reframe it as:
|
|
644
|
+
$$
|
|
645
|
+
\psi \mapsto \rho(\psi)u
|
|
646
|
+
$$
|
|
647
|
+
for a fixed reference spinor $u$, with a rank-checked inverse.
|
|
648
|
+
|
|
649
|
+
But I would **block the quaternion APIs** until you either:
|
|
650
|
+
|
|
651
|
+
1. provide a genuine quaternionic-block representation, or
|
|
652
|
+
2. clearly mark them as simple complex-pair packing rather than quaternionic Clifford matrix/spinor conversion.
|