Merge pull request #22 from kalmarek/enh/MatrixGroups

Enh/matrix groups
This commit is contained in:
Marek Kaluba 2022-04-03 23:16:41 +02:00 committed by GitHub
commit 2e544d623f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1109 additions and 61 deletions

View File

@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
version:
- '1.3'
- '1.6'
- '1'
- 'nightly'
os:

View File

@ -1,29 +1,31 @@
name = "Groups"
uuid = "5d8bd718-bd84-11e8-3b40-ad14f4a32557"
authors = ["Marek Kaluba <kalmar@amu.edu.pl>"]
version = "0.7.2"
version = "0.7.3"
[deps]
Folds = "41a02a25-b8f0-4f67-bc48-60067656b558"
GroupsCore = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120"
KnuthBendix = "c2604015-7b3d-4a30-8a26-9074551ec60a"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ThreadsX = "ac1d9e8a-700a-412c-b207-f0111f4b6c0d"
PermutationGroups = "8bc5a954-2dfc-11e9-10e6-cd969bffa420"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
[compat]
AbstractAlgebra = "0.22"
Folds = "0.2.7"
GroupsCore = "0.4"
KnuthBendix = "0.3"
OrderedCollections = "1"
PermutationGroups = "0.3"
ThreadsX = "0.1"
julia = "1.3"
StaticArrays = "1"
julia = "1.6"
[extras]
AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d"
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
PermutationGroups = "8bc5a954-2dfc-11e9-10e6-cd969bffa420"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets]
test = ["Test", "BenchmarkTools", "AbstractAlgebra", "PermutationGroups"]
test = ["Test", "BenchmarkTools"]

View File

@ -1,24 +1,40 @@
module Groups
import Folds
import Logging
using GroupsCore
using ThreadsX
import KnuthBendix
import KnuthBendix: AbstractWord, Alphabet, Word
import KnuthBendix: alphabet
import Random
import GroupsCore.Random
import OrderedCollections: OrderedSet
export Alphabet, AutomorphismGroup, FreeGroup, FreeGroup, FPGroup, FPGroupElement, SpecialAutomorphismGroup
import KnuthBendix
import KnuthBendix: AbstractWord, Alphabet, Word
import KnuthBendix: alphabet
export MatrixGroups
export Alphabet, AutomorphismGroup, FreeGroup, FreeGroup, FPGroup, FPGroupElement, SpecialAutomorphismGroup, Homomorphism
export alphabet, evaluate, word, gens
# general constructions
include(joinpath("constructions", "constructions.jl"))
import .Constructions
include("types.jl")
include("hashing.jl")
include("normalform.jl")
include("autgroups.jl")
include("homomorphisms.jl")
include("groups/sautFn.jl")
include("groups/mcg.jl")
include("aut_groups/sautFn.jl")
include("aut_groups/mcg.jl")
include("matrix_groups/MatrixGroups.jl")
using .MatrixGroups
include("abelianize.jl")
include("wl_ball.jl")
end # of module Groups

69
src/abelianize.jl Normal file
View File

@ -0,0 +1,69 @@
function _abelianize(
i::Integer,
source::AutomorphismGroup{<:FreeGroup},
target::MatrixGroups.SpecialLinearGroup{N, T}) where {N, T}
n = ngens(object(source))
@assert n == N
aut = alphabet(source)[i]
if aut isa Transvection
# we change (i,j) to (j, i) to be consistent with the action:
# Automorphisms act on the right which corresponds to action on
# the columns in the matrix case
eij = MatrixGroups.ElementaryMatrix{N}(
aut.j,
aut.i,
ifelse(aut.inv, -one(T), one(T))
)
k = alphabet(target)[eij]
return word_type(target)([k])
else
throw("unexpected automorphism symbol: $(typeof(aut))")
end
end
function _abelianize(
i::Integer,
source::AutomorphismGroup{<:Groups.SurfaceGroup},
target::MatrixGroups.SpecialLinearGroup{N, T}) where {N, T}
n = ngens(Groups.object(source))
@assert n == N
g = alphabet(source)[i].autFn_word
result = one(target)
for l in word(g)
append!(word(result), _abelianize(l, parent(g), target))
end
return word(result)
end
function Groups._abelianize(
i::Integer,
source::AutomorphismGroup{<:Groups.SurfaceGroup},
target::MatrixGroups.SymplecticGroup{N, T}
) where {N, T}
@assert iseven(N)
As = alphabet(source)
At = alphabet(target)
SlN = let genus = Groups.genus(Groups.object(source))
@assert 2genus == N
MatrixGroups.SpecialLinearGroup{2genus}(T)
end
ab = Groups.Homomorphism(Groups._abelianize, source, SlN, check=false)
matrix_spn_map = let S = gens(target)
Dict(MatrixGroups.matrix_repr(g)=> word(g) for g in union(S, inv.(S)))
end
# renumeration:
# (f1, f2, f3, f4, f5, f6) = (a₁, a₂, a₃, b₁, b₂, b₃) →
# → (b₃, a₃, b₂, a₂, b₁, a₁)
# hence p = [6, 4, 2, 5, 3, 1]
p = [reverse(2:2:N); reverse(1:2:N)]
g = source([i])
Mg = MatrixGroups.matrix_repr(ab(g))[p,p]
return matrix_spn_map[Mg]
end

