From e78520f90bf945fe4bbd0983ed2cf22025427bcf Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Sat, 2 Apr 2022 14:43:52 +0200 Subject: [PATCH] add Constructions module with Direct/Wreath product --- Project.toml | 1 + src/Groups.jl | 4 + src/constructions/constructions.jl | 10 ++ src/constructions/direct_power.jl | 112 ++++++++++++++++++++ src/constructions/direct_product.jl | 105 +++++++++++++++++++ src/constructions/wreath_product.jl | 136 +++++++++++++++++++++++++ src/matrix_groups/MatrixGroups.jl | 6 +- src/matrix_groups/abstract.jl | 7 +- src/matrix_groups/eltary_matrices.jl | 5 +- src/matrix_groups/eltary_symplectic.jl | 5 +- test/group_constructions.jl | 43 ++++++++ test/runtests.jl | 2 + 12 files changed, 424 insertions(+), 12 deletions(-) create mode 100644 src/constructions/constructions.jl create mode 100644 src/constructions/direct_power.jl create mode 100644 src/constructions/direct_product.jl create mode 100644 src/constructions/wreath_product.jl create mode 100644 test/group_constructions.jl diff --git a/Project.toml b/Project.toml index 3b50d0d..b7685fe 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ Folds = "41a02a25-b8f0-4f67-bc48-60067656b558" GroupsCore = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120" KnuthBendix = "c2604015-7b3d-4a30-8a26-9074551ec60a" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +PermutationGroups = "8bc5a954-2dfc-11e9-10e6-cd969bffa420" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [compat] diff --git a/src/Groups.jl b/src/Groups.jl index 5221df4..46ae545 100644 --- a/src/Groups.jl +++ b/src/Groups.jl @@ -15,6 +15,10 @@ export Alphabet, AutomorphismGroup, FreeGroup, FreeGroup, FPGroup, FPGroupElemen export alphabet, evaluate, word, gens +# general constructions +include(joinpath("constructions", "constructions.jl")) +using .Constructions + include("types.jl") include("hashing.jl") include("normalform.jl") diff --git a/src/constructions/constructions.jl b/src/constructions/constructions.jl new file mode 100644 index 0000000..e371da6 --- /dev/null +++ b/src/constructions/constructions.jl @@ -0,0 +1,10 @@ +module Constructions + +using GroupsCore +using Random + +include("direct_product.jl") +include("direct_power.jl") +include("wreath_product.jl") + +end # of module Constructions diff --git a/src/constructions/direct_power.jl b/src/constructions/direct_power.jl new file mode 100644 index 0000000..7ecfd2b --- /dev/null +++ b/src/constructions/direct_power.jl @@ -0,0 +1,112 @@ +struct DirectPower{Gr,N,GEl<:GroupsCore.GroupElement} <: GroupsCore.Group + group::Gr + + function DirectPower{N}(G::GroupsCore.Group) where {N} + @assert N > 1 + return new{typeof(G),N,eltype(G)}(G) + end +end + +struct DirectPowerElement{GEl,N,Gr<:GroupsCore.Group} <: GroupsCore.GroupElement + elts::NTuple{N,GEl} + parent::DirectPower{Gr,N,GEl} +end + +DirectPowerElement( + elts::AbstractVector{<:GroupsCore.GroupElement}, + G::DirectPower, +) = DirectPowerElement(ntuple(i -> elts[i], _nfold(G)), G) + +_nfold(::DirectPower{Gr,N}) where {Gr,N} = N + +Base.one(G::DirectPower) = + DirectPowerElement(ntuple(_ -> one(G.group), _nfold(G)), G) + +Base.eltype(::Type{<:DirectPower{Gr,N,GEl}}) where {Gr,N,GEl} = + DirectPowerElement{GEl,N,Gr} + +function Base.iterate(G::DirectPower) + itr = Iterators.ProductIterator(ntuple(i -> G.group, _nfold(G))) + res = iterate(itr) + @assert res !== nothing + elt = DirectPowerElement(first(res), G) + return elt, (iterator = itr, state = last(res)) +end + +function Base.iterate(G::DirectPower, state) + itr, st = state.iterator, state.state + res = iterate(itr, st) + res === nothing && return nothing + elt = DirectPowerElement(first(res), G) + return elt, (iterator = itr, state = last(res)) +end + +function Base.IteratorSize(::Type{<:DirectPower{Gr,N}}) where {Gr,N} + Base.IteratorSize(Gr) isa Base.HasLength && return Base.HasShape{N}() + Base.IteratorSize(Gr) isa Base.HasShape && return Base.HasShape{N}() + return Base.IteratorSize(Gr) +end + +Base.size(G::DirectPower) = ntuple(_ -> length(G.group), _nfold(G)) + +GroupsCore.order(::Type{I}, G::DirectPower) where {I<:Integer} = + convert(I, order(I, G.group)^_nfold(G)) + +GroupsCore.ngens(G::DirectPower) = _nfold(G)*ngens(G.group) + +function GroupsCore.gens(G::DirectPower, i::Integer) + k = ngens(G.group) + ci = CartesianIndices((k, _nfold(G))) + @boundscheck checkbounds(ci, i) + r, c = Tuple(ci[i]) + tup = ntuple(j -> j == c ? gens(G.group, r) : one(G.group), _nfold(G)) + return DirectPowerElement(tup, G) +end + +function GroupsCore.gens(G::DirectPower) + N = _nfold(G) + S = gens(G.group) + tups = [ntuple(j->(i == j ? s : one(s)), N) for i in 1:N for s in S] + + return [DirectPowerElement(elts, G) for elts in tups] +end + +Base.isfinite(G::DirectPower) = isfinite(G.group) + +function Base.rand( + rng::Random.AbstractRNG, + rs::Random.SamplerTrivial{<:DirectPower}, +) + G = rs[] + return DirectPowerElement(rand(rng, G.group, _nfold(G)), G) +end + +GroupsCore.parent(g::DirectPowerElement) = g.parent + +Base.:(==)(g::DirectPowerElement, h::DirectPowerElement) = + (parent(g) === parent(h) && g.elts == h.elts) + +Base.hash(g::DirectPowerElement, h::UInt) = hash(g.elts, hash(parent(g), h)) + +Base.deepcopy_internal(g::DirectPowerElement, stackdict::IdDict) = + DirectPowerElement(Base.deepcopy_internal(g.elts, stackdict), parent(g)) + +Base.inv(g::DirectPowerElement) = DirectPowerElement(inv.(g.elts), parent(g)) + +function Base.:(*)(g::DirectPowerElement, h::DirectPowerElement) + @assert parent(g) === parent(h) + return DirectPowerElement(g.elts .* h.elts, parent(g)) +end + +GroupsCore.order(::Type{I}, g::DirectPowerElement) where {I<:Integer} = + convert(I, reduce(lcm, (order(I, h) for h in g.elts), init = one(I))) + +Base.isone(g::DirectPowerElement) = all(isone, g.elts) + +function Base.show(io::IO, G::DirectPower) + n = _nfold(G) + nn = n == 1 ? "1-st" : n == 2 ? "2-nd" : n == 3 ? "3-rd" : "$n-th" + print(io, "Direct $(nn) power of $(G.group)") +end +Base.show(io::IO, g::DirectPowerElement) = + print(io, "( ", join(g.elts, ", "), " )") diff --git a/src/constructions/direct_product.jl b/src/constructions/direct_product.jl new file mode 100644 index 0000000..802931b --- /dev/null +++ b/src/constructions/direct_product.jl @@ -0,0 +1,105 @@ +using Random +using GroupsCore + +struct DirectProduct{Gt,Ht,GEl,HEl} <: GroupsCore.Group + first::Gt + last::Ht + + function DirectProduct(G::GroupsCore.Group, H::GroupsCore.Group) + return new{typeof(G),typeof(H),eltype(G),eltype(H)}(G, H) + end +end + +struct DirectProductElement{GEl,HEl,Gt,Ht} <: GroupsCore.GroupElement + elts::Tuple{GEl,HEl} + parent::DirectProduct{Gt,Ht,GEl,HEl} +end + +DirectProductElement(g, h, G::DirectProduct) = DirectProduct((g, h), G) + +Base.one(G::DirectProduct) = + DirectProductElement((one(G.first), one(G.last)), G) + +Base.eltype(::Type{<:DirectProduct{Gt,Ht,GEl,HEl}}) where {Gt,Ht,GEl,HEl} = + DirectProductElement{GEl,HEl,Gt,Ht} + +function Base.iterate(G::DirectProduct) + itr = Iterators.product(G.first, G.last) + res = iterate(itr) + @assert res !== nothing + elt = DirectProductElement(first(res), G) + return elt, (iterator = itr, state = last(res)) +end + +function Base.iterate(G::DirectProduct, state) + itr, st = state.iterator, state.state + res = iterate(itr, st) + res === nothing && return nothing + elt = DirectProductElement(first(res), G) + return elt, (iterator = itr, state = last(res)) +end + +function Base.IteratorSize(::Type{<:DirectProduct{Gt,Ht}}) where {Gt,Ht} + Gi = Base.IteratorSize(Gt) + Hi = Base.IteratorSize(Ht) + if Gi isa Base.IsInfinite || Hi isa Base.IsInfinite + return Base.IsInfinite() + elseif Gi isa Base.SizeUnknown || Hi isa Base.SizeUnknown + return Base.SizeUnknown() + else + return Base.HasShape{2}() + end +end + +Base.size(G::DirectProduct) = (length(G.first), length(G.last)) + +GroupsCore.order(::Type{I}, G::DirectProduct) where {I<:Integer} = + convert(I, order(I, G.first) * order(I, G.last)) + +GroupsCore.ngens(G::DirectProduct) = ngens(G.first) + ngens(G.last) + +function GroupsCore.gens(G::DirectProduct) + gens_first = [DirectProductElement((g, one(G.last)), G) for g in gens(G.first)] + + gens_last = [DirectProductElement((one(G.first), g), G) for g in gens(G.last)] + + return [gens_first; gens_last] +end + +Base.isfinite(G::DirectProduct) = isfinite(G.first) && isfinite(G.last) + +function Base.rand( + rng::Random.AbstractRNG, + rs::Random.SamplerTrivial{<:DirectProduct}, +) + G = rs[] + return DirectProductElement((rand(rng, G.first), rand(rng, G.last)), G) +end + +GroupsCore.parent(g::DirectProductElement) = g.parent + +Base.:(==)(g::DirectProductElement, h::DirectProductElement) = + (parent(g) === parent(h) && g.elts == h.elts) + +Base.hash(g::DirectProductElement, h::UInt) = hash(g.elts, hash(parent(g), h)) + +Base.deepcopy_internal(g::DirectProductElement, stackdict::IdDict) = + DirectProductElement(Base.deepcopy_internal(g.elts, stackdict), parent(g)) + +Base.inv(g::DirectProductElement) = + DirectProductElement(inv.(g.elts), parent(g)) + +function Base.:(*)(g::DirectProductElement, h::DirectProductElement) + @assert parent(g) === parent(h) + return DirectProductElement(g.elts .* h.elts, parent(g)) +end + +GroupsCore.order(::Type{I}, g::DirectProductElement) where {I<:Integer} = + convert(I, lcm(order(I, first(g.elts)), order(I, last(g.elts)))) + +Base.isone(g::DirectProductElement) = all(isone, g.elts) + +Base.show(io::IO, G::DirectProduct) = + print(io, "Direct product of $(G.first) and $(G.last)") +Base.show(io::IO, g::DirectProductElement) = + print(io, "( $(join(g.elts, ",")) )") diff --git a/src/constructions/wreath_product.jl b/src/constructions/wreath_product.jl new file mode 100644 index 0000000..41ad021 --- /dev/null +++ b/src/constructions/wreath_product.jl @@ -0,0 +1,136 @@ +import PermutationGroups: AbstractPermutationGroup, AbstractPerm, degree, SymmetricGroup + +""" + WreathProduct(G::Group, P::AbstractPermutationGroup) <: Group +Return the wreath product of a group `G` by permutation group `P`, usually +written as `G ≀ P`. + +As set `G ≀ P` is the same as `Gᵈ × P` and the group can be understood as a +semi-direct product of `P` acting on `d`-fold cartesian product of `G` by +permuting coordinates. To be more precise, the multiplication inside wreath +product is defined as +> `(n, σ) * (m, τ) = (n*(m^σ), σ*τ)` +where `m^σ` denotes the action (from the right) of the permutation `σ` on +`d`-tuples of elements from `G`. +""" +struct WreathProduct{DP<:DirectPower,PGr<:AbstractPermutationGroup} <: + GroupsCore.Group + N::DP + P::PGr + + function WreathProduct(G::Group, P::AbstractPermutationGroup) + N = DirectPower{degree(P)}(G) + return new{typeof(N),typeof(P)}(N, P) + end +end + +struct WreathProductElement{ + DPEl<:DirectPowerElement, + PEl<:AbstractPerm, + Wr<:WreathProduct, +} <: GroupsCore.GroupElement + n::DPEl + p::PEl + parent::Wr + + function WreathProductElement( + n::DirectPowerElement, + p::AbstractPerm, + W::WreathProduct, + ) + new{typeof(n),typeof(p),typeof(W)}(n, p, W) + end +end + +Base.one(W::WreathProduct) = WreathProductElement(one(W.N), one(W.P), W) + +Base.eltype(::Type{<:WreathProduct{DP,PGr}}) where {DP,PGr} = + WreathProductElement{eltype(DP),eltype(PGr),WreathProduct{DP,PGr}} + +function Base.iterate(G::WreathProduct) + itr = Iterators.product(G.N, G.P) + res = iterate(itr) + @assert res !== nothing + elt = WreathProductElement(first(res)..., G) + return elt, (iterator = itr, state = last(res)) +end + +function Base.iterate(G::WreathProduct, state) + itr, st = state.iterator, state.state + res = iterate(itr, st) + res === nothing && return nothing + elt = WreathProductElement(first(res)..., G) + return elt, (iterator = itr, state = last(res)) +end + +function Base.IteratorSize(::Type{<:WreathProduct{DP,PGr}}) where {DP,PGr} + dpI = Base.IteratorSize(DP) + pgI = Base.IteratorSize(PGr) + + if dpI isa Base.IsInfinite || pgI isa Base.IsInfinite + return Base.IsInfinite() + elseif dpI isa Base.SizeUnknown || pgI isa Base.SizeUnknown + return Base.SizeUnknown() + else + return Base.HasShape{2}() + end +end + +Base.size(G::WreathProduct) = (length(G.N), length(G.P)) + +GroupsCore.order(::Type{I}, G::WreathProduct) where {I<:Integer} = + convert(I, order(I, G.N) * order(I, G.P)) + +function GroupsCore.gens(G::WreathProduct) + N_gens = [WreathProductElement(n, one(G.P), G) for n in gens(G.N)] + P_gens = [WreathProductElement(one(G.N), p, G) for p in gens(G.P)] + return [N_gens; P_gens] +end + +Base.isfinite(G::WreathProduct) = isfinite(G.N) && isfinite(G.P) + +function Base.rand( + rng::Random.AbstractRNG, + rs::Random.SamplerTrivial{<:WreathProduct}, +) + + G = rs[] + return WreathProductElement(rand(rng, G.N), rand(rng, G.P), G) +end + +GroupsCore.parent(g::WreathProductElement) = g.parent + +Base.:(==)(g::WreathProductElement, h::WreathProductElement) = + parent(g) === parent(h) && g.n == h.n && g.p == h.p + +Base.hash(g::WreathProductElement, h::UInt) = + hash(g.n, hash(g.p, hash(g.parent, h))) + +function Base.deepcopy_internal(g::WreathProductElement, stackdict::IdDict) + return WreathProductElement( + Base.deepcopy_internal(g.n, stackdict), + Base.deepcopy_internal(g.p, stackdict), + parent(g), + ) +end + +_act(p::AbstractPerm, n::DirectPowerElement) = + DirectPowerElement(n.elts^p, parent(n)) + +function Base.inv(g::WreathProductElement) + pinv = inv(g.p) + return WreathProductElement(_act(pinv, inv(g.n)), pinv, parent(g)) +end + +function Base.:(*)(g::WreathProductElement, h::WreathProductElement) + @assert parent(g) === parent(h) + return WreathProductElement(g.n * _act(g.p, h.n), g.p * h.p, parent(g)) +end + +Base.isone(g::WreathProductElement) = isone(g.n) && isone(g.p) + +Base.show(io::IO, G::WreathProduct) = + print(io, "Wreath product of $(G.N.group) by $(G.P)") +Base.show(io::IO, g::WreathProductElement) = print(io, "( $(g.n)≀$(g.p) )") + +Base.copy(g::WreathProductElement) = WreathProductElement(g.n, g.p, parent(g)) diff --git a/src/matrix_groups/MatrixGroups.jl b/src/matrix_groups/MatrixGroups.jl index 4a5bb93..80ab1db 100644 --- a/src/matrix_groups/MatrixGroups.jl +++ b/src/matrix_groups/MatrixGroups.jl @@ -1,11 +1,13 @@ module MatrixGroups +using StaticArrays + using GroupsCore using Groups using KnuthBendix -using LinearAlgebra # Identity matrix -using Random # GroupsCore rand +import LinearAlgebra # Identity matrix +import Random # GroupsCore rand export SpecialLinearGroup, SymplecticGroup diff --git a/src/matrix_groups/abstract.jl b/src/matrix_groups/abstract.jl index fd70c41..5e19c86 100644 --- a/src/matrix_groups/abstract.jl +++ b/src/matrix_groups/abstract.jl @@ -1,7 +1,8 @@ abstract type MatrixGroup{N, T} <: Groups.AbstractFPGroup end const MatrixGroupElement{N, T} = Groups.AbstractFPGroupElement{<:MatrixGroup{N, T}} -Base.isone(g::MatrixGroupElement{N, T}) where {N, T} = matrix_repr(g) == I +Base.isone(g::MatrixGroupElement{N, T}) where {N, T} = + isone(word(g)) || matrix_repr(g) == LinearAlgebra.I function Base.:(==)(m1::M1, m2::M2) where {M1<:MatrixGroupElement, M2<:MatrixGroupElement} parent(m1) === parent(m2) || return false @@ -26,7 +27,9 @@ Base.getindex(sl::MatrixGroupElement, i, j) = matrix_repr(sl)[i,j] # Base.iterate(sl::MatrixGroupElement, state) = iterate(sl.elts, state) function matrix_repr(m::MatrixGroupElement{N, T}) where {N, T} - isempty(word(m)) && return StaticArrays.SMatrix{N, N, T}(I) + if isone(word(m)) + return StaticArrays.SMatrix{N, N, T}(LinearAlgebra.I) + end A = alphabet(parent(m)) return prod(matrix_repr(A[l]) for l in word(m)) end diff --git a/src/matrix_groups/eltary_matrices.jl b/src/matrix_groups/eltary_matrices.jl index f78fdff..947faca 100644 --- a/src/matrix_groups/eltary_matrices.jl +++ b/src/matrix_groups/eltary_matrices.jl @@ -1,6 +1,3 @@ -using Groups -using StaticArrays - struct ElementaryMatrix{N, T} <: Groups.GSymbol i::Int j::Int @@ -24,7 +21,7 @@ Base.inv(e::ElementaryMatrix{N}) where N = ElementaryMatrix{N}(e.i, e.j, -e.val) function matrix_repr(e::ElementaryMatrix{N, T}) where {N, T} - m = StaticArrays.MMatrix{N, N, T}(I) + m = StaticArrays.MMatrix{N, N, T}(LinearAlgebra.I) m[e.i, e.j] = e.val x = StaticArrays.SMatrix{N, N}(m) return x diff --git a/src/matrix_groups/eltary_symplectic.jl b/src/matrix_groups/eltary_symplectic.jl index 413fcea..dba3b43 100644 --- a/src/matrix_groups/eltary_symplectic.jl +++ b/src/matrix_groups/eltary_symplectic.jl @@ -1,6 +1,3 @@ -using Groups -using StaticArrays - struct ElementarySymplectic{N, T} <: Groups.GSymbol symbol::Symbol i::Int @@ -69,7 +66,7 @@ Base.inv(s::ElementarySymplectic{N}) where N = function matrix_repr(s::ElementarySymplectic{N, T}) where {N, T} @assert iseven(N) n = div(N, 2) - m = StaticArrays.MMatrix{N, N, T}(I) + m = StaticArrays.MMatrix{N, N, T}(LinearAlgebra.I) i,j = _ind(s) m[i,j] = s.val if s.symbol === :A diff --git a/test/group_constructions.jl b/test/group_constructions.jl new file mode 100644 index 0000000..09edc11 --- /dev/null +++ b/test/group_constructions.jl @@ -0,0 +1,43 @@ +@testset "GroupConstructions" begin + + @testset "DirectProduct" begin + GH = + let G = PermutationGroups.SymmetricGroup(3), + H = PermutationGroups.SymmetricGroup(4) + + Groups.Constructions.DirectProduct(G, H) + end + test_Group_interface(GH) + test_GroupElement_interface(rand(GH, 2)...) + + @test collect(GH) isa Array{eltype(GH), 2} + @test contains(sprint(print, GH), "Direct product") + @test sprint(print, rand(GH)) isa String + end + + @testset "DirectPower" begin + GGG = Groups.Constructions.DirectPower{3}( + PermutationGroups.SymmetricGroup(3), + ) + test_Group_interface(GGG) + test_GroupElement_interface(rand(GGG, 2)...) + + @test collect(GGG) isa Array{eltype(GGG), 3} + @test contains(sprint(print, GGG), "Direct 3-rd power") + @test sprint(print, rand(GGG)) isa String + end + @testset "WreathProduct" begin + W = + let G = PermutationGroups.SymmetricGroup(2), + P = PermutationGroups.SymmetricGroup(4) + + Groups.Constructions.WreathProduct(G, P) + end + test_Group_interface(W) + test_GroupElement_interface(rand(W, 2)...) + + @test collect(W) isa Array{eltype(W), 2} + @test contains(sprint(print, W), "Wreath product") + @test sprint(print, rand(W)) isa String + end +end diff --git a/test/runtests.jl b/test/runtests.jl index b731e8f..409f204 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -33,6 +33,8 @@ include(joinpath(pathof(GroupsCore), "..", "..", "test", "conformance_test.jl")) include("AutSigma_41.jl") include("AutSigma3.jl") + include("group_constructions.jl") + # if !haskey(ENV, "CI") # include("benchmarks.jl") # end