diff --git a/Project.toml b/Project.toml index ad8dff36a42c..65d51ce667e6 100644 --- a/Project.toml +++ b/Project.toml @@ -25,7 +25,7 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" cohomCalg_jll = "5558cf25-a90e-53b0-b813-cadaa3ae7ade" [compat] -AbstractAlgebra = "0.42.3" +AbstractAlgebra = "0.42.5" AlgebraicSolving = "0.5.1" Distributed = "1.6" GAP = "0.11.3" diff --git a/docs/oscar_references.bib b/docs/oscar_references.bib index 3e4463c66139..1e9ada469139 100644 --- a/docs/oscar_references.bib +++ b/docs/oscar_references.bib @@ -1091,6 +1091,17 @@ @InProceedings{GHJ16 year = {2016} } +@Article{GIR96, + author = {de Graaf, Willem A. and Ivanyos, Gábor and Rónyai, Lajos}, + title = {Computing Cartan subalgebras of Lie algebras}, + journal = {Appl. Algebra Eng. Commun. Comput.}, + volume = {7}, + number = {5}, + pages = {339--349}, + year = {1996}, + doi = {10.1007/BF01293593} +} + @InCollection{GJ00, author = {Gawrilow, Ewgenij and Joswig, Michael}, title = {polymake: a Framework for Analyzing Convex Polytopes}, @@ -1224,6 +1235,17 @@ @Article{Gat96 doi = {10.1007/BF01191379} } +@Book{Gra00, + author = {de Graaf, Willem A.}, + title = {Lie algebras: theory and algorithms}, + series = {North-Holland Mathematical Library}, + volume = {56}, + publisher = {North-Holland Publishing Co., Amsterdam}, + pages = {xii+393}, + year = {2000}, + url = {https://www.sciencedirect.com/bookseries/north-holland-mathematical-library/vol/56/} +} + @PhDThesis{Gro20, author = {Gromada, Daniel}, title = {Compact matrix quantum groups and their representation categories}, diff --git a/experimental/LieAlgebras/docs/src/ideals_and_subalgebras.md b/experimental/LieAlgebras/docs/src/ideals_and_subalgebras.md index e0c1c2bcad63..be741d34084d 100644 --- a/experimental/LieAlgebras/docs/src/ideals_and_subalgebras.md +++ b/experimental/LieAlgebras/docs/src/ideals_and_subalgebras.md @@ -17,7 +17,7 @@ They are used similarly in most cases. dim(::LieAlgebraIdeal) basis(::LieAlgebraIdeal) basis(::LieAlgebraIdeal, ::Int) -Base.in(::LieAlgebraElem, ::LieAlgebraIdeal) +Base.in(::LieAlgebraElem{C}, ::LieAlgebraIdeal{C}) where {C<:FieldElem} bracket(::LieAlgebraIdeal{C,LieT}, ::LieAlgebraIdeal{C,LieT}) where {C<:FieldElem,LieT<:LieAlgebraElem{C}} normalizer(::LieAlgebra, ::LieAlgebraIdeal) centralizer(::LieAlgebra, ::LieAlgebraIdeal) @@ -29,7 +29,7 @@ centralizer(::LieAlgebra, ::LieAlgebraIdeal) dim(::LieSubalgebra) basis(::LieSubalgebra) basis(::LieSubalgebra, ::Int) -Base.in(::LieAlgebraElem, ::LieSubalgebra) +Base.in(::LieAlgebraElem{C}, ::LieSubalgebra{C}) where {C<:FieldElem} bracket(::LieSubalgebra{C,LieT}, ::LieSubalgebra{C,LieT}) where {C<:FieldElem,LieT<:LieAlgebraElem{C}} normalizer(::LieAlgebra, ::LieSubalgebra) centralizer(::LieAlgebra, ::LieSubalgebra) diff --git a/experimental/LieAlgebras/src/AbstractLieAlgebra.jl b/experimental/LieAlgebras/src/AbstractLieAlgebra.jl index 367779d6d872..4f5f16ffcd98 100644 --- a/experimental/LieAlgebras/src/AbstractLieAlgebra.jl +++ b/experimental/LieAlgebras/src/AbstractLieAlgebra.jl @@ -95,22 +95,20 @@ end has_root_system(L::AbstractLieAlgebra) = isdefined(L, :root_system) function root_system(L::AbstractLieAlgebra) - @req has_root_system(L) "no root system known." + assure_root_system(L) return L.root_system end function chevalley_basis(L::AbstractLieAlgebra) - @req has_root_system(L) "no root system known." - # TODO: once there is root system detection, this function needs to be updated to indeed return the Chevalley basis - - npos = n_positive_roots(root_system(L)) - b = basis(L) - # root vectors - r_plus = b[1:npos] - r_minus = b[(npos + 1):(2 * npos)] - # basis for cartan algebra - h = b[(2 * npos + 1):dim(L)] - return (r_plus, r_minus, h) + assure_root_system(L) + return L.chevalley_basis::NTuple{3,Vector{elem_type(L)}} +end + +function set_root_system_and_chevalley_basis!( + L::AbstractLieAlgebra{C}, R::RootSystem, chev::NTuple{3,Vector{AbstractLieAlgebraElem{C}}} +) where {C<:FieldElem} + L.root_system = R + L.chevalley_basis = chev end ############################################################################### @@ -234,7 +232,7 @@ function lie_algebra( @req fl "Not closed under the bracket." struct_consts[i, j] = sparse_row(row) end - s = map(AbstractAlgebra.obj_to_string, basis) + s = map(AbstractAlgebra.obj_to_string_wrt_times, basis) return lie_algebra(R, struct_consts, s; check) end @@ -264,7 +262,14 @@ function lie_algebra( ] L = lie_algebra(R, struct_consts, s; check=false) - L.root_system = rs + + npos = n_positive_roots(rs) + # set Chevalley basis + r_plus = basis(L)[1:npos] + r_minus = basis(L)[(npos + 1):(2 * npos)] + h = basis(L)[(2 * npos + 1):end] + chev = (r_plus, r_minus, h) + set_root_system_and_chevalley_basis!(L, rs, chev) return L end diff --git a/experimental/LieAlgebras/src/DirectSumLieAlgebra.jl b/experimental/LieAlgebras/src/DirectSumLieAlgebra.jl index f03c10225694..88ccabac2793 100644 --- a/experimental/LieAlgebras/src/DirectSumLieAlgebra.jl +++ b/experimental/LieAlgebras/src/DirectSumLieAlgebra.jl @@ -135,9 +135,26 @@ end # ############################################################################### -# The following implementation needs direct sums of root systems, which -# is not yet implemented. -# has_root_system(D::DirectSumLieAlgebra) = all(has_root_system, D.summands) +has_root_system(D::DirectSumLieAlgebra) = isdefined(D, :root_system) + +function root_system(D::DirectSumLieAlgebra) + assure_root_system(D) + return D.root_system +end + +function chevalley_basis(D::DirectSumLieAlgebra) + assure_root_system(D) + return D.chevalley_basis::NTuple{3,Vector{elem_type(D)}} +end + +function set_root_system_and_chevalley_basis!( + D::DirectSumLieAlgebra{C}, + R::RootSystem, + chev::NTuple{3,Vector{DirectSumLieAlgebraElem{C}}}, +) where {C<:FieldElem} + D.root_system = R + D.chevalley_basis = chev +end ############################################################################### # diff --git a/experimental/LieAlgebras/src/LieAlgebra.jl b/experimental/LieAlgebras/src/LieAlgebra.jl index 1ed5624f7324..04a487f5d124 100644 --- a/experimental/LieAlgebras/src/LieAlgebra.jl +++ b/experimental/LieAlgebras/src/LieAlgebra.jl @@ -8,10 +8,10 @@ # symbols(L::MyLieAlgebra) -> Vector{Symbol} # bracket(x::MyLieAlgebraElem{C}, y::MyLieAlgebraElem{C}) -> MyLieAlgebraElem{C} # Base.show(io::IO, x::MyLieAlgebra) -# If the subtype supports root systems: # has_root_system(::MyLieAlgebra) -> Bool # root_system(::MyLieAlgebra) -> RootSystem -# chevalley_basis(L::MyLieAlgebra) -> NTuple{3,Vector{elem_type(L)}} +# chevalley_basis(L::MyLieAlgebra{C}) -> NTuple{3,Vector{MyLieAlgebraElem{C}}} +# set_root_system_and_chevalley_basis!(L::MyLieAlgebra{C}, R::RootSystem, chev::NTuple{3,Vector{MyLieAlgebraElem{C}}}}}) ############################################################################### # @@ -432,6 +432,310 @@ Return `true` if `L` is solvable, i.e. the derived series of `L` terminates in $ return dim(derived_series(L)[end]) == 0 end +############################################################################### +# +# Adjoint elements +# +############################################################################### + +@attr Vector{dense_matrix_type(C)} function adjoint_matrices( + L::LieAlgebra{C} +) where {C<:FieldElem} + return map(1:dim(L)) do i + x = basis(L, i) + A = zero_matrix(coefficient_ring(L), dim(L), dim(L)) + for (j, bj) in enumerate(basis(L)) + A[j, :] = _matrix(bracket(x, bj)) + end + return A + end +end + +function adjoint_matrix(x::LieAlgebraElem{C}) where {C<:FieldElem} + L = parent(x) + return sum( + c * g for (c, g) in zip(coefficients(x), adjoint_matrices(L)); + init=zero_matrix(coefficient_ring(L), dim(L), dim(L)), + ) +end + +function _adjoint_matrix(S::LieSubalgebra{C}, x::LieAlgebraElem{C}) where {C<:FieldElem} + L = parent(x) + @req base_lie_algebra(S) === L "Incompatible Lie algebras" + A = zero_matrix(coefficient_ring(L), dim(S), dim(S)) + for (i, bi) in enumerate(basis(S)) + A[i, :] = coefficient_vector(bracket(x, bi), S) + end + return A +end + +@attr dense_matrix_type(C) function killing_matrix(L::LieAlgebra{C}) where {C<:FieldElem} + R = coefficient_ring(L) + A = zero_matrix(R, dim(L), dim(L)) + for (i, adxi) in enumerate(adjoint_matrices(L)) + for (j, adxj) in enumerate(adjoint_matrices(L)) + i > j && continue # killing form is symmetric + val = tr(adxi * adxj) + A[j, i] = A[i, j] = val + end + end + return A +end + +@doc raw""" + is_ad_nilpotent(x::LieAlgebraElem{C}) -> Bool + +Return whether `x` is ad-nilpotent, i.e. whether the linear operator $\mathrm{ad}(x)$ is nilpotent. +""" +function is_ad_nilpotent(x::LieAlgebraElem{C}) where {C<:FieldElem} + return is_nilpotent(adjoint_matrix(x)) +end + +############################################################################### +# +# Root system detection +# +############################################################################### + +@doc raw""" + any_non_ad_nilpotent_element(L::LieAlgebra{C}) -> LieAlgebraElem{C} + +Return an element of `L` that is not ad-nilpotent, or the zero element if all elements are ad-nilpotent. + +The used algorithm is described in [GIR96; Ch. 3](@cite). +""" +function any_non_ad_nilpotent_element(L::LieAlgebra{C}) where {C<:FieldElem} + if dim(L) <= 1 + # L is abelian and hence nilpotent + elseif characteristic(L) == 0 + for x in basis(L) + !is_ad_nilpotent(x) && return x + end + for (i, x) in enumerate(basis(L)) + for (j, y) in enumerate(basis(L)) + i > j && continue + xy = x * y + !is_ad_nilpotent(xy) && return xy + end + end + else # characteristic > 0 + x = basis(L, 1) + !is_ad_nilpotent(x) && return x + K = sub(L, [x]; is_basis=true) + while dim(K) < dim(L) + # find an element b in L \ K with [b,K]⊆K + N = normalizer(L, K) + b = basis(N, findfirst(b -> !(b in K), basis(N))) + !is_ad_nilpotent(b) && return b + K = sub(L, [basis(K); b]; is_basis=true) + end + end + set_attribute!(L, :is_nilpotent, true) + return zero(L) +end + +@doc raw""" + engel_subalgebra(x::LieAlgebraElem{C}) -> LieSubalgebra{C,elem_type(parent(x))} + +Return the Engel subalgebra of `x`, i.e. the generalized eigenspace of the linear operator $\mathrm{ad}(x)$. +""" +function engel_subalgebra(x::LieAlgebraElem{C}) where {C<:FieldElem} + L = parent(x) + n = dim(L) + A = adjoint_matrix(x)^n + ker = kernel(A; side=:left) + basis = [L(ker[i, :]) for i in 1:nrows(ker)] + L0adx = sub(L, basis; is_basis=true) + return L0adx +end + +function _cartan_subalgebra(L::LieAlgebra{C}) where {C<:FieldElem} + F = coefficient_ring(L) + n = dim(L) + @req is_infinite(F) || length(F) > dim(L) "The implemented algorithm requires a large field" + x = any_non_ad_nilpotent_element(L) + if is_zero(x) # L is nilpotent + return sub(L) + end + + L0adx = engel_subalgebra(x) + while true # decreasing variant is dim(L0adx) + y = any_non_ad_nilpotent_element(L0adx) + if is_zero(y) # L0adx is nilpotent + return L0adx + end + + c_itr = + characteristic(F) == 0 ? (F(i) for i in 1:(n + 1)) : Iterators.filter(!iszero, F) + z = x + L0adz = L0adx + for c in c_itr # at most n+1 iterations + z = x + c * (y - x) + L0adz = engel_subalgebra(z) + if dim(L0adz) < dim(L0adx) && is_subset(L0adz, L0adx) + break + end + end + x = z + L0adx = L0adz + end +end + +function _root_system_and_chevalley_basis( + L::LieAlgebra{C}, H::LieSubalgebra{C}=_cartan_subalgebra(L) +) where {C<:FieldElem} + @req base_lie_algebra(H) === L "Incompatible Lie algebras." + # we just assume that H is indeed a Cartan subalgebra + + @req is_invertible(killing_matrix(L)) "The Killing form is degenerate" + + F = coefficient_ring(L) + + # compute the common eigenspaces of the adjoint action of H. + # B is a list of subspaces of L that gets refined in each iteration. + # to exploit existing functionality, we use the LieSubalgebra type even + # though the subspaces are in general not Lie subalgebras. With setting + # is_basis=true, we ensure that the subspace generators do not get + # extended to a subalgebra. + B = [sub(L, basis(L); is_basis=true)] + for h in basis(H) + B_new = empty(B) + for B_j in B + A = _adjoint_matrix(B_j, h) + facs = factor(minimal_polynomial(A)) + for (f, k) in facs + @assert k == 1 # TODO: is this always the case? + ker = kernel((f^k)(A); side=:left) + basis = [B_j(ker[i, :]) for i in 1:nrows(ker)] + push!(B_new, sub(L, basis; is_basis=true)) + end + end + B = B_new + end + filter!(!=(H), B) + @req all(B_j -> dim(B_j) == 1, B) "The Cartan subalgebra is not split" + + # compute the roots, i.e. the list of eigenvalues of basis(H) on each B_j + root_spaces = Dict( + begin + b = only(basis(B_j)) + root = [only(solve(_matrix(b), _matrix(bracket(h, b)); side=:left)) for h in basis(H)] + root => B_j + end for B_j in B + ) + roots = collect(keys(root_spaces)) + + # compute an R-basis of the root space, s.t. the corresponding co-roots are a basis of H + roots_basis = empty(roots) + basis_mat_H = zero_matrix(F, 0, dim(L)) + for root in roots + nrows(basis_mat_H) == dim(H) && break + x_j = only(basis(root_spaces[root])) + y_j = only(basis(root_spaces[-root])) + h_j = bracket(x_j, y_j) + if !can_solve(basis_mat_H, _matrix(h_j); side=:left) + basis_mat_H = vcat(basis_mat_H, _matrix(h_j)) + push!(roots_basis, root) + end + end + + function CartanInt(roots::Vector{RootType}, a::RootType, b::RootType) where {RootType} + # `a` and `b` are two roots in `roots`. + a == b && return 2 + a == -b && return -2 + # If a != ±b, the Cartan integer of `a` and `b` is `s-t`, where + # `s` and `t` are the largest integers such that `b-s*a` and `b+t*a` are still roots. + rt = b - a + s = 0 + while rt in roots + s += 1 + rt -= a + end + rt = b + a + t = 0 + while rt in roots + t += 1 + rt += a + end + return s - t + end + + # we define a root to be positive if the first of its non-zero Cartan integers with the R-basis is positive + roots_positive = empty(roots) + for root in roots + 2 * length(roots_positive) == length(roots) && break + root in roots_positive && continue + -root in roots_positive && continue + c = first( + Iterators.dropwhile( + iszero, Iterators.map(b_j -> CartanInt(roots, root, b_j), roots_basis) + ), + ) + if c > 0 + push!(roots_positive, root) + else + push!(roots_positive, -root) + end + end + + # a positive root is simple if it is not the sum of two positive roots + roots_simple = empty(roots) + roots_positive_sums = Set( + alpha_i + alpha_j for (i, alpha_i) in enumerate(roots_positive) for + (j, alpha_j) in enumerate(roots_positive) if i < j + ) + for root in roots_positive + if !(root in roots_positive_sums) + push!(roots_simple, root) + end + end + @assert length(roots_simple) == dim(H) + + # compute the Cartan matrix and abstract root system + cm = matrix( + ZZ, + [ + CartanInt(roots, alpha_i, alpha_j) for alpha_i in roots_simple, + alpha_j in roots_simple + ], + ) + type, ordering = cartan_type_with_ordering(cm; check=false) + permute!(roots_simple, ordering) + R = root_system(type) + + # compute a Chevalley basis of L + root_vectors = [ + begin + concrete_root = sum( + c .* root_simple for + (c, root_simple) in zip(coefficients(abstract_root), roots_simple) + ) + @assert concrete_root in roots + root_vector = only(basis(root_spaces[concrete_root])) + end for abstract_root in Oscar.roots(R) + ] + xs = root_vectors[1:n_positive_roots(R)] + ys = Vector{elem_type(L)}([ + begin + x = xs[i] + y = root_vectors[n_positive_roots(R) + i] + y *= 2//only(solve(_matrix(x), _matrix(bracket(bracket(x, y), x)); side=:left)) + y + end for i in 1:n_positive_roots(R) + ]) + hs = elem_type(L)[xs[i] * ys[i] for i in 1:n_simple_roots(R)] + + return R, (xs, ys, hs) +end + +function assure_root_system(L::LieAlgebra{C}) where {C<:FieldElem} + if !has_root_system(L) + R, chev = _root_system_and_chevalley_basis(L) + set_root_system_and_chevalley_basis!(L, R, chev) + end + @assert has_root_system(L) +end + ############################################################################### # # Root system getters @@ -450,10 +754,9 @@ has_root_system(L::LieAlgebra) = false # to be implemented by subtypes Return the root system of `L`. -This function will error if no root system is known (see [`has_root_system(::LieAlgebra)`](@ref)). +This function will error if no root system is known and none can be computed. """ function root_system(L::LieAlgebra) # to be implemented by subtypes - @req has_root_system(L) "root system of `L` not known." throw(Hecke.NotImplemented()) end @@ -464,13 +767,29 @@ Return the Chevalley basis of the Lie algebra `L` in three vectors, stating firs then the negative root vectors, and finally the basis of the Cartan subalgebra. The order of root vectors corresponds to the order of the roots in [`root_system(::LieAlgebra)`](@ref). -This function will error if no root system is known (see [`has_root_system(::LieAlgebra)`](@ref)). +This function will error if no root system is known and none can be computed. """ function chevalley_basis(L::LieAlgebra) # to be implemented by subtypes - @req has_root_system(L) "root system of `L` not known." throw(Hecke.NotImplemented()) end +@doc raw""" + cartan_subalgebra(L::LieAlgebra{C}) where {C<:FieldElem} -> LieSubalgebra{C,elem_type(L)} + +Return a Cartan subalgebra of `L`. + +If `L` knows its root system, this function uses the Chevalley basis to construct a Cartan subalgebra. +Otherise, it uses the algorithm described in [Gra00; Ch. 3.2](@cite). +The return value of this function may change when the root system of `L` is first computed. +""" +function cartan_subalgebra(L::LieAlgebra{C}) where {C<:FieldElem} + if has_root_system(L) + return sub(L, chevalley_basis(L)[3]; is_basis=true) + else + return _cartan_subalgebra(L) + end +end + ############################################################################### # # Universal enveloping algebra diff --git a/experimental/LieAlgebras/src/LieAlgebraIdeal.jl b/experimental/LieAlgebras/src/LieAlgebraIdeal.jl index 0e622304d5c5..73d8db05890b 100644 --- a/experimental/LieAlgebras/src/LieAlgebraIdeal.jl +++ b/experimental/LieAlgebras/src/LieAlgebraIdeal.jl @@ -50,6 +50,8 @@ Return the dimension of the ideal `I`. """ dim(I::LieAlgebraIdeal) = length(basis(I)) +coefficient_ring(I::LieAlgebraIdeal) = coefficient_ring(base_lie_algebra(I)) + ############################################################################### # # String I/O @@ -81,6 +83,55 @@ function Base.show(io::IO, I::LieAlgebraIdeal) end end +############################################################################### +# +# Parent object call overload +# +############################################################################### + +@doc raw""" + (I::LieAlgebraIdeal{C})() -> LieAlgebraElem{C} + +Return the zero element of the Lie algebra ideal `I`. +""" +function (I::LieAlgebraIdeal)() + return zero(base_lie_algebra(I)) +end + +@doc raw""" + (I::LieAlgebraIdeal{C})(v::Vector{Int}) -> LieAlgebraElem{C} + +Return the element of `I` with coefficient vector `v`. +Fail, if `Int` cannot be coerced into the base ring of `I`. +""" +function (I::LieAlgebraIdeal)(v::Vector{Int}) + return I(coefficient_ring(I).(v)) +end + +@doc raw""" + (I::LieAlgebraIdeal{C})(v::Vector{C}) -> LieAlgebraElem{C} + +Return the element of `I` with coefficient vector `v`. +""" +function (I::LieAlgebraIdeal{C})(v::Vector{C}) where {C<:FieldElem} + @req length(v) == dim(I) "Length of vector does not match dimension." + mat = matrix(coefficient_ring(I), 1, length(v), v) + L = base_lie_algebra(I) + return elem_type(L)(L, mat * basis_matrix(I)) +end + +@doc raw""" + (I::LieAlgebraIdeal{C})(mat::MatElem{C}) -> LieAlgebraElem{C} + +Return the element of `I` with coefficient vector equivalent to +the $1 \times \dim(I)$ matrix `mat`. +""" +function (I::LieAlgebraIdeal{C})(mat::MatElem{C}) where {C<:FieldElem} + @req size(mat) == (1, dim(I)) "Invalid matrix dimensions." + L = base_lie_algebra(I) + return elem_type(L)(L, mat * basis_matrix(I)) +end + ############################################################################### # # Comparisons @@ -116,14 +167,28 @@ end ############################################################################### @doc raw""" - in(x::LieAlgebraElem, I::LieAlgebraIdeal) -> Bool + in(x::LieAlgebraElem{C}, I::LieAlgebraIdeal{C}) -> Bool Return `true` if `x` is in the ideal `I`, `false` otherwise. """ -function Base.in(x::LieAlgebraElem, I::LieAlgebraIdeal) +function Base.in(x::LieAlgebraElem{C}, I::LieAlgebraIdeal{C}) where {C<:FieldElem} + @req parent(x) === base_lie_algebra(I) "Incompatible Lie algebras" return can_solve(basis_matrix(I), _matrix(x); side=:left) end +@doc raw""" + Oscar.LieAlgebras.coefficient_vector(x::LieAlgebraElem{C}, I::LieAlgebraIdeal{C}) -> Vector{C} + +Return the coefficient vector of `x` in the basis of `I`. +This function will throw an error if `x` is not in `I`. +""" +function coefficient_vector( + x::LieAlgebraElem{C}, I::LieAlgebraIdeal{C} +) where {C<:FieldElem} + @req parent(x) === base_lie_algebra(I) "Incompatible Lie algebras" + return solve(basis_matrix(I), _matrix(x); side=:left) +end + ############################################################################### # # Constructions diff --git a/experimental/LieAlgebras/src/LieAlgebraModule.jl b/experimental/LieAlgebras/src/LieAlgebraModule.jl index 562c86122eb6..08ddecd9f3b1 100644 --- a/experimental/LieAlgebras/src/LieAlgebraModule.jl +++ b/experimental/LieAlgebras/src/LieAlgebraModule.jl @@ -1413,17 +1413,8 @@ julia> dim_of_simple_module(L, [1, 1, 1]) ``` """ function dim_of_simple_module(T::Type, L::LieAlgebra, hw::Vector{<:IntegerUnion}) - if has_root_system(L) - R = root_system(L) - return dim_of_simple_module(T, R, hw) - else # TODO: remove branch once root system detection is implemented - @req is_dominant_weight(hw) "Not a dominant weight." - return T( - GAPWrap.DimensionOfHighestWeightModule( - codomain(Oscar.iso_oscar_gap(L)), GAP.Obj(hw; recursive=true) - ), - ) - end + R = root_system(L) + return dim_of_simple_module(T, R, hw) end function dim_of_simple_module(L::LieAlgebra, hw::Vector{<:IntegerUnion}) @@ -1456,24 +1447,8 @@ julia> dominant_weights(L, [1, 0, 3]) ``` """ function dominant_weights(T::Type, L::LieAlgebra, hw::Vector{<:IntegerUnion}) - if has_root_system(L) - R = root_system(L) - return dominant_weights(T, R, hw) - else # TODO: remove branch once root system detection is implemented - @req is_dominant_weight(hw) "Not a dominant weight." - return first.( - sort!( - collect( - T(w) => d for (w, d) in - zip( - GAP.Globals.DominantWeights( - GAP.Globals.RootSystem(codomain(Oscar.iso_oscar_gap(L))), - GAP.Obj(hw; recursive=true), - )..., - ) - ); by=last) - ) - end + R = root_system(L) + return dominant_weights(T, R, hw) end function dominant_weights(L::LieAlgebra, hw::Vector{<:IntegerUnion}) @@ -1503,20 +1478,8 @@ Dict{Vector{Int64}, Int64} with 4 entries: ``` """ function dominant_character(L::LieAlgebra, hw::Vector{<:IntegerUnion}) - if has_root_system(L) - R = root_system(L) - return dominant_character(R, hw) - else # TODO: remove branch once root system detection is implemented - @req is_dominant_weight(hw) "Not a dominant weight." - return Dict{Vector{Int},Int}( - Vector{Int}(w) => d for (w, d) in - zip( - GAPWrap.DominantCharacter( - codomain(Oscar.iso_oscar_gap(L)), GAP.Obj(hw; recursive=true) - )..., - ) - ) - end + R = root_system(L) + return dominant_character(R, hw) end @doc raw""" @@ -1547,22 +1510,8 @@ Dict{Vector{Int64}, Int64} with 10 entries: ``` """ function character(L::LieAlgebra, hw::Vector{<:IntegerUnion}) - if has_root_system(L) - R = root_system(L) - return character(R, hw) - else # TODO: remove branch once root system detection is implemented - @req is_dominant_weight(hw) "Not a dominant weight." - dc = dominant_character(L, hw) - c = Dict{Vector{Int},Int}() - W = GAPWrap.WeylGroup(GAPWrap.RootSystem(codomain(Oscar.iso_oscar_gap(L)))) - for (w, d) in dc - it = GAPWrap.WeylOrbitIterator(W, GAP.Obj(w)) - while !GAPWrap.IsDoneIterator(it) - push!(c, Vector{Int}(GAPWrap.NextIterator(it)) => d) - end - end - return c - end + R = root_system(L) + return character(R, hw) end @doc raw""" @@ -1595,19 +1544,6 @@ MSet{Vector{Int64}} with 6 elements: function tensor_product_decomposition( L::LieAlgebra, hw1::Vector{<:IntegerUnion}, hw2::Vector{<:IntegerUnion} ) - if has_root_system(L) - R = root_system(L) - return tensor_product_decomposition(R, hw1, hw2) - else # TODO: remove branch once root system detection is implemented - @req is_dominant_weight(hw1) && is_dominant_weight(hw2) "Both weights must be dominant." - return multiset( - Tuple{Vector{Vector{Int}},Vector{Int}}( - GAPWrap.DecomposeTensorProduct( - codomain(Oscar.iso_oscar_gap(L)), - GAP.Obj(hw1; recursive=true), - GAP.Obj(hw2; recursive=true), - ), - )..., - ) - end + R = root_system(L) + return tensor_product_decomposition(R, hw1, hw2) end diff --git a/experimental/LieAlgebras/src/LieSubalgebra.jl b/experimental/LieAlgebras/src/LieSubalgebra.jl index d9267e1403b0..f83919ff27cb 100644 --- a/experimental/LieAlgebras/src/LieSubalgebra.jl +++ b/experimental/LieAlgebras/src/LieSubalgebra.jl @@ -48,6 +48,8 @@ Return the dimension of the Lie subalgebra `S`. """ dim(S::LieSubalgebra) = length(basis(S)) +coefficient_ring(S::LieSubalgebra) = coefficient_ring(base_lie_algebra(S)) + ############################################################################### # # String I/O @@ -79,6 +81,55 @@ function Base.show(io::IO, S::LieSubalgebra) end end +############################################################################### +# +# Parent object call overload +# +############################################################################### + +@doc raw""" + (S::LieSubalgebra{C})() -> LieAlgebraElem{C} + +Return the zero element of the Lie subalgebra `S`. +""" +function (S::LieSubalgebra)() + return zero(base_lie_algebra(S)) +end + +@doc raw""" + (S::LieSubalgebra{C})(v::Vector{Int}) -> LieAlgebraElem{C} + +Return the element of `S` with coefficient vector `v`. +Fail, if `Int` cannot be coerced into the base ring of `S`. +""" +function (S::LieSubalgebra)(v::Vector{Int}) + return S(coefficient_ring(S).(v)) +end + +@doc raw""" + (S::LieSubalgebra{C})(v::Vector{C}) -> LieAlgebraElem{C} + +Return the element of `S` with coefficient vector `v`. +""" +function (S::LieSubalgebra{C})(v::Vector{C}) where {C<:FieldElem} + @req length(v) == dim(S) "Length of vector does not match dimension." + mat = matrix(coefficient_ring(S), 1, length(v), v) + L = base_lie_algebra(S) + return elem_type(L)(L, mat * basis_matrix(S)) +end + +@doc raw""" + (S::LieSubalgebra{C})(mat::MatElem{C}) -> LieAlgebraElem{C} + +Return the element of `S` with coefficient vector equivalent to +the $1 \times \dim(S)$ matrix `mat`. +""" +function (S::LieSubalgebra{C})(mat::MatElem{C}) where {C<:FieldElem} + @req size(mat) == (1, dim(S)) "Invalid matrix dimensions." + L = base_lie_algebra(S) + return elem_type(L)(L, mat * basis_matrix(S)) +end + ############################################################################### # # Comparisons @@ -114,14 +165,26 @@ end ############################################################################### @doc raw""" - in(x::LieAlgebraElem, S::LieSubalgebra) -> Bool + in(x::LieAlgebraElem{C}, S::LieSubalgebra{C}) -> Bool Return `true` if `x` is in the Lie subalgebra `S`, `false` otherwise. """ -function Base.in(x::LieAlgebraElem, S::LieSubalgebra) +function Base.in(x::LieAlgebraElem{C}, S::LieSubalgebra{C}) where {C<:FieldElem} + @req parent(x) === base_lie_algebra(S) "Incompatible Lie algebras" return can_solve(basis_matrix(S), _matrix(x); side=:left) end +@doc raw""" + Oscar.LieAlgebras.coefficient_vector(x::LieAlgebraElem{C}, S::LieSubalgebra{C}) -> Vector{C} + +Return the coefficient vector of `x` in the basis of `S`. +This function will throw an error if `x` is not in `S`. +""" +function coefficient_vector(x::LieAlgebraElem{C}, S::LieSubalgebra{C}) where {C<:FieldElem} + @req parent(x) === base_lie_algebra(S) "Incompatible Lie algebras" + return solve(basis_matrix(S), _matrix(x); side=:left) +end + ############################################################################### # # Constructions @@ -211,6 +274,18 @@ function is_self_normalizing(S::LieSubalgebra) return normalizer(base_lie_algebra(S), S) == S end +############################################################################### +# +# More misc stuff +# +############################################################################### + +function any_non_ad_nilpotent_element(S::LieSubalgebra) + LS, emb = lie_algebra(S) + x = any_non_ad_nilpotent_element(LS) + return emb(x) +end + ############################################################################### # # Conversion diff --git a/experimental/LieAlgebras/src/LinearLieAlgebra.jl b/experimental/LieAlgebras/src/LinearLieAlgebra.jl index 39328a3e17de..61d8183922ac 100644 --- a/experimental/LieAlgebras/src/LinearLieAlgebra.jl +++ b/experimental/LieAlgebras/src/LinearLieAlgebra.jl @@ -146,6 +146,31 @@ function bracket( return coerce_to_lie_algebra_elem(L, x_mat * y_mat - y_mat * x_mat) end +############################################################################### +# +# Root system getters +# +############################################################################### + +has_root_system(L::LinearLieAlgebra) = isdefined(L, :root_system) + +function root_system(L::LinearLieAlgebra) + assure_root_system(L) + return L.root_system +end + +function chevalley_basis(L::LinearLieAlgebra) + assure_root_system(L) + return L.chevalley_basis::NTuple{3,Vector{elem_type(L)}} +end + +function set_root_system_and_chevalley_basis!( + L::LinearLieAlgebra{C}, R::RootSystem, chev::NTuple{3,Vector{LinearLieAlgebraElem{C}}} +) where {C<:FieldElem} + L.root_system = R + L.chevalley_basis = chev +end + ############################################################################### # # Constructor @@ -179,7 +204,7 @@ function lie_algebra( @req all(parent(x) === parent_L for x in basis) "Elements not compatible." R = coefficient_ring(parent_L) n = parent_L.n - s = map(AbstractAlgebra.obj_to_string, basis) + s = map(AbstractAlgebra.obj_to_string_wrt_times, basis) return lie_algebra(R, n, matrix_repr.(basis), s; check) end diff --git a/experimental/LieAlgebras/src/Types.jl b/experimental/LieAlgebras/src/Types.jl index 7144089ed6f8..073a2c080081 100644 --- a/experimental/LieAlgebras/src/Types.jl +++ b/experimental/LieAlgebras/src/Types.jl @@ -157,6 +157,7 @@ abstract type LieAlgebraElem{C<:FieldElem} <: AbstractAlgebra.SetElem end # only set if known root_system::RootSystem + chevalley_basis::NTuple{3,Vector{<:LieAlgebraElem{C}}} function AbstractLieAlgebra{C}( R::Field, @@ -205,6 +206,10 @@ end basis::Vector{MatElem{C}} s::Vector{Symbol} + # only set if known + root_system::RootSystem + chevalley_basis::NTuple{3,Vector{<:LieAlgebraElem{C}}} + function LinearLieAlgebra{C}( R::Field, n::Int, @@ -240,6 +245,10 @@ end summands::Vector{<:LieAlgebra{C}} s::Vector{Symbol} + # only set if known + root_system::RootSystem + chevalley_basis::NTuple{3,Vector{<:LieAlgebraElem{C}}} + function DirectSumLieAlgebra{C}( R::Field, summands::Vector{<:LieAlgebra{C}}, @@ -248,6 +257,7 @@ end totaldim = sum(dim, summands; init=0) s = [Symbol("$(x)^($(i))") for (i, S) in enumerate(summands) for x in symbols(S)] L = new{C}(R, totaldim, summands, s) + # TODO: glue root systems if all summands have one return L end end diff --git a/experimental/LieAlgebras/src/exports.jl b/experimental/LieAlgebras/src/exports.jl index 18a021ab3bff..8383f35fcc20 100644 --- a/experimental/LieAlgebras/src/exports.jl +++ b/experimental/LieAlgebras/src/exports.jl @@ -20,11 +20,14 @@ export WeylOrbitIterator export abelian_lie_algebra export abstract_module +export adjoint_matrix +export any_non_ad_nilpotent_element export base_lie_algebra export bilinear_form export bracket export cartan_bilinear_form export cartan_matrix +export cartan_subalgebra export cartan_symmetrizer export cartan_type export cartan_type_with_ordering @@ -39,12 +42,14 @@ export derived_algebra export dim_of_simple_module export dominant_character export dominant_weights +export engel_subalgebra export exterior_power export fundamental_weight export fundamental_weights export general_linear_lie_algebra export induced_map_on_symmetric_power export induced_map_on_tensor_power +export is_ad_nilpotent export is_cartan_matrix export is_cartan_type export is_coroot @@ -65,6 +70,7 @@ export is_simple_coroot export is_simple_coroot_with_index export is_simple_root export is_simple_root_with_index +export killing_matrix export lie_algebra export lmul, lmul! export longest_element diff --git a/experimental/LieAlgebras/src/serialization.jl b/experimental/LieAlgebras/src/serialization.jl index 9d74c7a26357..c5178178c538 100644 --- a/experimental/LieAlgebras/src/serialization.jl +++ b/experimental/LieAlgebras/src/serialization.jl @@ -96,12 +96,13 @@ end function load_root_system_data(s::DeserializerState, L::LieAlgebra) if haskey(s, :root_system) - @assert L isa AbstractLieAlgebra # TODO: adapt once we have a proper interface for this - L.root_system = load_typed_object(s, :root_system) - chevalley_basis = load_object( - s, Tuple, [(Vector, (AbstractLieAlgebraElem, L)) for _ in 1:3], :chevalley_basis - ) - # chevalley basis will become an attribute in the near future + rs = load_typed_object(s, :root_system) + chev = NTuple{3,Vector{elem_type(L)}}( + load_object( + s, Tuple, [(Vector, (AbstractLieAlgebraElem, L)) for _ in 1:3], :chevalley_basis + ), + ) # coercion needed due to https://github.com/oscar-system/Oscar.jl/issues/3983 + set_root_system_and_chevalley_basis!(L, rs, chev) end end diff --git a/experimental/LieAlgebras/test/LieAlgebra-test.jl b/experimental/LieAlgebras/test/LieAlgebra-test.jl index 121d73446ccb..00d07c14bbf0 100644 --- a/experimental/LieAlgebras/test/LieAlgebra-test.jl +++ b/experimental/LieAlgebras/test/LieAlgebra-test.jl @@ -93,4 +93,25 @@ @test !is_nilpotent(L) @test !is_solvable(L) end + + @testset "adjoint_matrix" begin + @testset for n in 2:4, F in [QQ, GF(2), GF(3)] + L = general_linear_lie_algebra(F, n) + x = L(rand(-10:10, dim(L))) + y = L(rand(-10:10, dim(L))) + @test coefficients(x * y) == coefficients(y) * adjoint_matrix(x) + end + end + + @testset "Killing matrix" begin + # Example from Hum72, Ch. 5.1 + F = QQ + L = lie_algebra( + F, + 2, + [matrix(F, [0 1; 0 0]), matrix(F, [1 0; 0 -1]), matrix(F, [0 0; 1 0])], + [:x, :h, :y], + ) + @test killing_matrix(L) == matrix(F, [0 0 4; 0 8 0; 4 0 0]) + end end diff --git a/experimental/LieAlgebras/test/LieAlgebraModule-test.jl b/experimental/LieAlgebras/test/LieAlgebraModule-test.jl index 28abe42dee2f..c077a1862e11 100644 --- a/experimental/LieAlgebras/test/LieAlgebraModule-test.jl +++ b/experimental/LieAlgebras/test/LieAlgebraModule-test.jl @@ -772,10 +772,10 @@ @test dim == 16 end - let L = special_orthogonal_lie_algebra(QQ, 5) # type B_2 but without known root system - dim = @inferred dim_of_simple_module(L, ZZ.([1, 2])) + let L = special_orthogonal_lie_algebra(QQ, 7) # type B_3 but without known root system + dim = @inferred dim_of_simple_module(L, ZZ.([1, 2, 1])) @test dim isa Int - @test dim == 35 + @test dim == 2800 end end @@ -896,14 +896,22 @@ ) end - let L = special_orthogonal_lie_algebra(QQ, 5), hw = ZZ.([1, 2]) # type B_2 but without known root system + let L = special_orthogonal_lie_algebra(QQ, 7), hw = ZZ.([1, 2, 0]) # type B_3 but without known root system domchar = check_dominant_character(L, hw) @test domchar == Dict( - [1, 2] => 1, - [2, 0] => 1, - [0, 2] => 2, - [1, 0] => 3, - [0, 0] => 3, + [1, 2, 0] => 1, + [2, 0, 2] => 1, + [2, 1, 0] => 1, + [0, 1, 2] => 2, + [0, 2, 0] => 2, + [3, 0, 0] => 2, + [1, 0, 2] => 3, + [1, 1, 0] => 6, + [2, 0, 0] => 6, + [0, 0, 2] => 9, + [0, 1, 0] => 9, + [1, 0, 0] => 15, + [0, 0, 0] => 15, ) end end @@ -984,7 +992,7 @@ ) end - let L = special_orthogonal_lie_algebra(QQ, 5), hw = ZZ.([1, 2]) # type B_2 but without known root system + let L = special_orthogonal_lie_algebra(QQ, 7), hw = ZZ.([1, 2, 1]) # type B_3 but without known root system char = check_character(L, hw) end end @@ -1090,18 +1098,26 @@ )) end - let L = special_orthogonal_lie_algebra(QQ, 5), hw1 = ZZ.([1, 2]), hw2 = ZZ.([1, 1]) # type B_2 but without known root system + let L = special_orthogonal_lie_algebra(QQ, 7) # type B_3 but without known root system + hw1 = ZZ.([1, 1, 0]) + hw2 = ZZ.([0, 1, 1]) + dec = test_tensor_product_decomposition(L, hw1, hw2) @test dec == multiset( Dict( - [2, 3] => 1, - [3, 1] => 1, - [0, 5] => 1, - [1, 3] => 2, - [2, 1] => 2, - [0, 3] => 2, - [1, 1] => 2, - [0, 1] => 1, + [1, 2, 1] => 1, + [2, 0, 3] => 1, + [2, 1, 1] => 1, + [0, 1, 3] => 1, + [0, 2, 1] => 1, + [3, 0, 1] => 1, + [1, 0, 3] => 2, + [1, 1, 1] => 3, + [2, 0, 1] => 2, + [0, 0, 3] => 2, + [0, 1, 1] => 2, + [1, 0, 1] => 2, + [0, 0, 1] => 1, ), ) end diff --git a/experimental/LieAlgebras/test/setup_tests.jl b/experimental/LieAlgebras/test/setup_tests.jl index 2c55ed3ed13d..baaa7ee27ce9 100644 --- a/experimental/LieAlgebras/test/setup_tests.jl +++ b/experimental/LieAlgebras/test/setup_tests.jl @@ -110,14 +110,28 @@ if !isdefined(Main, :lie_algebra_conformance_test) || isinteractive() end @testset "Root systems" begin - if has_root_system(L) - rs = root_system(L) - @test rs isa RootSystem - @test dim(L) == n_roots(rs) + n_simple_roots(rs) - chev = @inferred chevalley_basis(L) - @test length(chev) == 3 - @test length(chev[1]) == length(chev[2]) - @test dim(L) == sum(length, chev; init=0) + if !(L isa DirectSumLieAlgebra) # TODO: make root_system work for DirectSumLieAlgebra + try + root_system(L) + catch e + e isa ArgumentError || rethrow() + @test any( + msg -> contains(e.msg, msg), + ["Killing form is degenerate", "Cartan subalgebra is not split"], + ) + end + if has_root_system(L) + rs = root_system(L) + @test rs isa RootSystem + @test dim(L) == n_roots(rs) + n_simple_roots(rs) + chev = @inferred chevalley_basis(L) + @test length(chev) == 3 + @test length(chev[1]) == length(chev[2]) + @test dim(L) == sum(length, chev; init=0) + H = cartan_subalgebra(L) + @test all(h -> h in H, chev[3]) + @test all(xs -> bracket(xs...) in H, zip(chev[1], chev[2])) + end end end