View File

@ -10,7 +10,10 @@ function SpecialAutomorphismGroup(F::FreeGroup; ordering = KnuthBendix.LenLex, k
maxrules = 1000*n
rws = KnuthBendix.RewritingSystem(rels, ordering(A))
KnuthBendix.knuthbendix!(rws; maxrules=maxrules, kwargs...)
Logging.with_logger(Logging.NullLogger()) do
# the rws is not confluent, let's suppress warning about it
KnuthBendix.knuthbendix!(rws; maxrules=maxrules, kwargs...)
end
return AutomorphismGroup(F, S, rws, ntuple(i -> gens(F, i), n))
end

View File

@ -0,0 +1,10 @@
module Constructions
using GroupsCore
import GroupsCore.Random
include("direct_product.jl")
include("direct_power.jl")
include("wreath_product.jl")
end # of module Constructions

View File

@ -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, ", "), " )")

View File

@ -0,0 +1,102 @@
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, ",")) )")

View File

@ -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))

114
src/homomorphisms.jl Normal file
View File

@ -0,0 +1,114 @@
"""
Homomorphism(f, G::AbstractFPGroup, H::AbstractFPGroup[, check=true])
Struct representing homomorphism map from `G` to `H` given by map `f`.
To define `h = Homomorphism(f, G, H)` function (or just callable) `f` must
implement method `f(i::Integer, source, target)::AbstractWord` with the
following meaning. Suppose that word `w = Word([i])` consists of a single
letter in the `alphabet` of `source` (usually it means that in `G` it
represents a generator or its inverse). Then `f(i, G, H)` must return the
**word** representing the image in `H` of `G(w)` under the homomorphism.
In more mathematical terms it means that if `h(G(w)) == h`, then
`f(i, G, H) == word(h)`.
Images of both `AbstractWord`s and elements of `G` can be obtained by simply
calling `h(w)`, or `h(g)`.
If `check=true` then the correctness of the definition of `h` will be performed
when creating the homomorphism.
!!! note
`f(i, G, H)` must be implemented for all letters in the alphabet of `G`,
not only for those `i` which represent `gens(G)`. Function `f` will be
evaluated exactly once per letter of `alphabet(G)` and the results will be
cached.
# Examples
```julia
julia> F₂ = FreeGroup(2)
free group on 2 generators
julia> g,h = gens(F₂)
2-element Vector{FPGroupElement{FreeGroup{Symbol, KnuthBendix.LenLex{Symbol}}, }}:
f1
f2
julia> ℤ² = FPGroup(F₂, [g*h => h*g])
Finitely presented group generated by:
{ f1 f2 },
subject to relations:
f1*f2 => f2*f1
julia> hom = Groups.Homomorphism(
(i, G, H) -> Groups.word_type(H)([i]),
F₂,
ℤ²
)
Homomorphism
from : free group on 2 generators
to : f1 f2 |
f1*f2 => f2*f1
julia> hom(g*h*inv(g))
f2
julia> hom(g*h*inv(g)) == hom(h)
true
```
"""
struct Homomorphism{Gr1, Gr2, I, W}
gens_images::Dict{I, W}
source::Gr1
target::Gr2
function Homomorphism(
f,
source::AbstractFPGroup,
target::AbstractFPGroup;
check=true
)
A = alphabet(source)
dct = Dict(i=>convert(word_type(target), f(i, source, target))
for i in 1:length(A))
I = eltype(word_type(source))
W = word_type(target)
hom = new{typeof(source), typeof(target), I, W}(dct, source, target)
if check
@assert hom(one(source)) == one(target)
for x in gens(source)
@assert hom(x^-1) == hom(x)^-1
for y in gens(source)
@assert hom(x*y) == hom(x)*hom(y)
@assert hom(x*y)^-1 == hom(y^-1)*hom(x^-1)
end
end
for (lhs, rhs) in relations(source)
relator = lhs*inv(alphabet(source), rhs)
im_r = hom.target(hom(relator))
@assert isone(im_r) "Map does not define a homomorphism: h($relator) = $(im_r)$(one(target))."
end
end
return hom
end
end
function (h::Homomorphism)(w::AbstractWord)
result = one(word_type(h.target)) # Word
for l in w
append!(result, h.gens_images[l])
end
return result
end
function (h::Homomorphism)(g::AbstractFPGroupElement)
@assert parent(g) === h.source
w = h(word(g))
return h.target(w)
end
Base.show(io::IO, h::Homomorphism) = print(io, "Homomorphism\n from : $(h.source)\n to : $(h.target)")

