diff --git a/docs/src/Groups/subgroups.md b/docs/src/Groups/subgroups.md index fccfea3e6bc8..811a95dcd917 100644 --- a/docs/src/Groups/subgroups.md +++ b/docs/src/Groups/subgroups.md @@ -112,24 +112,31 @@ subgroup_classes(G::GAPGroup) ```@docs GroupCoset +group(C::GroupCoset) +acting_group(C::GroupCoset) +representative(C::GroupCoset) right_coset(H::GAPGroup, g::GAPGroupElem) left_coset(H::GAPGroup, g::GAPGroupElem) -is_right(c::GroupCoset) -is_left(c::GroupCoset) -is_bicoset(C::GroupCoset) -acting_domain(C::GroupCoset) -representative(C::GroupCoset) +is_right(C::GroupCoset) +is_left(C::GroupCoset) right_cosets(G::GAPGroup, H::GAPGroup; check::Bool=true) left_cosets(G::GAPGroup, H::GAPGroup; check::Bool=true) right_transversal(G::T1, H::T2; check::Bool=true) where T1 <: GAPGroup where T2 <: GAPGroup left_transversal(G::T1, H::T2; check::Bool=true) where T1 <: GAPGroup where T2 <: GAPGroup +is_bicoset(C::GroupCoset) +``` + +```@docs GroupDoubleCoset{T <: GAPGroup, S <: GAPGroupElem} -double_coset(G::GAPGroup, g::GAPGroupElem, H::GAPGroup) -double_cosets(G::T, H::GAPGroup, K::GAPGroup; check::Bool=true) where T <: GAPGroup +group(C::GroupDoubleCoset) left_acting_group(C::GroupDoubleCoset) right_acting_group(C::GroupDoubleCoset) representative(C::GroupDoubleCoset) +double_coset(G::GAPGroup, g::GAPGroupElem, H::GAPGroup) +double_cosets(G::T, H::GAPGroup, K::GAPGroup; check::Bool=true) where T <: GAPGroup +``` + +```@docs order(C::Union{GroupCoset,GroupDoubleCoset}) Base.rand(C::Union{GroupCoset,GroupDoubleCoset}) -intersect(V::AbstractVector{Union{<: GAPGroup, GroupCoset, GroupDoubleCoset}}) ``` diff --git a/docs/src/Groups/tom.md b/docs/src/Groups/tom.md index 05f64e78bfe2..c871d647c8bd 100644 --- a/docs/src/Groups/tom.md +++ b/docs/src/Groups/tom.md @@ -11,8 +11,8 @@ Therefore a table of marks is sometimes called a *Burnside matrix*. The table of marks of a finite group ``G`` is a matrix whose rows and columns are labelled by the conjugacy classes of subgroups of ``G`` and where for two subgroups ``H`` and ``K`` the ``(H, K)``-entry is the number of -fixed points of ``K`` in the transitive action of ``G`` on the cosets of ``H`` -in ``G``. +fixed points of ``K`` in the transitive action of ``G`` on the right cosets +of ``H`` in ``G``. So the table of marks characterizes the set of all permutation representations of ``G``. Moreover, the table of marks gives a compact description of the diff --git a/src/Groups/cosets.jl b/src/Groups/cosets.jl index e637a543d01f..66032af7386b 100644 --- a/src/Groups/cosets.jl +++ b/src/Groups/cosets.jl @@ -1,20 +1,35 @@ # T=type of the group, S=type of the element @doc raw""" - GroupCoset{T<: Group, S <: GAPGroupElem} + GroupCoset{TG <: GAPGroup, TH <: GAPGroup, S <: GAPGroupElem} -Type of group cosets. -Two cosets are equal if, and only if, they are both left (resp. right) +Type of right and left cosets of subgroups in groups. + +For an element $g$ in a group $G$, and a subgroup $H$ of $G$, +the set $Hg = \{ hg; h \in H \}$ is a right coset of $H$ in $G$, +and the set $gH = \{ gh; h \in H \}$ is a left coset of $H$ in $G$. + +- [`group(C::GroupCoset)`](@ref) returns $G$. + +- [`acting_group(C::GroupCoset)`](@ref) returns $H$. + +- [`representative(C::GroupCoset)`](@ref) returns an element + (the same element for each call) of `C`. + +- [`is_right(C::GroupCoset)`](@ref) and [`is_left(C::GroupCoset)`](@ref) + return whether `C` is a right or left coset, respectively. + +Two cosets are equal if and only if they are both left or right, respectively, and they contain the same elements. """ -struct GroupCoset{T<: GAPGroup, S <: GAPGroupElem} - G::T # big group containing the subgroup and the element - H::GAPGroup # subgroup (may have a different type) +struct GroupCoset{TG <: GAPGroup, TH <: GAPGroup, S <: GAPGroupElem} + G::TG # big group containing the subgroup and the element + H::TH # subgroup (may have a different type) repr::S # element side::Symbol # says if the coset is left or right X::Ref{GapObj} # GapObj(H*repr) - function GroupCoset(G::T, H::GAPGroup, representative::S, side::Symbol) where {T<: GAPGroup, S<:GAPGroupElem} - return new{T, S}(G, H, representative, side, Ref{GapObj}()) + function GroupCoset(G::TG, H::TH, representative::S, side::Symbol) where {TG <: GAPGroup, TH <: GAPGroup, S <:GAPGroupElem} + return new{TG, TH, S}(G, H, representative, side, Ref{GapObj}()) end end @@ -27,11 +42,11 @@ GAP.@install function GapObj(obj::GroupCoset) obj.X[] = GAPWrap.RightCoset(GAPWrap.ConjugateSubgroup(GapObj(obj.H), GAPWrap.Inverse(g)), g) end end - return obj.X[] + return obj.X[]::GapObj end Base.hash(x::GroupCoset, h::UInt) = h # FIXME -Base.eltype(::Type{GroupCoset{T,S}}) where {T,S} = S +Base.eltype(::Type{GroupCoset{TG, TH, S}}) where {TG, TH, S} = S function ==(C1::GroupCoset, C2::GroupCoset) H = C1.H @@ -167,37 +182,64 @@ function Base.:*(c::GroupCoset, d::GroupCoset) return double_coset(c.H, representative(c)*representative(d), d.H) end + """ - acting_domain(C::GroupCoset) + group(C::GroupCoset) -If `C` = `Hx` or `xH`, return `H`. +Return the group `G` that is the parent of all elements in `C`. +That is, `C` is a left or right coset of a subgroup of `G` in `G`. # Examples ```jldoctest julia> G = symmetric_group(5) Sym(5) -julia> g = perm(G,[3,4,1,5,2]) -(1,3)(2,4,5) +julia> H = sylow_subgroup(G, 2)[1] +Permutation group of degree 5 and order 8 + +julia> C = right_coset(H, gen(G, 1)) +Right coset of permutation group of degree 5 and order 8 + with representative (1,2,3,4,5) + in Sym(5) + +julia> group(C) == G +true +``` +""" +group(C::GroupCoset) = C.G + + +""" + acting_group(C::GroupCoset) + +Return the group `H` such that `C` is `Hx` (if `C` is a right coset) +or `xH` (if `C` is a left coset), for an element `x` in `C`. + +# Examples +```jldoctest +julia> G = symmetric_group(5) +Sym(5) julia> H = symmetric_group(3) Sym(3) -julia> gH = left_coset(H,g) -Left coset of Sym(3) - with representative (1,3)(2,4,5) +julia> C = right_coset(H, gen(G, 1)) +Right coset of Sym(3) + with representative (1,2,3,4,5) in Sym(5) -julia> acting_domain(gH) -Sym(3) +julia> acting_group(C) == H +true ``` """ -acting_domain(C::GroupCoset) = C.H +acting_group(C::GroupCoset) = C.H """ representative(C::GroupCoset) -If `C` = `Hx` or `xH`, return `x`. +Return an element `x` in `group(C)` such that +`C` = `Hx` (if `C` is a right coset) +or `xH` (if `C` is a left coset). # Examples ```jldoctest @@ -210,12 +252,12 @@ julia> g = perm(G,[3,4,1,5,2]) julia> H = symmetric_group(3) Sym(3) -julia> gH = left_coset(H, g) -Left coset of Sym(3) +julia> Hg = right_coset(H, g) +Right coset of Sym(3) with representative (1,3)(2,4,5) in Sym(5) -julia> representative(gH) +julia> representative(Hg) (1,3)(2,4,5) ``` """ @@ -225,8 +267,10 @@ representative(C::GroupCoset) = C.repr """ is_bicoset(C::GroupCoset) -Return whether `C` is simultaneously a right coset and a left coset for the same subgroup `H`. This -is the case if and only if the coset representative normalizes the acting domain subgroup. +Return whether `C` is simultaneously a right coset and a left coset +for the same subgroup `H`. +This is the case if and only if the coset representative normalizes +`acting_group(C)`. # Examples ```jldoctest @@ -268,6 +312,8 @@ Return the G-set that describes the right cosets of `H` in `G`. If `check == false`, do not check whether `H` is a subgroup of `G`. +Use [`right_transversal`](@ref) to compute the vector of coset representatives. + # Examples ```jldoctest julia> G = symmetric_group(4) @@ -282,7 +328,7 @@ Right cosets of Sym(4) julia> collect(rc) -4-element Vector{GroupCoset{PermGroup, PermGroupElem}}: +4-element Vector{GroupCoset{PermGroup, PermGroup, PermGroupElem}}: Right coset of H with representative () Right coset of H with representative (1,4) Right coset of H with representative (1,4,2) @@ -290,7 +336,6 @@ julia> collect(rc) ``` """ function right_cosets(G::GAPGroup, H::GAPGroup; check::Bool=true) -#T _check_compatible(G, H) ? return GSetBySubgroupTransversal(G, H, :right, check = check) end @@ -301,6 +346,8 @@ Return the G-set that describes the left cosets of `H` in `G`. If `check == false`, do not check whether `H` is a subgroup of `G`. +Use [`left_transversal`](@ref) to compute the vector of coset representatives. + # Examples ```jldoctest julia> G = symmetric_group(4) @@ -400,6 +447,8 @@ they are created anew with each access to the transversal. If `check == false`, do not check whether `H` is a subgroup of `G`. +Use [`right_cosets`](@ref) to compute the G-set of right cosets. + # Examples ```jldoctest julia> G = symmetric_group(4) @@ -440,6 +489,8 @@ they are created anew with each access to the transversal. If `check == false`, do not check whether `H` is a subgroup of `G`. +Use [`left_cosets`](@ref) to compute the G-set of left cosets. + # Examples ```jldoctest julia> G = symmetric_group(4) @@ -470,22 +521,25 @@ function left_transversal(G::T1, H::T2; check::Bool=true) where T1 <: GAPGroup w GAPWrap.RightTransversal(GapObj(G), GapObj(H))) end -Base.IteratorSize(::Type{<:GroupCoset}) = Base.SizeUnknown() -Base.iterate(G::GroupCoset) = iterate(G, GAPWrap.Iterator(GapObj(G))) -function Base.iterate(G::GroupCoset, state) - GAPWrap.IsDoneIterator(state) && return nothing - i = GAPWrap.NextIterator(state)::GapObj - return group_element(G.G, i), state -end +@doc raw""" + GroupDoubleCoset{T<: Group, S <: GAPGroupElem} +Type of double cosets of subgroups in groups. +For an element $g$ in a group $G$, and two subgroups $H$, $K$ of $G$, +the set $HgK = \{ hgk; h \in H, k \in K \}$ is a $H-K$-double coset in $G$. -@doc raw""" - GroupDoubleCoset{T<: Group, S <: GAPGroupElem} +- [`group(C::GroupDoubleCoset)`](@ref) returns $G$. + +- [`left_acting_group(C::GroupDoubleCoset)`](@ref) returns $H$. -Group double coset. -Two double cosets are equal if, and only if, they contain the same elements. +- [`right_acting_group(C::GroupDoubleCoset)`](@ref) returns $H$. + +- [`representative(C::GroupDoubleCoset)`](@ref) returns an element + (the same element for each call) of `C`. + +Two double cosets are equal if and only if they contain the same elements. """ struct GroupDoubleCoset{T <: GAPGroup, S <: GAPGroupElem} # T=type of the group, S=type of the element @@ -495,17 +549,17 @@ struct GroupDoubleCoset{T <: GAPGroup, S <: GAPGroupElem} repr::S X::Ref{GapObj} size::Ref{ZZRingElem} - + function GroupDoubleCoset(G::T, H::GAPGroup, K::GAPGroup, representative::S) where {T<: GAPGroup, S<:GAPGroupElem} return new{T, S}(G, H, K, representative, Ref{GapObj}(), Ref{ZZRingElem}()) - end + end end GAP.@install function GapObj(C::GroupDoubleCoset) if !isassigned(C.X) C.X[] = GAPWrap.DoubleCoset(GapObj(C.H), GapObj(representative(C)), GapObj(C.K)) end - return C.X[] + return C.X[]::GapObj end Base.hash(x::GroupDoubleCoset, h::UInt) = h # FIXME @@ -613,7 +667,7 @@ function double_cosets(G::T, H::GAPGroup, K::GAPGroup; check::Bool=true) where T C.size[] = ZZRingElem(n) res[i] = C end - return res + return res end """ @@ -632,10 +686,9 @@ function order(::Type{T}, C::GroupDoubleCoset) where T <: IntegerUnion if !isassigned(C.size) C.size[] = ZZRingElem(GAPWrap.Size(GapObj(C))) end - return T(C.size[]) -end + return T(C.size[])::T +end -Base.length(C::Union{GroupCoset,GroupDoubleCoset}) = order(C) """ rand(rng::Random.AbstractRNG = Random.GLOBAL_RNG, C::Union{GroupCoset,GroupDoubleCoset}) @@ -650,28 +703,145 @@ function Base.rand(rng::Random.AbstractRNG, C::Union{GroupCoset,GroupDoubleCoset return group_element(C.G, s) end + +""" + group(C::GroupDoubleCoset) + +Return the group `G` that is the parent of all elements in `C`. +That is, `C` is a double coset of two subgroups of `G` in `G`. + +# Examples +```jldoctest +julia> G = symmetric_group(5) +Sym(5) + +julia> H = symmetric_group(3); K = symmetric_group(2); + +julia> HgK = double_coset(H, gen(G, 1), K) +Double coset of Sym(3) + and Sym(2) + with representative (1,2,3,4,5) + in Sym(5) + +julia> group(HgK) == G +true +``` +""" +group(C::GroupDoubleCoset) = C.G + """ representative(C::GroupDoubleCoset) -Return a representative `x` of the double coset `C` = `HxK`. +Return an element `x` of the double coset `C` = `HxK`. + +# Examples +```jldoctest +julia> G = symmetric_group(5) +Sym(5) + +julia> H = symmetric_group(3); K = symmetric_group(2); + +julia> HgK = double_coset(H, gen(G, 1), K) +Double coset of Sym(3) + and Sym(2) + with representative (1,2,3,4,5) + in Sym(5) + +julia> representative(HgK) +(1,2,3,4,5) +``` """ representative(C::GroupDoubleCoset) = C.repr """ left_acting_group(C::GroupDoubleCoset) -Given a double coset `C` = `HxK`, return `H`. +Return `H` if `C` = `HxK`. + +# Examples +```jldoctest +julia> G = symmetric_group(5) +Sym(5) + +julia> H = symmetric_group(3); K = symmetric_group(2); + +julia> HgK = double_coset(H, gen(G, 1), K) +Double coset of Sym(3) + and Sym(2) + with representative (1,2,3,4,5) + in Sym(5) + +julia> left_acting_group(HgK) == H +true +``` """ left_acting_group(C::GroupDoubleCoset) = C.H """ right_acting_group(C::GroupDoubleCoset) -Given a double coset `C` = `HxK`, return `K`. +Return `K` if `C` = `HxK`. + +# Examples +```jldoctest +julia> G = symmetric_group(5) +Sym(5) + +julia> H = symmetric_group(3); K = symmetric_group(2); + +julia> HgK = double_coset(H, gen(G, 1), K) +Double coset of Sym(3) + and Sym(2) + with representative (1,2,3,4,5) + in Sym(5) + +julia> right_acting_group(HgK) == K +true +``` """ right_acting_group(C::GroupDoubleCoset) = C.K + +############################################################################ +# +# iteration over cosets +# +function Base.in(g::GAPGroupElem, C::GroupCoset) + if is_right(C) + return g / representative(C) in acting_group(C) + else + return g \ representative(C) in acting_group(C) + end +end + +function Base.in(g::GAPGroupElem, C::GroupDoubleCoset) + return GapObj(g) in GapObj(C) +#TODO: avoid delegation to GAP? +# (GAP uses `RepresentativesContainedRightCosets`, `CanonicalRightCosetElement`) +end + +Base.IteratorSize(::Type{<:GroupCoset{TG, TH, S}}) where {TG, TH, S} = Base.IteratorSize(TH) + +# need this function just for the iterator +Base.length(C::Union{GroupCoset,GroupDoubleCoset}) = order(Int, C) + +function Base.iterate(C::GroupCoset) + return iterate(C, iterate(acting_group(C))) +end + +function Base.iterate(C::GroupCoset, state) + state === nothing && return nothing + G = group(C) + if is_right(C) + res = G(state[1]) * representative(C) + else + res = representative(C) * G(state[1]) + end + return res, iterate(acting_group(C), state[2]) +end + Base.IteratorSize(::Type{<:GroupDoubleCoset}) = Base.SizeUnknown() +Base.IteratorSize(::Type{GroupDoubleCoset{PermGroup, PermGroupElem}}) = Base.HasLength() Base.iterate(G::GroupDoubleCoset) = iterate(G, GAPWrap.Iterator(GapObj(G))) diff --git a/src/Groups/gsets.jl b/src/Groups/gsets.jl index a22df1cd98de..0c94b49427fe 100644 --- a/src/Groups/gsets.jl +++ b/src/Groups/gsets.jl @@ -598,7 +598,7 @@ The fields are - the (left or right) transversal, of type `SubgroupTransversal{T, S, E}`, - the dictionary used to store attributes (orbits, elements, ...). """ -@attributes mutable struct GSetBySubgroupTransversal{T, S, E} <: GSet{T,GroupCoset{T, E}} +@attributes mutable struct GSetBySubgroupTransversal{T, S, E} <: GSet{T,GroupCoset{T, S, E}} group::T subgroup::S side::Symbol @@ -672,7 +672,7 @@ function Base.iterate(Omega::GSetBySubgroupTransversal, state = 1) end end -Base.eltype(::Type{GSetBySubgroupTransversal{T, S, E}}) where {S, T, E} = GroupCoset{T, E} +Base.eltype(::Type{GSetBySubgroupTransversal{T, S, E}}) where {S, T, E} = GroupCoset{T, S, E} function Base.getindex(Omega::GSetBySubgroupTransversal, i::Int) if Omega.side == :right @@ -684,7 +684,7 @@ end is_transitive(Omega::GSetBySubgroupTransversal) = true -function orbit(G::T, omega::GroupCoset{T, S}) where T <: GAPGroup where S +function orbit(G::T, omega::GroupCoset{T, TH, S}) where {T <: GAPGroup, TH <: GAPGroup, S} @req G == omega.G "omega must be a left or right coset in G" return GSetBySubgroupTransversal(G, omega.H, omega.side, check = false) end @@ -692,7 +692,7 @@ end # One problem would be that `omega` would not be a point in the orbit, # according to the definition of equality for cosets. -function orbit(Omega::GSetBySubgroupTransversal{T, S, E}, omega::GroupCoset{T, E}) where T <: GAPGroup where S <: GAPGroup where E +function orbit(Omega::GSetBySubgroupTransversal{T, S, E}, omega::GroupCoset{T, S, E}) where {T <: GAPGroup, S <: GAPGroup, E} @req (Omega.group == omega.G && Omega.subgroup == omega.H && Omega.side == omega.side) "omega is not in Omega" return Omega end @@ -874,8 +874,6 @@ end ############################################################################ -acting_domain(Omega::GSet) = acting_group(Omega) - Base.length(Omega::GSetByElements) = length(elements(Omega)) Base.length(::Type{T}, Omega::GSetByElements) where T <: IntegerUnion = T(length(elements(Omega))) diff --git a/src/deprecations.jl b/src/deprecations.jl index 86c6106e18a2..fee339161138 100644 --- a/src/deprecations.jl +++ b/src/deprecations.jl @@ -148,3 +148,7 @@ Base.@deprecate_binding in_linear_system is_in_linear_system @deprecate minimal_generating_set(G::GAPGroup) minimal_size_generating_set(G) @deprecate has_minimal_generating_set(G::GAPGroup) has_minimal_size_generating_set(G) @deprecate set_minimal_generating_set(G::GAPGroup, v) set_minimal_size_generating_set(G, v) + +# deprecated for 1.3 +@deprecate acting_domain(C::GroupCoset) acting_group(C) +@deprecate acting_domain(Omega::GSet) acting_group(Omega) diff --git a/src/exports.jl b/src/exports.jl index b36c6c42271a..88d5d00374e9 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -201,7 +201,6 @@ export abelian_group export abelian_invariants export abelian_invariants_schur_multiplier export absolute_primary_decomposition -export acting_domain export acting_group export acting_subgroup export action diff --git a/test/Groups/gsets.jl b/test/Groups/gsets.jl index a252160ae5c6..86377eef8bdd 100644 --- a/test/Groups/gsets.jl +++ b/test/Groups/gsets.jl @@ -103,7 +103,7 @@ G = symmetric_group(6) Omega = gset(G, [Set([1, 2])]) @test representative(Omega) in Omega - @test acting_domain(Omega) == G + @test acting_group(Omega) == G # wrapped elements of G-sets G = symmetric_group(4) diff --git a/test/Groups/matrixgroups.jl b/test/Groups/matrixgroups.jl index 61ca115ce1c7..bbc14add47e5 100644 --- a/test/Groups/matrixgroups.jl +++ b/test/Groups/matrixgroups.jl @@ -622,7 +622,7 @@ end lc = x*H @test order(lc)==order(H) @test representative(lc)==x - @test acting_domain(lc)==H + @test acting_group(lc)==H @test x in lc C = centralizer(G,x)[1] @test order(C)==64 diff --git a/test/Groups/subgroups_and_cosets.jl b/test/Groups/subgroups_and_cosets.jl index 298fc50958c0..fb51e3a1f881 100644 --- a/test/Groups/subgroups_and_cosets.jl +++ b/test/Groups/subgroups_and_cosets.jl @@ -167,6 +167,10 @@ end @test index(G, H) == 4 C = right_coset(H, G[1]) + @test group(C) == G + @test acting_group(C) == H + @test representative(C) in C + @test all(x -> x/G[1] in H, C) @test is_right(C) @test order(C) == length(collect(C)) @test order(C) isa ZZRingElem @@ -197,8 +201,8 @@ end @test rc==H*x @test lc==x*H @test dc==H*x*K - @test acting_domain(rc) == H - @test acting_domain(lc) == H + @test acting_group(rc) == H + @test acting_group(lc) == H @test left_acting_group(dc) == H @test right_acting_group(dc) == K @test representative(rc) == x @@ -219,6 +223,10 @@ end @test issubset(left_coset(K,x),dc) @test !is_bicoset(rc) + @inferred GapObj(rc) + @inferred GapObj(lc) + @inferred GapObj(dc) + @test rc == H*x @test lc == x*H @test dc == H*x*K @@ -271,6 +279,7 @@ end x = G([2,3,4,5,1]) dc = double_coset(H,x,K) dc1 = double_coset(H, H[1]*x, K) + @test group(dc) == G @test representative(dc) != representative(dc1) @test dc == dc1 L = double_cosets(G,H,K) diff --git a/test/book/cornerstones/groups/actions.jlcon b/test/book/cornerstones/groups/actions.jlcon index b76d6835384b..8afa776b146e 100644 --- a/test/book/cornerstones/groups/actions.jlcon +++ b/test/book/cornerstones/groups/actions.jlcon @@ -17,7 +17,7 @@ julia> acting_group(r) Pc group of order 6 julia> collect(r) -3-element Vector{GroupCoset{PcGroup, PcGroupElem}}: +3-element Vector{GroupCoset{PcGroup, SubPcGroup, PcGroupElem}}: Right coset of U with representative of ... Right coset of U with representative f2 Right coset of U with representative f2^2