Skip to content

Commit b0a7e00

Browse files
thchrclaude
andcommitted
Fix centered-lattice symmetry analysis failures; all 230 SGs now pass
The remaining 7 failing SGs (68, 88, 141, 142, 214, 220, 230) were caused by `collect_compatible` primitivizing little groups with `modw=true`, which reduces translations mod the primitive lattice. For centered lattices, this discards lattice vectors that carry phase information: the phase factor exp(2πi(gk)·v) changes by exp(2πi(gk)·R) where R is the discarded vector and gk is generally non-integer at high-symmetry k-points. Fix: use Crystalline's `primitivize(::Collection{LGIrrep})` which passes `modw=false`, preserving unreduced translations for correct phase computation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 055fded commit b0a7e00

3 files changed

Lines changed: 58 additions & 32 deletions

File tree

PHASE6_NOTES.md

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,55 @@ Also added `using LinearAlgebra: dot` to berry.jl for the Haldane model comparis
163163
- Chern number tests: 27/27 PASS (including Haldane model comparison)
164164
- Symmetry analysis: same results as before (all pass except pre-existing centered failures)
165165

166-
### Next steps
166+
### Attempt 3: Fix centered-lattice failures via `primitivize(::Collection{LGIrrep})`
167+
168+
The remaining 7 failing SGs (68, 88, 141, 142, 214, 220, 230) were NOT pre-existing
169+
Crystalline.jl bugs — they were caused by a second bug in `collect_compatible`.
170+
171+
**Root cause:** `collect_compatible` primitivized the little groups via
172+
`primitivize(group(first(lgirs)))`, which calls `primitivize(g::LittleGroup)` with the
173+
default `modw=true`. This reduces translations modulo the primitive lattice. For centered
174+
lattices, the primitivization transforms conventional translations `v_conv` to primitive
175+
translations `P⁻¹ v_conv`, then reduces mod 1. The discarded lattice vector `R` changes
176+
the phase `e^{2πi(gk)·v}` by a factor `e^{2πi(gk)·R}`, which is ≠ 1 when `gk` is not an
177+
integer vector (i.e., at most non-Γ high-symmetry points).
178+
179+
**Concrete example:** SG 88, operation `{-4⁺₀₀₁|¼,¾,¾}` at the P-point `[½,½,½]`:
180+
- Conventional translation: `v = [1/4, 3/4, 3/4]`
181+
- Primitivized (full): `P⁻¹ v = [3/2, 1, 1]`
182+
- Primitivized (reduced mod 1): `[1/2, 0, 0]` — discards lattice vector `R = [1, 1, 0]`
183+
- `gk · R = -1/4`, giving phase error `exp(2πi × 0.25) = i`
184+
185+
**Fix:** Use Crystalline's `primitivize(::Collection{LGIrrep})` which internally calls
186+
`primitivize(g::LittleGroup, false)` with `modw=false`, preserving full (unreduced)
187+
translations. Changed `collect_compatible` from:
188+
```julia
189+
lgirsv = irreps(cbr)
190+
lgs = [primitivize(group(first(lgirs))) for lgirs in lgirsv]
191+
```
192+
to:
193+
```julia
194+
clgirsv = irreps(cbr)
195+
lgirsv = primitivize.(clgirsv)
196+
lgs = group.(lgirsv)
197+
```
198+
199+
**Result: all 230 SGs pass, all dimensions, all EBRs. Zero failures.**
200+
201+
## Summary of error classes and fixes
202+
203+
The symmetry analysis bug (PR #89) had three distinct error classes:
204+
205+
1. **Θ_G sign** — Used `Θ_G` instead of `conj(Θ_G)` to match `calc_bandreps`' trace
206+
convention. Fix: use `-G` in `reciprocal_translation_phase`. Resolved all 2D failures.
167207

168-
1. The pre-existing centered-lattice failures (SG 68, 88, 141, 142, 214, 220, 230) should
169-
be investigated separately — likely a Crystalline.jl issue with orbit/position handling
170-
for non-primitive lattices
171-
2. Consider whether the `SiteInducedSGRepElement` functor itself should be fixed (and
172-
constraint building updated) vs keeping the correction localized to `symmetry_eigenvalues`
173-
3. Run the full test suite to verify nothing else is broken
174-
4. Prepare a clean commit with just the `symmetry_analysis.jl` fix + test changes
208+
2. **Global phase** — The `SiteInducedSGRepElement` functor computes `e^{-2πi(gk)·v}`
209+
(physical Convention 1), but `calc_bandreps` uses `e^{+2πi(gk)·v}` (Crystalline.jl
210+
issue #12). Fix: multiply by `cispi(4dot(gk, v))` in `symmetry_eigenvalues`. Resolved
211+
3D failures at k-points with non-symmorphic operations.
175212

213+
3. **Translation reduction under primitivization**`primitivize(::LittleGroup)` with
214+
default `modw=true` discards lattice vectors from translations, corrupting phase factors
215+
`e^{2πi(gk)·v}` at non-Γ k-points in centered lattices. Fix: use
216+
`primitivize(::Collection{LGIrrep})` which passes `modw=false`. Resolved all remaining
217+
centered-lattice failures (SG 68, 88, 141, 142, 214, 220, 230).

src/symmetry_analysis.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ function Crystalline.collect_compatible(
3939
isempty(tbm.terms) && error("`ptbm` is an empty tight-binding model")
4040
cbr = CompositeBandRep(tbm)
4141

42-
lgirsv = irreps(cbr) # get irreps associated to the EBRs
43-
lgs = [primitivize(group(first(lgirs))) for lgirs in lgirsv]
42+
clgirsv = irreps(cbr) # irreps associated to the EBRs (conventional setting operations)
43+
lgirsv = primitivize.(clgirsv)
44+
lgs = group.(lgirsv) # little groups associated to the EBRs (primitive setting)
4445
ops = unique(Iterators.flatten(lgs))
4546

4647
# determine the induced space group rep associated with `cbr` across all `ops`
@@ -156,7 +157,8 @@ Useful for annotating irrep labels in band structure plots (via the Makie extens
156157
`plot(ks, energies; annotations=collect_irrep_annotations(ptbm))`)
157158
"""
158159
function Crystalline.collect_irrep_annotations(ptbm::ParameterizedTightBindingModel; kws...)
159-
lgirsv = irreps(ptbm.tbm.cbr) # get irreps associated to the EBRs
160-
symeigsv = [eachcol(symmetry_eigenvalues(ptbm, primitivize(group(lgirs)))) for lgirs in lgirsv]
160+
clgirsv = irreps(ptbm.tbm.cbr) # irreps associated to the EBRs (conventional setting)
161+
lgirsv = primitivize.(clgirsv) # convert associated groups & irreps to primitive setting
162+
symeigsv = [eachcol(symmetry_eigenvalues(ptbm, group(lgirs))) for lgirs in lgirsv]
161163
return collect_irrep_annotations(symeigsv, lgirsv; kws...)
162164
end

test/symmetry_analysis.jl

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,6 @@ using SymmetricTightBinding
44
using Crystalline
55
using Crystalline: free
66

7-
# pre-existing failures in centered lattices (see devdocs/symmetry_eigenvalue_conventions.md)
8-
const KNOWN_FAILURES = Dict(
9-
# SG => Set of broken EBR indices
10-
68 => Set(1:14), # Ccca: all EBRs fail (C-centered)
11-
88 => Set([5, 6, 8, 9]), # I4₁/a: 4a and 4b positions
12-
141 => Set([9,10,11,12,14,15,16,17]), # I4₁/amd: 4a and 4b (excl. E irreps)
13-
142 => Set([1, 2, 5, 8]), # I4₁/acd: 16e and 8b positions
14-
214 => Set([1,4,5,8,9,10,12,13]), # I4₁32: 12c, 12d, 8a, 8b positions
15-
220 => Set([3, 4, 6, 7]), # I-43d: 12a and 12b positions
16-
230 => Set([4, 7, 8, 9]), # Ia-3d: 24c and 16b positions
17-
)
18-
197
function _test_symmetry_analysis(brs, i, αβγ; rng_seed=1234)
208
coefs = zeros(length(brs))
219
coefs[i] = 1
@@ -28,7 +16,6 @@ function _test_symmetry_analysis(brs, i, αβγ; rng_seed=1234)
2816
return !isempty(ns) && sum(ns) == SymmetryVector(cbr)
2917
end
3018

31-
max_sgnum_3d = 230 # NB: set low if exploring failures: "high" `sgnum`s take a lot of time
3219
@testset "Symmetry analysis (full scan over EBRs)" begin
3320
# construct a tb-model for every EBR and verify that `collect_compatible` returns a set
3421
# of symmetry vectors whose sum is equal to the underlying EBR's: note that we cannot
@@ -38,16 +25,11 @@ max_sgnum_3d = 230 # NB: set low if exploring failures: "high" `sgnum`s take a l
3825
# connected band or not, only what the "sum" of symmetry vectors will be
3926
for D in 1:3
4027
αβγ = D == 1 ? [.1] : D == 2 ? [.1, .2] : [.1, .2, .3] # for `pin_free!`
41-
for sgnum in 1:min(MAX_SGNUM[D], max_sgnum_3d)
28+
for sgnum in MAX_SGNUM[D]
4229
@testset "Space group $sgnum in dimension $D" begin
4330
brs = calc_bandreps(sgnum, Val(D))
4431
for i in eachindex(brs)
45-
is_known_failure = i in get(KNOWN_FAILURES, sgnum, Set{Int}())
46-
if is_known_failure
47-
@test_broken _test_symmetry_analysis(brs, i, αβγ)
48-
else
49-
@test _test_symmetry_analysis(brs, i, αβγ)
50-
end
32+
@test _test_symmetry_analysis(brs, i, αβγ)
5133
end
5234
end
5335
end

0 commit comments

Comments
 (0)