View File

@ -0,0 +1,19 @@
module MatrixGroups
import LinearAlgebra # Identity matrix
using StaticArrays
using GroupsCore
import GroupsCore.Random # GroupsCore rand
using ..Groups
using Groups.KnuthBendix
export SpecialLinearGroup, SymplecticGroup
include("abstract.jl")
include("SLn.jl")
include("Spn.jl")
end # module

42
src/matrix_groups/SLn.jl Normal file
View File

@ -0,0 +1,42 @@
include("eltary_matrices.jl")
struct SpecialLinearGroup{N, T, R, A, S} <: MatrixGroup{N,T}
base_ring::R
alphabet::A
gens::S
function SpecialLinearGroup{N}(base_ring) where N
S = [ElementaryMatrix{N}(i,j, one(base_ring)) for i in 1:N for j in 1:N if i≠j]
alphabet = Alphabet(S)
return new{
N,
eltype(base_ring),
typeof(base_ring),
typeof(alphabet),
typeof(S)
}(base_ring, alphabet, S)
end
end
GroupsCore.ngens(SL::SpecialLinearGroup{N}) where N = N^2 - N
Base.show(io::IO, SL::SpecialLinearGroup{N, T}) where {N, T} =
print(io, "special linear group of $N×$N matrices over $T")
function Base.show(
io::IO,
::MIME"text/plain",
sl::Groups.AbstractFPGroupElement{<:SpecialLinearGroup{N}}
) where N
Groups.normalform!(sl)
print(io, "SL{$N,$(eltype(sl))} matrix: ")
KnuthBendix.print_repr(io, word(sl), alphabet(sl))
println(io)
Base.print_array(io, matrix_repr(sl))
end
Base.show(io::IO, sl::Groups.AbstractFPGroupElement{<:SpecialLinearGroup}) =
KnuthBendix.print_repr(io, word(sl), alphabet(sl))

70
src/matrix_groups/Spn.jl Normal file
View File

@ -0,0 +1,70 @@
include("eltary_symplectic.jl")
struct SymplecticGroup{N, T, R, A, S} <: MatrixGroup{N,T}
base_ring::R
alphabet::A
gens::S
function SymplecticGroup{N}(base_ring) where N
S = symplectic_gens(N, eltype(base_ring))
alphabet = Alphabet(S)
return new{
N,
eltype(base_ring),
typeof(base_ring),
typeof(alphabet),
typeof(S)
}(base_ring, alphabet, S)
end
end
GroupsCore.ngens(Sp::SymplecticGroup) = length(Sp.gens)
Base.show(io::IO, ::SymplecticGroup{N}) where N = print(io, "group of $N×$N symplectic matrices")
function Base.show(
io::IO,
::MIME"text/plain",
sp::Groups.AbstractFPGroupElement{<:SymplecticGroup{N}}
) where {N}
Groups.normalform!(sp)
print(io, "$N×$N symplectic matrix: ")
KnuthBendix.print_repr(io, word(sp), alphabet(sp))
println(io)
Base.print_array(io, matrix_repr(sp))
end
_offdiag_idcs(n) = ((i,j) for i in 1:n for j in 1:n if i j)
function symplectic_gens(N, T=Int8)
iseven(N) || throw(ArgumentError("N needs to be even!"))
n = N÷2
a_ijs = [ElementarySymplectic{N}(:A, i,j, one(T)) for (i,j) in _offdiag_idcs(n)]
b_is = [ElementarySymplectic{N}(:B, n+i,i, one(T)) for i in 1:n]
c_ijs = [ElementarySymplectic{N}(:B, n+i,j, one(T)) for (i,j) in _offdiag_idcs(n)]
S = [a_ijs; b_is; c_ijs]
S = [S; transpose.(S)]
return unique(S)
end
function _std_symplectic_form(m::AbstractMatrix)
r,c = size(m)
r == c || return false
iseven(r) || return false
n = r÷2
𝕆 = zeros(eltype(m), n, n)
𝕀 = one(eltype(m))*LinearAlgebra.I
Ω = [𝕆 -𝕀
𝕀 𝕆]
return Ω
end
function issymplectic(mat::M, Ω = _std_symplectic_form(mat)) where M <: AbstractMatrix
return Ω == transpose(mat) * Ω * mat
end

