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