diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 9609aaf411..e4c0427b9c 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -95,6 +95,7 @@ export default defineConfig({ { text: 'Introduction', link: 'manual/algebras/intro'}, { text: 'Basics', link: 'manual/algebras/basics'}, { text: 'Structure constant algebras', link: 'manual/algebras/structureconstant'}, + { text: 'Group algebras', link: 'manual/algebras/groupalgebras'}, { text: 'Ideals', link: 'manual/algebras/ideals'}, ] }, diff --git a/docs/src/manual/algebras/groupalgebras.md b/docs/src/manual/algebras/groupalgebras.md new file mode 100644 index 0000000000..ffdd9ffbc8 --- /dev/null +++ b/docs/src/manual/algebras/groupalgebras.md @@ -0,0 +1,70 @@ +# Group algebras + +```@meta +CurrentModule = Hecke +DocTestSetup = quote + using Hecke +end +``` + +As is natural, the basis of a group algebra $K[G]$ correspond to the elements of $G$ with respect +to some arbitrary ordering. + +## Creation + +```@docs +group_algebra(::Field, ::Group) +``` + +Note that by default, this construction requires enumerating all elements of +the group and thus is inefficient for large groups. Using the optional argument `sparse = true`, +the algebra can be constructed with a different internal model. This allows for much larger groups, +but not all functionality is available in this case. + +```jldoctest +julia> G = abelian_group([2 for i in 1:10]) # group of order 2^10 +(Z/2)^10 + +julia> QG = group_algebra(QQ, G; sparse = true); +``` + +## Elements + +Given a group algebra `A` and an element of a group `g`, the corresponding group algebra element +can be constructed using the syntax `A(g)`. + +```jldoctest +julia> G = abelian_group([2, 2]); a = G([0, 1]); + +julia> QG = group_algebra(QQ, G); +sparse = false + +julia> x = QG(a) +[0, 0, 1, 0] +``` + +Vice versa, one can obtain the coefficient of a group algebra element `x` with respect to a group +element `a` using the syntax `x[a]`. + +```jldoctest +julia> G = abelian_group([2, 2]); a = G([0, 1]); + +julia> QG = group_algebra(QQ, G); + +julia> x = QG(a) +[0, 0, 1, 0] + +julia> x[a] +1 +``` + +It is also possible to create elements from dictionaries: + +```jldoctest +julia> G = abelian_group([2, 2]); a = G([0, 1]); + +julia> QG = group_algebra(QQ, G); + +julia> QG(Dict(a => 2, zero(G) => 1)) == 2 * QG(a) + 1 * QG(zero(G)) +true +``` diff --git a/src/AlgAss/AbsAlgAss.jl b/src/AlgAss/AbsAlgAss.jl index 979ab75f20..0714afefd0 100644 --- a/src/AlgAss/AbsAlgAss.jl +++ b/src/AlgAss/AbsAlgAss.jl @@ -1,3 +1,18 @@ +################################################################################ +# +# Internal thingy +# +################################################################################ + +# check if elements are represented using a sparse row + +_is_sparse(A::AbstractAssociativeAlgebra) = false + +_is_sparse(A::GroupAlgebra) = A.sparse + +# because we are lazy +_is_dense(A::AbstractAssociativeAlgebra) = !_is_sparse(A) + _base_ring(A::AbstractAssociativeAlgebra) = base_ring(A) @doc raw""" diff --git a/src/AlgAss/AlgGrp.jl b/src/AlgAss/AlgGrp.jl index 5569d2be57..918854ca0d 100644 --- a/src/AlgAss/AlgGrp.jl +++ b/src/AlgAss/AlgGrp.jl @@ -59,17 +59,25 @@ end ################################################################################ @doc raw""" - group_algebra(K::Ring, G; op = *) -> GroupAlgebra + group_algebra(K::Ring, G::Group; sparse::Bool = false, + cached::Bool = true) -> GroupAlgebra -Returns the group ring $K[G]$. -$G$ may be any set and `op` a group operation on $G$. -""" -group_algebra(K::Ring, G; op = *, cached::Bool = true, sparse::Bool = false) = GroupAlgebra(K, G; op, sparse, cached) +Return the group algebra of the group $G$ over the ring $R$. -group_algebra(K::Ring, G::FinGenAbGroup; cached::Bool = true, sparse::Bool = false) = GroupAlgebra(K, G; sparse, cached) +# Examples -function group_algebra(K::Field, G; op = *, sparse::Bool = false, cached::Bool = true) - A = GroupAlgebra(K, G; op, sparse, cached) +```jldoctest +julia> QG = group_algebra(QQ, small_group(8, 5)) +Group algebra + of generic group of order 8 with multiplication table + over rational field +``` +""" +function group_algebra(K::Ring, G; op = *, sparse::Bool = false, cached::Bool = true) + A = GroupAlgebra(K, G; op = op , sparse = sparse, cached = cached) + if !(K isa Field) + return A + end if iszero(characteristic(K)) A.issemisimple = 1 else @@ -78,18 +86,8 @@ function group_algebra(K::Field, G; op = *, sparse::Bool = false, cached::Bool = return A end -function group_algebra(K::Field, G::FinGenAbGroup) - A = group_algebra(K, G, op = +) - A.is_commutative = true - return A -end - -@doc raw""" - (K::Ring)[G::Group] -> GroupAlgebra - (K::Ring)[G::FinGenAbGroup] -> GroupAlgebra +group_algebra(K::Ring, G::FinGenAbGroup; cached::Bool = true, sparse::Bool = false) = GroupAlgebra(K, G, cached, sparse) -Returns the group ring $K[G]$. -""" getindex(K::Ring, G::Group) = group_algebra(K, G) getindex(K::Ring, G::FinGenAbGroup) = group_algebra(K, G) @@ -629,7 +627,7 @@ const _reps = [(i=24,j=12,n=5,dims=(1,1,2,3,3), # ################################################################################ -mutable struct AbsAlgAssMorGen{S, T, U, V} <: Map{S, T, HeckeMap, AbsAlgAssMorGen} +mutable struct AbsAlgAssMorGen{S, T, U, V} <: Map{S, T, HeckeMap, Any}#AbsAlgAssMorGen} domain::S codomain::T tempdomain::U diff --git a/src/AlgAss/Elem.jl b/src/AlgAss/Elem.jl index 712d87c90a..f12acc9125 100644 --- a/src/AlgAss/Elem.jl +++ b/src/AlgAss/Elem.jl @@ -4,9 +4,9 @@ # ################################################################################ -_is_sparse(a::AbstractAssociativeAlgebraElem) = false +_is_sparse(a::AbstractAssociativeAlgebraElem) = _is_sparse(parent(a)) -_is_sparse(a::GroupAlgebraElem) = parent(a).sparse +_is_dense(a::AbstractAssociativeAlgebraElem) = _is_dense(parent(a)) function AbstractAlgebra.promote_rule(U::Type{<:AbstractAssociativeAlgebraElem{T}}, ::Type{S}) where {T, S} if AbstractAlgebra.promote_rule(T, S) === T @@ -94,7 +94,7 @@ function one(A::AbstractAssociativeAlgebra) if !has_one(A) error("Algebra does not have a one") end - if !A.sparse + if _is_dense(A) return A(deepcopy(A.one)) # deepcopy needed by mul! else return A(deepcopy(A.sparse_one)) @@ -681,7 +681,17 @@ end #end function (A::GroupAlgebra{T, S, R})(c::R) where {T, S, R} - return GroupAlgebraElem{T, typeof(A)}(A, deepcopy(c)) + return GroupAlgebraElem{T, typeof(A)}(A, c) +end + +function (A::GroupAlgebra{T, S, R})(d::Dict{R, <: Any}) where {T, S, R} + K = base_ring(A) + dd = sparse_row(base_ring(A), [(__elem_index(A, g), K(i)) for (g, i) in d]) + if _is_dense(A) + return GroupAlgebraElem{T, typeof(A)}(A, Vector(dd, dim(A))) + else + return GroupAlgebraElem{T, typeof(A)}(A, dd) + end end # Generic.Mat needs it @@ -727,18 +737,22 @@ function show(io::IO, a::AbstractAssociativeAlgebraElem) if get(io, :compact, false) print(io, coefficients(a, copy = false)) else - if a isa GroupAlgebraElem && parent(a).sparse + if _is_sparse(a) sum = Expr(:call, :+) if !iszero(a) for (i, ci) in a.coeffs_sparse push!(sum.args, Expr(:call, :*, AbstractAlgebra.expressify(ci, context = io), - AbstractAlgebra.expressify(parent(a).base_to_group[i], context = io))) + AbstractAlgebra.expressify(parent(a).base_to_group[i], context = IOContext(io, :compact => true)))) end end print(io, AbstractAlgebra.expr_to_string(AbstractAlgebra.canonicalize(sum))) else - print(io, coefficients(a, copy = false)) + ve = Expr(:vect) + for ci in coefficients(a, copy = false) + push!(ve.args, AbstractAlgebra.expressify(ci, context = io)) + end + print(io, AbstractAlgebra.expr_to_string(AbstractAlgebra.canonicalize(ve))) end end end @@ -781,7 +795,11 @@ end function ==(a::AbstractAssociativeAlgebraElem{T}, b::AbstractAssociativeAlgebraElem{T}) where {T} parent(a) != parent(b) && return false - return coefficients(a, copy = false) == coefficients(b, copy = false) + if !_is_sparse(a) + return coefficients(a, copy = false) == coefficients(b, copy = false) + else + return a.coeffs_sparse == b.coeffs_sparse + end end ################################################################################ diff --git a/src/AlgAss/Types.jl b/src/AlgAss/Types.jl index 254d402a0f..bb4675fda3 100644 --- a/src/AlgAss/Types.jl +++ b/src/AlgAss/Types.jl @@ -130,6 +130,13 @@ end @attributes mutable struct GroupAlgebra{T, S, R} <: AbstractAssociativeAlgebra{T} base_ring::Ring group::S + # We represent elements using a coefficient vector, + # so all we have to keep track of is which group element corresponds to + # which basis element of the algebra + # This is what group_to_base, base_to_group are for. They realize the map + # G -> {1,...,n} + # {1,...n} -> G + group_to_base::Dict{R, Int} base_to_group::Vector{R} one::Vector{T} @@ -145,18 +152,21 @@ end center maps_to_numberfields maximal_order + + # For the sparse presentation sparse::Bool - ind::Int - sparse_one + ind::Int # This is the number of group elements currently stored in + # group_to_base and base_to_group. + sparse_one # Store the sparse row for the one element - function GroupAlgebra(K::Ring, G::FinGenAbGroup, cached::Bool = true) - A = GroupAlgebra(K, G, op = +, cached = cached) + function GroupAlgebra(K::Ring, G::FinGenAbGroup, cached::Bool = true, sparse::Bool = false) + A = GroupAlgebra(K, G; op = +, cached = cached, sparse = sparse) A.is_commutative = true return A end - function GroupAlgebra(K::Ring, G; op = *, cached = true, sparse::Bool = false) - return get_cached!(GroupAlgebraID, (K, G, op), cached) do + function GroupAlgebra(K::Ring, G; op = *, cached::Bool = true, sparse::Bool = false) + return get_cached!(GroupAlgebraID, (K, G, op, sparse), cached) do A = new{elem_type(K), typeof(G), elem_type(G)}() A.sparse = sparse A.ind = -1 @@ -165,9 +175,14 @@ end A.issemisimple = 0 A.base_ring = K A.group = G - d = Int(order(G)) A.group_to_base = Dict{elem_type(G), Int}() - A.base_to_group = Vector{elem_type(G)}(undef, d) + if !sparse + @assert is_finite(G) + d = order(Int, G) + A.base_to_group = Vector{elem_type(G)}(undef, d) + else + A.base_to_group = Vector{elem_type(G)}(undef, 1) + end if A.sparse if G isa FinGenAbGroup @@ -218,7 +233,7 @@ end end end -const GroupAlgebraID = AbstractAlgebra.CacheDictType{Tuple{Ring, Any, Any}, GroupAlgebra}() +const GroupAlgebraID = AbstractAlgebra.CacheDictType{Tuple{Ring, Any, Any, Bool}, GroupAlgebra}() mutable struct GroupAlgebraElem{T, S} <: AbstractAssociativeAlgebraElem{T} parent::S @@ -241,11 +256,7 @@ mutable struct GroupAlgebraElem{T, S} <: AbstractAssociativeAlgebraElem{T} function GroupAlgebraElem{T, S}(A::S, g::U) where {T, S, U} if A.sparse - i = get!(A.group_to_base, g) do - A.ind += 1 - A.base_to_group[A.ind] = g - return A.ind - end + i = __elem_index(A, g) a = GroupAlgebraElem{T, S}(A) a.coeffs_sparse = sparse_row(base_ring(A), [i], [one(base_ring(A))]) return a @@ -272,11 +283,11 @@ end __elem_index(A, g) = get!(A.group_to_base, g) do A.ind += 1 + resize!(A.base_to_group, max(A.ind, length(A.base_to_group))) A.base_to_group[A.ind] = g return A.ind end - ################################################################################ # # AbsAlgAssIdl diff --git a/src/Grp/GenGrp.jl b/src/Grp/GenGrp.jl index 3222ce0730..e8d9d12c72 100644 --- a/src/Grp/GenGrp.jl +++ b/src/Grp/GenGrp.jl @@ -199,6 +199,8 @@ end # ################################################################################ +is_finite(::MultTableGroup) = true + elem_type(::Type{MultTableGroup}) = MultTableGroupElem Base.hash(G::MultTableGroupElem, h::UInt) = Base.hash(G.i, h) @@ -256,10 +258,12 @@ end # ################################################################################ -function order(G::MultTableGroup) +function order(::Type{Int}, G::MultTableGroup) return size(G.mult_table, 1) end +order(G::MultTableGroup) = order(Int, G) + length(G::MultTableGroup) = order(G) ################################################################################ diff --git a/src/GrpAb/GrpAbFinGen.jl b/src/GrpAb/GrpAbFinGen.jl index a84c70118f..e0431f8627 100644 --- a/src/GrpAb/GrpAbFinGen.jl +++ b/src/GrpAb/GrpAbFinGen.jl @@ -586,6 +586,8 @@ function order(A::FinGenAbGroup) return prod(elementary_divisors(A)) end +order(::Type{Int}, A::FinGenAbGroup) = Int(order(A)) + ################################################################################ # # Exponent diff --git a/test/AlgAss/AlgGrp.jl b/test/AlgAss/AlgGrp.jl index 4b41f9cd2f..2a75057137 100644 --- a/test/AlgAss/AlgGrp.jl +++ b/test/AlgAss/AlgGrp.jl @@ -73,4 +73,15 @@ end end end + + # abelian groups + + QG = group_algebra(QQ, abelian_group([2, 2])) + @test QG isa GroupAlgebra + @test QG !== group_algebra(QQ, abelian_group([2, 2]); cached = false) + @test QG !== group_algebra(QQ, abelian_group([2, 2]); sparse = true) + + QG = group_algebra(QQ, abelian_group([2 for i in 1:10]); sparse = true) + @test QG isa GroupAlgebra + @test QG !== group_algebra(QQ, abelian_group([2 for i in 1:10]); sparse = true, cached = false) end diff --git a/test/AlgAss/Elem.jl b/test/AlgAss/Elem.jl index b08b5eb9af..d46812d583 100644 --- a/test/AlgAss/Elem.jl +++ b/test/AlgAss/Elem.jl @@ -85,4 +85,11 @@ Hecke.add!(b,b) @test b == A(matrix(QQ, [6 8; 10 12])) end + + # fancy group algebra element constructor + G = abelian_group([2, 2]); a = G([0, 1]); + QG = group_algebra(QQ, G); + @test QG(Dict(a => 2, zero(G) => 1)) == 2 * QG(a) + 1 * QG(zero(G)) + QG = group_algebra(QQ, G; sparse = true); + @test QG(Dict(a => 2, zero(G) => 1)) == 2 * QG(a) + 1 * QG(zero(G)) end