View File

@ -0,0 +1,40 @@
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} =
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
word(m1) == word(m2) && return true
return matrix_repr(m1) == matrix_repr(m2)
end
Base.size(m::MatrixGroupElement{N}) where N = (N, N)
Base.eltype(m::MatrixGroupElement{N, T}) where {N, T} = T
# three structural assumptions about matrix groups
Groups.word(sl::MatrixGroupElement) = sl.word
Base.parent(sl::MatrixGroupElement) = sl.parent
Groups.alphabet(M::MatrixGroup) = M.alphabet
Groups.rewriting(M::MatrixGroup) = alphabet(M)
Base.hash(sl::MatrixGroupElement, h::UInt) =
hash(matrix_repr(sl), hash(parent(sl), h))
function matrix_repr(m::MatrixGroupElement{N, T}) where {N, T}
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
function Base.rand(
rng::Random.AbstractRNG,
rs::Random.SamplerTrivial{<:MatrixGroup},
)
Mgroup = rs[]
S = gens(Mgroup)
return prod(g -> rand(Bool) ? g : inv(g), rand(S, rand(1:30)))
end

View File

@ -0,0 +1,28 @@
struct ElementaryMatrix{N, T} <: Groups.GSymbol
i::Int
j::Int
val::T
ElementaryMatrix{N}(i, j, val=1) where N =
(@assert i≠j; new{N, typeof(val)}(i, j, val))
end
function Base.show(io::IO, e::ElementaryMatrix)
print(io, 'E', Groups.subscriptify(e.i), Groups.subscriptify(e.j))
!isone(e.val) && print(io, "^$(e.val)")
end
Base.:(==)(e::ElementaryMatrix{N}, f::ElementaryMatrix{N}) where N =
e.i == f.i && e.j == f.j && e.val == f.val
Base.hash(e::ElementaryMatrix, h::UInt) =
hash(typeof(e), hash((e.i, e.j, e.val), h))
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}(LinearAlgebra.I)
m[e.i, e.j] = e.val
x = StaticArrays.SMatrix{N, N}(m)
return x
end

View File

