Skip to content

Commit 71c0450

Browse files
thchrclaude
andcommitted
Annotate phase-convention-adjusted code with [⚠️ phase] markers + move investigation notes to devdocs
Add [⚠️ phase] annotations at phase-convention-sensitive locations in symmetry_analysis.jl (the only file that intentionally deviates from the physical Convention 1 formulas to match Crystalline.jl's calc_bandreps). Consolidate PHASE6_NOTES.md investigation log into symmetry_eigenvalue_conventions.md (inlining the SG 88 worked example and the note about Hamiltonian phase independence), then delete the separate investigation file. Add entry to devdocs README. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b0a7e00 commit 71c0450

4 files changed

Lines changed: 53 additions & 229 deletions

File tree

PHASE6_NOTES.md

Lines changed: 0 additions & 217 deletions
This file was deleted.

docs/src/devdocs/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,8 @@ user-facing theory exposition, see [`theory.md`](../theory.md).
1616
- **[`1d_example.md`](1d_example.md)** — Worked example of a 1D bipartite lattice with
1717
inversion, illustrating the M-matrix approach by comparing a hand-derived Hamiltonian with
1818
the symmetry-constrained result.
19+
20+
- **[`symmetry_eigenvalue_conventions.md`](symmetry_eigenvalue_conventions.md)** — Phase
21+
convention mismatch between the physical derivation (`theory.md`) and Crystalline.jl's
22+
`calc_bandreps`, how `symmetry_eigenvalues` corrects for it, the centered-lattice
23+
primitivization fix, and options for future cleanup.

docs/src/devdocs/symmetry_eigenvalue_conventions.md

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,14 +195,46 @@ In this option, `theory.md` should also gain a note (or a new devdoc section) ex
195195
Since `symmetry_eigenvalues` must match Crystalline.jl's irreps for subduction to work, we
196196
adopt the conjugated convention for $\mathbf{D}_\mathbf{k}$."
197197

198-
## Test results with the fix
198+
## The centering fix: `primitivize` with `modw=false`
199+
200+
Beyond the phase convention mismatch, a separate bug affected all centered lattices (C- and
201+
I-centered space groups: SG 68, 88, 141, 142, 214, 220, 230). The original
202+
`collect_compatible` primitivized little groups via `primitivize(group(first(lgirs)))`, which
203+
calls `primitivize(g::LittleGroup)` with the default `modw=true`, reducing translations
204+
modulo the primitive lattice. For centered lattices, the primitivization transforms
205+
conventional translations $\mathbf{v}_\text{conv}$ to primitive translations
206+
$P^{-1}\mathbf{v}_\text{conv}$, then reduces mod 1. The discarded lattice vector
207+
$\mathbf{R}$ changes the phase $e^{2\pi i(g\mathbf{k})\cdot\mathbf{v}}$ by a factor
208+
$e^{2\pi i(g\mathbf{k})\cdot\mathbf{R}} \neq 1$ at non-$\Gamma$ high-symmetry points.
209+
210+
**Concrete example:** SG 88 ($I4_1/a$), operation $\{-4^+_{001}|\frac{1}{4},\frac{3}{4},\frac{3}{4}\}$ at the P-point $[\frac{1}{2},\frac{1}{2},\frac{1}{2}]$:
211+
- Conventional translation: $\mathbf{v} = [\frac{1}{4}, \frac{3}{4}, \frac{3}{4}]$
212+
- Primitivized (full): $P^{-1}\mathbf{v} = [\frac{3}{2}, 1, 1]$
213+
- Primitivized (reduced mod 1): $[\frac{1}{2}, 0, 0]$ — discards lattice vector $\mathbf{R} = [1, 1, 0]$
214+
- $g\mathbf{k} \cdot \mathbf{R} = -\frac{1}{4}$, giving phase error $e^{2\pi i \times 0.25} = i$
215+
216+
The fix uses `primitivize(::Collection{LGIrrep})`, which internally passes `modw=false`,
217+
preserving full (unreduced) translations:
218+
219+
```julia
220+
clgirsv = irreps(cbr)
221+
lgirsv = primitivize.(clgirsv)
222+
lgs = group.(lgirsv)
223+
```
224+
225+
## Note: the Hamiltonian Fourier phase is independent
226+
227+
The Hamiltonian evaluation phase ($e^{-2\pi i \mathbf{k}\cdot\boldsymbol{\delta}}$ in
228+
`evaluate_tight_binding_term!`) does **not** affect symmetry analysis results. This was
229+
verified during the investigation: the phase sign only transposes $H(\mathbf{k}) \to H(-\mathbf{k})$,
230+
and eigenvectors at the high-symmetry $\mathbf{k}$-points used by `symmetry_eigenvalues` are
231+
obtained from `solve(ptbm, k)` which always uses the actual $\mathbf{k}$. Changing the
232+
Hamiltonian phase sign is therefore not a route to fixing symmetry analysis.
233+
234+
## Test results with all fixes
199235

200236
Across all 230 3D space groups (and all 1D and 2D space groups):
201237

202238
- **All 1D:** pass
203239
- **All 2D:** pass (including p3, p6, which previously failed at the K-point)
204-
- **3D:** 223/230 pass; 7 fail (SG 68, 88, 141, 142, 214, 220, 230)
205-
- The 7 remaining failures are **pre-existing** bugs (present in the original code before
206-
the fix), all in centered lattices (C or I centering). These are a separate class of issue,
207-
likely originating in Crystalline.jl's orbit/position handling for non-primitive lattices.
208-
The fix actually *improves* some of these (SG 214: 10→8 failures; SG 220: 6→4; SG 230: 9→4).
240+
- **All 3D:** pass (including all centered lattices)

src/symmetry_analysis.jl

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Note [⚠️ phase]: Several phase choices in this file intentionally deviate from the physical
2+
# Convention 1 formulas derived in `docs/src/theory.md` to match Crystalline.jl's
3+
# `calc_bandreps` convention. See `docs/src/devdocs/symmetry_eigenvalue_conventions.md`.
4+
15
"""
26
collect_compatible(ptbm::ParameterizedTightBindingModel{D}; multiplicities_kws...)
37
@@ -40,7 +44,7 @@ function Crystalline.collect_compatible(
4044
cbr = CompositeBandRep(tbm)
4145

4246
clgirsv = irreps(cbr) # irreps associated to the EBRs (conventional setting operations)
43-
lgirsv = primitivize.(clgirsv)
47+
lgirsv = primitivize.(clgirsv) # [⚠️ phase]: must use `modw=false` (via Collection method)
4448
lgs = group.(lgirsv) # little groups associated to the EBRs (primitive setting)
4549
ops = unique(Iterators.flatten(lgs))
4650

@@ -110,14 +114,14 @@ function symmetry_eigenvalues(
110114
g = sgrep.op
111115
gk = compose(g, ReciprocalPoint{D}(k)) # NB: for k ∈ Gₖ, there exist G st g∘k = k+G
112116
G = gk - k # the possible reciprocal vector-difference G between k & g∘k; for Θᴳ
113-
# NB: we use -G (i.e., `conj(Θᴳ)`) rather than G because the symmetry eigenvalue
114-
# formula `⟨ψ|ĝ|ψ⟩ = w† Θ_G† D_k w` uses the physical Convention 1 result with
115-
# Θ_G†; but `calc_bandreps` in Crystalline.jl (following Cano et al.) computes
117+
# [⚠️ phase]: we use -G (i.e., `conj(Θᴳ)`) rather than G because the symmetry
118+
# eigenvalue formula `⟨ψ|ĝ|ψ⟩ = w† Θ_G† D_k w` uses the physical Conv 1 result
119+
# with Θ_G†; but `calc_bandreps` in Crystalline.jl (following Cano et al.) computes
116120
# characters as `Tr(Θ_G D_k)` (not Θ_G†). To match, we compute `w† Θ_G D_k w`,
117121
# achieved by placing `conj(Θ_G)` in the conjugated slot of the dot product.
118122
Θᴳ_conj = reciprocal_translation_phase(orbital_positions(ptbm), -G) # = conj(Θᴳ)
119-
# NB: the `sgrep` functor computes `D_k(g) = e^{-2πi(gk)·v} ρ(h)` (physical Conv 1),
120-
# but `calc_bandreps` in Crystalline.jl uses the conjugated global phase
123+
# [⚠️ phase]: the `sgrep` functor computes `D_k(g) = e^{-2πi(gk)·v} ρ(h)` (physical
124+
# Conv 1), but `calc_bandreps` in Crystalline.jl uses the conjugated global phase
121125
# `e^{+2πi(gk)·v}` (cf. Crystalline.jl issue #12). To match, we conjugate the
122126
# global phase by multiplying by `e^{+4πi(gk)·v}` (flipping the sign of the
123127
# exponent).

0 commit comments

Comments
 (0)