############################################################################### # # GroupRing & GroupRingElem structs # ############################################################################### mutable struct GroupRing{R<:Ring, Gr<:Group, El<:GroupElem} <: NCRing base_ring::R group::Gr basis::Vector{El} basis_dict::Dict{El, Int32} pm::Matrix{Int32} function GroupRing(coeffring::Ring, group::Group, basis::Vector{<:GroupElem}; halfradius_length::Integer=0) elem_type(group) == eltype(basis) || throw(ArgumentError("Basis must consist of elements of $G")) RG = new{typeof(coeffring), typeof(group), eltype(basis)}( coeffring, group, basis, reverse_dict(basis)) if halfradius_length > 0 pm = zeros(Int32, halfradius_length, halfradius_length) setcache!(RG, pm) end return RG end function GroupRing(coeffring::Ring, group::Group, basis::Vector{<:GroupElem}, basis_dict::Dict{<:GroupElem,<:Integer}, pm::Matrix{<:Integer}) size(pm,1) == size(pm,2) || throw(ArgumentError("Multiplication table must be square, got one of size $(size(pm))")) elem_type(group) == eltype(basis) || throw(ArgumentError("Basis must consist of elements of $G")) RG = new{typeof(coeffring), typeof(group), eltype(basis)}( coeffring, group, basis, basis_dict, pm) return RG end function GroupRing(coeffring::Ring, group::Group, pm::Matrix{<:Integer}) size(pm,1) == size(pm,2) || throw(ArgumentError("Multiplication table must be square, got one of size $(size(pm))")) RG = new{typeof(coeffring), typeof(group), elem_type(group)}(coeffring, group) setcache!(RG, pm) return RG end end mutable struct GroupRingElem{T, GR<:GroupRing} <: NCRingElem coeffs::SparseVector{T, Int} parent::GR function GroupRingElem(c::AbstractVector{T}, RG::GR; check::Bool=true) where {T, GR} if check T == elem_type(base_ring(RG)) || throw(DomainError(c, "Call parent group ring on the vector to coerce the coefficients.s")) length(c) == length(RG) || throw(DomainError(c, "Can not coerce vector to $RG -- lengths differ:\n $(length(c)) ≠ $(length(RG))")) end return new{eltype(c), GR}(sparse(c), RG) end end ############################################################################### # # Constructors & parent object call overloads (NCRing interface) # ############################################################################### (RG::GroupRing)(i::Integer=0)= _coerce_scalar(RG, i) (RG::GroupRing)(x::RingElem) = _coerce_scalar(RG, x) function (RG::GroupRing)(X::GroupRingElem{T}) where {T} if RG == parent(X) && elem_type(base_ring(RG)) == T return X end if RG.group == parent(X).group return GroupRingElem(base_ring(RG).(X.coeffs), RG) # we do checks here end throw(ArgumentError("Can not coerce to $RG.")) end ############################################################################### # # Additional Constructors / parent call overloads # ############################################################################### function GroupRing(coeffring::Ring, group::Group, basis::Vector{<:GroupElem}, pm::Matrix{<:Integer}) return GroupRing(coeffring, group, basis, reverse_dict(basis), pm) end function (RG::GroupRing)(g::GroupElem, val=one(base_ring(RG))) v = sparsevec([RG[g]], [base_ring(RG)(val)], length(RG)) return GroupRingElem(v, RG, check=false) end function (RG::GroupRing{R, G, El})(V::AbstractVector{El}, vals=[one(base_ring(RG)) for _ in V]) where {R, G, El} hasbasis(RG) || throw(ArgumentError("Can not embedd without basis of $RG")) l = length(RG) nzind = [RG[g] for g in V] return GroupRingElem(sparsevec(nzind, base_ring(RG).(vals), l), RG, check=false) end function (RG::GroupRing)(f, X::GroupRingElem) hasbasis(RG) || throw(ArgumentError("Can not embedd without basis of $RG")) suppX = supp(X) return RG([f(g) for g in suppX], [X[g] for g in suppX]) end function (RG::GroupRing)(v::AbstractVector; adjust_length::Bool=false, widen_coefficients::Bool=false) if adjust_length l = length(RG) if length(v) < l @warn "Coefficients vector is length-defficient; adjusting." v = sparse(v) v = sparse(v.nzind, v.nzval, l) elseif length(c) > l throw(DomainError(v, "Can not coerce vector to $RG -- lengths differ:\n $(length(v)) ≠ $(length(RG))")) end end if widen_coefficients parent(first(v)) != base_ring(RG) RG = change_base_ring(RG, parent(first(v))) return GroupRingElem(v, RG) else R = base_ring(RG) return GroupRingElem(R.(v), RG) end end function AbstractAlgebra.change_base_ring(RG::GroupRing, R::Ring) if base_ring(RG) == R return RG end if hasbasis(RG) && cachesmultiplication(RG) return GroupRing(R, RG.group, RG.basis, RG.basis_dict, RG.pm) elseif hasbasis(RG) return GroupRing(R, RG.group, RG.basis, RG.basis_dict) elseif cachesmultiplication(RG) return GroupRing(R, RG.group, RG.pm) end throw(ArgumentError("Could not change the base ring of $RG to $R")) end function AbstractAlgebra.change_base_ring(X::GroupRingElem, R::Ring) if base_ring(parent(X)) == R return X else RG = change_base_ring(parent(X), R) return RG(X.coeffs) end end ############################################################################### # # Cache Manipulation # ############################################################################### hasbasis(RG::GroupRing) = isdefined(RG, :basis) cachesmultiplication(RG::GroupRing) = isdefined(RG, :pm) reverse_dict(iter) = reverse_dict(Int32, iter) function reverse_dict(::Type{I}, iter) where I<:Integer length(iter) > typemax(I) && error("Can not produce reverse dict: $(length(iter)) is too large for $I") return Dict{eltype(iter), I}(x => i for (i,x) in enumerate(iter)) end setcache!(RG::GroupRing, pm::Matrix{<:Integer}) = (RG.pm = pm; return RG) function complete!(RG::GroupRing, indX=1:size(RG.pm, 1), indY=1:size(RG.pm, 2); twisted::Bool=false) @assert hasbasis(RG) # preallocate elements: res = RG.group() x = RG.group() i_old = 0 for (i,j) in Iterators.ProductIterator((indX, indY)) if iszero(RG.pm[i,j]) if i == i_old x = ifelse(twisted, inv(RG[i]), RG[i]) i_old = i end RG.pm[i,j] = RG[AbstractAlgebra.mul!(res, x, RG[j])] end end return RG end function complete!(RG::GroupRing, limit::Integer; twisted::Bool=false, check::Bool=true) hasbasis(RG) || throw(ArgumentError("Provide basis for completion first!")) if !cachesmultiplication(RG) setcache!(RG, create_pm(RG.basis, RG.basis_dict, limit, twisted=twisted, check=false)) # we check correctness below else complete!(RG, 1:limit, 1:limit; twisted=twisted) end check && @assert check_pm(RG.pm, RG.basis; twisted=twisted) return RG end function create_pm(basis::AbstractVector{T}, basis_dict::Dict{T, <:Integer}, limit::Integer=length(basis); twisted::Bool=false, check::Bool=true) where T product_matrix = zeros(Int32, limit, limit) Threads.@threads for i in 1:size(product_matrix, 1) x = ifelse(twisted, inv(basis[i]), basis[i]) for j in 1:size(product_matrix, 2) product_matrix[i,j] = basis_dict[x*basis[j]] end end # exceptions in threaded code are not critical so we need to check && @assert check_pm(product_matrix, basis, twisted=twisted) return product_matrix end function check_pm(pm::Matrix{<:Integer}, basis::Vector; twisted::Bool=false) idx = (0,0) for i in 1:size(pm, 1), j in 1:size(pm,2) # @info "" i j if iszero(pm[i,j]) idx = (i,j) break end end if idx == (0,0) return true else i,j = Tuple(idx) x = ifelse(twisted, inv(basis[i]), basis[i]) @error "Product x*y is not supported on basis:" x y=basis[j] throw(KeyError(x*basis[j])) end end