@ -0,0 +1,76 @@
struct ElementarySymplectic{N, T} <: Groups.GSymbol
symbol::Symbol
i::Int
j::Int
val::T
function ElementarySymplectic{N}(s::Symbol, i::Integer, j::Integer, val=1) where N
@assert s (:A, :B)
@assert iseven(N)
n = N÷2
if s === :A
@assert 1 i n && 1 j n && i j
elseif s === :B
@assert xor(1 i n, 1 j n) && xor(n < i N, n < j N)
end
return new{N, typeof(val)}(s, i, j, val)
end
end
function Base.show(io::IO, s::ElementarySymplectic)
i, j = Groups.subscriptify(s.i), Groups.subscriptify(s.j)
print(io, s.symbol, i, j)
!isone(s.val) && print(io, "^$(s.val)")
end
_ind(s::ElementarySymplectic{N}) where N = (s.i, s.j)
_local_ind(N_half::Integer, i::Integer) = ifelse(i<=N_half, i, i-N_half)
function _dual_ind(s::ElementarySymplectic{N}) where N
if s.symbol === :A && return _ind(s)
else#if s.symbol === :B
return _dual_ind(N÷2, s.i, s.j)
end
end
function _dual_ind(N_half, i, j)
@assert i <= N_half < j || j <= N_half < i
if i <= N_half # && j > N_half
i, j = j - N_half, i + N_half
else
i, j = j + N_half, i - N_half
end
return i, j
end
function Base.:(==)(s::ElementarySymplectic{N}, t::ElementarySymplectic{M}) where {N, M}
N == M || return false
s.symbol == t.symbol || return false
s.val == t.val || return false
return _ind(t) == _ind(s) || _ind(t) == _dual_ind(s)
end
Base.hash(s::ElementarySymplectic, h::UInt) =
hash(Set([_ind(s); _dual_ind(s)]), hash(s.symbol, hash(s.val, h)))
LinearAlgebra.transpose(s::ElementarySymplectic{N}) where N =
ElementarySymplectic{N}(s.symbol, s.j, s.i, s.val)
Base.inv(s::ElementarySymplectic{N}) where N =
ElementarySymplectic{N}(s.symbol, s.i, s.j, -s.val)
function matrix_repr(s::ElementarySymplectic{N, T}) where {N, T}
@assert iseven(N)
n = div(N, 2)
m = StaticArrays.MMatrix{N, N, T}(LinearAlgebra.I)
i,j = _ind(s)
m[i,j] = s.val
if s.symbol === :A
m[n+j, n+i] = -s.val
else#if s.symbol === :B
if i > n
m[j+n, i-n] = s.val
else
m[j-n, i+n] = s.val
end
end
return StaticArrays.SMatrix{N, N}(m)
end

View File

