Skip to content

Commit cee7aa2

Browse files
committed
Updates and new methods associated with BandGraphs work (#49)
This merges work from the BandGraphs branch (bandpaths-data), that related directly to Crystalline. Mainly, this is involves a handful of new or revised features: - `remap_to_kstar`: obtain `lgirreps` at another k-point in the star of the original k-point (exported) - `conjugacy_relations`: obtain transformations between the settings of sub/supergroups (exported) - `can_intersect`: check whether two `AbstractVec` can intersect, for some values of their free parameters (not exported). New implementation is more robust than previous, e.g., allowing more generic kinds of intersections, and return more information about the intersection. There are small minor nits/improvements/fixes/extensions to existing functionality. ----- * wip: crawl and parse k-space connectivity data * WIP: BandGraphs work * progress on BandGraphs; working as a separate module - graph plotting via extension system * fixes to BandGraphsGraphMakie extension * holdover updates to BandBraphs stuff * aggregate updates to BandGraphs implementation; mostly groundwork but some physics and preliminary results also * use a proper prime unicode character for BandRep sitesym irrep labels (to be consistent with `mulliken`) * fix bug in chinese postman single-vertex handling * nits * add `cosets` function for computing a set of coset representatives * fix a bug in `rotation_axis_3d` that could cause failures on certain axis orientations * add `conjugacy_relations` - returns the set of possible transformations between sub or supergraphs, exploring all possible paths through the conjugacy classes and sub/supergraph structure * add a note about testing `BandRep` site-symmetry group labels; should probably be an issue * fix doctest syntax * more fixes to broken doctest * add careful subgroup checks to the weyl identification scheme, to exploit subgroup relations - this does quite an involved set of checks to carefully understand how a given irrep may be subduced into irreps of a subgroup; the tricky thing is to keep in mind that the subduction might involve a basis change (of the k-point, the little group operations; it may even impact the irreps of nonsymmorphic cases) - eventually, much of this should be factored out into separate methods since it is not specific to Weyl points - also adds some graph construction to visualize the relationships between base * comment-nit * fix typo for Y `LGIrrep`s of plane group 7 - bad copy-paste typo in `build/setup_2d_littlegroup_irreps_nonzymmorph.jl` that meant we had never actually recorded the Y-irreps of plane group 7 (p2mg) - update the irrep data for plane groups to fix this - update our calculated `BandRepSet`s for plane group 7 as well - fortunately, this omission does not change what is inferred about the possible symmetry-detectable topology in plane group 7: e.g., there are still no fragile phases detectable after the correction. * bump version to v0.5.2 * minor consistency and clarity nits to code; non-functional changes * make the free parameter of Δ in plane group 12 "u" rather than "v" for consistency with other plane group irreps * fix `compose(::SymOperation, ::KVec)` (fixes #57) * fix small typo in `littlegroup` * add `cosets` function for computing a set of coset representatives * more fixes to broken doctest * allow `KVec` and `RVec` to feature basic use of multiplication signs before free variables - this is in order to parse formats from Bilbao more correctly in general * update crawled 3D ½k-space connectivity data: `connectionsd(-tr).jld2` and `subductionsd(-tr).jld2` - this fixes issues for space groups 143, 147, 149, 150, 156, 157, 162, 164, 168, 174, 175, 177, 183, 187, 189, 191 where the parsed `KVec` was previously wrong (because Bilbao included a multiplication `*` sign in their **k**-vector listings for these groups, which we didn't parse correctly; we do now, cf. 03a9e315304c44085d508f4ddbc06913d4cd3954). - additionally, it seems that Bilbao has been updated to include several (for some ~151 groups in TR-invariant cases, e.g.,) additional monodromy-related points in their k-connectivity listings of nonsymmorphic groups; we now included these as well. * merge `_can_intersect` and `is_compatible` to `is_compatible` and return info about _how_ they are compatible - also make a bit more general * fix `compose(::SymOperation, ::KVec)` (fixes #57) * correct parsing & ensure roundtrippability of `KVec` and `RVec` for fractions in free part * move `is_compatible` to `/src/compability.jl` and rename it `can_intersect` - also various improvements to generality and type-stability; can now be used whole-sale. * fix rollback from accidental overriding merge * add `IrrepCollection` type to improve treatment (e.g., `show`) of collections of `AbstractIrrep`s - also improve associated `show` methods * update README.md * add an in-place `realify!` method working on `AbstractDict`s to simplify "adding" time-reversal to a dict of `LGIrrep`s - export `realify!` as well * fix a typo-bug in arithmetic (`+` & `-`) between `AbstractVector`s and `AbstractVec`s * implement (unexported) `remap_lgirreps_to_point_in_kstar`, which gives the computes the set of `LGIrrep`s associated with a point in the star of an input set of `LGIrrep`s - TODO: tests and docstring; only used in BandGraphs atm * leftover: use `realify!` in a spot * aggregate commit to `BandGraphs` utility: myriad changes, fixes, improvements, and new functionality * fixes after merge - fixes a few real bugs from merge, but mainly avoids a set of pointless differences with master branch * nit to nit * use the new `SymmetryVector` from Crystalline instead of old `SymVector` from BandGraphs * drop vendored copy of `eulerian` (now in Graphs.jl) * add fast-path checks for subset-sum checks - speeds up `solve_subset_sum_variant` dramatically for most cases - also improve how `Model` is instantiated, to save time on this * improve type-stability of loading subduction tables in `__init__` & fix two issues with BCS band-paths data - we now load a list of corrections into the tabulated band-paths data (corrections stored in `src/subduction-table-corrections.jl`) * BIG chunk of updates - this commits a large number of updates to the BandGraphs code; many aspects have received corrections, very aggressive performance optimizations (e.g., work-arrays), as well as algorithmic improvements (e.g., checks of articulation point), and many changes to the types - we also now do a much more general job of splitting degeneracies: in particular, we now allow splitting of degeneracies connected to non-nondegenerate nonmaximal irreps; this required multiset permutations. - the main point of entry at the moment is in `test/scan-separable-irreps.jl` * a nit to Crystalline's `test/calc_bandreps.jl`: special casing no longer needed (#59 has been resolved earlier) * aggregate updates to BandGraphs - correctness fixes - improvements to structs, paving way to recursive permutation walk work and for photonic band connectivity work - ahead of time/commit changes in preparation for changes/additions to Crystalline * implement functionality necessary to analyze photonic band graphs with singular zero-frequency content - adds a weak dep on PhotonicBandConnectivity.jl * implement a recursive approach to searching the space of graph permutations - this exploits the fact that every (appropriate, with some structural notions) induced subgraph is separable only if the original graph is * remove all code associated with BandGraphs.jl; moving to separate repo * add doc strings to new functionality and set Crystalline version to 0.6.8 * improve documentation of conjugacy_relations; previously had bugged jldoctest
1 parent d650171 commit cee7aa2

File tree

15 files changed

+436
-102
lines changed

15 files changed

+436
-102
lines changed

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "Crystalline"
22
uuid = "ae5e2be0-a263-11e9-351e-f94dad1eb351"
33
authors = ["Thomas Christensen <thomas@dtu.dk>"]
4-
version = "0.6.7"
4+
version = "0.6.8"
55

66
[deps]
77
Bravais = "ada6cbde-b013-4edf-aa94-f6abe8bd6e6b"
@@ -34,9 +34,9 @@ Combinatorics = "1.0"
3434
DelimitedFiles = "1"
3535
DocStringExtensions = "0.8, 0.9"
3636
GraphMakie = "0.5"
37-
Graphs = "1.7"
37+
Graphs = "1.10"
3838
JLD2 = "0.5"
39-
LayeredLayouts = "0.2.5"
39+
LayeredLayouts = "0.2.8"
4040
Meshing = "0.5, 0.6"
4141
PrettyTables = "2"
4242
PyPlot = "2"

src/Crystalline.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ include("symops.jl") # symmetry operations for space, plane, and line groups
118118
export @S_str, compose,
119119
issymmorph, littlegroup, orbit,
120120
reduce_ops,
121-
issubgroup, isnormal
121+
issubgroup, isnormal,
122+
cosets
122123

123124
include("conjugacy.jl") # construction of conjugacy classes
124125
export classes, is_abelian
@@ -160,7 +161,7 @@ export ModulatedFourierLattice,
160161
modulate, normscale, normscale!
161162

162163
include("compatibility.jl")
163-
export subduction_count
164+
export subduction_count, remap_to_kstar
164165

165166
include("bandrep.jl")
166167
export bandreps, classification, nontrivial_factors, basisdim
@@ -172,7 +173,7 @@ include("deprecations.jl")
172173
export get_littlegroups, get_lgirreps, get_pgirreps, WyckPos, kvec, wyck, kstar
173174

174175
include("grouprelations/grouprelations.jl")
175-
export maximal_subgroups, minimal_supergroups
176+
export maximal_subgroups, minimal_supergroups, conjugacy_relations
176177

177178
# some functions are extensions of base-owned names; we need to (re)export them in order to
178179
# get the associated docstrings listed by Documeter.jl

src/compatibility.jl

Lines changed: 198 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ With following enterpretatation for compatibility relations between irreps at Γ
4444
where, in this case, all the small irreps are one-dimensional.
4545
"""
4646
function subduction_count(Dᴳᵢ::T, Dᴴⱼ::T,
47-
αβγᴴⱼ::Union{Vector{<:Real},Nothing}=nothing) where T<:AbstractIrrep
47+
αβγᴴⱼ::Union{<:AbstractVector{<:Real},Nothing}=nothing
48+
) where T<:AbstractIrrep
4849
# find matching operations between H & G and verify that H<G
4950
boolsubgroup, idxsᴳ²ᴴ = _findsubgroup(operations(Dᴳᵢ), operations(Dᴴⱼ))
5051
!boolsubgroup && throw(DomainError("Provided irreps are not H<G subgroups"))
@@ -77,13 +78,13 @@ end
7778
"""
7879
$(TYPEDSIGNATURES)
7980
"""
80-
function find_compatible(kv::KVec{D}, kvs′::Vector{KVec{D}}) where D
81+
function find_compatible(kv::KVec{D}, kvs′::AbstractVector{KVec{D}}) where D
8182
isspecial(kv) || throw(DomainError(kv, "input kv must be a special k-point"))
8283

83-
compat_idxs = Vector{Int}()
84+
compat_idxs = Int[]
8485
@inbounds for (idx′, kv′) in enumerate(kvs′)
8586
isspecial(kv′) && continue # must be a line/plane/general point to match a special point kv
86-
is_compatible(kv, kv′) && push!(compat_idxs, idx′)
87+
can_intersect(kv, kv′).bool && push!(compat_idxs, idx′)
8788
end
8889

8990
return compat_idxs
@@ -92,40 +93,204 @@ end
9293
"""
9394
$(TYPEDSIGNATURES)
9495
95-
Check whether a special k-point `kv` is compatible with a non-special k-point `kv′`. Note
96-
that, in general, this is only meaningful if the basis of `kv` and `kv′` is primitive.
96+
Check whether two `AbstractVec`s `v` and `v′` can intersect, i.e., whether there exist free
97+
parameters such that they are equivalent modulo an integer lattice vector.
9798
98-
TODO: This method should eventually be merged with the equivalently named method in
99-
PhotonicBandConnectivity/src/connectivity.jl, which handles everything more correctly,
100-
but currently has a slightly incompatible API.
101-
"""
102-
function is_compatible(kv::KVec{D}, kv′::KVec{D}) where D
103-
isspecial(kv) || throw(DomainError(kv, "must be special"))
104-
isspecial(kv′) && return false
99+
Returns a `NamedTuple` `(; bool, αβγ, αβγ′, L)`. If `bool = true`, `kv` and `kv′` are
100+
compatible in the sense that `v(αβγ) == v′(αβγ′) + L` if `bool == true` where `L` is
101+
an integer-valued lattice vector.
102+
If `bool = false`, they are incompatible (and zero-valued vectors are returned for `αβγ`,
103+
`αβγ′`, and `L`).
104+
105+
## Extended help
105106
106-
return _can_intersect(kv′, kv)
107-
# TODO: We need some way to also get the intersection αβγ and reciprocal lattice vector
108-
# difference, if any.
107+
- The keyword argument `atol` (default, $DEFAULT_ATOL) specifies the absolute tolerance for
108+
the comparison.
109+
- If both `v` and `v′` are not special, i.e., both have free parameters, the intersection
110+
point may not be unique (e.g., for co-linear `v` and `v′`).
111+
- The implementation currently only checks the immediately adjacent lattice vectors for
112+
equivalence; if there is equivalence, but the the required elements of `L` would have
113+
`|Lᵢ| > 1`, the currently implementation will not identify the equivalence.
114+
- This operation is usually only meaningful if the bases of `kv` and `kv′` agree and are
115+
primitive.
116+
"""
117+
function can_intersect(v::T, v′::T; atol::Real=DEFAULT_ATOL) where T<:AbstractVec{D} where D
118+
# check if solution exists to [A] v′ = v(αβγ) or [B] v′(αβγ′) = v(αβγ) by solving
119+
# a least squares problem and then checking if it is a strict solution. Details:
120+
# Let v(αβγ) = v₀ + V*αβγ and v′(αβγ′) = v₀′ + V′*αβγ′
121+
# [A] v₀′ = v₀ + V*αβγ ⇔ V*αβγ = v₀′-v₀
122+
# [B] v₀′ + V′*αβγ′ = v₀ + V*αβγ ⇔ V*αβγ - V′*αβγ′ = v₀′-v₀
123+
# ⇔ hcat(V,-V′)*vcat(αβγ,αβγ′) = v₀′-v₀
124+
# these equations can always be solved in the least squares sense using the
125+
# pseudoinverse; we can then subsequently check if the residual of that solution is in
126+
# fact zero, in which can the least squares solution is a "proper" solution, signaling
127+
# that `v` and `v′` can intersect (at the found values of `αβγ` and `αβγ′`)
128+
Δcnst = constant(v′) - constant(v)
129+
if isspecial(v′)
130+
Δfree = free(v) # D×D matrix
131+
return _can_intersect_equivalence_check(Δcnst, Δfree, atol, false)
132+
elseif isspecial(v)
133+
Δfree = -free(v′) # D×D matrix
134+
return _can_intersect_equivalence_check(Δcnst, Δfree, atol, true)
135+
else # neither `v′` nor `v` are special
136+
Δfree = hcat(free(v), -free(v′)) # D×2D matrix
137+
return _can_intersect_equivalence_check(Δcnst, Δfree, atol, false)
138+
end
139+
# NB: the above seemingly trivial splitting of return statements is intentional & to
140+
# avoid type-instability (because the type of `Δfree` differs in the brances)
109141
end
110142

111-
#=
112-
function compatibility_matrix(brs::BandRepSet)
113-
lgirs_in, lgirs_out = matching_lgirreps(brs::BandRepSet)
114-
for (iᴳ, Dᴳᵢ) in enumerate(lgirs_in) # super groups
115-
for (jᴴ, Dᴴⱼ) in enumerate(lgirs_out) # sub groups
116-
# we ought to only check this on a per-kvec basis instead of
117-
# on a per-lgir basis to avoid redunant checks, but can't be asked...
118-
compat_bool = is_compatible(position(Dᴳᵢ), position(Dᴴⱼ))
119-
# TODO: Get associated (αβγ, G) "matching" values that makes kvⱼ and kvᵢ and
120-
# compatible; use to get correct lgirs at their "intersection".
121-
if compat_bool
122-
nᴳᴴᵢⱼ = subduction_count(Dᴳᵢ, Dᴴⱼ, αβγ)
123-
if !iszero(nᴳᴴᵢⱼ)
124-
# TODO: more complicated than I thought: have to match across different
125-
special lgirreps.
126-
end
143+
function _can_intersect_equivalence_check(Δcnst::StaticVector{D}, Δfree::StaticMatrix{D},
144+
atol::Real, inverted_order::Bool=false) where D
145+
# to be safe, we have to check for equivalence between `v` and `v′` while accounting
146+
# for the fact that they could differ by a lattice vector; in practice, for the wyckoff
147+
# listings that we have have in 3D, this seems to only make a difference in a single
148+
# case (SG 130, wyckoff position 8f) - but there the distinction is actually needed
149+
Δfree⁻¹ = pinv(Δfree)
150+
for _L in Iterators.product(ntuple(_->(0, -1, 1), Val(D))...) # loop over adjacent lattice vectors
151+
L = SVector{D,Int}(_L)
152+
Δcnst_plus_L = Δcnst + L
153+
_αβγ = Δfree⁻¹*Δcnst_plus_L # either `D`-dim `αβγ` or `2D`-dim `vcat(αβγ, αβγ′)`
154+
Δ = Δcnst_plus_L - Δfree*_αβγ # residual of least squares solve
155+
if norm(Δ) < atol
156+
if length(_αβγ) == D
157+
αβγ = _αβγ
158+
αβγ′ = zero(αβγ)
159+
if inverted_order
160+
αβγ, αβγ′ = αβγ′, αβγ
161+
end
162+
else # size(_αβγ, 2) == 2D
163+
αβγ = _αβγ[SOneTo{D}()]
164+
αβγ′ = _αβγ[StaticArrays.SUnitRange{D+1,D}()]
127165
end
166+
return (; bool=true, αβγ=αβγ, αβγ′=αβγ′, L=L)
128167
end
129168
end
169+
sentinel = zero(SVector{D, Int})
170+
return (; bool=false, αβγ=sentinel, αβγ′=sentinel, L=sentinel)
130171
end
131-
=#
172+
173+
"""
174+
remap_to_kstar(
175+
lgirs::AbstractVector{LGIrrep{D}},
176+
kv′::KVec{D},
177+
coset_representatives::AbstractVector{SymOperation{D}}
178+
) --> Collection{LGIrrep{D}}
179+
180+
Given an set of `LGIrrep`s `lgirs` defined at a **k**-vector `kv`, remap the irrep data to
181+
a different **k**-vector `kv′` in the star of `kv`.
182+
183+
The remapping is done by identifying an operation `g` s.t. `kv′ = g * kv` with `g` in the
184+
space group of `lgirs` (more precisely, from among the coset representatives of the little
185+
group in the space group). The original irreps ``D(h)`` with ``h`` in the little group of
186+
`kv` are then transformed according to ``D′(h′) = D(h) = D(g⁻¹h′g)`` with ``h′`` from the
187+
little group of `kv′`.
188+
189+
The coset representatives can be specified as an optional argument, to avoid repeated
190+
recomputation and simplify the associated computation of `g`. The coset representatives
191+
generate the star of `kv`.
192+
"""
193+
function remap_to_kstar(
194+
lgirs::AbstractVector{LGIrrep{D}},
195+
kv′::KVec{D},
196+
coset_representatives::AbstractVector{SymOperation{D}} =
197+
cosets(reduce_ops(spacegroup(num(first(lgirs)), Val{D}()),
198+
centering(num(first(lgirs)))),
199+
group(first(lgirs)))
200+
) where D
201+
202+
kv = position(first(lgirs))
203+
kv′ == kv && return Collection(lgirs)
204+
205+
# feasibility checks
206+
if freeparams(kv) != freeparams(kv′)
207+
error(lazy"kv=$kv and kv′=$kv′ do not have the same free parameters")
208+
end
209+
special_bool = isspecial(kv)
210+
211+
# check if `kv′` is in the star of `kv`
212+
kv_star = map(coset_representatives) do g # compute {star(k)}
213+
g * kv
214+
end
215+
idx = begin
216+
idx′ = findfirst((kv′), kv_star)
217+
if !isnothing(idx′)
218+
# as first priority, we return an exact match if it exists
219+
idx′
220+
else
221+
# otherwise, we look for any compatible match
222+
findfirst(kv_star) do kv′′
223+
if special_bool
224+
can_intersect(kv′′, kv′).bool
225+
else
226+
# nonspecial pts: check if parallel & possibly separated by a reciprocal
227+
# vector; this is slightly more annoying because we might then have to deal
228+
# later with a nonzero reciprocal vector
229+
(kv′′.free == kv′.free || kv′′.free == -kv′.free) &&
230+
all(isinteger, kv′′.cnst - kv′.cnst)
231+
end
232+
end
233+
end
234+
end
235+
isnothing(idx) && error(lazy"kv′=$kv′ is not compatible with any element in star(k)=$kv_star")
236+
g = coset_representatives[something(idx)] # g ∘ kv = kv′
237+
238+
# remap irrep operations according to D′(h′) = D(h) = D(g⁻¹h′g), (w/ D referencing
239+
# `kv′`, and D′ referencing `kv`). I.e., we have h = g⁻¹h′g s.t. h′ = ghg⁻¹
240+
lg = group(first(lgirs))
241+
ops′ = similar(operations(lg));
242+
for (idx, h) in enumerate(lg)
243+
h′ = compose(g, compose(h, inv(g), #=modτ=#false), #=modτ=#false)
244+
ops′[idx] = h′
245+
end
246+
lg′ = LittleGroup{D}(num(lg), kv′, klabel(lg), ops′)
247+
248+
# build provisional `LGIrrep`s with above operator-sorting
249+
lgirs′ = map(lgirs) do lgir
250+
matrices = [copy(m) for m in lgir.matrices]
251+
translations = [rotation(g) * τ for τ in lgir.translations] # see (⋆)
252+
# (⋆) note the rotation of the translation vector: this is necessary since the
253+
# translation part of the original irrep has a form exp(ik⋅τ) - and in the new
254+
# setting, it needs to be in the form exp(ik′⋅τ′) but to agree with the original
255+
# phase for every free parameter, i.e., we need exp(ik⋅τ)=exp(ik′⋅τ′). Since
256+
# k′(G) = g(R)⁻¹ᵀk(G), this translates to the requirement that
257+
# τ′(R) = rotation(g)(R)τ(R).
258+
LGIrrep{D}(lgir.cdml, lg′, matrices, translations, lgir.reality, lgir.iscorep)
259+
end
260+
261+
# if the equivalence between kv and kv′ involves a nonzero G-vector, _AND_ if the
262+
# any of `lgirs` depends on k explicitly, i.e., if any τ∈`translations.(lgirs)` are
263+
# nonzero, and this dependence could impart a dependence on the nonzero G-vector,
264+
# i.e., if exp(2πik(αβγ)⋅τ) could actually vary with αβγ, we need to revise the irrep
265+
# data accordingly, to account for the new "starting point"; for now, we don't do this,
266+
# but just throw an error to be conservative. To figure out if we're in this case,
267+
# recall that k(αβγ) = constant(k) + free(k)⋅αβγ, so that the αβγ-dependent part of the
268+
# phase factor depends on a term αβγ ⋅ free(k)ᵀτ - so if free(k)ᵀτ is zero, we are safe
269+
# TODO: Try to actually do this without failing; should be possible if we decompose G
270+
# into parts that are ∥/⟂ to free(k)ᵀτ (only parallel parts matter)
271+
ΔG = kv_star[something(idx)].cnst - kv′.cnst
272+
if (!special_bool &&
273+
norm(ΔG) > DEFAULT_ATOL &&
274+
any(lgir ->
275+
any-> norm(transpose(free(kv)) * τ) > DEFAULT_ATOL, lgir.translations),
276+
lgirs)
277+
)
278+
error("nonzero reciprocal vector between nonspecial kv and kv′ requires explicit handling: not yet implemented")
279+
end
280+
281+
# the remapped little group `lg′` might have translations that are not in a primitive
282+
# setting; that's not great for comparison with tables later, so we need to change the
283+
# irreps accordingly now. This may affect the irreps of nonsymmorphic groups. We correct
284+
# by effectivly "multiplying" - via the translations term - with a suitable Bloch phase
285+
lg′_reduced = reduce_ops(lg′, centering(num(lg′), D))
286+
for i in eachindex(lg′)
287+
Δt = translation(lg′_reduced[i]) - translation(lg′[i])
288+
norm(Δt) > Crystalline.DEFAULT_ATOL || continue
289+
for lgir′ in lgirs′
290+
lgir′.translations[i] += Δt
291+
end
292+
lg′.operations[i] = lg′_reduced[i]
293+
end
294+
295+
return Collection(lgirs′)
296+
end

0 commit comments

Comments
 (0)