diff --git a/docs/oscar_references.bib b/docs/oscar_references.bib index a0173fbb0b00..fd02b3133020 100644 --- a/docs/oscar_references.bib +++ b/docs/oscar_references.bib @@ -1785,6 +1785,18 @@ @Misc{Stacks url = {https://stacks.math.columbia.edu/} } +@InCollection{Ste01, + author = {Stembridge, John R.}, + title = {Computational aspects of root systems, {C}oxeter groups, and {W}eyl characters}, + booktitle = {Interaction of combinatorics and representation theory}, + series = {MSJ Memoirs}, + volume = {11}, + publisher = {The Mathematical Society of Japan}, + pages = {1--38}, + year = {2001}, + doi = {10.2969/msjmemoirs/01101C010} +} + @InCollection{Ste91, author = {Stevens, Jan}, title = {On the versal deformation of cyclic quotient singularities}, diff --git a/experimental/LieAlgebras/src/CartanMatrix.jl b/experimental/LieAlgebras/src/CartanMatrix.jl index 268bc194eafb..e887ff3c6beb 100644 --- a/experimental/LieAlgebras/src/CartanMatrix.jl +++ b/experimental/LieAlgebras/src/CartanMatrix.jl @@ -181,38 +181,46 @@ function cartan_symmetrizer(gcm::ZZMatrix; check::Bool=true) undone[plan[head]] = false end + prev = head i = plan[head] for j in 1:rk if i == j continue end - if undone[j] && !is_zero_entry(gcm, i, j) - head += 1 - plan[head] = j - undone[j] = false - - if diag[i] * gcm[i, j] == diag[j] * gcm[j, i] - continue - elseif gcm[i, j] == gcm[j, i] - diag[i] = lcm(diag[i], diag[j]) - diag[j] = diag[i] - continue - end + if !undone[j] || is_zero_entry(gcm, i, j) + continue + end + + head += 1 + plan[head] = j + undone[j] = false + + if diag[i] * gcm[i, j] == diag[j] * gcm[j, i] + continue + elseif gcm[i, j] == gcm[j, i] + diag[i] = lcm(diag[i], diag[j]) + diag[j] = diag[i] + continue + end - if gcm[j, i] < -1 + if gcm[j, i] < -1 + tail += 1 + v = -gcm[j, i] + while tail < head + diag[plan[tail]] *= v tail += 1 - v = -gcm[j, i] - while tail < head - diag[plan[tail]] *= v - tail += 1 - end - end - if gcm[i, j] < -1 - diag[j] *= -gcm[i, j] - tail = head - 1 end end + if gcm[i, j] < -1 + diag[j] *= -gcm[i, j] + tail = head - 1 + end + end + + # we found new roots, meaning we are done with this component of the root system + if prev == head + tail = head end end diff --git a/experimental/LieAlgebras/src/CoxeterGroup.jl b/experimental/LieAlgebras/src/CoxeterGroup.jl index 2c4e870ef4b8..80c7833e0f6b 100644 --- a/experimental/LieAlgebras/src/CoxeterGroup.jl +++ b/experimental/LieAlgebras/src/CoxeterGroup.jl @@ -1,4 +1,4 @@ -abstract type CoxeterGroup end # <: Group +abstract type CoxeterGroup end #function is_coxeter_matrix(M::ZZMatrix) end diff --git a/experimental/LieAlgebras/src/LieAlgebras.jl b/experimental/LieAlgebras/src/LieAlgebras.jl index d77cf6a5e198..cf56a7f3ea85 100644 --- a/experimental/LieAlgebras/src/LieAlgebras.jl +++ b/experimental/LieAlgebras/src/LieAlgebras.jl @@ -5,7 +5,9 @@ module LieAlgebras using ..Oscar -import Oscar: GAPWrap, IntegerUnion, MapHeader +import Oscar: GAPWrap, GroupsCore, IntegerUnion, MapHeader + +import Random # not importet in Oscar using AbstractAlgebra: CacheDictType, ProductIterator, get_cached!, ordinal_number_string @@ -62,6 +64,7 @@ import ..Oscar: matrix, ngens, normalizer, + order, parent_type, rank, root, @@ -91,6 +94,7 @@ export RootSpaceElem export RootSystem export WeightLatticeElem export WeylGroup, WeylGroupElem +export WeylOrbitIterator export abelian_lie_algebra export abstract_module @@ -107,6 +111,7 @@ export coefficient_vector export coerce_to_lie_algebra_elem export combinations export conjugate_dominant_weight +export conjugate_dominant_weight_with_elem export coroot export coroots export coxeter_matrix @@ -122,9 +127,10 @@ export induced_map_on_symmetric_power export induced_map_on_tensor_power export is_cartan_matrix export is_cartan_type +export is_coroot_with_index export is_direct_sum +export is_dominant export is_dual -export is_coroot_with_index export is_negative_coroot_with_index export is_negative_root_with_index export is_positive_coroot_with_index @@ -136,8 +142,9 @@ export is_simple_root_with_index export is_standard_module export is_symmetric_power export is_tensor_power +export is_tensor_product export lie_algebra -export lmul! +export lmul, lmul! export longest_element export lower_central_series export matrix_repr_basis @@ -174,6 +181,7 @@ export tensor_product_decomposition export trivial_module export universal_enveloping_algebra export weyl_group +export weyl_orbit export word include("Combinatorics.jl") @@ -213,6 +221,7 @@ export RootSpaceElem export RootSystem export WeightLatticeElem export WeylGroup, WeylGroupElem +export WeylOrbitIterator export abelian_lie_algebra export abstract_module @@ -227,6 +236,7 @@ export cartan_type_with_ordering export chevalley_basis export coerce_to_lie_algebra_elem export conjugate_dominant_weight +export conjugate_dominant_weight_with_elem export coroot export coroots export coxeter_matrix @@ -242,9 +252,10 @@ export induced_map_on_symmetric_power export induced_map_on_tensor_power export is_cartan_matrix export is_cartan_type +export is_coroot_with_index export is_direct_sum +export is_dominant export is_dual -export is_coroot_with_index export is_negative_coroot_with_index export is_negative_root_with_index export is_positive_coroot_with_index @@ -258,11 +269,10 @@ export is_symmetric_power export is_tensor_power export is_tensor_product export lie_algebra -export lmul! +export lmul, lmul! export longest_element export lower_central_series export matrix_repr_basis -export matrix_repr_basis export negative_coroot export negative_coroots export negative_root @@ -295,4 +305,5 @@ export tensor_product_decomposition export trivial_module export universal_enveloping_algebra export weyl_group +export weyl_orbit export word diff --git a/experimental/LieAlgebras/src/RootSystem.jl b/experimental/LieAlgebras/src/RootSystem.jl index 7d66469cb348..9372537e94b3 100644 --- a/experimental/LieAlgebras/src/RootSystem.jl +++ b/experimental/LieAlgebras/src/RootSystem.jl @@ -344,6 +344,10 @@ function simple_roots(R::RootSystem) return positive_roots(R)[1:rank(R)] end +function type(R::RootSystem) + return R.type +end + @doc raw""" simple_coroot(R::RootSystem, i::Int) -> RootSpaceElem @@ -756,6 +760,34 @@ function conjugate_dominant_weight(w::WeightLatticeElem) return conj end +@doc raw""" + conjugate_dominant_weight_with_elem(w::WeightLatticeElem) -> Tuple{WeightLatticeElem, WeylGroupElem} + +Returns the unique dominant weight `dom` conjugate to `w` and a Weyl group element `x` +such that `x*w == dom`. +""" +function conjugate_dominant_weight_with_elem(w::WeightLatticeElem) + R = root_system(w) + wt = deepcopy(w) + + # determine the Weyl group element taking w to the fundamental chamber + word = sizehint!(UInt8[], count(<(0), coefficients(wt))^2) + s = 1 + while s <= rank(R) + if wt[s] < 0 + push!(word, UInt8(s)) + reflect!(wt, s) + s = 1 + else + s += 1 + end + end + + # reversing word means it is in short revlex normal form + # and it is the element taking w to wt + return wt, weyl_group_elem(R, reverse!(word); normalize=false) +end + function expressify(w::WeightLatticeElem, s=:w; context=nothing) sum = Expr(:call, :+) for i in 1:length(w.vec) @@ -765,6 +797,10 @@ function expressify(w::WeightLatticeElem, s=:w; context=nothing) end @enable_all_show_via_expressify WeightLatticeElem +function is_dominant(w::WeightLatticeElem) + return all(>=(0), coefficients(w)) +end + @doc raw""" reflect(w::WeightLatticeElem, s::Int) -> WeightLatticeElem @@ -837,10 +873,11 @@ function positive_roots_and_reflections(cartan_matrix::ZZMatrix) # sort roots by height perm = sortperm(roots; by=sum) + invp = invperm(perm) - table = zero_matrix(ZZ, rank, length(roots)) + table = zeros(UInt, rank, length(roots)) for i in 1:length(roots), s in 1:rank - table[s, i] = refl[s, perm[i]] + table[s, i] = iszero(refl[s, perm[i]]) ? 0 : invp[refl[s, perm[i]]] end roots[perm], coroots[perm], table diff --git a/experimental/LieAlgebras/src/WeylGroup.jl b/experimental/LieAlgebras/src/WeylGroup.jl index 865e3dd4365f..d313ec4a28b5 100644 --- a/experimental/LieAlgebras/src/WeylGroup.jl +++ b/experimental/LieAlgebras/src/WeylGroup.jl @@ -7,38 +7,110 @@ # ############################################################################### -struct WeylGroup <: CoxeterGroup +# TODO: Replace GroupsCore with AA and add conformance tests +struct WeylGroup <: GroupsCore.Group finite::Bool # finite indicates whether the Weyl group is finite - refl::ZZMatrix # see positive_roots_and_reflections + refl::Matrix{UInt} # see positive_roots_and_reflections root_system::RootSystem # root_system is the RootSystem from which the Weyl group was constructed - function WeylGroup(finite::Bool, refl::ZZMatrix, root_system::RootSystem) + function WeylGroup(finite::Bool, refl::Matrix{UInt}, root_system::RootSystem) return new(finite, refl, root_system) end end +struct WeylGroupElem <: GroupsCore.GroupElement + parent::WeylGroup # parent group + word::Vector{UInt8} # short revlex normal form of the word + + function WeylGroupElem(W::WeylGroup, word::Vector{<:Integer}; normalize::Bool=true) + if !normalize + if word isa Vector{UInt8} + return new(W, word) + else + return new(W, UInt8.(word)) + end + end + + @req all(1 <= i <= ngens(W) for i in word) "word contains invalid generators" + x = new(W, sizehint!(UInt8[], length(word))) + for s in Iterators.reverse(word) + lmul!(x, s) + end + + return x + end +end + +const WeylIteratorNoCopyState = Tuple{WeightLatticeElem,WeylGroupElem} + +@doc raw""" + weyl_group(cartan_matrix::ZZMatrix) -> WeylGroup + +Returns the Weyl group defined by a generalized Cartan matrix `cartan_matrix`. +""" function weyl_group(cartan_matrix::ZZMatrix) return weyl_group(root_system(cartan_matrix)) end +@doc raw""" + weyl_group(fam::Symbol, rk::Int) -> WeylGroup + +Returns the Weyl group defined by . +""" function weyl_group(fam::Symbol, rk::Int) return weyl_group(root_system(fam, rk)) end -function (W::WeylGroup)(word::Vector{Int}) - return WeylGroupElem(W, word) +@doc raw""" + weyl_group(type::Tuple{Symbol, Int}...) -> WeylGroup + +Returns the Weyl group defined by . +""" +function weyl_group(type::Tuple{Symbol,Int}...) + return weyl_group(root_system(type...)) +end + +@doc raw""" + (W::WeylGroup)(word::Vector{Int}) -> WeylGroupElem +""" +function (W::WeylGroup)(word::Vector{<:Integer}; normalize::Bool=true) + return weyl_group_elem(W, word; normalize=normalize) end function Base.IteratorSize(::Type{WeylGroup}) return Base.SizeUnknown() end -function Base.isfinite(G::WeylGroup) - return G.finite +function Base.eltype(::Type{WeylGroup}) + return WeylGroupElem +end + +function Base.iterate(W::WeylGroup) + state = weyl_vector(root_system(W)), one(W) + return one(W), state +end + +function Base.iterate(W::WeylGroup, state::WeylIteratorNoCopyState) + state = _iterate_nocopy(state) + if isnothing(state) + return nothing + end + + return deepcopy(state[2]), state end +@doc raw""" + isfinite(W::WeylGroup) -> Bool +""" +function Base.isfinite(W::WeylGroup) + return W.finite +end + +@doc raw""" + one(W::WeylGroup) -> WeylGroupElem +""" function Base.one(W::WeylGroup) - return WeylGroupElem(W, UInt8[]) + return W(UInt8[]; normalize=false) end function Base.show(io::IO, W::WeylGroup) @@ -53,45 +125,87 @@ function elem_type(::Type{WeylGroup}) return WeylGroupElem end -function gen(W::WeylGroup, i::Int) +@doc raw""" + gen(W::WeylGroup, i::Int) -> WeylGroupElem + +Returns the `i`th simple reflection (with respect to the underlying root system) of `W`. +""" +function gen(W::WeylGroup, i::Integer) @req 1 <= i <= ngens(W) "invalid index" - return WeylGroupElem(W, UInt8[i]) + return W(UInt8[i]; normalize=false) end +@doc raw""" + gens(W::WeylGroup) -> WeylGroupElem + +Returns the simple reflections (with respect to the underlying root system) of `W`. +""" function gens(W::WeylGroup) return [gen(W, i) for i in 1:ngens(W)] end +@doc raw""" + longest_element(W::WeylGroup) -> WeylGroupElem + +Returns the unique longest element of `W`. +""" function longest_element(W::WeylGroup) @req isfinite(W) "$W is not finite" - rk = rank(root_system(W)) - w = -weyl_vector(root_system(W)) + _, w0 = conjugate_dominant_weight_with_elem(-weyl_vector(root_system(W))) + return w0 +end - word = zeros(UInt8, ncols(W.refl)) - s = 1 - i = length(word) - while s <= rk - if w[s] < 0 - word[i] = UInt8(s) - reflect!(w, s) - s = 1 - i -= 1 +@doc raw""" + ngens(W::WeylGroup) -> Int + +Returns the number of generators of the `W`, i.e. the rank of the underyling root system. +""" +function ngens(W::WeylGroup) + return rank(root_system(W)) +end + +@doc raw""" + order(::Type{T}, W::WeylGroup) where {T} -> T + +Returns the order of `W`. +""" +function order(::Type{T}, W::WeylGroup) where {T} + if !isfinite(W) + throw(GroupsCore.InfiniteOrder(W)) + end + + ord = T(1) + for (fam, rk) in type(root_system(W)) + if fam == :A + ord *= T(factorial(rk + 1)) + elseif fam == :B || fam == :C + ord *= T(2^rk * factorial(rk)) + elseif fam == :D + ord *= T(2^(rk - 1) * factorial(rk)) + elseif fam == :E + if rk == 6 + ord *= T(51840) + elseif rk == 7 + ord *= T(2903040) + elseif rk == 8 + ord *= T(696729600) + end + elseif fam == :F + ord *= T(1152) else - s += 1 + ord *= T(12) end end - return WeylGroupElem(W, word) -end - -function ngens(W::WeylGroup) - return rank(root_system(W)) + return ord end -#function order(G::WeylGroup) -#end +@doc raw""" + root_system(W::WeylGroup) -> RootSystem +Returns the underlying root system of `W`. +""" function root_system(W::WeylGroup) return W.root_system end @@ -99,35 +213,22 @@ end ############################################################################### # Weyl group elements -struct WeylGroupElem - parent::WeylGroup # parent group - word::Vector{UInt8} # short revlex normal form of the word - - function WeylGroupElem(W::WeylGroup, word::Vector{UInt8}) - return new(W, word) - end +function weyl_group_elem(R::RootSystem, word::Vector{<:Integer}; normalize::Bool=true) + return WeylGroupElem(weyl_group(R), word; normalize=normalize) end -function WeylGroupElem(W::WeylGroup, word::Vector{Int}) - @req all(1 <= i <= ngens(W) for i in word) "word $word contains invalid generators" - - w = UInt8[] - for s in Iterators.reverse(word) - _lmul!(W.refl, w, UInt8(s)) - end - - return WeylGroupElem(W, w) +function weyl_group_elem(W::WeylGroup, word::Vector{<:Integer}; normalize::Bool=true) + return WeylGroupElem(W, word; normalize=normalize) end function Base.:(*)(x::WeylGroupElem, y::WeylGroupElem) @req x.parent === y.parent "$x, $y must belong to the same Weyl group" - word = deepcopy(y.word) - for s in Iterators.reverse(x.word) - _lmul!(x.parent.refl, word, s) + p = deepcopy(y) + for s in Iterators.reverse(word(x)) + lmul!(p, s) end - - return WeylGroupElem(x.parent, word) + return p end function Base.:(*)(x::WeylGroupElem, w::WeightLatticeElem) @@ -159,6 +260,33 @@ function Base.:(^)(x::WeylGroupElem, n::Int) return px end +@doc raw""" + Base.:(<)(x::WeylGroupElem, y::WeylGroupElem) + +Returns whether `x` is smaller than `y` with respect to the Bruhat order. +""" +function Base.:(<)(x::WeylGroupElem, y::WeylGroupElem) + @req parent(x) === parent(y) "$x, $y must belong to the same Weyl group" + + if length(x) >= length(y) + return false + end + + # x < y in the Bruhat order, iff some (not necessarily connected) subexpression + # of a reduced decomposition of y, is a reduced decomposition of x + j = length(x) + for i in length(y):-1:1 + if word(y)[i] == word(x)[j] + j -= 1 + if j == 0 + return true + end + end + end + + return false +end + function Base.:(==)(x::WeylGroupElem, y::WeylGroupElem) return x.parent === y.parent && x.word == y.word end @@ -168,11 +296,20 @@ function Base.deepcopy_internal(x::WeylGroupElem, dict::IdDict) return dict[x] end - y = WeylGroupElem(x.parent, deepcopy_internal(x.word, dict)) + y = parent(x)(deepcopy_internal(word(x), dict); normalize=false) dict[x] = y return y end +@doc raw""" + getindex(x::WeylGroupElem, i::Int) -> UInt8 + +Returns the index of simple reflection at the `i`th position in the normal form of `x`. +""" +function Base.getindex(x::WeylGroupElem, i::Int) + return word(x)[i] +end + function Base.hash(x::WeylGroupElem, h::UInt) b = 0x80f0abce1c544784 % UInt h = hash(x.parent, h) @@ -181,27 +318,56 @@ function Base.hash(x::WeylGroupElem, h::UInt) return xor(h, b) end +@doc raw""" + inv(x::WeylGroupElem) -> WeylGroupElem + +Returns the inverse of `x`. +""" function Base.inv(x::WeylGroupElem) - w = UInt8[] - sizehint!(w, length(word(x))) + y = parent(x)(sizehint!(UInt8[], length(x)); normalize=false) for s in word(x) - _lmul!(parent(x).refl, w, s) + lmul!(y, s) end - return WeylGroupElem(parent(x), w) + return y end +@doc raw""" + isone(x::WeylGroupElem) -> Bool + +Returns whether `x` is the identity. +""" function Base.isone(x::WeylGroupElem) - return isempty(x.word) + return isempty(word(x)) end +@doc raw""" + length(x::WeylGroupElem) -> Int + +Returns the length of `x`. +""" function Base.length(x::WeylGroupElem) - return length(x.word) + return length(word(x)) end +@doc raw""" + parent(x::WeylGroupElem) -> WeylGroup + +Returns the Weyl group that `x` is an element of. +""" function Base.parent(x::WeylGroupElem) return x.parent end +@doc raw""" + rand(rng::Random.AbstractRNG, rs::Random.SamplerTrivial{WeylGroup}) + +Returns a random element of the Weyl group. The elements are not uniformally distributed. +""" +function Base.rand(rng::Random.AbstractRNG, rs::Random.SamplerTrivial{WeylGroup}) + W = rs[] + return W(Int.(Random.randsubseq(rng, word(longest_element(W)), 2 / 3))) +end + function Base.show(io::IO, x::WeylGroupElem) if length(x.word) == 0 print(io, "id") @@ -210,13 +376,48 @@ function Base.show(io::IO, x::WeylGroupElem) end end +@doc raw""" + lmul(x::WeylGroupElem, i::Integer) -> WeylGroupElem + +Returns the result of multiplying `x` from the left by the `i`th simple reflection. +""" function lmul(x::WeylGroupElem, i::Integer) return lmul!(deepcopy(x), i) end +@doc raw""" + lmul!(x::WeylGroupElem, i::Integer) -> WeylGroupElem + +Returns the result of multiplying `x` in place from the left by the `i`th simple reflection. +""" function lmul!(x::WeylGroupElem, i::Integer) @req 1 <= i <= rank(root_system(parent(x))) "Invalid generator" - _lmul!(parent(x).refl, word(x), UInt8(i)) + + insert_index = 1 + insert_letter = UInt8(i) + + root = insert_letter + for s in 1:length(x) + if x[s] == root + deleteat!(word(x), s) + return x + end + + root = parent(x).refl[Int(x[s]), Int(root)] + if iszero(root) + # r is no longer a minimal root, meaning we found the best insertion point + break + end + + # check if we have a better insertion point now. Since word[i] is a simple + # root, if root < word[i] it must be simple. + if root < x[s] + insert_index = s + 1 + insert_letter = UInt8(root) + end + end + + insert!(word(x), insert_index, insert_letter) return x end @@ -224,6 +425,7 @@ function parent_type(::Type{WeylGroupElem}) return WeylGroup end +# rename to reduced decompositions ? function reduced_expressions(x::WeylGroupElem; up_to_commutation::Bool=false) return ReducedExpressionIterator(x, up_to_commutation) end @@ -236,7 +438,7 @@ function word(x::WeylGroupElem) end ############################################################################### -# Iterators +# ReducedExpressionIterator struct ReducedExpressionIterator el::WeylGroupElem # the Weyl group element for which we a searching reduced expressions @@ -301,33 +503,164 @@ function Base.iterate(iter::ReducedExpressionIterator, word::Vector{UInt8}) end end -# ----- internal ----- +############################################################################### +# WeylIteratorNoCopy + +# Iterates over all weights in the Weyl group orbit of the dominant weight `weight`, +# or analogously over all elements in the quotient W/W_P +# The iterator returns a tuple (wt, x), such that x*wt == iter.weight; +# this choice is made to align with conjugate_dominant_weight_with_elem +struct WeylIteratorNoCopy + weight::WeightLatticeElem # dominant weight + weyl_group::WeylGroup + + function WeylIteratorNoCopy(wt::WeightLatticeElem) + return new(conjugate_dominant_weight(wt), weyl_group(root_system(wt))) + end +end + +function Base.IteratorSize(::Type{WeylIteratorNoCopy}) + return Base.SizeUnknown() +end -function _lmul!(refl::ZZMatrix, word::Vector{T}, s::T) where {T<:Unsigned} - insert_index = 1 - insert_letter = s +function Base.eltype(::Type{WeylIteratorNoCopy}) + return WeylIteratorNoCopyState +end + +function Base.iterate(iter::WeylIteratorNoCopy) + state = deepcopy(iter.weight), one(iter.weyl_group) + return state, state +end + +# based on [Ste01], 4.C and 4.D +function Base.iterate(iter::WeylIteratorNoCopy, state::WeylIteratorNoCopyState) + state = _iterate_nocopy(state) + if isnothing(state) + return nothing + end + return state, state +end + +function _iterate_nocopy(state::WeylIteratorNoCopyState) + wt, path = state[1], word(state[2]) + R = root_system(wt) - root = s - for i in 1:length(word) - if word[i] == root - deleteat!(word, i) + ai = isempty(path) ? UInt8(0) : path[end] + # compute next descendant index + di = UInt8(0) + while true + di = next_descendant_index(Int(ai), Int(di), wt) + if !iszero(di) + break + elseif isempty(path) return nothing + elseif iszero(di) + reflect!(wt, Int(ai)) + di = pop!(path) + ai = isempty(path) ? UInt8(0) : path[end] end + end - root = refl[Int(word[i]), Int(root)] - if root == 0 - # r is no longer a minimal root, meaning we found the best insertion point - break + push!(path, di) + reflect!(wt, Int(di)) + return state +end + +# based on [Ste01], 4.D +function next_descendant_index(ai::Int, di::Int, wt::WeightLatticeElem) + if iszero(ai) + for j in (di + 1):rank(root_system(wt)) + if !iszero(wt[j]) + return j + end end + return 0 + end - # check if we have a better insertion point now. Since word[i] is a simple - # root, if root < word[i] it must be simple. - if root < word[i] - insert_index = i + 1 - insert_letter = T(root) + for j in (di + 1):(ai - 1) + if !iszero(wt[j]) + return j end end - insert!(word, insert_index, insert_letter) - return nothing + for j in (max(ai, di) + 1):rank(root_system(wt)) + if is_zero_entry(cartan_matrix(root_system(wt)), ai, j) + continue + end + + ok = true + for k in ai:(j - 1) + if reflect(wt, j)[k] < 0 + ok = false + break + end + end + if ok + return j + end + end + + return 0 +end + +############################################################################### +# WeylOrbitIterator + +struct WeylOrbitIterator + nocopy::WeylIteratorNoCopy + + function WeylOrbitIterator(wt::WeightLatticeElem) + return new(WeylIteratorNoCopy(wt)) + end +end + +@doc raw""" + weyl_orbit(wt::WeightLatticeElem) + +Returns an iterator over the Weyl group orbit at the weight `wt`. +""" +function weyl_orbit(wt::WeightLatticeElem) + return WeylOrbitIterator(wt) +end + +@doc raw""" + weyl_orbit(R::RootSystem, vec::Vector{<:Integer}) + +Shorthand for `weyl_orbit(WeightLatticeElem(R, vec))`. +""" +function weyl_orbit(R::RootSystem, vec::Vector{<:Integer}) + return weyl_orbit(WeightLatticeElem(R, vec)) +end + +@doc raw""" + weyl_orbit(W::WeylGroup, vec::Vector{<:Integer}) + +Shorthand for `weyl_orbit(root_system(R), vec)`. +""" +function weyl_orbit(W::WeylGroup, vec::Vector{<:Integer}) + return weyl_orbit(root_system(W), vec) +end + +function Base.IteratorSize(::Type{WeylOrbitIterator}) + return Base.IteratorSize(WeylIteratorNoCopy) +end + +function Base.eltype(::Type{WeylOrbitIterator}) + return WeightLatticeElem +end + +function Base.iterate(iter::WeylOrbitIterator) + (wt, _), data = iterate(iter.nocopy) + # wt is already a copy, so here we don't need to make one + return wt, data +end + +function Base.iterate(iter::WeylOrbitIterator, state::WeylIteratorNoCopyState) + it = iterate(iter.nocopy, state) + if isnothing(it) + return nothing + end + + (wt, _), state = it + return deepcopy(wt), state end diff --git a/experimental/LieAlgebras/test/CartanMatrix-test.jl b/experimental/LieAlgebras/test/CartanMatrix-test.jl index e7460c64cd6f..8d53a14c0159 100644 --- a/experimental/LieAlgebras/test/CartanMatrix-test.jl +++ b/experimental/LieAlgebras/test/CartanMatrix-test.jl @@ -83,10 +83,10 @@ @test cartan_symmetrizer(cartan_matrix(:F, 4)) == [2, 2, 1, 1] @test cartan_symmetrizer(cartan_matrix(:G, 2)) == [1, 3] - @test_skip cartan_symmetrizer(cartan_matrix((:A, 3), (:B, 3))) == [[1, 1, 1]; [2, 2, 1]] # TODO @felix-roehrich - @test_skip cartan_symmetrizer(cartan_matrix((:F, 4), (:D, 4))) == + @test cartan_symmetrizer(cartan_matrix((:A, 3), (:B, 3))) == [[1, 1, 1]; [2, 2, 1]] + @test cartan_symmetrizer(cartan_matrix((:F, 4), (:D, 4))) == [[2, 2, 1, 1]; [1, 1, 1, 1]] - @test_skip cartan_symmetrizer(cartan_matrix((:C, 4), (:G, 2))) == [[1, 1, 1, 2]; [1, 3]] + @test cartan_symmetrizer(cartan_matrix((:C, 4), (:G, 2))) == [[1, 1, 1, 2]; [1, 3]] @test cartan_symmetrizer(ZZ[2 -2 0; -1 2 -1; 0 -1 2]) == [1, 2, 2] @test cartan_symmetrizer(ZZ[2 0 -1 0; 0 2 0 -2; -2 0 2 0; 0 -1 0 2]) == [2, 1, 1, 2] diff --git a/experimental/LieAlgebras/test/RootSystem-test.jl b/experimental/LieAlgebras/test/RootSystem-test.jl index f33092d06cbf..9e4b04199abc 100644 --- a/experimental/LieAlgebras/test/RootSystem-test.jl +++ b/experimental/LieAlgebras/test/RootSystem-test.jl @@ -1,4 +1,21 @@ @testset "LieAlgebras.RootSystem" begin + @testset "conjugate_dominant_weight_with_elem(w::WeightLatticeElem)" begin + for (R, vec) in [ + (root_system(:A, 5), [1, -1, 2, 0, 2]), + (root_system(:B, 3), [1, 1, 1]), + (root_system(:C, 4), [2, 1, 0, 1]), + (root_system(:D, 5), [-1, 2, 2, -1, -1]), + (root_system(:E, 6), [1, 2, 0, 0, 2, 1]), + (root_system(:F, 4), [1, 2, 3, 4]), + (root_system(:G, 2), [-1, -1]), + ] + wt = WeightLatticeElem(R, vec) + d, x = conjugate_dominant_weight_with_elem(wt) + @test is_dominant(d) + @test x * wt == d + end + end + @testset "root_system(cartan_matrix::ZZMatrix)" begin R = root_system(:F, 4) @test num_positive_roots(R) == 24 diff --git a/experimental/LieAlgebras/test/WeylGroup-test.jl b/experimental/LieAlgebras/test/WeylGroup-test.jl index b71a805af1c0..2dcb57a8c84c 100644 --- a/experimental/LieAlgebras/test/WeylGroup-test.jl +++ b/experimental/LieAlgebras/test/WeylGroup-test.jl @@ -1,5 +1,10 @@ @testset "LieAlgebras.WeylGroup" begin - @testset "weyl_group(cartan_matrix::ZZMatrix)" begin + b3_w0 = UInt8[3, 2, 3, 1, 2, 3, 1, 2, 1] + b4_w0 = UInt8[4, 3, 4, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 1, 2, 1] + f4_w0 = UInt8[4, 3, 2, 3, 1, 2, 3, 4, 3, 2, 3, 1, 2, 3, 4, 3, 2, 3, 1, 2, 3, 1, 2, 1] + g2_w0 = UInt8[2, 1, 2, 1, 2, 1] + + @testset "weyl_group(::ZZMatrix)" begin W = weyl_group(cartan_matrix(:A, 2)) @test isfinite(W) == true @test ngens(W) == 2 @@ -16,10 +21,60 @@ @test isfinite(W) == false end - @testset "accessors" begin + @testset "weyl_group(::Symbol, ::Int)" begin + @test weyl_group(:A, 2) isa WeylGroup + @test weyl_group(:B, 4) isa WeylGroup + @test weyl_group(:C, 3) isa WeylGroup + @test weyl_group(:D, 5) isa WeylGroup + @test weyl_group(:E, 7) isa WeylGroup + @test weyl_group(:F, 4) isa WeylGroup + @test weyl_group(:G, 2) isa WeylGroup + + @test_throws ArgumentError weyl_group(:F, 2) + end + + @testset "weyl_group(::Tuple{Symbol, Int}...)" begin + @test weyl_group((:A, 2), (:B, 4)) isa WeylGroup + @test weyl_group((:C, 3), (:D, 5)) isa WeylGroup + @test weyl_group((:E, 7)) isa WeylGroup + @test weyl_group((:F, 4), (:G, 2)) isa WeylGroup + + @test_throws ArgumentError weyl_group((:F, 2), (:B, 4)) + @test_throws ArgumentError weyl_group((:B, 2), (:G, 4)) + end + + @testset "inv(x::WeylGroupElem)" begin W = weyl_group(:A, 2) - @test isfinite(W) === W.finite - @test root_system(W) === W.root_system + s = gens(W) + @test inv(s[1]) == s[1] + @test inv(s[1] * s[2]) == s[2] * s[1] + @test inv(s[2] * s[1] * s[2]) == s[1] * s[2] * s[1] + + W = weyl_group(:B, 4) + s = gens(W) + @test inv(s[2] * s[1]) == s[1] * s[2] + @test inv(s[3] * s[1]) == s[3] * s[1] + @test inv(s[2] * s[4] * s[3] * s[4]) == s[4] * s[3] * s[4] * s[2] + + @testset for (fam, rk) in + [(:A, 1), (:A, 5), (:B, 3), (:C, 4), (:D, 5), (:F, 4), (:G, 2)] + W = weyl_group(fam, rk) + for x in W + ix = inv(x) + @test length(ix) == length(x) + @test isone(ix * x) == isone(x * ix) == true + end + end + end + + @testset "iterate(W::WeylGroup)" begin + @testset for (fam, rk) in + [(:A, 1), (:A, 5), (:B, 3), (:C, 4), (:D, 5), (:F, 4), (:G, 2)] + W = weyl_group(fam, rk) + elems = collect(W) + @test allunique(elems) + @test length(elems) == order(W) + end end @testset "longest_element(W::WeylGroup)" begin @@ -37,54 +92,63 @@ # B3 W = weyl_group(:B, 3) - @test word(longest_element(W)) == UInt8[3, 2, 3, 1, 2, 3, 1, 2, 1] + @test word(longest_element(W)) == b3_w0 # F4 W = weyl_group(:F, 4) - @test word(longest_element(W)) == - UInt8[4, 3, 2, 3, 1, 2, 3, 4, 3, 2, 3, 1, 2, 3, 4, 3, 2, 3, 1, 2, 3, 1, 2, 1] + @test word(longest_element(W)) == f4_w0 # G2 W = weyl_group(:G, 2) - @test word(longest_element(W)) == UInt8[2, 1, 2, 1, 2, 1] + @test word(longest_element(W)) == g2_w0 + end + + @testset "ngens(W::WeylGroup)" begin + @test ngens(weyl_group(:A, 2)) == 2 + @test ngens(weyl_group(:B, 4)) == 4 + @test ngens(weyl_group(:C, 3)) == 3 + @test ngens(weyl_group(:D, 5)) == 5 + @test ngens(weyl_group(:E, 7)) == 7 + @test ngens(weyl_group(:F, 4)) == 4 + @test ngens(weyl_group(:G, 2)) == 2 + + @test ngens(weyl_group((:A, 2), (:B, 4))) == 6 + @test ngens(weyl_group((:C, 3), (:E, 7))) == 10 + @test ngens(weyl_group((:F, 4), (:G, 2))) == 6 end @testset "Base.:(*)(x::WeylGroupElem, y::WeylGroupElem)" begin - # test A2 + # test short revlex normal form W = weyl_group(:A, 2) s = gens(W) - @test parent(s[1] * s[2]) === parent(s[1]) === parent(s[2]) @test word(s[2] * s[1]) == UInt[2, 1] @test word(s[1] * s[2]) == UInt[1, 2] - @test word(s[1] * s[2] * s[1]) == UInt[1, 2, 1] @test word(s[2] * s[1] * s[2]) == UInt[1, 2, 1] # test A3 W = weyl_group(:A, 3) s = gens(W) - @test parent(s[1] * s[2]) === parent(s[1]) === parent(s[2]) - @test word(s[2] * s[1]) == UInt8[2, 1] - @test word(s[1] * s[2]) == UInt8[1, 2] - @test word(s[3] * s[1]) == UInt8[3, 1] @test word(s[1] * s[3]) == UInt8[3, 1] - - @test word(s[3] * s[2]) == UInt8[3, 2] - @test word(s[2] * s[3]) == UInt8[2, 3] - @test word(s[1] * s[3] * s[1]) == UInt8[3] @test word(s[3] * s[1] * s[3]) == UInt8[1] - @test word(s[1] * s[2] * s[1]) == UInt8[1, 2, 1] - @test word(s[2] * s[1] * s[2]) == UInt8[1, 2, 1] - - @test word(s[2] * s[3] * s[2]) == UInt8[2, 3, 2] @test word(s[3] * s[2] * s[3]) == UInt8[2, 3, 2] + + # test general multiplication behaviour + W = weyl_group(:B, 4) + @test W(b4_w0) == W(b4_w0; normalize=false) + + W = weyl_group(:F, 4) + @test W(f4_w0) == W(f4_w0; normalize=false) + + W = weyl_group(:G, 2) + @test W(g2_w0) == W(g2_w0; normalize=false) end @testset "Base.:(^)(x::WeylGroupElem, n::Int)" begin @@ -115,6 +179,17 @@ @test longest_element(W) * rho == -rho end + @testset "parent(::WeylGroupElem)" begin + W = weyl_group(:A, 5) + x = one(W) + @test parent(x) === x.parent + @test parent(x) isa WeylGroup + + x = W([1, 3, 5, 4, 2]) + @test parent(x) === x.parent + @test parent(x) isa WeylGroup + end + @testset "ReducedExpressionIterator" begin W = weyl_group(:A, 3) s = gens(W) @@ -148,4 +223,112 @@ @test re[1] == word(w0) @test re[8] == UInt8[3, 2, 3, 1, 2, 3] end + + @testset "WeylIteratorNoCopy" begin + WeylIteratorNoCopy = Oscar.LieAlgebras.WeylIteratorNoCopy + + # test simple root systems + @testset for ((fam, rk), vec) in [ + ((:A, 1), [-42]), + ((:A, 3), [0, 0, 1]), + ((:A, 3), [1, 0, 0]), + ((:A, 5), [1, -1, 2, 0, 2]), + ((:B, 3), [1, 1, 1]), + ((:C, 4), [2, 1, 0, 1]), + ((:D, 5), [-1, 2, 2, -1, -1]), + ((:E, 6), [1, 2, 0, 0, 2, 1]), + ((:F, 4), [1, 2, 3, 4]), + ((:G, 2), [-1, -1]), + ] + R = root_system(fam, rk) + wt = WeightLatticeElem(R, vec) + dom_wt, conj = conjugate_dominant_weight_with_elem(wt) + orb = Tuple{WeightLatticeElem,WeylGroupElem}[] + for tup in WeylIteratorNoCopy(wt) + push!(orb, deepcopy(tup)) + end + + @test !isnothing(findfirst(==((wt, inv(conj))), orb)) + @test allunique(first.(orb)) + for (ow, x) in orb + @test x * ow == dom_wt + end + + gap_num = 0 + gap_W = GAPWrap.WeylGroup( + GAPWrap.RootSystem( + GAP.Globals.SimpleLieAlgebra(GAP.Obj(fam), rk, GAP.Globals.Rationals) + ), + ) + it = GAPWrap.WeylOrbitIterator(gap_W, GAP.Obj(vec)) + while !GAPWrap.IsDoneIterator(it) + _ = GAPWrap.NextIterator(it) + gap_num += 1 + end + @test length(orb) == gap_num + end + + # test composite root systems + @testset for (type, vec) in [ + ([(:A, 1), (:A, 3), (:A, 3)], [-3, 0, 0, 1, 1, 0, 0]), + ([(:A, 5), (:B, 3)], [1, -1, 2, 0, 2, 1, 1, 1]), + ([(:C, 2), (:D, 5)], [0, 1, -1, 2, 2, -1, -1]), + ([(:E, 6)], [1, 2, 0, 0, 2, 1]), + ([(:F, 4), (:G, 2)], [1, 2, 3, 4, -1, -1]), + ] + R = root_system(type...) + wt = WeightLatticeElem(R, vec) + dom_wt, conj = conjugate_dominant_weight_with_elem(wt) + orb = Tuple{WeightLatticeElem,WeylGroupElem}[] + for tup in WeylIteratorNoCopy(wt) + push!(orb, deepcopy(tup)) + end + + @test !isnothing(findfirst(==((wt, inv(conj))), orb)) + @test allunique(first.(orb)) + for (ow, x) in orb + @test x * ow == dom_wt + end + + gap_num = 0 + gap_L = GAP.Globals.DirectSumOfAlgebras( + GAP.Obj([ + GAP.Globals.SimpleLieAlgebra(GAP.Obj(fam), rk, GAP.Globals.Rationals) for + (fam, rk) in type + ]), + ) + gap_W = GAPWrap.WeylGroup(GAPWrap.RootSystem(gap_L)) + it = GAPWrap.WeylOrbitIterator(gap_W, GAP.Obj(vec)) + while !GAPWrap.IsDoneIterator(it) + _ = GAPWrap.NextIterator(it) + gap_num += 1 + end + @test length(orb) == gap_num + end + end + + @testset "WeylOrbitIterator" begin + @test eltype(WeylOrbitIterator) == WeightLatticeElem + + @testset for ((fam, rk), vec) in [ + ((:A, 1), [-42]), + ((:A, 3), [0, 0, 1]), + ((:A, 3), [1, 0, 0]), + ((:A, 5), [1, -1, 2, 0, 2]), + ((:B, 3), [1, 1, 1]), + ((:C, 4), [2, 1, 0, 1]), + ((:D, 5), [-1, 2, 2, -1, -1]), + ((:E, 6), [1, 2, 0, 0, 2, 1]), + ((:F, 4), [1, 2, 3, 4]), + ((:G, 2), [-1, -1]), + ] + R = root_system(fam, rk) + wt = WeightLatticeElem(R, vec) + dom_wt, conj = conjugate_dominant_weight_with_elem(wt) + orb = collect(WeylOrbitIterator(wt)) + + @test !isnothing(findfirst(==(wt), orb)) + @test allunique(orb) + end + end end diff --git a/experimental/LieAlgebras/test/setup_tests.jl b/experimental/LieAlgebras/test/setup_tests.jl index fe0d6fb87829..234c424de6f4 100644 --- a/experimental/LieAlgebras/test/setup_tests.jl +++ b/experimental/LieAlgebras/test/setup_tests.jl @@ -1,3 +1,7 @@ +if !isdefined(Main, :GAPWrap) + import Oscar: GAPWrap +end + if !isdefined(Main, :lie_algebra_conformance_test) function lie_algebra_conformance_test( L::LieAlgebra{C}, parentT::DataType, elemT::DataType; num_random_tests::Int=10