@ -8,14 +8,14 @@ radius and multiplication operation to be used.
"""
function wlmetric_ball_serial(S::AbstractVector{T}, center::T=one(first(S)); radius = 2, op = *) where {T}
@assert radius >= 1
old = unique!([center, [center*s for s in S]...])
old = union!([center], [center*s for s in S])
return _wlmetric_ball(S, old, radius, op, collect, unique!)
end
function wlmetric_ball_thr(S::AbstractVector{T}, center::T=one(first(S)); radius = 2, op = *) where {T}
@assert radius >= 1
old = unique!([center, [center*s for s in S]...])
return _wlmetric_ball(S, old, radius, op, ThreadsX.collect, ThreadsX.unique)
old = union!([center], [center*s for s in S])
return _wlmetric_ball(S, old, radius, op, Folds.collect, Folds.unique)
end
function _wlmetric_ball(S, old, radius, op, collect, unique)

View File

@ -96,9 +96,7 @@
@test evaluate(g*h) == evaluate(h*g)
@test (g*h).savedhash == zero(UInt)
if VERSION >= v"1.6.0"
@test sprint(show, typeof(g)) == "Automorphism{FreeGroup{Symbol, KnuthBendix.LenLex{Symbol}}, …}"
end
@test sprint(show, typeof(g)) == "Automorphism{FreeGroup{Symbol, KnuthBendix.LenLex{Symbol}}, …}"
a = g*h
b = h*g
@ -176,12 +174,13 @@
)
end
@testset "GroupsCore conformance" begin
test_Group_interface(A)
g = A(rand(1:length(alphabet(A)), 10))
h = A(rand(1:length(alphabet(A)), 10))
Logging.with_logger(Logging.NullLogger()) do
@testset "GroupsCore conformance" begin
test_Group_interface(A)
g = A(rand(1:length(alphabet(A)), 10))
h = A(rand(1:length(alphabet(A)), 10))
test_GroupElement_interface(g, h)
test_GroupElement_interface(g, h)
end
end
end

View File

@ -121,15 +121,22 @@ using Groups.KnuthBendix
u = (a6 * a5)^-1 * b1 * (a6 * a5)
x = (a6 * a5 * a4 * a3 * a2 * u * a1^-1 * a2^-1 * a3^-1 * a4^-1) # yet another auxillary
# x = (a4^-1*a3^-1*a2^-1*a1^-1*u*a2*a3*a4*a5*a6)
@time evaluate(x)
b3 = x * a0 * x^-1
b3im = @time evaluate(b3)
b3cim = @time let g = b3
b3im = evaluate(b3)
b3cim = let g = b3
f = Groups.compiled(g)
f(Groups.domain(g))
end
@test b3im == b3cim
@test a0 * b2 * b1 == a1 * a3 * a5 * b3
@time evaluate(x)
let g = b3
f = Groups.compiled(g)
f(Groups.domain(g))
@time f(Groups.domain(g))
end
end
end

View File

@ -2,7 +2,6 @@ using BenchmarkTools
using Test
using Groups
using Groups.New
function wl_ball(F; radius::Integer)
g, state = iterate(F)
@ -47,7 +46,7 @@ end
@testset "iteration: SAut(F_n)" begin
R = 4
FN = FreeGroup(N)
SAutFN = New.SpecialAutomorphismGroup(FN)
SAutFN = SpecialAutomorphismGroup(FN)
let G = SAutFN
S = unique([gens(G); inv.(gens(G))])

View File

@ -54,9 +54,14 @@
Groups.normalform!(h)
@test h == H([5])
@testset "GroupsCore conformance: H" begin
test_Group_interface(H)
test_GroupElement_interface(rand(H, 2)...)
end
@test_logs (:warn, "using generic isfiniteorder(::AbstractFPGroupElement): the returned `false` might be wrong") isfiniteorder(h)
@test_logs (:warn, "using generic isfinite(::AbstractFPGroup): the returned `false` might be wrong") isfinite(H)
Logging.with_logger(Logging.NullLogger()) do
@testset "GroupsCore conformance: H" begin
test_Group_interface(H)
test_GroupElement_interface(rand(H, 2)...)
end
end
end

View File

@ -37,6 +37,8 @@
@test k == a*b^-1
@time k = test_iteration(F3, 1000)
@time test_iteration(F3, 1000)
@test k == (a^2)*c^2*a^-1
end
@ -67,5 +69,4 @@
test_Group_interface(F3)
test_GroupElement_interface(rand(F3, 2)...)
end
end

View File

@ -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

71
test/homomorphisms.jl Normal file
View File

@ -0,0 +1,71 @@
function test_homomorphism(hom)
F = hom.source
@test isone(hom(one(F)))
@test all(inv(hom(g)) == hom(inv(g)) for g in gens(F))
@test all(isone(hom(g) * hom(inv(g))) for g in gens(F))
@test all(hom(g * h) == hom(g) * hom(h) for g in gens(F) for h in gens(F))
@test all(
hom(inv(g * h)) == inv(hom(g * h)) == hom(inv(h)) * hom(inv(g)) for
g in gens(F) for h in gens(F)
)
end
@testset "Homomorphisms" begin
F₂ = FreeGroup(2)
g,h = gens(F₂)
ℤ² = FPGroup(F₂, [g*h => h*g])
let hom = Groups.Homomorphism((i, G, H) -> Groups.word_type(H)([i]), F₂, ℤ²)
@test hom(word(g)) == word(g)
@test hom(word(g*h*inv(g))) == [1,3,2]
@test hom(g*h*inv(g)) == hom(h)
@test isone(hom(g*h*inv(g)*inv(h)))
@test contains(sprint(print, hom), "Homomorphism")
test_homomorphism(hom)
end
SAutF3 = SpecialAutomorphismGroup(FreeGroup(3))
SL3Z = MatrixGroups.SpecialLinearGroup{3}(Int8)
let hom = Groups.Homomorphism(
Groups._abelianize,
SAutF3,
SL3Z,
)
A = alphabet(SAutF3)
g = SAutF3([A[Groups.ϱ(1,2)]])
h = SAutF3([A[Groups.λ(1,2)]])^-1
@test !isone(g) && !isone(hom(g))
@test !isone(h) && !isone(hom(h))
@test !isone(g*h) && isone(hom(g*h))
test_homomorphism(hom)
end
@testset "Correctness of autπ₁Σ → SpN" begin
GENUS = 3
π₁Σ = Groups.SurfaceGroup(GENUS, 0)
autπ₁Σ = AutomorphismGroup(π₁Σ)
SpN = MatrixGroups.SymplecticGroup{2GENUS}(Int8)
hom = Groups.Homomorphism(
Groups._abelianize,
autπ₁Σ,
SpN,
check = false,
)
test_homomorphism(hom)
end
end

79
test/matrix_groups.jl Normal file
View File

@ -0,0 +1,79 @@
using Groups.MatrixGroups
@testset "Matrix Groups" begin
@testset "SL(n, )" begin
SL3Z = SpecialLinearGroup{3}(Int8)
S = gens(SL3Z); union!(S, inv.(S))
E, sizes = Groups.wlmetric_ball(S, radius=4)
@test sizes == [13, 121, 883, 5455]
E(i,j) = SL3Z([A[MatrixGroups.ElementaryMatrix{3}(i,j, Int8(1))]])
A = alphabet(SL3Z)
w = E(1,2)
r = E(2,3)^-3
s = E(1,3)^2*E(3,2)^-1
S = [w,r,s]; S = unique([S; inv.(S)]);
_, sizes = Groups.wlmetric_ball(S, radius=4);
@test sizes == [7, 33, 141, 561]
_, sizes = Groups.wlmetric_ball_serial(S, radius=4);
@test sizes == [7, 33, 141, 561]
Logging.with_logger(Logging.NullLogger()) do
@testset "GroupsCore conformance" begin
test_Group_interface(SL3Z)
g = SL3Z(rand(1:length(alphabet(SL3Z)), 10))
h = SL3Z(rand(1:length(alphabet(SL3Z)), 10))
test_GroupElement_interface(g, h)
end
end
x = w*inv(w)*r
@test length(word(x)) == 5
@test size(x) == (3,3)
@test eltype(x) == Int8
@test contains(sprint(print, SL3Z), "special linear group of 3×3")
@test contains(sprint(show, MIME"text/plain"(), x), "SL{3,Int8} matrix:")
@test sprint(print, x) isa String
@test length(word(x)) == 3
end
@testset "Sp(6, )" begin
Sp6 = MatrixGroups.SymplecticGroup{6}(Int8)
@testset "GroupsCore conformance" begin
test_Group_interface(Sp6)
g = Sp6(rand(1:length(alphabet(Sp6)), 10))
h = Sp6(rand(1:length(alphabet(Sp6)), 10))
test_GroupElement_interface(g, h)
end
@test contains(sprint(print, Sp6), "group of 6×6 symplectic matrices")
x = gens(Sp6, 1)
x *= inv(x) * gens(Sp6, 2)
@test length(word(x)) == 3
@test size(x) == (6,6)
@test eltype(x) == Int8
@test contains(sprint(show, MIME"text/plain"(), x), "6×6 symplectic matrix:")
@test sprint(print, x) isa String
@test length(word(x)) == 1
for g in gens(Sp6)
@test MatrixGroups.issymplectic(MatrixGroups.matrix_repr(g))
end
end
end

View File

@ -1,6 +1,8 @@
using Test
import AbstractAlgebra
using Groups
using PermutationGroups
import Logging
import KnuthBendix: Word
@ -9,27 +11,30 @@ include(joinpath(pathof(GroupsCore), "..", "..", "test", "conformance_test.jl"))
@testset "Groups" begin
@testset "wlmetric_ball" begin
M = AbstractAlgebra.MatrixAlgebra(AbstractAlgebra.zz, 3)
w = one(M); w[1,2] = 1;
r = one(M); r[2,3] = -3;
s = one(M); s[1,3] = 2; s[3,2] = -1;
_, t = @timed include("free_groups.jl")
@info "free_groups.jl took $(round(t, digits=2))s"
_, t = @timed include("fp_groups.jl")
@info "fp_groups.jl took $(round(t, digits=2))s"
S = [w,r,s]; S = unique([S; inv.(S)]);
_, sizes = Groups.wlmetric_ball(S, radius=4);
@test sizes == [7, 33, 141, 561]
_, sizes = Groups.wlmetric_ball_serial(S, radius=4);
@test sizes == [7, 33, 141, 561]
_, t = @timed include("matrix_groups.jl")
@info "matrix_groups.jl took $(round(t, digits=2))s"
_, t = @timed include("AutFn.jl")
@info "AutFn.jl took $(round(t, digits=2))s"
_, t = @timed include("homomorphisms.jl")
@info "homomorphisms.jl took $(round(t, digits=2))s"
if haskey(ENV, "CI")
_, t = @timed include("AutSigma_41.jl")
@info "AutSigma_41 took $(round(t, digits=2))s"
_, t = @timed include("AutSigma3.jl")
@info "AutSigma3 took $(round(t, digits=2))s"
end
include("free_groups.jl")
include("fp_groups.jl")
include("AutFn.jl")
include("AutSigma_41.jl")
include("AutSigma3.jl")
# if !haskey(ENV, "CI")
# include("benchmarks.jl")
# end
_, t = @timed include("group_constructions.jl")
@info "Constructions took $(round(t, digits=2))s"
end
if !haskey(ENV, "CI")
include("benchmarks.jl")
end