Skip to content

Commit 216f17a

Browse files
committed
corner charge formula implementation
1 parent 3a2164a commit 216f17a

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed

src/Crystalline.jl

+3
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ export get_littlegroups, get_lgirreps, get_pgirreps, WyckPos, kvec, wyck, kstar
161161
include("grouprelations/grouprelations.jl")
162162
export maximal_subgroups, minimal_supergroups
163163

164+
include("corner_anomaly.jl")
165+
export corner_anomaly
166+
164167
# some functions are extensions of base-owned names; we need to (re)export them in order to
165168
# get the associated docstrings listed by Documeter.jl
166169
export position, inv

src/corner_anomaly.jl

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
struct ModdedLinearRelation{T}
2+
irlabs :: Vector{String}
3+
coefs :: Vector{T}
4+
ambiguity :: T
5+
end
6+
7+
struct LinearRelation{T}
8+
irlabs :: Vector{String}
9+
coefs :: Vector{T}
10+
equal :: T
11+
end
12+
function Base.:+(x::LinearRelation, y::LinearRelation)
13+
x.irlabs == y.irlabs || error("irrep labels must match")
14+
return LinearRelation(x.irlabs, x.coefs + y.coefs, x.equal + y.equal)
15+
end
16+
Base.:-(x::LinearRelation) = LinearRelation(x.irlabs, -x.coefs, -x.equal)
17+
Base.:-(x::LinearRelation, y::LinearRelation) = x + (-y)
18+
19+
function Base.show(io::IO, f::Union{ModdedLinearRelation, LinearRelation})
20+
# trivial case
21+
if all(iszero, f.coefs) && isone(f.ambiguity)
22+
print(io, "(mod 1)")
23+
return
24+
end
25+
26+
# prefactor
27+
prefactor = gcd(f.coefs)
28+
printed_prefactor = false
29+
if prefactor 1
30+
if denominator(prefactor) == 1
31+
print(io, numerator(prefactor))
32+
else
33+
print(io, "(", numerator(prefactor), "/", denominator(prefactor))
34+
end
35+
print(io, ")×(")
36+
printed_prefactor = true
37+
end
38+
39+
# components in formula
40+
first = true
41+
for (i, c) in enumerate(f.coefs)
42+
iszero(c) && continue
43+
if !first
44+
print(io, signbit(c) ? " - " : " + ")
45+
else
46+
print(io, signbit(c) ? "-" : "")
47+
first = false
48+
end
49+
c′ = c//prefactor
50+
n, d = numerator(c′), denominator(c′)
51+
if isone(d)
52+
if !isone(abs(n))
53+
print(io, abs(n))
54+
end
55+
else
56+
print(io, "(", abs(n), "/", d, ")")
57+
end
58+
print(io, f.irlabs[i])
59+
end
60+
printed_prefactor && print(io, ")")
61+
62+
# mod part
63+
if f isa ModdedLinearRelation
64+
print(io, " (mod ", numerator(f.ambiguity))
65+
if !isone(denominator(f.ambiguity))
66+
print(io, "/", denominator(f.ambiguity))
67+
end
68+
print(io, ")")
69+
elseif f isa LinearRelation
70+
print(io, " = ", f.equal)
71+
end
72+
end
73+
74+
75+
"""
76+
corner_anomaly(brs::BandRepSet, Qd::Dict{String, Rational{Int}})
77+
78+
Compute a formula for the corner anomaly ```Q``` in terms of the irrep multiplicities of a
79+
band representation set `brs`.
80+
The fractional corner anomaly assigned to a minimal set of bands occupying each individual
81+
Wyckoff position, typically obtained from an explicit counting argument, must be provided
82+
as a dictionary `Qd`.
83+
84+
## Extended description
85+
The corner anomaly gives the fractional integrated probability density (or, for photonic
86+
systems, normalized modal energy density) per symmetry-related sector of the unit cell
87+
(colloquially, per "corner"); for electronic systems, this quantity is a related to charge
88+
and is often called the ``corner charge''.
89+
90+
The construction of the corner anomaly formula using the algorithm described by
91+
> K. Naito, R. Takahashi, H. Watanabe, and S. Murakami, *Fractional hinge and corner charges
92+
> in various crystal shapes with cubic symmetry*,
93+
> [Phys. Rev. B **105**, 045126 (2022)](https://doi.org/10.1103/PhysRevB.105.045126).
94+
95+
## Example
96+
We may compute the corner anomaly formula in plane group 2:
97+
```jl
98+
julia> Qd = Dict("1a"=>0//1, "1b"=>0//1, "1c"=>0//1, "1d"=>1//2)
99+
julia> corner_anomaly(bandreps(2, 2), Qd)
100+
Q = (1/4)×(3Y₁ + 3B₁ + A₁ + Γ₁) (mod 1)
101+
```
102+
"""
103+
function corner_anomaly(brs::BandRepSet, Qd::Dict{String, Rational{Int}})
104+
relation_d = wyckoff_occupation(brs)
105+
coefs = sum(Qd[w]*relation.coefs for (w, relation) in relation_d)
106+
ambiguity_coefs = [Qd[w]*relation.ambiguity for (w, relation) in relation_d]
107+
108+
ambiguity = gcd(ambiguity_coefs)
109+
coefs .= evenmod.(coefs, ambiguity)
110+
111+
return ModdedLinearRelation(first(values(relation_d)).irlabs, coefs, ambiguity)
112+
end
113+
114+
function wyckoff_occupation(brs::BandRepSet)
115+
A = matrix(brs; includedim=true)
116+
Nⁱʳ, Nᴱᴮᴿ = size(A, 1), size(A, 2)
117+
irlabs = length(brs.irlabs) == Nⁱʳ ? brs.irlabs : vcat(brs.irlabs, "μ")
118+
119+
F = smith(A) # A = SΛT
120+
S⁻¹, T⁻¹ = F.Sinv, F.Tinv
121+
Λᵍ = diagm(Nᴱᴮᴿ, Nⁱʳ, map-> iszero(λ) ? 0//1 : 1//λ, F.SNF))
122+
Aᵍ = T⁻¹*Λᵍ*S⁻¹ # generalized inverse of A
123+
124+
# map global EBR-indices (i) to local Wyckoff position indices (w,α), with w as Dict
125+
# keys and α = 1,…,n as local, linear indices per Wyckoff position
126+
wα_idxs = Dict{String, Vector{Int}}()
127+
w_mults = Dict{String, Int}()
128+
for (i, br) in enumerate(brs)
129+
w = br.wyckpos
130+
if haskey(wα_idxs, w)
131+
push!(wα_idxs[w], i)
132+
else
133+
wα_idxs[w] = [i]
134+
w_mults[w] = parse(Int, first(w))
135+
end
136+
end
137+
wps = keys(wα_idxs)
138+
139+
siteir_dims_d = Dict{String, Dict{Int, Int}}() # dim(w|α)/multiplicity(w) = dim(ρʷ_α)
140+
for (w, αs) in wα_idxs
141+
siteir_dims_d[w] = Dict(i => convert(Int, brs[i][end]//w_mults[w]) for i in αs)
142+
end
143+
144+
# coefficients in the "occupation" formula
145+
δ(i,j) = i==j ? 1 : 0
146+
coefs_d = Dict(wp => zeros(Rational{Int}, Nⁱʳ) for wp in wps) # Q = coefs⋅n mod p
147+
ambiguity_coefs_d = Dict(wp => zeros(Int, Nᴱᴮᴿ) for wp in wps) # p = (..)⋅y
148+
149+
AᵍA = convert.(Int, Aᵍ*A) # integer elements by definition
150+
for (w, αs) in wα_idxs
151+
for j in 1:Nⁱʳ
152+
coefs_d[w][j] = sum(siteir_dims_d[w][i]*Aᵍ[i,j] for i in αs)
153+
end
154+
for k in 1:Nᴱᴮᴿ
155+
ambiguity_coefs_d[w][k] = sum(siteir_dims_d[w][i]*(-δ(i,k) + AᵍA[i,k]) for i in αs)
156+
end
157+
end
158+
159+
relation_d = Dict{String, ModdedLinearRelation}()
160+
for w in wps
161+
ambiguity = gcd(ambiguity_coefs_d[w])
162+
coefs′ = evenmod.(coefs_d[w], ambiguity) # reduce coefficient mod ambiguity
163+
relation_d[w] = ModdedLinearRelation(irlabs, coefs′, Rational(ambiguity))
164+
end
165+
return relation_d
166+
end
167+
168+
# analogous to `mod(x, r)`, but returns an answer in an even interval around 0, i.e. in
169+
# `]-r/2, r/2]` rather than in `[0, r[`
170+
function evenmod(x, r)
171+
y = mod(x, r)
172+
return y > r//2 ? y-r : y
173+
end
174+
175+
function compatibility_relations(F::Smith)
176+
S⁻¹ = F.Sinv
177+
dᵇˢ = count(!iszero, F.SNF)
178+
return S⁻¹[dᵇˢ+1:end,:]
179+
end
180+
181+
function pretty_compatibility_relations(brs)
182+
F = smith(matrix(brs; includedim=true))
183+
C = compatibility_relations(F)
184+
irlabs = vcat(brs.irlabs, "μ")
185+
return [LinearRelation(irlabs, Vector(r), 0) for r in eachrow(C)]
186+
end
187+
188+
189+
function compatibility_basis(F::Smith)
190+
# a basis of vectors that obey the compatibility relations; the non-empty null-space
191+
# of the compability relation matrix C
192+
C = compatibility_relations(F)
193+
Cᵍ = generalized_inv(C)
194+
nullC = convert.(Int, I-Cᵍ*C)
195+
return filter(!iszero, eachcol(nullC))
196+
end
197+
198+
function generalized_inv(X)
199+
F = smith(X)
200+
Λᵍ = diagm(size(X,2), size(X,1), [iszero(x) ? 0//1 : 1//x for x in F.SNF])
201+
return F.Tinv*Λᵍ*F.Sinv
202+
end

0 commit comments

Comments
 (0)