From 3e670f7f618607140e0b5a02bd7a4b5a617fb35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20G=C3=B6ttgens?= Date: Fri, 22 Nov 2024 15:15:46 +0100 Subject: [PATCH] Root system documentation (#4297) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Felix Röhrich --- .../src/MainAlgorithm.jl | 18 +- .../test/MainAlgorithm-test.jl | 6 - experimental/LieAlgebras/docs/doc.main | 2 + .../LieAlgebras/docs/src/cartan_matrix.md | 46 +- .../LieAlgebras/docs/src/introduction.md | 2 +- .../LieAlgebras/docs/src/root_systems.md | 223 ++++ .../LieAlgebras/docs/src/weyl_groups.md | 85 ++ experimental/LieAlgebras/src/CartanMatrix.jl | 123 +- .../LieAlgebras/src/LieAlgebraIdeal.jl | 2 +- experimental/LieAlgebras/src/LieAlgebras.jl | 2 + experimental/LieAlgebras/src/LieSubalgebra.jl | 2 +- .../LieAlgebras/src/LinearLieAlgebra.jl | 45 - experimental/LieAlgebras/src/RootSystem.jl | 1070 ++++++++++++----- experimental/LieAlgebras/src/Types.jl | 71 +- experimental/LieAlgebras/src/WeylGroup.jl | 166 ++- experimental/LieAlgebras/src/exports.jl | 5 +- .../LieAlgebras/test/CartanMatrix-test.jl | 2 +- .../LieAlgebras/test/RootSystem-test.jl | 33 +- .../LieAlgebras/test/WeylGroup-test.jl | 5 +- 19 files changed, 1440 insertions(+), 468 deletions(-) create mode 100644 experimental/LieAlgebras/docs/src/root_systems.md create mode 100644 experimental/LieAlgebras/docs/src/weyl_groups.md diff --git a/experimental/BasisLieHighestWeight/src/MainAlgorithm.jl b/experimental/BasisLieHighestWeight/src/MainAlgorithm.jl index 310c4629d834..889fc3ae0161 100644 --- a/experimental/BasisLieHighestWeight/src/MainAlgorithm.jl +++ b/experimental/BasisLieHighestWeight/src/MainAlgorithm.jl @@ -203,7 +203,7 @@ function compute_monomials( # if highest_weight is not a fundamental weight, partition into smaller summands is possible. This is the basecase of # the recursion. dim = dim_of_simple_module(L, highest_weight) - if is_zero(highest_weight) || is_fundamental(highest_weight) + if is_zero(highest_weight) || is_fundamental_weight(highest_weight) push!(no_minkowski, highest_weight) monomials = add_by_hand( L, birational_seq, ZZx, highest_weight, monomial_ordering, Set{ZZMPolyRingElem}() @@ -473,22 +473,6 @@ function operators_lusztig(L::LieAlgebra, reduced_expression::Vector{Int}) return operators end -# TODO: upstream to LieAlgebras/RootSystem.jl -function is_fundamental(highest_weight::WeightLatticeElem) - hasone = false - for i in coefficients(highest_weight) - if iszero(i) - continue - elseif isone(i) - hasone && return false - hasone = true - else - return false - end - end - return hasone -end - function sub_weights(w::WeightLatticeElem) # returns list of weights v != 0, highest_weight with 0 <= v <= w elementwise @req is_dominant(w) "The input must be a dominant weight" diff --git a/experimental/BasisLieHighestWeight/test/MainAlgorithm-test.jl b/experimental/BasisLieHighestWeight/test/MainAlgorithm-test.jl index 88eae9d1ddaa..b5c9a0d8a9b2 100644 --- a/experimental/BasisLieHighestWeight/test/MainAlgorithm-test.jl +++ b/experimental/BasisLieHighestWeight/test/MainAlgorithm-test.jl @@ -31,12 +31,6 @@ function check_dimension( @test gap_dim == dim(basis) == length(monomials(basis)) # check if dimension is correct end -@testset "is_fundamental" begin - R = root_system(:B, 3) - @test BasisLieHighestWeight.is_fundamental(WeightLatticeElem(R, [ZZ(0), ZZ(1), ZZ(0)])) - @test !BasisLieHighestWeight.is_fundamental(WeightLatticeElem(R, [ZZ(0), ZZ(1), ZZ(1)])) -end - @testset "sub_weights(_proper)" begin sub_weights = BasisLieHighestWeight.sub_weights sub_weights_proper = BasisLieHighestWeight.sub_weights_proper diff --git a/experimental/LieAlgebras/docs/doc.main b/experimental/LieAlgebras/docs/doc.main index d2cf400f1b04..50f30650ee38 100644 --- a/experimental/LieAlgebras/docs/doc.main +++ b/experimental/LieAlgebras/docs/doc.main @@ -7,5 +7,7 @@ "modules.md", "module_homs.md", "cartan_matrix.md", + "root_systems.md", + "weyl_groups.md", ], ] diff --git a/experimental/LieAlgebras/docs/src/cartan_matrix.md b/experimental/LieAlgebras/docs/src/cartan_matrix.md index 4f15e3cf32c9..aca018c4dc4c 100644 --- a/experimental/LieAlgebras/docs/src/cartan_matrix.md +++ b/experimental/LieAlgebras/docs/src/cartan_matrix.md @@ -5,12 +5,46 @@ DocTestSetup = Oscar.doctestsetup() # Cartan Matrices +Cartan matrices can be constructed from a Cartan type, and are represented as a square `ZZMatrix`. + +Many functions taking a Cartan matrix as input (like [`root_system`](@ref) and [`weyl_group`](@ref)) will also accept a Cartan type as input. Both cases are semantically equivalent, but the latter may be more efficient. + +!!! note + The convention for Cartan matrices in OSCAR is $(a_{ij}) = (\langle \alpha_i^\vee, \alpha_j \rangle)$ for simple roots $\alpha_i$. + +## Table of contents + +```@contents +Pages = ["cartan_matrix.md"] +Depth = 2:5 +``` + +## Constructors + ```@docs cartan_matrix(::Symbol, ::Int) -cartan_matrix(::Tuple{Symbol,Int}...) -is_cartan_matrix(::ZZMatrix; generalized::Bool) -cartan_symmetrizer(::ZZMatrix; check::Bool) -cartan_bilinear_form(::ZZMatrix; check::Bool) -cartan_type(::ZZMatrix; check::Bool) -cartan_type_with_ordering(::ZZMatrix; check::Bool) +cartan_matrix(::Vector{Tuple{Symbol,Int}}) +``` + + +## Properties + +```@docs +is_cartan_matrix(::ZZMatrix) +cartan_symmetrizer(::ZZMatrix) +cartan_bilinear_form(::ZZMatrix) +``` + + +## Cartan types + +The following function is used to verify the validity of a Cartan type, and is thus used in input sanitization. +```@docs +is_cartan_type(::Symbol, ::Int) +``` + +Given a Cartan matrix, the following functions can be used to determine its Cartan type. +```@docs +cartan_type(::ZZMatrix) +cartan_type_with_ordering(::ZZMatrix) ``` diff --git a/experimental/LieAlgebras/docs/src/introduction.md b/experimental/LieAlgebras/docs/src/introduction.md index 5b80ffc1f547..2e8f52cded99 100644 --- a/experimental/LieAlgebras/docs/src/introduction.md +++ b/experimental/LieAlgebras/docs/src/introduction.md @@ -16,7 +16,7 @@ This part of OSCAR is in an experimental state; please see [Adding new projects Please direct questions about this part of OSCAR to the following people: * [Lars Göttgens](https://lgoe.li/) -* [Laura Voggesberger](https://www.ruhr-uni-bochum.de/ffm/Lehrstuehle/Lehrstuhl-VI/voggesberger.html) +* [Felix Röhrich](https://www.art.rwth-aachen.de/cms/~xlgua) You can ask questions in the [OSCAR Slack](https://www.oscar-system.org/community/#slack). diff --git a/experimental/LieAlgebras/docs/src/root_systems.md b/experimental/LieAlgebras/docs/src/root_systems.md new file mode 100644 index 000000000000..e61c7f80cef5 --- /dev/null +++ b/experimental/LieAlgebras/docs/src/root_systems.md @@ -0,0 +1,223 @@ +```@meta +CurrentModule = Oscar +DocTestSetup = Oscar.doctestsetup() +``` + +# Root systems + +Root systems in this module are meant to be abstract root systems, i.e. they are represented by a set of roots (vectors in an euclidean space). + +The relevant types around root systems are: +- `RootSystem` for the root system itself, +- `RootSpaceElem` for elements in the root space, i.e. roots and linear combinations thereof, +- `DualRootSpaceElem` for elements in the dual root space, i.e. coroots and linear combinations thereof, +- `WeightLatticeElem` for elements in the weight lattice, i.e. weights and linear combinations thereof. + +!!! warning + Most functionality around root systems is currently only intended to be used with root systems of finite type. + For root systems of affine type, some documentation may be ill-phrased or incorrect, and some functions may not work as intended. + +## Table of contents + +```@contents +Pages = ["root_systems.md"] +Depth = 2:5 +``` + +## Constructing root systems + +```@docs +root_system(::ZZMatrix) +root_system(::Symbol, ::Int64) +root_system(::Vector{Tuple{Symbol, Int64}}) +``` + + +## Properties of root systems + +```@docs +is_simple(::RootSystem) +rank(::RootSystem) +``` + + +### Cartan matrix and Weyl group +```@docs +cartan_matrix(::RootSystem) +``` +```@docs; canonical=false +weyl_group(::RootSystem) +``` + + +### Root system type +```@docs +has_root_system_type(::RootSystem) +root_system_type(::RootSystem) +root_system_type_with_ordering(::RootSystem) +``` + + +### Root getters +```@docs +number_of_roots(::RootSystem) +number_of_positive_roots(::RootSystem) +number_of_simple_roots(::RootSystem) +``` + +The following functions return roots, see [Root space elements](@ref) for more information. +```@docs +root(::RootSystem, ::Int64) +roots(::RootSystem) +simple_root(::RootSystem, ::Int64) +simple_roots(::RootSystem) +positive_root(::RootSystem, ::Int64) +positive_roots(::RootSystem) +negative_root(::RootSystem, ::Int64) +negative_roots(::RootSystem) +``` + + +### Coroot getters +The following functions return coroots, see [Dual root space elements](@ref) for more information. +```@docs +coroot(::RootSystem, ::Int64) +coroots(::RootSystem) +simple_coroot(::RootSystem, ::Int64) +simple_coroots(::RootSystem) +positive_coroot(::RootSystem, ::Int64) +positive_coroots(::RootSystem) +negative_coroot(::RootSystem, ::Int64) +negative_coroots(::RootSystem) +``` + + +### Weight getters +The following functions return weights, see [Weight lattice elements](@ref) for more information. +```@docs +fundamental_weight(::RootSystem, ::Int64) +fundamental_weights(::RootSystem) +weyl_vector(::RootSystem) +``` + + +## Root space elements + +```@docs +RootSpaceElem(::RootSystem, ::Vector{<:RationalUnion}) +RootSpaceElem(::RootSystem, ::QQMatrix) +RootSpaceElem(::WeightLatticeElem) +zero(::Type{RootSpaceElem}, ::RootSystem) +``` + +```@docs +root_system(::RootSpaceElem) +``` + +Basic arithmetic operations like `zero`, `+`, `-`, `*` (with rational scalars), and `==` are supported. + +```@docs +coeff(::RootSpaceElem, ::Int) +coefficients(::RootSpaceElem) +``` + +```@docs +height(::RootSpaceElem) +iszero(::RootSpaceElem) +``` + +### Root testing +```@docs +is_root(::RootSpaceElem) +is_root_with_index(::RootSpaceElem) +is_simple_root(::RootSpaceElem) +is_simple_root_with_index(::RootSpaceElem) +is_positive_root(::RootSpaceElem) +is_positive_root_with_index(::RootSpaceElem) +is_negative_root(::RootSpaceElem) +is_negative_root_with_index(::RootSpaceElem) +``` + +### Reflections +```@docs +reflect(::RootSpaceElem, ::Int) +reflect!(::RootSpaceElem, ::Int) +``` + + +## Dual root space elements + +```@docs +DualRootSpaceElem(::RootSystem, ::Vector{<:RationalUnion}) +DualRootSpaceElem(::RootSystem, ::QQMatrix) +zero(::Type{DualRootSpaceElem}, ::RootSystem) +``` + +```@docs +root_system(::DualRootSpaceElem) +``` + +Basic arithmetic operations like `zero`, `+`, `-`, `*` (with rational scalars), and `==` are supported. + +```@docs +coeff(::DualRootSpaceElem, ::Int) +coefficients(::DualRootSpaceElem) +``` + +```@docs +height(::DualRootSpaceElem) +iszero(::DualRootSpaceElem) +``` + +### Coroot testing +```@docs +is_coroot(::DualRootSpaceElem) +is_coroot_with_index(::DualRootSpaceElem) +is_simple_coroot(::DualRootSpaceElem) +is_simple_coroot_with_index(::DualRootSpaceElem) +is_positive_coroot(::DualRootSpaceElem) +is_positive_coroot_with_index(::DualRootSpaceElem) +is_negative_coroot(::DualRootSpaceElem) +is_negative_coroot_with_index(::DualRootSpaceElem) +``` + + +## Weight lattice elements + +```@docs +WeightLatticeElem(::RootSystem, ::Vector{<:IntegerUnion}) +WeightLatticeElem(::RootSystem, ::ZZMatrix) +WeightLatticeElem(::RootSpaceElem) +zero(::Type{WeightLatticeElem}, ::RootSystem) +``` + +```@docs +root_system(::WeightLatticeElem) +``` + +Basic arithmetic operations like `zero`, `+`, `-`, `*` (with integer scalars), and `==` are supported. + +```@docs +coeff(::WeightLatticeElem, ::Int) +coefficients(::WeightLatticeElem) +``` + +```@docs +iszero(::WeightLatticeElem) +is_dominant(::WeightLatticeElem) +is_fundamental_weight(::WeightLatticeElem) +is_fundamental_weight_with_index(::WeightLatticeElem) +``` + +### Reflections +```@docs +reflect(::WeightLatticeElem, ::Int) +reflect!(::WeightLatticeElem, ::Int) +``` + +### Conjugate dominant weight +```@docs +conjugate_dominant_weight(::WeightLatticeElem) +conjugate_dominant_weight_with_left_elem(::WeightLatticeElem) +conjugate_dominant_weight_with_right_elem(::WeightLatticeElem) +``` diff --git a/experimental/LieAlgebras/docs/src/weyl_groups.md b/experimental/LieAlgebras/docs/src/weyl_groups.md new file mode 100644 index 000000000000..78a31ad11b38 --- /dev/null +++ b/experimental/LieAlgebras/docs/src/weyl_groups.md @@ -0,0 +1,85 @@ +```@meta +CurrentModule = Oscar +DocTestSetup = Oscar.doctestsetup() +``` + +# Weyl groups + +Weyl groups are represented by objects of type `WeylGroup <: Group`, and their elements by `WeylGroupElem <: GroupElement`. + +## Table of contents + +```@contents +Pages = ["weyl_groups.md"] +Depth = 2:5 +``` + +## Constructing Weyl groups +```@docs +weyl_group(::RootSystem) +weyl_group(::ZZMatrix) +weyl_group(::Symbol, ::Int) +weyl_group(::Vector{Tuple{Symbol,Int}}) +``` + +## Basic properties +Basic group arithmetic like `*`, and `inv` are defined for `WeylGroupElem` objects. + +Using `(W::WeylGroup)(word::Vector{<:Integer})`, one can construct group elements from a word in the generators. + +```@docs +is_finite(::WeylGroup) +one(::WeylGroup) +isone(::WeylGroupElem) +gen(::WeylGroup, ::Int) +gens(::WeylGroup) +number_of_generators(::WeylGroup) +order(::Type{T}, ::WeylGroup) where {T} +``` + +```@docs +root_system(::WeylGroup) +``` + +### Words and length +```@docs +word(::WeylGroupElem) +length(::WeylGroupElem) +longest_element(::WeylGroup) +``` + +### Bruhat order +```@docs +<(::WeylGroupElem, ::WeylGroupElem) +``` + + +## Conversion to other group types + +For many computations, it may be suitable to have a `WeylGroup` as a different kind of group object, to e.g. use functionality that is only available for that other type. + +The conversion functions come in pairs: one only creates an isomorphic group object, the other also computes the isomorphism. + +```@docs +fp_group(::WeylGroup) +isomorphism(::Type{FPGroup}, ::WeylGroup) +``` + + +## Reduced expressions + +```@docs +reduced_expressions(::WeylGroupElem) +``` + + +## Action on roots and weights + +```@docs +*(::WeylGroupElem, ::Union{RootSpaceElem,WeightLatticeElem}) +*(::Union{RootSpaceElem,WeightLatticeElem}, ::WeylGroupElem) +``` + + +### Orbits +TODO diff --git a/experimental/LieAlgebras/src/CartanMatrix.jl b/experimental/LieAlgebras/src/CartanMatrix.jl index a61b15333d01..faa75fd21e45 100644 --- a/experimental/LieAlgebras/src/CartanMatrix.jl +++ b/experimental/LieAlgebras/src/CartanMatrix.jl @@ -9,10 +9,11 @@ cartan_matrix(fam::Symbol, rk::Int) -> ZZMatrix Return the Cartan matrix of finite type, where `fam` is the family ($A$, $B$, $C$, $D$, $E$, $F$ $G$) -and `rk` is the rank of the associated the root system; for $B$ and $C$ the rank has to be at least 2, for $D$ at least 4. -The convention is $(a_{ij}) = (\langle \alpha_i^\vee, \alpha_j \rangle)$ for simple roots $\alpha_i$. +and `rk` is the rank of the associated the root system. -# Example +The input must be a valid Cartan type, see [`is_cartan_type(::Symbol, ::Int)`](@ref). + +# Examples ```jldoctest julia> cartan_matrix(:B, 2) [ 2 -1] @@ -81,17 +82,27 @@ end @doc raw""" cartan_matrix(type::Vector{Tuple{Symbol,Int}}) -> ZZMatrix + cartan_matrix(type::Tuple{Symbol,Int}...) -> ZZMatrix -Returns a block diagonal matrix of indecomposable Cartan matrices as defined by type. -For allowed values see `cartan_matrix(fam::Symbol, rk::Int)`. +Construct a block diagonal matrix of indecomposable Cartan matrices as defined by `type` +that are constructed by [`cartan_matrix(::Symbol, ::Int)`](@ref). -# Example +Each element of `type` must be a valid Cartan type, see [`is_cartan_type(::Symbol, ::Int)`](@ref). +The vararg version needs at least one element. + +# Examples ```jldoctest julia> cartan_matrix([(:A, 2), (:B, 2)]) [ 2 -1 0 0] [-1 2 0 0] [ 0 0 2 -1] [ 0 0 -2 2] + +julia> cartan_matrix((:C, 2), (:A, 2)) +[ 2 -2 0 0] +[-1 2 0 0] +[ 0 0 2 -1] +[ 0 0 -1 2] ``` """ function cartan_matrix(type::Vector{Tuple{Symbol,Int}}) @@ -101,34 +112,17 @@ function cartan_matrix(type::Vector{Tuple{Symbol,Int}}) return block_diagonal_matrix(blocks) end -@doc raw""" - cartan_matrix(type::Tuple{Symbol,Int}...) -> ZZMatrix - -Return a block diagonal matrix of indecomposable Cartan matrices as defined by type. -For allowed values see `cartan_matrix(fam::Symbol, rk::Int)`. - -# Example -```jldoctest -julia> cartan_matrix((:A, 2), (:B, 2)) -[ 2 -1 0 0] -[-1 2 0 0] -[ 0 0 2 -1] -[ 0 0 -2 2] -``` -""" -function cartan_matrix(type::Tuple{Symbol,Int}...) - @req length(type) > 0 "At least one type is required" - - return cartan_matrix(collect(type)) +function cartan_matrix(type1::Tuple{Symbol,Int}, type::Tuple{Symbol,Int}...) + return cartan_matrix([type1; type...]) end @doc raw""" is_cartan_matrix(mat::ZZMatrix; generalized::Bool=true) -> Bool -Checks if `mat` is a generalized Cartan matrix. The keyword argument `generalized` -can be set to `false` to restrict this to Cartan matrices of finite type. +Check if `mat` is a generalized Cartan matrix. +If `generalized=false` this check ist restricted to Cartan matrices of finite type. -# Example +# Examples ```jldoctest julia> is_cartan_matrix(ZZ[2 -2; -2 2]) true @@ -172,9 +166,10 @@ end Return a vector $d$ of coprime integers such that $(d_i a_{ij})_{ij}$ is a symmetric matrix, where $a_{ij}$ are the entries of the Cartan matrix `gcm`. -The keyword argument `check` can be set to `false` to skip verification whether `gcm` is indeed a generalized Cartan matrix. -# Example +If `check=true` the function will verify that `gcm` is indeed a generalized Cartan matrix. + +# Examples ```jldoctest julia> cartan_symmetrizer(cartan_matrix(:B, 2)) 2-element Vector{ZZRingElem}: @@ -183,7 +178,7 @@ julia> cartan_symmetrizer(cartan_matrix(:B, 2)) ``` """ function cartan_symmetrizer(gcm::ZZMatrix; check::Bool=true) - @req !check || is_cartan_matrix(gcm) "Requires a generalized Cartan matrix" + check && @req is_cartan_matrix(gcm) "not a Cartan matrix" rk = nrows(gcm) diag = ones(ZZRingElem, rk) @@ -251,14 +246,19 @@ end @doc raw""" cartan_bilinear_form(gcm::ZZMatrix; check::Bool=true) -> ZZMatrix -Return the matrix of the symmetric bilinear form associated to the Cartan matrix from `cartan_symmetrizer`. -The keyword argument `check` can be set to `false` to skip verification whether `gcm` is indeed a generalized Cartan matrix. +Return the matrix of the symmetric bilinear form associated to the Cartan matrix from +[`cartan_symmetrizer`](@ref). + +If `check=true` the function will verify that `gcm` is indeed a generalized Cartan matrix. -# Example +# Examples ```jldoctest -julia> cartan_bilinear_form(cartan_matrix(:B, 2)) +julia> bil = cartan_bilinear_form(cartan_matrix(:B, 2)) [ 4 -2] [-2 2] + +julia> is_symmetric(bil) +true ``` """ function cartan_bilinear_form(gcm::ZZMatrix; check::Bool=true) @@ -273,15 +273,22 @@ end @doc raw""" cartan_type(gcm::ZZMatrix; check::Bool=true) -> Vector{Tuple{Symbol, Int}} -Return the Cartan type of a Cartan matrix `gcm` (currently only Cartan matrices of finite type are supported). -This function is left inverse to `cartan_matrix`, i.e. in the case of isomorphic types (e.g. $B_2$ and $C_2$) -the ordering of the roots does matter (see the example below). -The keyword argument `check` can be set to `false` to skip verification whether `gcm` is indeed a Cartan matrix of finite type. +Return the Cartan type of a Cartan matrix `gcm`. + +This function is left inverse to [`cartan_matrix`](@ref), +i.e., in the case of isomorphic types (e.g. $B_2$ and $C_2$) the ordering of the roots does matter (see the example below). + +If `check=true` the function will verify that `gcm` is indeed a Cartan matrix. -The order of returned components is, in general, not unique and might change between versions. -If this function is called with the output of `cartan_matrix(type)`, it will keep the order of `type`. +!!! note + The order of returned components is, in general, not unique and might change between versions. + But we guarantee that if this function is called with the output of [`cartan_matrix(::Vector{Tuple{Symbol,Int}})`](@ref cartan_matrix(type::Vector{Tuple{Symbol,Int}})), + it will keep the order of `type`. -# Example +!!! warning + Currently only Cartan matrices of finite type are supported. + +# Examples ```jldoctest julia> cartan_type(ZZ[2 -1; -2 2]) 1-element Vector{Tuple{Symbol, Int64}}: @@ -301,15 +308,19 @@ end cartan_type_with_ordering(gcm::ZZMatrix; check::Bool=true) -> Vector{Tuple{Symbol, Int}}, Vector{Int} Return the Cartan type of a Cartan matrix `gcm` together with a vector indicating a canonical ordering -of the roots in the Dynkin diagram (currently only Cartan matrices of finite type are supported). -The keyword argument `check` can be set to `false` to skip verification whether `gcm` is indeed a -Cartan matrix of finite type. +of the roots in the Dynkin diagram. + +If `check=true` the function will verify that `gcm` is indeed a Cartan matrix. + +!!! note + The order of returned components is, in general, not unique and might change between versions. + But we guarantee that if this function is called with the output of [`cartan_matrix(::Vector{Tuple{Symbol,Int}})`](@ref cartan_matrix(type::Vector{Tuple{Symbol,Int}})), + it will keep the order of `type` and the ordering will be the identity. -The order of returned components and the ordering is, in general, not unique and might change between versions. -If this function is called with the output of `cartan_matrix(type)`, it will keep the order of `type` and the -returned ordering will be the identity. +!!! warning + Currently only Cartan matrices of finite type are supported. -# Example +# Examples ```jldoctest julia> cartan_type_with_ordering(cartan_matrix(:E, 6)) ([(:E, 6)], [1, 2, 3, 4, 5, 6]) @@ -319,7 +330,8 @@ julia> cartan_type_with_ordering(ZZ[2 0 -1 0; 0 2 0 -2; -2 0 2 0; 0 -1 0 2]) ``` """ function cartan_type_with_ordering(gcm::ZZMatrix; check::Bool=true) - @req !check || is_cartan_matrix(gcm; generalized=false) "requires Cartan matrix of finite type" + check && + @req is_cartan_matrix(gcm; generalized=false) "not a Cartan matrix of finite type" rk = nrows(gcm) type = Tuple{Symbol,Int}[] @@ -461,8 +473,15 @@ end @doc raw""" is_cartan_type(fam::Symbol, rk::Int) -> Bool -Checks if the pair (`fam`, `rk`) is a valid Cartan type, -i.e. one of `A_l` (l >= 1), `B_l` (l >= 2), `C_l` (l >= 2), `D_l` (l >= 4), `E_6`, `E_7`, `E_8`, `F_4`, `G_2`. +Check if the pair (`fam`, `rk`) is a valid Cartan type, +i.e., one of +- `A_l` ($l >= 1$), +- `B_l` ($l >= 2$), +- `C_l` ($l >= 2$), +- `D_l` ($l >= 4$), +- `E_6`, `E_7`, `E_8`, +- `F_4`, +- `G_2`. """ function is_cartan_type(fam::Symbol, rk::Int) fam in [:A, :B, :C, :D, :E, :F, :G] || return false diff --git a/experimental/LieAlgebras/src/LieAlgebraIdeal.jl b/experimental/LieAlgebras/src/LieAlgebraIdeal.jl index da7854b2b5da..f16075edafe0 100644 --- a/experimental/LieAlgebras/src/LieAlgebraIdeal.jl +++ b/experimental/LieAlgebras/src/LieAlgebraIdeal.jl @@ -262,7 +262,7 @@ end ############################################################################### @doc raw""" - lie_algebra(I::LieAlgebraIdeal) -> LieAlgebra + lie_algebra(I::LieAlgebraIdeal) -> LieAlgebra, LieAlgebraHom Return `I` as a Lie algebra `LI`, together with an embedding `LI -> L`, where `L` is the Lie algebra where `I` lives in. diff --git a/experimental/LieAlgebras/src/LieAlgebras.jl b/experimental/LieAlgebras/src/LieAlgebras.jl index e866dfbd8e64..cde9d32e94b9 100644 --- a/experimental/LieAlgebras/src/LieAlgebras.jl +++ b/experimental/LieAlgebras/src/LieAlgebras.jl @@ -22,6 +22,7 @@ import ..Oscar: _vec, action, add!, + addmul!, basis_matrix, basis, canonical_injection, @@ -68,6 +69,7 @@ import ..Oscar: kernel, lower_central_series, matrix, + mul!, neg!, normalizer, number_of_generators, diff --git a/experimental/LieAlgebras/src/LieSubalgebra.jl b/experimental/LieAlgebras/src/LieSubalgebra.jl index c5ddfc2ed057..3f876c3db902 100644 --- a/experimental/LieAlgebras/src/LieSubalgebra.jl +++ b/experimental/LieAlgebras/src/LieSubalgebra.jl @@ -293,7 +293,7 @@ end ############################################################################### @doc raw""" - lie_algebra(S::LieSubalgebra) -> LieAlgebra + lie_algebra(S::LieSubalgebra) -> LieAlgebra, LieAlgebraHom Return `S` as a Lie algebra `LS`, together with an embedding `LS -> L`, where `L` is the Lie algebra where `S` lives in. diff --git a/experimental/LieAlgebras/src/LinearLieAlgebra.jl b/experimental/LieAlgebras/src/LinearLieAlgebra.jl index 80cb4c0cba33..e6cbae94b3ad 100644 --- a/experimental/LieAlgebras/src/LinearLieAlgebra.jl +++ b/experimental/LieAlgebras/src/LinearLieAlgebra.jl @@ -99,51 +99,6 @@ function Base.show(io::IO, L::LinearLieAlgebra) end end -#= -function Base.show(io::IO, mime::MIME"text/plain", L::AbstractLieAlgebra) - @show_name(io, L) - @show_special(io, mime, L) - io = pretty(io) - println(io, "Abstract Lie algebra") - if has_root_system(L) - rs = root_system(L) - if has_root_system_type(rs) - type, ord = root_system_type_with_ordering(rs) - print(io, Indent(), "of type ", _root_system_type_string(type)) - if !issorted(ord) - print(io, " (non-canonical ordering)") - end - println(io, Dedent()) - end - end - println(io, Indent(), "of dimension ", dim(L), Dedent()) - print(io, "over ") - print(io, Lowercase(), coefficient_ring(L)) -end - -function Base.show(io::IO, L::AbstractLieAlgebra) - @show_name(io, L) - @show_special(io, L) - if is_terse(io) - print(io, "Abstract Lie algebra") - else - io = pretty(io) - print(io, "Abstract Lie algebra") - if has_root_system(L) - rs = root_system(L) - if has_root_system_type(rs) - type, ord = root_system_type_with_ordering(rs) - print(io, " of type ", _root_system_type_string(type)) - if !issorted(ord) - print(io, " (non-canonical ordering)") - end - end - end - print(terse(io), " over ", Lowercase(), coefficient_ring(L)) - end -end -=# - function _lie_algebra_type_to_string(type::Symbol, n::Int) if type == :general_linear return "General linear Lie algebra of degree $n" diff --git a/experimental/LieAlgebras/src/RootSystem.jl b/experimental/LieAlgebras/src/RootSystem.jl index 720777c74d38..48dc0e030b83 100644 --- a/experimental/LieAlgebras/src/RootSystem.jl +++ b/experimental/LieAlgebras/src/RootSystem.jl @@ -6,10 +6,11 @@ @doc raw""" root_system(cartan_matrix::ZZMatrix; check::Bool=true, detect_type::Bool=true) -> RootSystem - root_system(cartan_matrix::Matrix{Int}; check::Bool=true, detect_type::Bool=true) -> RootSystem + root_system(cartan_matrix::Matrix{<:Integer}; check::Bool=true, detect_type::Bool=true) -> RootSystem -Construct the root system defined by the Cartan matrix. -If `check` is `true`, checks that `cartan_matrix` is a generalized Cartan matrix. +Construct the root system defined by the given (generalized) Cartan matrix. + +If `check=true` the function will verify that `cartan_matrix` is indeed a generalized Cartan matrix. Passing `detect_type=false` will skip the detection of the root system type. # Examples @@ -38,7 +39,9 @@ end @doc raw""" root_system(fam::Symbol, rk::Int) -> RootSystem -Construct the root system of the given type. See `cartan_matrix(fam::Symbol, rk::Int)` for allowed combinations. +Construct the root system of the given type. + +The input must be a valid Cartan type, see [`is_cartan_type(::Symbol, ::Int)`](@ref). # Examples ```jldoctest @@ -56,8 +59,12 @@ end @doc raw""" root_system(type::Vector{Tuple{Symbol,Int}}) -> RootSystem + root_system(type::Tuple{Symbol,Int}...) -> RootSystem -Construct the root system of the given type. See `cartan_matrix(fam::Symbol, rk::Int)` for allowed combinations of tuples. +Construct the root system of the given type. + +Each element of `type` must be a valid Cartan type, see [`is_cartan_type(::Symbol, ::Int)`](@ref). +The vararg version needs at least one element. # Examples ```jldoctest @@ -77,8 +84,8 @@ function root_system(type::Vector{Tuple{Symbol,Int}}) return R end -function root_system(type::Tuple{Symbol,Int}...) - return root_system(collect(type)) +function root_system(type1::Tuple{Symbol,Int}, type::Tuple{Symbol,Int}...) + return root_system([type1; type...]) end function Base.show(io::IO, mime::MIME"text/plain", R::RootSystem) @@ -109,7 +116,6 @@ function Base.show(io::IO, R::RootSystem) print(io, "Root system") if has_root_system_type(R) && ((type, ord) = root_system_type_with_ordering(R); !isempty(type)) - type, ord = root_system_type_with_ordering(R) print(io, " of type ", _root_system_type_string(type)) if !issorted(ord) print(io, " (non-canonical ordering)") @@ -164,205 +170,220 @@ end return diagonal_matrix(ZZ, cartan_symmetrizer(R)) end -@doc raw""" - coroot(R::RootSystem, i::Int) -> RootSpaceElem +function Base.hash(R::RootSystem, h::UInt) + # even though we don't have a == method for RootSystem, we add a hash method + # to make hashing of RootSpaceElem and WeightLatticeElem more deterministic + b = 0xeb5362118dea2a0e % UInt + h = hash(cartan_matrix(R), h) + return xor(b, h) +end -Returns the `i`-th coroot of `R`, i.e. the `i`-th root of the dual root system of `R`. -This is a more efficient version for `coroots(R)[i]`. +@doc raw""" + is_simple(R::RootSystem) -> Bool -Also see: `coroots`. +Check if `R` is a simple root system. -!!! note - This function does not return a copy of the asked for object, - but the internal field of the root system. - Mutating the returned object will lead to undefined behavior. +!!! warning + Currently only root systems of finite type are supported. """ -function coroot(R::RootSystem, i::Int) - if i <= n_positive_roots(R) - return positive_coroot(R, i) - else - return negative_coroot(R, i - n_positive_roots(R)) +function is_simple(R::RootSystem) + if is_finite(weyl_group(R)) + return length(root_system_type(R)) == 1 end + error("Not implemented") # TODO: implement is_simple end @doc raw""" - coroots(R::RootSystem) -> Vector{RootSpaceElem} - -Returns the coroots of `R`, starting with the coroots of positive roots and then the negative roots, -in the order of `positive_coroots` and `negative_coroots`. + number_of_positive_roots(R::RootSystem) -> Int -Also see: `coroot`. +Return the number of positive roots of `R`. This is the same as the number of negative roots. -!!! note - This function does not return a copy of the asked for object, - but the internal field of the root system. - Mutating the returned object will lead to undefined behavior. +See also: [`positive_roots(::RootSystem)`](@ref positive_roots), [`negative_roots(::RootSystem)`](@ref negative_roots). """ -function coroots(R::RootSystem) - return [[r for r in positive_coroots(R)]; [-r for r in positive_coroots(R)]] +function number_of_positive_roots(R::RootSystem) + return length(positive_roots(R)) end -function fundamental_weight(R::RootSystem, i::Int) - @req 1 <= i <= rank(R) "invalid index" - return WeightLatticeElem(R, matrix(ZZ, 1, rank(R), i .== 1:rank(R))) +@doc raw""" + number_of_roots(R::RootSystem) -> Int + +Return the number of roots of `R`. + +See also: [`roots(::RootSystem)`](@ref roots). +""" +function number_of_roots(R::RootSystem) + return 2 * number_of_positive_roots(R) end @doc raw""" - fundamental_weights(R::RootSystem) -> Vector{WeightLatticeElem} + number_of_simple_roots(R::RootSystem) -> Int -Return the fundamental weights corresponding to the `simple_roots` of `R`. +Return the number of simple roots of `R`. -# Examples -```jldoctest -julia> fundamental_weights(root_system(:A, 2)) -2-element Vector{WeightLatticeElem}: - w_1 - w_2 -``` +See also: [`simple_roots(::RootSystem)`](@ref simple_roots). """ -function fundamental_weights(R::RootSystem) - return [fundamental_weight(R, i) for i in 1:rank(R)] +function number_of_simple_roots(R::RootSystem) + return rank(R) end -function Base.hash(R::RootSystem, h::UInt) - # even though we don't have a == method for RootSystem, we add a hash method - # to make hashing of RootSpaceElem and WeightLatticeElem more deterministic - b = 0xeb5362118dea2a0e % UInt - h = hash(cartan_matrix(R), h) - return xor(b, h) -end +@doc raw""" + rank(R::RootSystem) -> Int -function is_simple(R::RootSystem) - if is_finite(weyl_group(R)) - return length(root_system_type(R)) == 1 - end - error("Not implemented") # TODO: implement is_simple +Return the rank of `R`, i.e., the number of simple roots. + +See also: [`number_of_simple_roots(::RootSystem)`](@ref number_of_simple_roots). +""" +function rank(R::RootSystem) + return nrows(cartan_matrix(R)) end @doc raw""" - negative_root(R::RootSystem, i::Int) -> RootSpaceElem + root_system_type(R::RootSystem) -> Vector{Tuple{Symbol,Int}} -Returns the `i`-th negative root of `R`. -This is a more efficient version for `negative_roots(R)[i]`. +Return the Cartan type of `R`. -Also see: `negative_roots`. +If the type is already known, it is returned directly. +This can be checked with [`has_root_system_type(::RootSystem)`](@ref). -!!! note - This function does not return a copy of the asked for object, - but the internal field of the root system. - Mutating the returned object will lead to undefined behavior. +If the type is not known, it is determined and stored in `R`. + +See also: [`root_system_type_with_ordering(::RootSystem)`](@ref). + +!!! warning + This function will error if the type is not known yet and the Weyl group is infinite. """ -function negative_root(R::RootSystem, i::Int) - return -(R.positive_roots::Vector{RootSpaceElem})[i] +function root_system_type(R::RootSystem) + assure_root_system_type(R) + return R.type end @doc raw""" - negative_roots(R::RootSystem) -> Vector{RootSpaceElem} + root_system_type_with_ordering(R::RootSystem) -> Vector{Tuple{Symbol,Int}}, Vector{Int} -Returns the negative roots of `R`. The $i$-th element of the returned vector is the negative root corresponding to the $i$-th positive root. +Return the Cartan type of `R`, together with the ordering of the simple roots. -Also see: `negative_root`. +If the type is already known, it is returned directly. +This can be checked with [`has_root_system_type(::RootSystem)`](@ref). -!!! note - This function does not return a copy of the asked for object, - but the internal field of the root system. - Mutating the returned object will lead to undefined behavior. +If the type is not known, it is determined and stored in `R`. -# Examples -```jldoctest -julia> negative_roots(root_system(:A, 2)) -3-element Vector{RootSpaceElem}: - -a_1 - -a_2 - -a_1 - a_2 -``` +See also: [`root_system_type(::RootSystem)`](@ref). + +!!! warning + This function will error if the type is not known yet and the Weyl group is infinite. """ -function negative_roots(R::RootSystem) - return [-r for r in positive_roots(R)] +function root_system_type_with_ordering(R::RootSystem) + assure_root_system_type(R) + return R.type, R.type_ordering end @doc raw""" - negative_coroot(R::RootSystem, i::Int) -> RootSpaceElem + has_root_system_type(R::RootSystem) -> Bool -Returns the coroot corresponding to the `i`-th negative root of `R` -This is a more efficient version for `negative_coroots(R)[i]`. - -Also see: `negative_coroots`. +Check if the root system `R` already knows its Cartan type. -!!! note - This function does not return a copy of the asked for object, - but the internal field of the root system. - Mutating the returned object will lead to undefined behavior. +The type can then be queried with [`root_system_type(::RootSystem)`](@ref) +and [`root_system_type_with_ordering(::RootSystem)`](@ref). """ -function negative_coroot(R::RootSystem, i::Int) - return -(R.positive_coroots::Vector{DualRootSpaceElem})[i] +function has_root_system_type(R::RootSystem) + return isdefined(R, :type) && isdefined(R, :type_ordering) end -@doc raw""" - negative_coroot(R::RootSystem, i::Int) -> RootSpaceElem +function assure_root_system_type(R::RootSystem) + has_root_system_type(R) && return nothing + @req is_finite(weyl_group(R)) "Root system type cannot be determined for infinite Weyl groups" + set_root_system_type!(R, cartan_type_with_ordering(cartan_matrix(R); check=false)...) +end -Returns the negative coroots of `R`. The $i$-th element of the returned vector is the negative coroot corresponding to the $i$-th positive coroot. +function set_root_system_type!(R::RootSystem, type::Vector{Tuple{Symbol,Int}}) + return set_root_system_type!(R, type, 1:sum(t[2] for t in type; init=0)) +end -Also see: `negative_coroot`. +function set_root_system_type!( + R::RootSystem, type::Vector{Tuple{Symbol,Int}}, ordering::AbstractVector{Int} +) + R.type = type + R.type_ordering = collect(ordering) + return nothing +end -!!! note - This function does not return a copy of the asked for object, - but the internal field of the root system. - Mutating the returned object will lead to undefined behavior. +@doc raw""" + weyl_group(R::RootSystem) -> WeylGroup + +Return the Weyl group of `R`. # Examples ```jldoctest -julia> negative_coroots(root_system(:A, 2)) -3-element Vector{DualRootSpaceElem}: - -a^v_1 - -a^v_2 - -a^v_1 - a^v_2 +julia> weyl_group(root_system([2 -1; -1 2])) +Weyl group + of root system of rank 2 + of type A2 + +julia> weyl_group(root_system(matrix(ZZ, 2, 2, [2, -1, -1, 2]); detect_type=false)) +Weyl group + of root system of rank 2 + of unknown type + +julia> weyl_group(root_system(matrix(ZZ, [2 -1 -2; -1 2 0; -1 0 2]))) +Weyl group + of root system of rank 3 + of type C3 (with non-canonical ordering of simple roots) ``` """ -function negative_coroots(R::RootSystem) - return [-r for r in positive_coroots(R)] +function weyl_group(R::RootSystem) + return R.weyl_group::WeylGroup end -@doc raw""" - number_of_positive_roots(R::RootSystem) -> Int +############################################################################### +# root constructors -Returns the number of positive roots of `R`. This is the same as the number of negative roots. +@doc raw""" + root(R::RootSystem, i::Int) -> RootSpaceElem -Also see: `positive_roots`, `negative_roots`. -""" -function number_of_positive_roots(R::RootSystem) - return length(positive_roots(R)) -end +Return the `i`-th root of `R`. -@doc raw""" - number_of_roots(R::RootSystem) -> Int +This is a more efficient version for `roots(R)[i]`. -Returns the number of roots of `R`. +See also: [`roots(::RootSystem)`](@ref). -Also see: `roots`. +!!! note + This function does not return a copy of the asked for object, + but the internal field of the root system. + Mutating the returned object will lead to undefined behavior. """ -function number_of_roots(R::RootSystem) - return 2 * number_of_positive_roots(R) +function root(R::RootSystem, i::Int) + if i <= n_positive_roots(R) + return positive_root(R, i) + else + return negative_root(R, i - n_positive_roots(R)) + end end @doc raw""" - number_of_simple_roots(R::RootSystem) -> Int + roots(R::RootSystem) -> Vector{RootSpaceElem} + +Return the roots of `R`, starting with the positive roots and then the negative roots, +in the order of [`positive_roots(::RootSystem)`](@ref positive_roots) and [`negative_roots(::RootSystem)`](@ref negative_roots). -Returns the number of simple roots of `R`. +See also: [`root(::RootSystem, ::Int)`](@ref). -Also see: `simple_roots`. +!!! note + This function does not return a copy of the asked for object, + but the internal field of the root system. + Mutating the returned object will lead to undefined behavior. """ -function number_of_simple_roots(R::RootSystem) - return rank(R) +function roots(R::RootSystem) + return [[r for r in positive_roots(R)]; [-r for r in positive_roots(R)]] end @doc raw""" positive_root(R::RootSystem, i::Int) -> RootSpaceElem -Returns the `i`-th positive root of `R`. +Return the `i`-th positive root of `R`. + This is a more efficient version for `positive_roots(R)[i]`. -Also see: `positive_roots`. +See also: [`positive_roots(::RootSystem)`](@ref). !!! note This function does not return a copy of the asked for object, @@ -376,10 +397,10 @@ end @doc raw""" positive_roots(R::RootSystem) -> Vector{RootSpaceElem} -Returns the positive roots of `R`, starting with the simple roots in the order of `simple_roots`, -and then increasing in height. +Return the positive roots of `R`, starting with the simple roots in the order +of [`simple_roots(::RootSystem)`](@ref simple_roots), and then increasing in height. -Also see: `positive_root`, `number_of_positive_roots`. +See also: [`positive_root(::RootSystem, ::Int)`](@ref). !!! note This function does not return a copy of the asked for object, @@ -400,28 +421,31 @@ function positive_roots(R::RootSystem) end @doc raw""" - positive_coroot(R::RootSystem, i::Int) -> RootSpaceElem + negative_root(R::RootSystem, i::Int) -> RootSpaceElem -Returns the coroot corresponding to the `i`-th positive root of `R` -This is a more efficient version for `positive_coroots(R)[i]`. +Return the `i`-th negative root of `R`, i.e. the negative of the `i`-th positive root. + +This is a more efficient version for `negative_roots(R)[i]`. -Also see: `positive_coroots`. +See also: [`negative_roots(::RootSystem)`](@ref). !!! note This function does not return a copy of the asked for object, but the internal field of the root system. Mutating the returned object will lead to undefined behavior. """ -function positive_coroot(R::RootSystem, i::Int) - return (R.positive_coroots::Vector{DualRootSpaceElem})[i] +function negative_root(R::RootSystem, i::Int) + return -(R.positive_roots::Vector{RootSpaceElem})[i] end @doc raw""" - positive_coroot(R::RootSystem, i::Int) -> RootSpaceElem + negative_roots(R::RootSystem) -> Vector{RootSpaceElem} + +Return the negative roots of `R`. -Returns the coroots corresponding to the positive roots of `R` +The $i$-th element of the returned vector is the negative root corresponding to the $i$-th positive root. -Also see: `positive_coroots`. +See also: [`positive_root(::RootSystem, ::Int)`](@ref). !!! note This function does not return a copy of the asked for object, @@ -430,137 +454,191 @@ Also see: `positive_coroots`. # Examples ```jldoctest -julia> positive_coroots(root_system(:A, 2)) -3-element Vector{DualRootSpaceElem}: - a^v_1 - a^v_2 - a^v_1 + a^v_2 +julia> negative_roots(root_system(:A, 2)) +3-element Vector{RootSpaceElem}: + -a_1 + -a_2 + -a_1 - a_2 ``` """ -function positive_coroots(R::RootSystem) - return R.positive_coroots::Vector{DualRootSpaceElem} +function negative_roots(R::RootSystem) + return [-r for r in positive_roots(R)] end @doc raw""" - rank(R::RootSystem) -> Int + simple_root(R::RootSystem, i::Int) -> RootSpaceElem -Return the rank of `R`, i.e. the number of simple roots. -""" -function rank(R::RootSystem) - return nrows(cartan_matrix(R)) -end +Return the `i`-th simple root of `R`. -function root_system_type(R::RootSystem) - assure_root_system_type(R) - return R.type -end +This is a more efficient version for `simple_roots(R)[i]`. -function root_system_type_with_ordering(R::RootSystem) - assure_root_system_type(R) - return R.type, R.type_ordering -end +See also: [`simple_roots(::RootSystem)`](@ref). -function has_root_system_type(R::RootSystem) - return isdefined(R, :type) && isdefined(R, :type_ordering) +!!! note + This function does not return a copy of the asked for object, + but the internal field of the root system. + Mutating the returned object will lead to undefined behavior. +""" +function simple_root(R::RootSystem, i::Int) + @req 1 <= i <= rank(R) "Invalid index" + return positive_root(R, i) end -function assure_root_system_type(R::RootSystem) - has_root_system_type(R) && return nothing - @req is_finite(weyl_group(R)) "Root system type cannot be determined for infinite Weyl groups" - set_root_system_type!(R, cartan_type_with_ordering(cartan_matrix(R))...) -end +@doc raw""" + simple_roots(R::RootSystem) -> Vector{RootSpaceElem} -function set_root_system_type!(R::RootSystem, type::Vector{Tuple{Symbol,Int}}) - return set_root_system_type!(R, type, 1:sum(t[2] for t in type; init=0)) -end +Return the simple roots of `R`. -function set_root_system_type!( - R::RootSystem, type::Vector{Tuple{Symbol,Int}}, ordering::AbstractVector{Int} -) - R.type = type - R.type_ordering = collect(ordering) - return nothing +See also: [`simple_root(::RootSystem, ::Int)`](@ref). + +!!! note + This function does not return a copy of the asked for object, + but the internal field of the root system. + Mutating the returned object will lead to undefined behavior. +""" +function simple_roots(R::RootSystem) + return positive_roots(R)[1:rank(R)] end +############################################################################### +# coroot constructors + @doc raw""" - root(R::RootSystem, i::Int) -> RootSpaceElem + coroot(R::RootSystem, i::Int) -> DualRootSpaceElem -Returns the `i`-th root of `R`. -This is a more efficient version for `roots(R)[i]`. +Return the coroot of the `i`-th root of `R`. -Also see: `roots`. +This is a more efficient version for `coroots(R)[i]`. + +See also: [`coroots(::RootSystem)`](@ref). !!! note This function does not return a copy of the asked for object, but the internal field of the root system. Mutating the returned object will lead to undefined behavior. """ -function root(R::RootSystem, i::Int) +function coroot(R::RootSystem, i::Int) if i <= n_positive_roots(R) - return positive_root(R, i) + return positive_coroot(R, i) else - return negative_root(R, i - n_positive_roots(R)) + return negative_coroot(R, i - n_positive_roots(R)) end end @doc raw""" - roots(R::RootSystem) -> Vector{RootSpaceElem} + coroots(R::RootSystem) -> Vector{DualRootSpaceElem} -Returns the roots of `R`, starting with the positive roots and then the negative roots, -in the order of `positive_roots` and `negative_roots`. +Return the coroots of `R` in the order of [`roots(::RootSystem)`](@ref roots), +i.e. starting with the coroots of positive roots and then those of negative roots, +each in the order of [`positive_coroots(::RootSystem)`](@ref positive_coroots) and [`negative_coroots(::RootSystem)`](@ref negative_coroots), repectively. -Also see: `root`. +See also: [`coroot(::RootSystem, ::Int)`](@ref). !!! note This function does not return a copy of the asked for object, but the internal field of the root system. Mutating the returned object will lead to undefined behavior. """ -function roots(R::RootSystem) - return [[r for r in positive_roots(R)]; [-r for r in positive_roots(R)]] +function coroots(R::RootSystem) + return [[r for r in positive_coroots(R)]; [-r for r in positive_coroots(R)]] end @doc raw""" - simple_root(R::RootSystem, i::Int) -> RootSpaceElem + positive_coroot(R::RootSystem, i::Int) -> DualRootSpaceElem -Returns the `i`-th simple root of `R`. -This is a more efficient version for `simple_roots(R)[i]`. +Return the coroot of the `i`-th positive root of `R`. + +This is a more efficient version for `positive_coroots(R)[i]`. -Also see: `simple_roots`. +See also: [`positive_coroots(::RootSystem)`](@ref). !!! note This function does not return a copy of the asked for object, but the internal field of the root system. Mutating the returned object will lead to undefined behavior. """ -function simple_root(R::RootSystem, i::Int) - @req 1 <= i <= rank(R) "Invalid index" - return positive_root(R, i) +function positive_coroot(R::RootSystem, i::Int) + return (R.positive_coroots::Vector{DualRootSpaceElem})[i] end @doc raw""" - simple_roots(R::RootSystem) -> Vector{RootSpaceElem} + positive_coroots(R::RootSystem) -> DualRootSpaceElem -Returns the simple roots of `R`. +Return the coroots corresponding to the positive roots of `R`, +in the order of [`positive_roots(::RootSystem)`](@ref positive_roots). -Also see: `simple_root`. +See also: [`positive_coroot(::RootSystem, ::Int)`](@ref). !!! note This function does not return a copy of the asked for object, but the internal field of the root system. Mutating the returned object will lead to undefined behavior. + +# Examples +```jldoctest +julia> positive_coroots(root_system(:A, 2)) +3-element Vector{DualRootSpaceElem}: + a_1^v + a_2^v + a_1^v + a_2^v +``` """ -function simple_roots(R::RootSystem) - return positive_roots(R)[1:rank(R)] +function positive_coroots(R::RootSystem) + return R.positive_coroots::Vector{DualRootSpaceElem} end @doc raw""" - simple_coroot(R::RootSystem, i::Int) -> RootSpaceElem + negative_coroot(R::RootSystem, i::Int) -> DualRootSpaceElem + +Return the coroot of the `i`-th negative root of `R`. + +This is a more efficient version for `negative_coroots(R)[i]`. + +See also: [`negative_coroots(::RootSystem)`](@ref). + +!!! note + This function does not return a copy of the asked for object, + but the internal field of the root system. + Mutating the returned object will lead to undefined behavior. +""" +function negative_coroot(R::RootSystem, i::Int) + return -(R.positive_coroots::Vector{DualRootSpaceElem})[i] +end + +@doc raw""" + negative_coroots(R::RootSystem) -> DualRootSpaceElem + +Return the coroots corresponding to the negative roots of `R`, +in the order of [`negative_roots(::RootSystem)`](@ref negative_roots). + +See also: [`negative_coroot(::RootSystem, ::Int)`](@ref). + +!!! note + This function does not return a copy of the asked for object, + but the internal field of the root system. + Mutating the returned object will lead to undefined behavior. + +# Examples +```jldoctest +julia> negative_coroots(root_system(:A, 2)) +3-element Vector{DualRootSpaceElem}: + -a_1^v + -a_2^v + -a_1^v - a_2^v +``` +""" +function negative_coroots(R::RootSystem) + return [-r for r in positive_coroots(R)] +end + +@doc raw""" + simple_coroot(R::RootSystem, i::Int) -> DualRootSpaceElem + +Return the coroot of the `i`-th simple root of `R`. -Returns the coroot corresponding to the `i`-th simple root of `R` This is a more efficient version for `simple_coroots(R)[i]`. -Also see: `simple_coroots`. +See also: [`simple_coroots(::RootSystem)`](@ref). !!! note This function does not return a copy of the asked for object, @@ -573,11 +651,12 @@ function simple_coroot(R::RootSystem, i::Int) end @doc raw""" - simple_coroot(R::RootSystem, i::Int) -> RootSpaceElem + simple_coroots(R::RootSystem) -> DualRootSpaceElem -Returns the coroots corresponding to the simple roots of `R` +Return the coroots corresponding to the simple roots of `R`, +in the order of [`simple_roots(::RootSystem)`](@ref simple_roots). -Also see: `simple_coroots`. +See also: [`simple_coroot(::RootSystem, ::Int)`](@ref). !!! note This function does not return a copy of the asked for object, @@ -588,38 +667,49 @@ function simple_coroots(R::RootSystem) return positive_coroots(R)[1:rank(R)] end +############################################################################### +# weight constructors + @doc raw""" - weyl_group(R::RootSystem) -> WeylGroup + fundamental_weight(R::RootSystem, i::Int) -> WeightLatticeElem -Return the Weyl group of `R`. +Return the `i`-th fundamental weight of `R`. -# Examples -```jldoctest -julia> weyl_group(root_system([2 -1; -1 2])) -Weyl group - of root system of rank 2 - of type A2 +This is a more efficient version for `fundamental_weights(R)[i]`. -julia> weyl_group(root_system(matrix(ZZ, 2, 2, [2, -1, -1, 2]); detect_type=false)) -Weyl group - of root system of rank 2 - of unknown type +See also: [`fundamental_weight(::RootSystem)`](@ref). +""" +function fundamental_weight(R::RootSystem, i::Int) + @req 1 <= i <= rank(R) "invalid index" + return WeightLatticeElem(R, matrix(ZZ, 1, rank(R), i .== 1:rank(R))) +end -julia> weyl_group(root_system(matrix(ZZ, [2 -1 -2; -1 2 0; -1 0 2]))) -Weyl group - of root system of rank 3 - of type C3 (with non-canonical ordering of simple roots) +@doc raw""" + fundamental_weights(R::RootSystem) -> Vector{WeightLatticeElem} + +Return the fundamental weights corresponding to the simple roots of `R`, +in the order of [`simple_roots(::RootSystem)`](@ref simple_roots). + +See also: [`fundamental_weight(::RootSystem, ::Int)`](@ref). + +# Examples +```jldoctest +julia> fundamental_weights(root_system(:A, 2)) +2-element Vector{WeightLatticeElem}: + w_1 + w_2 ``` """ -function weyl_group(R::RootSystem) - return R.weyl_group::WeylGroup +function fundamental_weights(R::RootSystem) + return [fundamental_weight(R, i) for i in 1:rank(R)] end @doc raw""" weyl_vector(R::RootSystem) -> WeightLatticeElem -Return the Weyl vector $\rho$ of `R`, which is the sum of all fundamental weights, -or half the sum of all positive roots. +Return the Weyl vector $\rho$ of `R`, +that is the sum of all fundamental weights or, +equivalently, half the sum of all positive roots. """ function weyl_vector(R::RootSystem) return WeightLatticeElem(R, matrix(ZZ, 1, rank(R), fill(1, rank(R)))) @@ -631,16 +721,31 @@ end # ############################################################################### +@doc raw""" + RootSpaceElem(R::RootSystem, vec::Vector{<:RationalUnion}) -> RootSpaceElem + +Construct a root space element in the root system `R` with the given coefficients w.r.t. the simple roots of `R`. +""" function RootSpaceElem(root_system::RootSystem, vec::Vector{<:RationalUnion}) return RootSpaceElem(root_system, matrix(QQ, 1, length(vec), vec)) end +@doc raw""" + RootSpaceElem(w::WeightLatticeElem) -> RootSpaceElem + +Construct a root space element from the weight lattice element `w`. +""" function RootSpaceElem(w::WeightLatticeElem) R = root_system(w) coeffs = coefficients(w) * cartan_matrix_inv_tr(R) return RootSpaceElem(R, matrix(QQ, coeffs)) end +@doc raw""" + zero(::Type{RootSpaceElem}, R::RootSystem) -> RootSpaceElem + +Return the neutral additive element in the root space of `R`. +""" function zero(::Type{RootSpaceElem}, R::RootSystem) return RootSpaceElem(R, zero_matrix(QQ, 1, rank(R))) end @@ -653,6 +758,10 @@ function Base.:*(q::RationalUnion, r::RootSpaceElem) return RootSpaceElem(root_system(r), q * r.vec) end +function Base.:*(r::RootSpaceElem, q::RationalUnion) + return RootSpaceElem(root_system(r), r.vec * q) +end + function Base.:+(r::RootSpaceElem, r2::RootSpaceElem) @req root_system(r) === root_system(r2) "parent root system mismatch" @@ -674,24 +783,52 @@ function zero!(r::RootSpaceElem) return r end -function add!(rr::RootSpaceElem, r1::RootSpaceElem, r2::RootSpaceElem) - @req root_system(rr) === root_system(r1) === root_system(r2) "parent root system mismatch" - rr.vec = add!(rr.vec, r1.vec, r2.vec) +function add!(rr::RootSpaceElem, r1::RootSpaceElem, r2::RootSpaceElem) + @req root_system(rr) === root_system(r1) === root_system(r2) "parent root system mismatch" + rr.vec = add!(rr.vec, r1.vec, r2.vec) + return rr +end + +function neg!(rr::RootSpaceElem, r::RootSpaceElem) + @req root_system(rr) === root_system(r) "parent root system mismatch" + rr.vec = neg!(rr.vec, r.vec) + return rr +end + +function sub!(rr::RootSpaceElem, r1::RootSpaceElem, r2::RootSpaceElem) + @req root_system(rr) === root_system(r1) === root_system(r2) "parent root system mismatch" + rr.vec = sub!(rr.vec, r1.vec, r2.vec) + return rr +end + +function mul!(rr::RootSpaceElem, r::RootSpaceElem, q::RationalUnion) + @req root_system(rr) === root_system(r) "parent root system mismatch" + rr.vec = mul!(rr.vec, r.vec, q) + return rr +end + +function mul!(rr::RootSpaceElem, q::RationalUnion, r::RootSpaceElem) + @req root_system(rr) === root_system(r) "parent root system mismatch" + rr.vec = mul!(rr.vec, q, r.vec) return rr end -function neg!(rr::RootSpaceElem, r::RootSpaceElem) +function addmul!(rr::RootSpaceElem, r::RootSpaceElem, q::RationalUnion) @req root_system(rr) === root_system(r) "parent root system mismatch" - rr.vec = neg!(rr.vec, r.vec) + rr.vec = addmul!(rr.vec, r.vec, q) return rr end -function sub!(rr::RootSpaceElem, r1::RootSpaceElem, r2::RootSpaceElem) - @req root_system(rr) === root_system(r1) === root_system(r2) "parent root system mismatch" - rr.vec = sub!(rr.vec, r1.vec, r2.vec) +function addmul!(rr::RootSpaceElem, q::RationalUnion, r::RootSpaceElem) + @req root_system(rr) === root_system(r) "parent root system mismatch" + rr.vec = addmul!(rr.vec, q, r.vec) return rr end +# ignore temp storage +addmul!(rr::RootSpaceElem, r::RootSpaceElem, q::RationalUnion, t) = addmul!(rr, r, q) +addmul!(rr::RootSpaceElem, q::RationalUnion, r::RootSpaceElem, t) = addmul!(rr, q, r) + function Base.:(==)(r::RootSpaceElem, r2::RootSpaceElem) return r.root_system === r2.root_system && r.vec == r2.vec end @@ -706,15 +843,6 @@ function Base.deepcopy_internal(r::RootSpaceElem, dict::IdDict) return w2 end -@doc raw""" - getindex(r::RootSpaceElem, i::Int) -> QQRingElem - -Return the coefficient of the `i`-th simple root in `r`. -""" -function Base.getindex(r::RootSpaceElem, i::Int) - return coeff(r, i) -end - function Base.hash(r::RootSpaceElem, h::UInt) b = 0xbe7603eb38c985ad % UInt h = hash(r.root_system, h) @@ -722,14 +850,36 @@ function Base.hash(r::RootSpaceElem, h::UInt) return xor(b, h) end +@doc raw""" + coefficients(r::RootSpaceElem) -> QQMatrix + +Return the coefficients of the root space element `r` +w.r.t. the simple roots as a row vector. + +!!! note + The return type may not be relied on; + we only guarantee that it is a one-dimensional iterable with `eltype` `QQFieldElem` + that can be indexed with integers. +""" function coefficients(r::RootSpaceElem) return r.vec end +@doc raw""" + coeff(r::RootSpaceElem, i::Int) -> QQFieldElem + +Return the coefficient of the `i`-th simple root in `r`. + +This can be also accessed via `r[i]`. +""" function coeff(r::RootSpaceElem, i::Int) return r.vec[i] end +function Base.getindex(r::RootSpaceElem, i::Int) + return coeff(r, i) +end + function dot(r1::RootSpaceElem, r2::RootSpaceElem) @req root_system(r1) === root_system(r2) "parent root system mismatch" @@ -766,10 +916,27 @@ function height(r::RootSpaceElem) return sum(coefficients(r)) end +@doc raw""" + is_root(r::RootSpaceElem) -> Bool + +Check if `r` is a root of its root system. + +See also: [`is_root_with_index(::RootSpaceElem)`](@ref). +""" function is_root(r::RootSpaceElem) return is_positive_root(r) || is_negative_root(r) end +@doc raw""" + is_root_with_index(r::RootSpaceElem) -> Bool, Int + +Check if `r` is a root of its root system +and return this together with the index of the root in [`roots(::RootSystem)`](@ref). + +If `r` is not a root, the second return value is arbitrary. + +See also: [`is_root(::RootSpaceElem)`](@ref). +""" function is_root_with_index(r::RootSpaceElem) fl, i = is_positive_root_with_index(r) if fl @@ -782,10 +949,27 @@ function is_root_with_index(r::RootSpaceElem) return false, 0 end +@doc raw""" + is_positive_root(r::RootSpaceElem) -> Bool + +Check if `r` is a positive root of its root system. + +See also: [`is_positive_root_with_index(::RootSpaceElem)`](@ref). +""" function is_positive_root(r::RootSpaceElem) return haskey(root_system(r).positive_roots_map, coefficients(r)) end +@doc raw""" + is_positive_root_with_index(r::RootSpaceElem) -> Bool, Int + +Check if `r` is a positive root of its root system +and return this together with the index of the root in [`positive_roots(::RootSystem)`](@ref). + +If `r` is not a positive root, the second return value is arbitrary. + +See also: [`is_positive_root(::RootSpaceElem)`](@ref). +""" function is_positive_root_with_index(r::RootSpaceElem) i = get(root_system(r).positive_roots_map, coefficients(r), nothing) if isnothing(i) @@ -795,10 +979,27 @@ function is_positive_root_with_index(r::RootSpaceElem) end end +@doc raw""" + is_negative_root(r::RootSpaceElem) -> Bool + +Check if `r` is a negative root of its root system. + +See also: [`is_negative_root_with_index(::RootSpaceElem)`](@ref). +""" function is_negative_root(r::RootSpaceElem) return haskey(root_system(r).positive_roots_map, -coefficients(r)) end +@doc raw""" + is_negative_root_with_index(r::RootSpaceElem) -> Bool, Int + +Check if `r` is a negative root of its root system +and return this together with the index of the root in [`negative_roots(::RootSystem)`](@ref). + +If `r` is not a negative root, the second return value is arbitrary. + +See also: [`is_negative_root(::RootSpaceElem)`](@ref). +""" function is_negative_root_with_index(r::RootSpaceElem) i = get(root_system(r).positive_roots_map, -coefficients(r), nothing) if isnothing(i) @@ -808,10 +1009,27 @@ function is_negative_root_with_index(r::RootSpaceElem) end end +@doc raw""" + is_simple_root(r::RootSpaceElem) -> Bool + +Check if `r` is a simple root of its root system. + +See also: [`is_simple_root_with_index(::RootSpaceElem)`](@ref). +""" function is_simple_root(r::RootSpaceElem) return is_simple_root_with_index(r)[1] end +@doc raw""" + is_simple_root_with_index(r::RootSpaceElem) -> Bool, Int + +Check if `r` is a simple root of its root system +and return this together with the index of the root in [`simple_roots(::RootSystem)`](@ref). + +If `r` is not a simple root, the second return value is arbitrary. + +See also: [`is_simple_root(::RootSpaceElem)`](@ref). +""" function is_simple_root_with_index(r::RootSpaceElem) i = get(root_system(r).positive_roots_map, coefficients(r), nothing) if isnothing(i) || i > number_of_simple_roots(root_system(r)) @@ -821,14 +1039,33 @@ function is_simple_root_with_index(r::RootSpaceElem) end end +@doc raw""" + iszero(r::RootSpaceElem) -> Bool + +Check if `r` is the neutral additive element in the root space of its root system. +""" function Base.iszero(r::RootSpaceElem) return iszero(r.vec) end +@doc raw""" + reflect(r::RootSpaceElem, s::Int) -> RootSpaceElem + +Return the reflection of `r` in the hyperplane orthogonal to the `s`-th simple root. + +See also: [`reflect!(::RootSpaceElem, ::Int)`](@ref). +""" function reflect(r::RootSpaceElem, s::Int) return reflect!(deepcopy(r), s) end +@doc raw""" + reflect!(r::RootSpaceElem, s::Int) -> RootSpaceElem + +Reflect `r` in the hyperplane orthogonal to the `s`-th simple root, and return it. + +This is a mutating version of [`reflect(::RootSpaceElem, ::Int)`](@ref). +""" function reflect!(r::RootSpaceElem, s::Int) sub!( Nemo.mat_entry_ptr(r.vec, 1, s), dot(view(cartan_matrix(root_system(r)), s, :), r.vec) @@ -836,6 +1073,11 @@ function reflect!(r::RootSpaceElem, s::Int) return r end +@doc raw""" + root_system(r::RootSpaceElem) -> RootSystem + +Return the root system `r` belongs to. +""" function root_system(r::RootSpaceElem) return r.root_system end @@ -846,10 +1088,20 @@ end # ############################################################################### +@doc raw""" + DualRootSpaceElem(R::RootSystem, vec::Vector{<:RationalUnion}) -> DualRootSpaceElem + +Construct a dual root space element in the root system `R` with the given coefficients w.r.t. the simple coroots of `R`. +""" function DualRootSpaceElem(root_system::RootSystem, vec::Vector{<:RationalUnion}) return DualRootSpaceElem(root_system, matrix(QQ, 1, length(vec), vec)) end +@doc raw""" + zero(::Type{DualRootSpaceElem}, R::RootSystem) -> DualRootSpaceElem + +Return the neutral additive element in the dual root space of `R`. +""" function zero(::Type{DualRootSpaceElem}, R::RootSystem) return DualRootSpaceElem(R, zero_matrix(QQ, 1, rank(R))) end @@ -862,6 +1114,10 @@ function Base.:*(q::RationalUnion, r::DualRootSpaceElem) return DualRootSpaceElem(root_system(r), q * r.vec) end +function Base.:*(r::DualRootSpaceElem, q::RationalUnion) + return DualRootSpaceElem(root_system(r), r.vec * q) +end + function Base.:+(r::DualRootSpaceElem, r2::DualRootSpaceElem) @req root_system(r) === root_system(r2) "parent root system mismatch" @@ -901,6 +1157,36 @@ function sub!(rr::DualRootSpaceElem, r1::DualRootSpaceElem, r2::DualRootSpaceEle return rr end +function mul!(rr::DualRootSpaceElem, r::DualRootSpaceElem, q::RationalUnion) + @req root_system(rr) === root_system(r) "parent root system mismatch" + rr.vec = mul!(rr.vec, r.vec, q) + return rr +end + +function mul!(rr::DualRootSpaceElem, q::RationalUnion, r::DualRootSpaceElem) + @req root_system(rr) === root_system(r) "parent root system mismatch" + rr.vec = mul!(rr.vec, q, r.vec) + return rr +end + +function addmul!(rr::DualRootSpaceElem, r::DualRootSpaceElem, q::RationalUnion) + @req root_system(rr) === root_system(r) "parent root system mismatch" + rr.vec = addmul!(rr.vec, r.vec, q) + return rr +end + +function addmul!(rr::DualRootSpaceElem, q::RationalUnion, r::DualRootSpaceElem) + @req root_system(rr) === root_system(r) "parent root system mismatch" + rr.vec = addmul!(rr.vec, q, r.vec) + return rr +end + +# ignore temp storage +addmul!(rr::DualRootSpaceElem, r::DualRootSpaceElem, q::RationalUnion, t) = + addmul!(rr, r, q) +addmul!(rr::DualRootSpaceElem, q::RationalUnion, r::DualRootSpaceElem, t) = + addmul!(rr, q, r) + function Base.:(==)(r::DualRootSpaceElem, r2::DualRootSpaceElem) return r.root_system === r2.root_system && r.vec == r2.vec end @@ -915,15 +1201,6 @@ function Base.deepcopy_internal(r::DualRootSpaceElem, dict::IdDict) return w2 end -@doc raw""" - getindex(r::DualRootSpaceElem, i::Int) -> QQRingElem - -Returns the coefficient of the `i`-th simple root in `r`. -""" -function Base.getindex(r::DualRootSpaceElem, i::Int) - return coeff(r, i) -end - function Base.hash(r::DualRootSpaceElem, h::UInt) b = 0x721bec0418bdbe0f % UInt h = hash(r.root_system, h) @@ -931,26 +1208,48 @@ function Base.hash(r::DualRootSpaceElem, h::UInt) return xor(b, h) end +@doc raw""" + coefficients(r::DualRootSpaceElem) -> QQMatrix + +Return the coefficients of the dual root space element `r` +w.r.t. the simple coroots as a row vector. + +!!! note + The return type may not be relied on; + we only guarantee that it is a one-dimensional iterable with `eltype` `QQFieldElem` + that can be indexed with integers. +""" function coefficients(r::DualRootSpaceElem) return r.vec end +@doc raw""" + coeff(r::DualRootSpaceElem, i::Int) -> QQFieldElem + +Returns the coefficient of the `i`-th simple coroot in `r`. + +This can be also accessed via `r[i]`. +""" function coeff(r::DualRootSpaceElem, i::Int) return r.vec[i] end +function Base.getindex(r::DualRootSpaceElem, i::Int) + return coeff(r, i) +end + function expressify(r::DualRootSpaceElem; context=nothing) if is_unicode_allowed() return expressify(r, :α̌; context) else - return expressify(r, Symbol("a^v"); context) + return expressify(r, :a, Symbol("^v"); context) end end -function expressify(r::DualRootSpaceElem, s; context=nothing) +function expressify(r::DualRootSpaceElem, s, s_suffix=Symbol(""); context=nothing) sum = Expr(:call, :+) for i in 1:length(r.vec) - push!(sum.args, Expr(:call, :*, expressify(r.vec[i]; context), "$(s)_$(i)")) + push!(sum.args, Expr(:call, :*, expressify(r.vec[i]; context), "$(s)_$(i)$(s_suffix)")) end return sum end @@ -966,10 +1265,27 @@ function height(r::DualRootSpaceElem) return sum(coefficients(r)) end +@doc raw""" + is_coroot(r::DualRootSpaceElem) -> Bool + +Check if `r` is a coroot of its root system. + +See also: [`is_coroot_with_index(::DualRootSpaceElem)`](@ref). +""" function is_coroot(r::DualRootSpaceElem) return is_positive_coroot(r) || is_negative_coroot(r) end +@doc raw""" + is_coroot_with_index(r::DualRootSpaceElem) -> Bool, Int + +Check if `r` is a coroot of its root system +and return this together with the index of the coroot in [`coroots(::RootSystem)`](@ref). + +If `r` is not a coroot, the second return value is arbitrary. + +See also: [`is_coroot(::DualRootSpaceElem)`](@ref). +""" function is_coroot_with_index(r::DualRootSpaceElem) fl, i = is_positive_coroot_with_index(r) if fl @@ -982,10 +1298,27 @@ function is_coroot_with_index(r::DualRootSpaceElem) return false, 0 end +@doc raw""" + is_positive_coroot(r::DualRootSpaceElem) -> Bool + +Check if `r` is a positive coroot of its root system. + +See also: [`is_positive_coroot_with_index(::DualRootSpaceElem)`](@ref). +""" function is_positive_coroot(r::DualRootSpaceElem) return haskey(root_system(r).positive_coroots_map, coefficients(r)) end +@doc raw""" + is_positive_coroot_with_index(r::DualRootSpaceElem) -> Bool, Int + +Check if `r` is a positive coroot of its root system +and return this together with the index of the coroot in [`positive_coroots(::RootSystem)`](@ref). + +If `r` is not a positive coroot, the second return value is arbitrary. + +See also: [`is_positive_coroot(::DualRootSpaceElem)`](@ref). +""" function is_positive_coroot_with_index(r::DualRootSpaceElem) i = get(root_system(r).positive_coroots_map, coefficients(r), nothing) if isnothing(i) @@ -995,10 +1328,27 @@ function is_positive_coroot_with_index(r::DualRootSpaceElem) end end +@doc raw""" + is_negative_coroot(r::DualRootSpaceElem) -> Bool + +Check if `r` is a negative coroot of its root system. + +See also: [`is_negative_coroot_with_index(::DualRootSpaceElem)`](@ref). +""" function is_negative_coroot(r::DualRootSpaceElem) return haskey(root_system(r).positive_coroots_map, -coefficients(r)) end +@doc raw""" + is_negative_coroot_with_index(r::DualRootSpaceElem) -> Bool, Int + +Check if `r` is a negative coroot of its root system +and return this together with the index of the coroot in [`negative_coroots(::RootSystem)`](@ref). + +If `r` is not a negative coroot, the second return value is arbitrary. + +See also: [`is_negative_coroot(::DualRootSpaceElem)`](@ref). +""" function is_negative_coroot_with_index(r::DualRootSpaceElem) i = get(root_system(r).positive_coroots_map, -coefficients(r), nothing) if isnothing(i) @@ -1008,10 +1358,27 @@ function is_negative_coroot_with_index(r::DualRootSpaceElem) end end +@doc raw""" + is_simple_coroot(r::DualRootSpaceElem) -> Bool + +Check if `r` is a simple coroot of its root system. + +See also: [`is_simple_coroot_with_index(::DualRootSpaceElem)`](@ref). +""" function is_simple_coroot(r::DualRootSpaceElem) return is_simple_coroot_with_index(r)[1] end +@doc raw""" + is_simple_coroot_with_index(r::DualRootSpaceElem) -> Bool, Int + +Check if `r` is a simple coroot of its root system +and return this together with the index of the coroot in [`simple_coroots(::RootSystem)`](@ref). + +If `r` is not a simple coroot, the second return value is arbitrary. + +See also: [`is_simple_coroot(::DualRootSpaceElem)`](@ref). +""" function is_simple_coroot_with_index(r::DualRootSpaceElem) i = get(root_system(r).positive_roots_map, coefficients(r), nothing) if isnothing(i) || i > number_of_simple_roots(root_system(r)) @@ -1021,10 +1388,20 @@ function is_simple_coroot_with_index(r::DualRootSpaceElem) end end +@doc raw""" + iszero(r::DualRootSpaceElem) -> Bool + +Check if `r` is the neutral additive element in the dual root space of its root system. +""" function Base.iszero(r::DualRootSpaceElem) return iszero(r.vec) end +@doc raw""" + root_system(r::DualRootSpaceElem) -> RootSystem + +Return the root system `r` belongs to. +""" function root_system(r::DualRootSpaceElem) return r.root_system end @@ -1036,14 +1413,19 @@ end ############################################################################### @doc raw""" - WeightLatticeElem(R::RootSystem, v::Vector{IntegerUnion}) -> WeightLatticeElem + WeightLatticeElem(R::RootSystem, vec::Vector{<:IntegerUnion}) -> WeightLatticeElem -Return the weight defined by the coefficients `v` of the fundamental weights with respect to the root system `R`. +Construct a weight lattice element in the root system `R` with the given coefficients w.r.t. the fundamental weights of `R`. """ function WeightLatticeElem(R::RootSystem, v::Vector{<:IntegerUnion}) return WeightLatticeElem(R, matrix(ZZ, 1, rank(R), v)) end +@doc raw""" + WeightLatticeElem(r::RootSpaceElem) -> WeightLatticeElem + +Construct a weight lattice element from the root space element `r`. +""" function WeightLatticeElem(r::RootSpaceElem) R = root_system(r) coeffs = coefficients(r) * cartan_matrix_tr(R) @@ -1051,6 +1433,11 @@ function WeightLatticeElem(r::RootSpaceElem) return WeightLatticeElem(R, matrix(ZZ, coeffs)) end +@doc raw""" + zero(::Type{WeightLatticeElem}, R::RootSystem) -> WeightLatticeElem + +Return the neutral additive element in the weight lattice of `R`. +""" function zero(::Type{WeightLatticeElem}, R::RootSystem) return WeightLatticeElem(R, zero_matrix(ZZ, 1, rank(R))) end @@ -1063,6 +1450,10 @@ function Base.:*(n::IntegerUnion, w::WeightLatticeElem) return WeightLatticeElem(root_system(w), n * w.vec) end +function Base.:*(w::WeightLatticeElem, n::IntegerUnion) + return WeightLatticeElem(root_system(w), w.vec * n) +end + function Base.:+(w::WeightLatticeElem, w2::WeightLatticeElem) @req root_system(w) === root_system(w2) "parent root system mismatch" @@ -1102,6 +1493,34 @@ function sub!(wr::WeightLatticeElem, w1::WeightLatticeElem, w2::WeightLatticeEle return wr end +function mul!(wr::WeightLatticeElem, w::WeightLatticeElem, n::IntegerUnion) + @req root_system(wr) === root_system(w) "parent root system mismatch" + wr.vec = mul!(wr.vec, w.vec, n) + return wr +end + +function mul!(wr::WeightLatticeElem, n::IntegerUnion, w::WeightLatticeElem) + @req root_system(wr) === root_system(w) "parent root system mismatch" + wr.vec = mul!(wr.vec, n, w.vec) + return wr +end + +function addmul!(wr::WeightLatticeElem, w::WeightLatticeElem, n::IntegerUnion) + @req root_system(wr) === root_system(w) "parent root system mismatch" + wr.vec = addmul!(wr.vec, w.vec, n) + return wr +end + +function addmul!(wr::WeightLatticeElem, n::IntegerUnion, w::WeightLatticeElem) + @req root_system(wr) === root_system(w) "parent root system mismatch" + wr.vec = addmul!(wr.vec, n, w.vec) + return wr +end + +# ignore temp storage +addmul!(wr::WeightLatticeElem, w::WeightLatticeElem, n::IntegerUnion, t) = addmul!(wr, w, n) +addmul!(wr::WeightLatticeElem, n::IntegerUnion, w::WeightLatticeElem, t) = addmul!(wr, n, w) + function Base.:(==)(w::WeightLatticeElem, w2::WeightLatticeElem) return w.root_system === w2.root_system && w.vec == w2.vec end @@ -1116,15 +1535,6 @@ function Base.deepcopy_internal(w::WeightLatticeElem, dict::IdDict) return w2 end -@doc raw""" - getindex(w::WeightLatticeElem, i::Int) -> ZZRingElem - -Return the coefficient of the `i`-th fundamental weight in `w`. -""" -function Base.getindex(w::WeightLatticeElem, i::Int) - return coeff(w, i) -end - function Base.hash(w::WeightLatticeElem, h::UInt) b = 0x7b2fefadacf46f4e % UInt h = hash(w.root_system, h) @@ -1141,18 +1551,42 @@ function Base.iszero(w::WeightLatticeElem) return iszero(w.vec) end +@doc raw""" + coefficients(w::WeightLatticeElem) -> ZZMatrix + +Return the coefficients of the weight lattice element `w` +w.r.t. the fundamental weights as a row vector. + +!!! note + The return type may not be relied on; + we only guarantee that it is a one-dimensional iterable with `eltype` `ZZRingElem` + that can be indexed with integers. +""" function coefficients(w::WeightLatticeElem) return w.vec end +@doc raw""" + coeff(w::WeightLatticeElem, i::Int) -> ZZRingElem + +Return the coefficient of the `i`-th fundamental weight in `w`. + +This can be also accessed via `w[i]`. +""" function coeff(w::WeightLatticeElem, i::Int) return w.vec[i] end +function Base.getindex(w::WeightLatticeElem, i::Int) + return coeff(w, i) +end + @doc raw""" conjugate_dominant_weight(w::WeightLatticeElem) -> WeightLatticeElem Return the unique dominant weight conjugate to `w`. + +See also: [`conjugate_dominant_weight_with_left_elem(::WeightLatticeElem)`](@ref), [`conjugate_dominant_weight_with_right_elem(::WeightLatticeElem)`](@ref). """ function conjugate_dominant_weight(w::WeightLatticeElem) return conjugate_dominant_weight!(deepcopy(w)) @@ -1175,16 +1609,18 @@ function conjugate_dominant_weight!(w::WeightLatticeElem) end @doc raw""" - conjugate_dominant_weight_with_elem(w::WeightLatticeElem) -> Tuple{WeightLatticeElem, WeylGroupElem} + conjugate_dominant_weight_with_left_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`. +such that `x * w == dom`. + +See also: [`conjugate_dominant_weight_with_right_elem(::WeightLatticeElem)`](@ref). """ -function conjugate_dominant_weight_with_elem(w::WeightLatticeElem) - return conjugate_dominant_weight_with_elem!(deepcopy(w)) +function conjugate_dominant_weight_with_left_elem(w::WeightLatticeElem) + return conjugate_dominant_weight_with_left_elem!(deepcopy(w)) end -function conjugate_dominant_weight_with_elem!(w::WeightLatticeElem) +function conjugate_dominant_weight_with_left_elem!(w::WeightLatticeElem) R = root_system(w) # determine the Weyl group element taking w to the fundamental chamber @@ -1206,6 +1642,23 @@ function conjugate_dominant_weight_with_elem!(w::WeightLatticeElem) return w, weyl_group(R)(reverse!(word); normalize=false) end +@doc raw""" + conjugate_dominant_weight_with_right_elem(w::WeightLatticeElem) -> Tuple{WeightLatticeElem, WeylGroupElem} + +Returns the unique dominant weight `dom` conjugate to `w` and a Weyl group element `x` +such that `w * x == dom`. + +See also: [`conjugate_dominant_weight_with_left_elem(::WeightLatticeElem)`](@ref). +""" +function conjugate_dominant_weight_with_right_elem(w::WeightLatticeElem) + return conjugate_dominant_weight_with_right_elem!(deepcopy(w)) +end + +function conjugate_dominant_weight_with_right_elem!(w::WeightLatticeElem) + w, x = conjugate_dominant_weight_with_left_elem!(w) + return w, inv(x) +end + function dot(w1::WeightLatticeElem, w2::WeightLatticeElem) @req root_system(w1) === root_system(w2) "parent root system mismatch" R = root_system(w1) @@ -1233,14 +1686,58 @@ function expressify(w::WeightLatticeElem, s; context=nothing) end @enable_all_show_via_expressify WeightLatticeElem +@doc raw""" + is_dominant(w::WeightLatticeElem) -> Bool + +Check if `w` is a dominant weight, i.e. if all coefficients are non-negative. +""" function is_dominant(w::WeightLatticeElem) return all(>=(0), coefficients(w)) end +@doc raw""" + is_fundamental_weight(w::WeightLatticeElem) -> Bool + +Check if `w` is a fundamental weight, i.e. exactly one coefficient is equal to 1 and all others are zero. + +See also: [`is_fundamental_weight_with_index(::WeightLatticeElem)`](@ref). +""" +function is_fundamental_weight(w::WeightLatticeElem) + fl, _ = is_fundamental_weight_with_index(w) + return fl +end + +@doc raw""" + is_fundamental_weight_with_index(w::WeightLatticeElem) -> Bool, Int + +Check if `w` is a fundamental weight and return this together with the index of the fundamental weight in [`fundamental_weights(::RootSystem)`](@ref fundamental_weights(root_system(w))). + +If `w` is not a fundamental weight, the second return value is arbitrary. + +See also: [`is_fundamental_weight(::WeightLatticeElem)`](@ref). +""" +function is_fundamental_weight_with_index(w::WeightLatticeElem) + ind = 0 + coeffs = coefficients(w) + for i in 1:size(coeffs, 2) + if is_zero_entry(coeffs, 1, i) + continue + elseif is_one(coeffs[1, i]) + ind != 0 && return false, 0 + ind = i + else + return false, 0 + end + end + return ind != 0, ind +end + @doc raw""" reflect(w::WeightLatticeElem, s::Int) -> WeightLatticeElem - -Return the `w` reflected at the `s`-th simple root. + +Return the reflection of `w` in the hyperplane orthogonal to the `s`-th simple root. + +See also: [`reflect!(::WeightLatticeElem, ::Int)`](@ref). """ function reflect(w::WeightLatticeElem, s::Int) return reflect!(deepcopy(w), s) @@ -1248,14 +1745,21 @@ end @doc raw""" reflect!(w::WeightLatticeElem, s::Int) -> WeightLatticeElem - -Reflects the `w` at the `s`-th simple root in place and returns `w`. + +Reflect `w` in the hyperplane orthogonal to the `s`-th simple root, and return it. + +This is a mutating version of [`reflect(::WeightLatticeElem, ::Int)`](@ref). """ function reflect!(w::WeightLatticeElem, s::Int) w.vec = addmul!(w.vec, view(cartan_matrix_tr(root_system(w)), s:s, :), -w.vec[s]) # change to submul! once available return w end +@doc raw""" + root_system(w::WeightLatticeElem) -> RootSystem + +Return the root system `w` belongs to. +""" function root_system(w::WeightLatticeElem) return w.root_system end @@ -1599,7 +2103,7 @@ function tensor_product_decomposition( for (w_, m) in dominant_character(R, hw1) for w in weyl_orbit(WeightLatticeElem(R, w_)) add!(w, hw2_plus_rho) - w_dom, x = conjugate_dominant_weight_with_elem!(w) + w_dom, x = conjugate_dominant_weight_with_left_elem!(w) if all(!iszero, coefficients(w_dom)) sub!(w_dom, rho) coeff = m * (-1)^length(x) diff --git a/experimental/LieAlgebras/src/Types.jl b/experimental/LieAlgebras/src/Types.jl index 8acf63cf1d32..155637542896 100644 --- a/experimental/LieAlgebras/src/Types.jl +++ b/experimental/LieAlgebras/src/Types.jl @@ -4,6 +4,13 @@ # ############################################################################### +@doc raw""" + RootSystem + +Type for abstract root systems. + +See [`root_system(::ZZMatrix)`](@ref) for the constructor. +""" @attributes mutable struct RootSystem cartan_matrix::ZZMatrix # (generalized) Cartan matrix positive_roots::Vector #::Vector{RootSpaceElem} (cyclic reference) @@ -17,7 +24,7 @@ type_ordering::Vector{Int} function RootSystem(mat::ZZMatrix; check::Bool=true, detect_type::Bool=true) - @req !check || is_cartan_matrix(mat) "Requires a generalized Cartan matrix" + check && @req is_cartan_matrix(mat) "Requires a generalized Cartan matrix" pos_roots, pos_coroots, refl = positive_roots_and_reflections(mat) finite = count(refl .== 0) == nrows(mat) @@ -40,30 +47,66 @@ end end +@doc raw""" + RootSpaceElem + +Type for roots and linear combinations thereof. +""" mutable struct RootSpaceElem root_system::RootSystem vec::QQMatrix # the coordinate (row) vector with respect to the simple roots - function RootSpaceElem(root_system::RootSystem, vec::QQMatrix) - @req size(vec) == (1, rank(root_system)) "Invalid dimension" - return new(root_system, vec) + @doc raw""" + RootSpaceElem(R::RootSystem, vec::QQMatrix) -> RootSpaceElem + + Construct a root space element in the root system `R` with the given coefficien vector w.r.t. the simple roots of `R`. + + `vec` must be a row vector of the same length as the rank of `R`. + """ + function RootSpaceElem(R::RootSystem, vec::QQMatrix) + @req size(vec) == (1, rank(R)) "Invalid dimension" + return new(R, vec) end end +@doc raw""" + DualRootSpaceElem + +Type for coroots and linear combinations thereof. +""" mutable struct DualRootSpaceElem root_system::RootSystem vec::QQMatrix # the coordinate (row) vector with respect to the simple coroots + @doc raw""" + DualRootSpaceElem(R::RootSystem, vec::QQMatrix) -> DualRootSpaceElem + + Construct a dual root space element in the root system `R` with the given coefficien vector w.r.t. the simple coroots of `R`. + + `vec` must be a row vector of the same length as the rank of `R`. + """ function DualRootSpaceElem(root_system::RootSystem, vec::QQMatrix) @req size(vec) == (1, rank(root_system)) "Invalid dimension" return new(root_system, vec) end end +@doc raw""" + WeightLatticeElem + +Type for weights and linear combinations thereof. +""" mutable struct WeightLatticeElem root_system::RootSystem vec::ZZMatrix # the coordinate (row) vector with respect to the fundamental weights + @doc raw""" + WeightLatticeElem(R::RootSystem, vec::ZZMatrix) -> WeightLatticeElem + + Construct a weight lattice element in the root system `R` with the given coefficient vector w.r.t. the fundamental weights of `R`. + + `vec` must be a row vector of the same length as the rank of `R`. + """ function WeightLatticeElem(root_system::RootSystem, vec::ZZMatrix) @req size(vec) == (1, rank(root_system)) "Invalid dimension" return new(root_system, vec) @@ -76,6 +119,13 @@ end # ############################################################################### +@doc raw""" + WeylGroup <: Group + +Type for Weyl groups of root systems. + +See [`weyl_group(::RootSystem)`](@ref) for the constructor. +""" @attributes mutable struct WeylGroup <: AbstractAlgebra.Group finite::Bool # finite indicates whether the Weyl group is finite refl::Matrix{UInt} # see positive_roots_and_reflections @@ -86,6 +136,11 @@ end end end +@doc raw""" + WeylGroupElem <: GroupElem + +Type for elements of Weyl groups. +""" struct WeylGroupElem <: AbstractAlgebra.GroupElem parent::WeylGroup # parent group word::Vector{UInt8} # short revlex normal form of the word @@ -111,9 +166,15 @@ end const WeylIteratorNoCopyState = Tuple{WeightLatticeElem,WeylGroupElem} +@doc raw""" + ReducedExpressionIterator + +Iterator for reduced expressions of a Weyl group element. + +See [`reduced_expressions(::WeylGroupElem)`](@ref) for the constructor. +""" struct ReducedExpressionIterator el::WeylGroupElem # the Weyl group element for which we a searching reduced expressions - #letters::Vector{UInt8} # letters are the simple reflections occuring in one (hence any) reduced expression of el up_to_commutation::Bool # if true and say s1 and s3 commute, we only list s3*s1 and not s1*s3 end diff --git a/experimental/LieAlgebras/src/WeylGroup.jl b/experimental/LieAlgebras/src/WeylGroup.jl index fff0ca142754..6683f177baf9 100644 --- a/experimental/LieAlgebras/src/WeylGroup.jl +++ b/experimental/LieAlgebras/src/WeylGroup.jl @@ -10,7 +10,7 @@ @doc raw""" weyl_group(cartan_matrix::ZZMatrix) -> WeylGroup -Returns the Weyl group defined by a generalized Cartan matrix `cartan_matrix`. +Construct the Weyl group defined by the given (generalized) Cartan matrix. """ function weyl_group(cartan_matrix::ZZMatrix) return weyl_group(root_system(cartan_matrix)) @@ -19,7 +19,9 @@ end @doc raw""" weyl_group(fam::Symbol, rk::Int) -> WeylGroup -Returns the Weyl group of the given type. See `cartan_matrix(fam::Symbol, rk::Int)` for allowed combinations. +Construct the Weyl group of the given type. + +The input must be a valid Cartan type, see [`is_cartan_type(::Symbol, ::Int)`](@ref). # Examples ```jldoctest @@ -35,27 +37,40 @@ end @doc raw""" weyl_group(type::Vector{Tuple{Symbol,Int}}) -> WeylGroup + weyl_group(type::Tuple{Symbol,Int}...) -> WeylGroup + +Construct the Weyl group of the given type. + +Each element of `type` must be a valid Cartan type, see [`is_cartan_type(::Symbol, ::Int)`](@ref). +The vararg version needs at least one element. -Returns the Weyl group of the given type. See `cartan_matrix(fam::Symbol, rk::Int)` for allowed combinations. +# Examples +```jldoctest +julia> weyl_group([(:G, 2), (:D, 4)]) +Weyl group + of root system of rank 6 + of type G2 x D4 +``` """ function weyl_group(type::Vector{Tuple{Symbol,Int}}) return weyl_group(root_system(type)) end -@doc raw""" - weyl_group(type::Tuple{Symbol,Int}...) -> WeylGroup - -Returns the Weyl group of the given type. See `cartan_matrix(fam::Symbol, rk::Int)` for allowed combinations. -""" -function weyl_group(type::Tuple{Symbol,Int}...) - return weyl_group(root_system(collect(type))) +function weyl_group(type1::Tuple{Symbol,Int}, type::Tuple{Symbol,Int}...) + return weyl_group(root_system([type1; type...])) end @doc raw""" - (W::WeylGroup)(word::Vector{Int}) -> WeylGroupElem + (W::WeylGroup)(word::Vector{<:Integer}) -> WeylGroupElem + +Construct a Weyl group element from the given word. + +The word must be a list of integers, where each integer is the index of a simple reflection. + +If the word is known to be in reduced form, the normalization can be skipped by setting `normalize=false`. """ function (W::WeylGroup)(word::Vector{<:Integer}; normalize::Bool=true) - return WeylGroupElem(W, word; normalize=normalize) + return WeylGroupElem(W, word; normalize) end function Base.IteratorSize(::Type{WeylGroup}) @@ -81,7 +96,9 @@ function Base.iterate(W::WeylGroup, state::WeylIteratorNoCopyState) end @doc raw""" - isfinite(W::WeylGroup) -> Bool + is_finite(W::WeylGroup) -> Bool + +Return whether `W` is finite. """ function is_finite(W::WeylGroup) return W.finite @@ -89,6 +106,8 @@ end @doc raw""" one(W::WeylGroup) -> WeylGroupElem + +Return the identity element of `W`. """ function Base.one(W::WeylGroup) return W(UInt8[]; normalize=false) @@ -126,7 +145,7 @@ end @doc raw""" gen(W::WeylGroup, i::Int) -> WeylGroupElem -Returns the `i`th simple reflection (with respect to the underlying root system) of `W`. +Return 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" @@ -136,7 +155,7 @@ end @doc raw""" gens(W::WeylGroup) -> WeylGroupElem -Returns the simple reflections (with respect to the underlying root system) of `W`. +Return 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)] @@ -145,28 +164,32 @@ end @doc raw""" longest_element(W::WeylGroup) -> WeylGroupElem -Returns the unique longest element of `W`. +Return the unique longest element of `W`. +This only exists if `W` is finite. """ function longest_element(W::WeylGroup) - @req is_finite(W) "$W is not finite" + @req is_finite(W) "Weyl group is not finite" - _, w0 = conjugate_dominant_weight_with_elem(-weyl_vector(root_system(W))) + _, w0 = conjugate_dominant_weight_with_left_elem(-weyl_vector(root_system(W))) return w0 end @doc raw""" number_of_generators(W::WeylGroup) -> Int -Returns the number of generators of the `W`, i.e. the rank of the underyling root system. +Return the number of generators of the `W`, i.e. the rank of the underyling root system. """ function number_of_generators(W::WeylGroup) return rank(root_system(W)) end @doc raw""" + order(W::WeylGroup) -> ZZRingELem order(::Type{T}, W::WeylGroup) where {T} -> T -Returns the order of `W`. +Return the order of `W`. + +If `W` is infinite, an `InfiniteOrderError` exception will be thrown. """ function order(::Type{T}, W::WeylGroup) where {T} if !is_finite(W) @@ -202,7 +225,7 @@ end @doc raw""" root_system(W::WeylGroup) -> RootSystem -Returns the underlying root system of `W`. +Return the underlying root system of `W`. """ function root_system(W::WeylGroup) return W.root_system @@ -211,7 +234,7 @@ end ############################################################################### # Weyl group elements -function Base.:(*)(x::WeylGroupElem, y::WeylGroupElem) +function Base.:*(x::WeylGroupElem, y::WeylGroupElem) @req x.parent === y.parent "$x, $y must belong to the same Weyl group" p = deepcopy(y) @@ -221,7 +244,15 @@ function Base.:(*)(x::WeylGroupElem, y::WeylGroupElem) return p end -function Base.:(*)(x::WeylGroupElem, rw::Union{RootSpaceElem,WeightLatticeElem}) +@doc raw""" + *(x::WeylGroupElem, r::RootSpaceElem) -> RootSpaceElem + *(x::WeylGroupElem, w::WeightLatticeElem) -> WeightLatticeElem + +Return the result of acting with `x` **from the left** on `r` or `w`. + +See also: [`*(::Union{RootSpaceElem,WeightLatticeElem}, ::WeylGroupElem)`](@ref). +""" +function Base.:*(x::WeylGroupElem, rw::Union{RootSpaceElem,WeightLatticeElem}) @req root_system(parent(x)) === root_system(rw) "Incompatible root systems" rw2 = deepcopy(rw) @@ -232,7 +263,15 @@ function Base.:(*)(x::WeylGroupElem, rw::Union{RootSpaceElem,WeightLatticeElem}) return rw2 end -function Base.:(*)(rw::Union{RootSpaceElem,WeightLatticeElem}, x::WeylGroupElem) +@doc raw""" + *(r::RootSpaceElem, x::WeylGroupElem) -> RootSpaceElem + *(w::WeightLatticeElem, x::WeylGroupElem) -> WeightLatticeElem + +Return the result of acting with `x` **from the right** on `r` or `w`. + +See also: [`*(::WeylGroupElem, ::Union{RootSpaceElem,WeightLatticeElem})`](@ref). +""" +function Base.:*(rw::Union{RootSpaceElem,WeightLatticeElem}, x::WeylGroupElem) @req root_system(parent(x)) === root_system(rw) "Incompatible root systems" rw2 = deepcopy(rw) @@ -264,7 +303,7 @@ end @doc raw""" <(x::WeylGroupElem, y::WeylGroupElem) -> Bool -Returns whether `x` is smaller than `y` with respect to the Bruhat order, +Return whether `x` is smaller than `y` with respect to the Bruhat order, i.e., whether some (not necessarily connected) subexpression of a reduced decomposition of `y`, is a reduced decomposition of `x`. """ @@ -312,7 +351,7 @@ 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`. +Return 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] @@ -326,11 +365,6 @@ 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) y = parent(x)(sizehint!(UInt8[], length(x)); normalize=false) for s in word(x) @@ -342,7 +376,7 @@ end @doc raw""" isone(x::WeylGroupElem) -> Bool -Returns whether `x` is the identity. +Return whether `x` is the identity element of its parent. """ function Base.isone(x::WeylGroupElem) return isempty(word(x)) @@ -351,7 +385,7 @@ end @doc raw""" length(x::WeylGroupElem) -> Int -Returns the length of `x`. +Return the length of `x`. """ function Base.length(x::WeylGroupElem) return length(word(x)) @@ -360,7 +394,7 @@ end @doc raw""" parent(x::WeylGroupElem) -> WeylGroup -Returns the Weyl group that `x` is an element of. +Return the Weyl group that `x` is an element of. """ function Base.parent(x::WeylGroupElem) return x.parent @@ -369,7 +403,7 @@ 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. +Return 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[] @@ -389,7 +423,7 @@ end @doc raw""" lmul(x::WeylGroupElem, i::Integer) -> WeylGroupElem -Returns the result of multiplying `x` from the left by the `i`th simple reflection. +Return 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) @@ -398,7 +432,7 @@ 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. +Multiply `x` in-place from the left by the `i`-th simple reflection, and return the result. """ function lmul!(x::WeylGroupElem, i::Integer) b, j, r = explain_lmul(x, i) @@ -412,7 +446,7 @@ function lmul!(x::WeylGroupElem, i::Integer) end # explains what multiplication of s_i from the left will do. -# Returns a tuple where the first entry is true/false, depending on whether an insertion or deletion will happen, +# Return a tuple where the first entry is true/false, depending on whether an insertion or deletion will happen, # the second entry is the position, and the third is the simple root. function explain_lmul(x::WeylGroupElem, i::Integer) @req 1 <= i <= rank(root_system(parent(x))) "Invalid generator" @@ -447,22 +481,70 @@ function parent_type(::Type{WeylGroupElem}) return WeylGroup end -# rename to reduced decompositions ? +@doc raw""" + reduced_expressions(x::WeylGroupElem; up_to_commutation::Bool=false) -> ReducedExpressionIterator + +Return an iterator over all reduced expressions of `x`. + +If `up_to_commutation` is `true`, the iterator will not return an expression that only +differs from a previous one by a swap of two adjacent commuting simple reflections. + +# Examples +```jldoctest +julia> W = weyl_group(:A, 3); + +julia> x = W([1,2,3,1]); + +julia> collect(reduced_expressions(x)) +3-element Vector{Vector{UInt8}}: + [0x01, 0x02, 0x03, 0x01] + [0x01, 0x02, 0x01, 0x03] + [0x02, 0x01, 0x02, 0x03] + +julia> collect(reduced_expressions(x; up_to_commutation=true)) +2-element Vector{Vector{UInt8}}: + [0x01, 0x02, 0x03, 0x01] + [0x02, 0x01, 0x02, 0x03] +``` +The second expression of the first iterator is not contained in the second iterator +because it only differs from the first expression by a swap of two the two commuting simple reflections `s1` and `s3`. +""" function reduced_expressions(x::WeylGroupElem; up_to_commutation::Bool=false) return ReducedExpressionIterator(x, up_to_commutation) end @doc raw""" word(x::WeylGroupElem) -> Vector{UInt8} + +Return `x` as a list of indices of simple reflections, in reduced form. + +This function is right inverse to calling `(W::WeylGroup)(word::Vector{<:Integer})`. """ function word(x::WeylGroupElem) return x.word end +@doc raw""" + fp_group(W::WeylGroup) -> FPGroup + +Construct a group of type `FPGroup` that is isomorphic to `W`. + +The `FPGroup` will be the quotient of a free group with the same rank as `W`, +where we have the natural 1-to-1 correspondence of generators, modulo the Coxeter relations of `W`. + +Also see: [`isomorphism(::Type{FPGroup}, ::WeylGroup)`](@ref). +""" function fp_group(W::WeylGroup; set_properties::Bool=true) return codomain(isomorphism(FPGroup, W; set_properties)) end +@doc raw""" + isomorphism(::Type{FPGroup}, W::WeylGroup) -> Map{WeylGroup, FPGroup} + +Construct an isomorphism between `W` and a group of type `FPGroup`. + +The properties of the codomain group and the isomorphism are described in [`fp_group(::WeylGroup)`](@ref). +""" function isomorphism(::Type{FPGroup}, W::WeylGroup; set_properties::Bool=true) R = root_system(W) F = free_group(rank(R)) @@ -612,7 +694,9 @@ end # 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 +# this choice is made to align with conjugate_dominant_weight_with_left_elem + +# TODO: add a way to iterate aligned with conjugate_dominant_weight_with_right_elem function Base.IteratorSize(::Type{WeylIteratorNoCopy}) return Base.SizeUnknown() @@ -704,7 +788,7 @@ end @doc raw""" weyl_orbit(wt::WeightLatticeElem) -Returns an iterator over the Weyl group orbit at the weight `wt`. +Return an iterator over the Weyl group orbit at the weight `wt`. """ function weyl_orbit(wt::WeightLatticeElem) return WeylOrbitIterator(wt) diff --git a/experimental/LieAlgebras/src/exports.jl b/experimental/LieAlgebras/src/exports.jl index 8383f35fcc20..9f88d9cd6c11 100644 --- a/experimental/LieAlgebras/src/exports.jl +++ b/experimental/LieAlgebras/src/exports.jl @@ -34,7 +34,8 @@ 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 conjugate_dominant_weight_with_left_elem +export conjugate_dominant_weight_with_right_elem export coroot export coroots export coxeter_matrix @@ -55,6 +56,8 @@ export is_cartan_type export is_coroot export is_coroot_with_index export is_dominant +export is_fundamental_weight +export is_fundamental_weight_with_index export is_negative_coroot export is_negative_coroot_with_index export is_negative_root diff --git a/experimental/LieAlgebras/test/CartanMatrix-test.jl b/experimental/LieAlgebras/test/CartanMatrix-test.jl index a03474f0a56f..f8b3f8408978 100644 --- a/experimental/LieAlgebras/test/CartanMatrix-test.jl +++ b/experimental/LieAlgebras/test/CartanMatrix-test.jl @@ -29,7 +29,7 @@ @test cartan_matrix((:A, 2), (:B, 2)) == block_diagonal_matrix([cartan_matrix(:A, 2), cartan_matrix(:B, 2)]) - @test_throws ArgumentError cartan_matrix() + @test_throws MethodError cartan_matrix() end @testset "is_cartan_matrix(mat::ZZMatrix; generalized::Bool=true)" begin diff --git a/experimental/LieAlgebras/test/RootSystem-test.jl b/experimental/LieAlgebras/test/RootSystem-test.jl index 58263e4c2c12..4a4bca04ab5b 100644 --- a/experimental/LieAlgebras/test/RootSystem-test.jl +++ b/experimental/LieAlgebras/test/RootSystem-test.jl @@ -1,5 +1,5 @@ @testset "LieAlgebras.RootSystem" begin - @testset "conjugate_dominant_weight_with_elem(w::WeightLatticeElem)" 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]), @@ -10,9 +10,13 @@ (root_system(:G, 2), [-1, -1]), ] wt = WeightLatticeElem(R, vec) - d, x = conjugate_dominant_weight_with_elem(wt) + d, x = conjugate_dominant_weight_with_left_elem(wt) @test is_dominant(d) @test x * wt == d + + d, x = conjugate_dominant_weight_with_right_elem(wt) + @test is_dominant(d) + @test wt * x == d end end @@ -52,7 +56,7 @@ @test all(i -> negative_root(R, i) == negative_roots(R)[i], 1:npositive_roots) @test simple_roots(R) == positive_roots(R)[1:rk] @test all(is_root, roots(R)) - n_roots(R) >= 1 && @test !is_root(root(R, 1) - root(R, 1)) + @test !is_root(zero(RootSpaceElem, R)) @test all(r -> !is_root(2 * r), roots(R)) @test all(is_root_with_index(r) == (true, i) for (i, r) in enumerate(roots(R))) @test all(r -> is_positive_root(r) == is_positive_root_with_index(r)[1], roots(R)) @@ -90,7 +94,7 @@ @test all(i -> negative_coroot(R, i) == negative_coroots(R)[i], 1:npositive_roots) @test simple_coroots(R) == positive_coroots(R)[1:rk] @test all(is_coroot, coroots(R)) - n_roots(R) >= 1 && @test !is_coroot(coroot(R, 1) - coroot(R, 1)) + @test !is_coroot(zero(DualRootSpaceElem, R)) @test all(r -> !is_coroot(2 * r), coroots(R)) @test all(is_coroot_with_index(r) == (true, i) for (i, r) in enumerate(coroots(R))) @test all( @@ -130,6 +134,15 @@ @test length(fundamental_weights(R)) == rank(R) @test all(i -> fundamental_weight(R, i) == fundamental_weights(R)[i], 1:rk) @test all(w -> w == WeightLatticeElem(RootSpaceElem(w)), fundamental_weights(R)) + @test all(is_fundamental_weight, fundamental_weights(R)) + @test all( + is_fundamental_weight_with_index(w) == (true, i) for (i, w) in + enumerate(fundamental_weights(R)) + ) + @test !is_fundamental_weight(zero(WeightLatticeElem, R)) + rk != 1 && @test !is_fundamental_weight( + sum(fundamental_weights(R); init=zero(WeightLatticeElem, R)) + ) @test all( dot(simple_root(R, i), fundamental_weight(R, j)) == (i == j ? cartan_symmetrizer(R)[i] : 0) for i in 1:rk, j in 1:rk @@ -143,7 +156,7 @@ for _ in 1:10 a = T(R, rand(-10:10, rk)) b = T(R, rand(-10:10, rk)) - c = T(R, rand(-10:10, rk)) + n = rand(-10:10) test_mutating_op_like_zero(zero, zero!, a) @@ -151,6 +164,16 @@ test_mutating_op_like_add(+, add!, a, b) test_mutating_op_like_add(-, sub!, a, b) + + test_mutating_op_like_add(*, mul!, a, n, T) + test_mutating_op_like_add(*, mul!, n, a, T) + test_mutating_op_like_add(*, mul!, a, ZZ(n), T) + test_mutating_op_like_add(*, mul!, ZZ(n), a, T) + + test_mutating_op_like_addmul((a, b, c) -> a + b * c, addmul!, a, b, n, T) + test_mutating_op_like_addmul((a, b, c) -> a + b * c, addmul!, a, n, b, T) + test_mutating_op_like_addmul((a, b, c) -> a + b * c, addmul!, a, b, ZZ(n), T) + test_mutating_op_like_addmul((a, b, c) -> a + b * c, addmul!, a, ZZ(n), b, T) end end diff --git a/experimental/LieAlgebras/test/WeylGroup-test.jl b/experimental/LieAlgebras/test/WeylGroup-test.jl index 18c39e2766d2..3dc027355d99 100644 --- a/experimental/LieAlgebras/test/WeylGroup-test.jl +++ b/experimental/LieAlgebras/test/WeylGroup-test.jl @@ -482,7 +482,7 @@ include( ] R = root_system(fam, rk) wt = WeightLatticeElem(R, vec) - dom_wt, conj = conjugate_dominant_weight_with_elem(wt) + dom_wt, conj = conjugate_dominant_weight_with_left_elem(wt) orb = Tuple{WeightLatticeElem,WeylGroupElem}[] for tup in WeylIteratorNoCopy(wt) push!(orb, deepcopy(tup)) @@ -518,7 +518,7 @@ include( ] R = root_system(type...) wt = WeightLatticeElem(R, vec) - dom_wt, conj = conjugate_dominant_weight_with_elem(wt) + dom_wt, conj = conjugate_dominant_weight_with_left_elem(wt) orb = Tuple{WeightLatticeElem,WeylGroupElem}[] for tup in WeylIteratorNoCopy(wt) push!(orb, deepcopy(tup)) @@ -564,7 +564,6 @@ include( ] 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))