mirror of
https://github.com/kalmarek/Groups.jl.git
synced 2024-10-15 07:20:35 +02:00
Merge pull request #17 from kalmarek/enh/v0.6.0
v0.6.0 built on GroupsCore and Words
This commit is contained in:
commit
73131307bf
2
.github/workflows/runtests.yml
vendored
2
.github/workflows/runtests.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
matrix:
|
||||
version:
|
||||
- '1.3'
|
||||
- '1.5'
|
||||
- '1'
|
||||
- 'nightly'
|
||||
os:
|
||||
- ubuntu-latest
|
||||
|
18
Project.toml
18
Project.toml
@ -1,20 +1,28 @@
|
||||
name = "Groups"
|
||||
uuid = "5d8bd718-bd84-11e8-3b40-ad14f4a32557"
|
||||
authors = ["Marek Kaluba <kalmar@amu.edu.pl>"]
|
||||
version = "0.5.2"
|
||||
version = "0.6.0"
|
||||
|
||||
[deps]
|
||||
AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d"
|
||||
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
||||
GroupsCore = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120"
|
||||
KnuthBendix = "c2604015-7b3d-4a30-8a26-9074551ec60a"
|
||||
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||
ThreadsX = "ac1d9e8a-700a-412c-b207-f0111f4b6c0d"
|
||||
|
||||
[compat]
|
||||
AbstractAlgebra = "^0.10.0"
|
||||
AbstractAlgebra = "0.15, 0.16"
|
||||
GroupsCore = "^0.3"
|
||||
KnuthBendix = "^0.2.1"
|
||||
OrderedCollections = "1"
|
||||
ThreadsX = "^0.1.0"
|
||||
julia = "1.3, 1.4, 1.5"
|
||||
julia = "1.3, 1.4, 1.5, 1.6"
|
||||
|
||||
[extras]
|
||||
AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d"
|
||||
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
|
||||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||
|
||||
[targets]
|
||||
test = ["Test"]
|
||||
test = ["Test", "BenchmarkTools", "AbstractAlgebra"]
|
||||
|
150
README.md
150
README.md
@ -2,6 +2,152 @@
|
||||
[![Build Status](https://travis-ci.org/kalmarek/Groups.jl.svg?branch=master)](https://travis-ci.org/kalmarek/Groups.jl)
|
||||
[![codecov](https://codecov.io/gh/kalmarek/Groups.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/kalmarek/Groups.jl)
|
||||
|
||||
A very rudimentary implementation of finitely-presented groups (syllable representation). Relatively complete are only [automorphism groups of free groups](https://github.com/kalmarek/Groups.jl/blob/master/src/AutGroup.jl) and [wreath products](https://github.com/kalmarek/Groups.jl/blob/master/src/WreathProducts.jl) (which are not finitely-presented, but based on the standard normal form).
|
||||
An implementation of finitely-presented groups together with normalization (using Knuth-Bendix procedure).
|
||||
|
||||
Have a look into `test` directory for eample use.
|
||||
The package implements `AbstractFPGroup` with three concrete types: `FreeGroup`, `FPGroup` and `AutomorphismGroup`. Here's an example usage:
|
||||
|
||||
```julia
|
||||
julia> using Groups, GroupsCore
|
||||
|
||||
julia> A = Alphabet([:a, :A, :b, :B, :c, :C], [2, 1, 4, 3, 6, 5])
|
||||
Alphabet of Symbol:
|
||||
1. :a = (:A)⁻¹
|
||||
2. :A = (:a)⁻¹
|
||||
3. :b = (:B)⁻¹
|
||||
4. :B = (:b)⁻¹
|
||||
5. :c = (:C)⁻¹
|
||||
6. :C = (:c)⁻¹
|
||||
|
||||
julia> F = FreeGroup(A)
|
||||
free group on 3 generators
|
||||
|
||||
julia> a,b,c = gens(F)
|
||||
3-element Vector{FPGroupElement{FreeGroup{Symbol}, KnuthBendix.Word{UInt8}}}:
|
||||
a
|
||||
b
|
||||
c
|
||||
|
||||
julia> a*inv(a)
|
||||
(empty word)
|
||||
|
||||
julia> (a*b)^2
|
||||
a*b*a*b
|
||||
|
||||
julia> commutator(a, b)
|
||||
A*B*a*b
|
||||
|
||||
julia> x = a*b; y = inv(b)*a;
|
||||
|
||||
julia> x*y
|
||||
a^2
|
||||
|
||||
```
|
||||
Let's create a quotient of the free group above:
|
||||
```julia
|
||||
julia> ε = one(F);
|
||||
|
||||
julia> G = FPGroup(F, [a^2 => ε, b^3=> ε, (a*b)^7=>ε, (a*b*a*inv(b))^6 => ε, commutator(a, c) => ε, commutator(b, c) => ε ])
|
||||
┌ Warning: Maximum number of rules (100) reached. The rewriting system may not be confluent.
|
||||
│ You may retry `knuthbendix` with a larger `maxrules` kwarg.
|
||||
└ @ KnuthBendix ~/.julia/packages/KnuthBendix/i93Np/src/kbs.jl:6
|
||||
⟨a, b, c | a^2 => (empty word), b^3 => (empty word), a*b*a*b*a*b*a*b*a*b*a*b*a*b => (empty word), a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B => (empty word), A*C*a*c => (empty word), B*C*b*c => (empty word)⟩
|
||||
|
||||
```
|
||||
As you can see from the warning, the Knuth-Bendix procedure has not completed successfully. This means that we only are able to approximate the word problem in `G`, i.e. if the equality (`==`) of two group elements may return `false` even if group elements are equal. Let us try with a larger maximal number of rules in the underlying rewriting system.
|
||||
|
||||
```julia
|
||||
julia> G = FPGroup(F, [a^2 => ε, b^3=> ε, (a*b)^7=>ε, (a*b*a*inv(b))^6 => ε, commutator(a, c) => ε, commutator(b, c) => ε ], maxrules=500)
|
||||
⟨a, b, c | a^2 => (empty word), b^3 => (empty word), a*b*a*b*a*b*a*b*a*b*a*b*a*b => (empty word), a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B => (empty word), A*C*a*c => (empty word), B*C*b*c => (empty word)⟩
|
||||
|
||||
```
|
||||
This time there was no warning, i.e. Knuth-Bendix completion was successful and we may treat the equality (`==`) as true mathematical equality. Note that `G` is the direct product of `ℤ = ⟨ c ⟩` and a quotient of van Dyck `(2,3,7)`-group. Let's create a random word and reduce it as an element of `G`.
|
||||
```julia
|
||||
julia> using Random; Random.seed!(1); w = Groups.Word(rand(1:length(A), 16))
|
||||
KnuthBendix.Word{UInt16}: 4·6·1·1·1·6·5·1·5·2·3·6·2·4·2·6
|
||||
|
||||
julia> F(w) # freely reduced w
|
||||
B*C*a^4*c*A*b*C*A*B*A*C
|
||||
|
||||
julia> G(w) # w as an element of G
|
||||
B*a*b*a*B*a*C^2
|
||||
|
||||
julia> F(w) # freely reduced w
|
||||
B*C*a^4*c*A*b*C*A*B*A*C
|
||||
|
||||
julia> word(ans) # the underlying word in A
|
||||
KnuthBendix.Word{UInt8}: 4·6·1·1·1·1·5·2·3·6·2·4·2·6
|
||||
|
||||
julia> G(w) # w as an element of G
|
||||
B*a*b*a*B*a*C^2
|
||||
|
||||
julia> word(ans) # the underlying word in A
|
||||
KnuthBendix.Word{UInt8}: 4·1·3·1·4·1·6·6
|
||||
|
||||
```
|
||||
As we can see the underlying words change according to where they are reduced.
|
||||
Note that a word `w` (of type `Word <: AbstractWord`) is just a sequence of numbers -- pointers to letters of an `Alphabet`. Without the alphabet `w` has no meaning.
|
||||
|
||||
### Automorphism Groups
|
||||
|
||||
Relatively complete is the support for the automorphisms of free groups, as given by Gersten presentation:
|
||||
```julia
|
||||
julia> saut = SpecialAutomorphismGroup(F, maxrules=100)
|
||||
┌ Warning: Maximum number of rules (100) reached. The rewriting system may not be confluent.
|
||||
│ You may retry `knuthbendix` with a larger `maxrules` kwarg.
|
||||
└ @ KnuthBendix ~/.julia/packages/KnuthBendix/i93Np/src/kbs.jl:6
|
||||
automorphism group of free group on 3 generators
|
||||
|
||||
julia> S = gens(saut)
|
||||
12-element Vector{Automorphism{FreeGroup{Symbol},…}}:
|
||||
ϱ₁.₂
|
||||
ϱ₁.₃
|
||||
ϱ₂.₁
|
||||
ϱ₂.₃
|
||||
ϱ₃.₁
|
||||
ϱ₃.₂
|
||||
λ₁.₂
|
||||
λ₁.₃
|
||||
λ₂.₁
|
||||
λ₂.₃
|
||||
λ₃.₁
|
||||
λ₃.₂
|
||||
|
||||
julia> x, y, z = S[1], S[12], S[6];
|
||||
|
||||
julia> f = x*y*inv(z)
|
||||
ϱ₁.₂*λ₃.₂*ϱ₃.₂^-1
|
||||
|
||||
julia> g = inv(z)*y*x
|
||||
ϱ₃.₂^-1*ϱ₁.₂*λ₃.₂
|
||||
|
||||
julia> word(f), word(g)
|
||||
(KnuthBendix.Word{UInt8}: 1·12·18, KnuthBendix.Word{UInt8}: 18·1·12)
|
||||
|
||||
```
|
||||
Even though Knuth-Bendix did not finish successfully in automorphism groups we have another ace in our sleeve to solve the word problem: evaluation.
|
||||
Lets have a look at the images of generators under those automorphisms:
|
||||
```julia
|
||||
julia> evaluate(f) # or to be more verbose...
|
||||
(a*b, b, b*c*B)
|
||||
|
||||
julia> Groups.domain(g)
|
||||
(a, b, c)
|
||||
|
||||
julia> Groups.evaluate!(Groups.domain(g), g)
|
||||
(a*b, b, b*c*B)
|
||||
|
||||
```
|
||||
Since these automorphism map the standard generating set to the same new generating set, they should be considered as equal! And indeed they are:
|
||||
```julia
|
||||
julia> f == g
|
||||
true
|
||||
```
|
||||
This is what is happening behind the scenes:
|
||||
1. words are reduced using a rewriting system
|
||||
2. if resulting words are equal `true` is returned
|
||||
3. if they are not equal `Groups.equality_data` is computed for each argument (here: the images of generators) and the result of comparison is returned.
|
||||
|
||||
Moreover we try to amortize the cost of computing those images. That is a hash of `equality_daata` is lazily stored in each group element and used as needed. Essentially only if `true` is returned, but comparison of words returns `false` recomputation of images is needed (to guard against hash collisions).
|
||||
|
||||
----
|
||||
This package was developed for computations in [1712.07167](https://arxiv.org/abs/1712.07167) and in [1812.03456](https://arxiv.org/abs/1812.03456). If you happen to use this package please cite either of them.
|
||||
|
346
src/AutGroup.jl
346
src/AutGroup.jl
@ -1,346 +0,0 @@
|
||||
export Automorphism, AutGroup, Aut, SAut
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# AutSymbol/ AutGroup / Automorphism
|
||||
#
|
||||
|
||||
struct RTransvect
|
||||
i::Int8
|
||||
j::Int8
|
||||
end
|
||||
|
||||
struct LTransvect
|
||||
i::Int8
|
||||
j::Int8
|
||||
end
|
||||
|
||||
struct FlipAut
|
||||
i::Int8
|
||||
end
|
||||
|
||||
struct PermAut
|
||||
perm::Generic.Perm{Int8}
|
||||
end
|
||||
|
||||
struct Identity end
|
||||
|
||||
struct AutSymbol <: GSymbol
|
||||
id::Symbol
|
||||
pow::Int8
|
||||
fn::Union{LTransvect, RTransvect, PermAut, FlipAut, Identity}
|
||||
end
|
||||
|
||||
# taken from ValidatedNumerics, under under the MIT "Expat" License:
|
||||
# https://github.com/JuliaIntervals/ValidatedNumerics.jl/blob/master/LICENSE.md
|
||||
function subscriptify(n::Integer)
|
||||
subscript_0 = Int(0x2080) # Char(0x2080) -> subscript 0
|
||||
return join([Char(subscript_0 + i) for i in reverse(digits(n))], "")
|
||||
end
|
||||
|
||||
function id_autsymbol()
|
||||
return AutSymbol(Symbol("(id)"), 0, Identity())
|
||||
end
|
||||
|
||||
function transvection_R(i::Integer, j::Integer, pow::Integer=1)
|
||||
if 0 < i < 10 && 0 < j < 10
|
||||
id = Symbol(:ϱ, subscriptify(i), subscriptify(j))
|
||||
else
|
||||
id = Symbol(:ϱ, subscriptify(i), "." ,subscriptify(j))
|
||||
end
|
||||
return AutSymbol(id, pow, RTransvect(i, j))
|
||||
end
|
||||
|
||||
function transvection_L(i::Integer, j::Integer, pow::Integer=1)
|
||||
if 0 < i < 10 && 0 < j < 10
|
||||
id = Symbol(:λ, subscriptify(i), subscriptify(j))
|
||||
else
|
||||
id = Symbol(:λ, subscriptify(i), "." ,subscriptify(j))
|
||||
end
|
||||
return AutSymbol(id, pow, LTransvect(i, j))
|
||||
end
|
||||
|
||||
function flip(i::Integer, pow::Integer=1)
|
||||
iseven(pow) && return id_autsymbol()
|
||||
id = Symbol(:ɛ, subscriptify(i))
|
||||
return AutSymbol(id, 1, FlipAut(i))
|
||||
end
|
||||
|
||||
function AutSymbol(p::Generic.Perm, pow::Integer=1)
|
||||
if pow != 1
|
||||
p = p^pow
|
||||
end
|
||||
|
||||
if any(p.d[i] != i for i in eachindex(p.d))
|
||||
id = Symbol(:σ, "₍", join([subscriptify(i) for i in p.d],""), "₎")
|
||||
return AutSymbol(id, 1, PermAut(p))
|
||||
end
|
||||
return id_autsymbol()
|
||||
end
|
||||
|
||||
ϱ(i::Integer, j::Integer, pow::Integer=1) = transvection_R(i, j, pow)
|
||||
λ(i::Integer, j::Integer, pow::Integer=1) = transvection_L(i, j, pow)
|
||||
ε(i::Integer, pow::Integer=1) = flip(i, pow)
|
||||
σ(v::Generic.Perm, pow::Integer=1) = AutSymbol(v, pow)
|
||||
|
||||
function change_pow(s::AutSymbol, n::Integer)
|
||||
iszero(n) && id_autsymbol()
|
||||
|
||||
symbol = s.fn
|
||||
if symbol isa FlipAut
|
||||
return flip(symbol.i, n)
|
||||
elseif symbol isa PermAut
|
||||
return AutSymbol(symbol.perm, n)
|
||||
elseif symbol isa RTransvect
|
||||
return transvection_R(symbol.i, symbol.j, n)
|
||||
elseif symbol isa LTransvect
|
||||
return transvection_L(symbol.i, symbol.j, n)
|
||||
elseif symbol isa Identity
|
||||
return id_autsymbol()
|
||||
else
|
||||
throw(DomainError("Unknown type of AutSymbol: $s"))
|
||||
end
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# AutGroup / Automorphism
|
||||
#
|
||||
|
||||
mutable struct AutGroup{N} <: AbstractFPGroup
|
||||
objectGroup::FreeGroup
|
||||
gens::Vector{AutSymbol}
|
||||
end
|
||||
|
||||
mutable struct Automorphism{N} <: GWord{AutSymbol}
|
||||
symbols::Vector{AutSymbol}
|
||||
modified::Bool
|
||||
savedhash::UInt
|
||||
parent::AutGroup{N}
|
||||
|
||||
function Automorphism{N}(f::Vector{AutSymbol}) where {N}
|
||||
return new{N}(f, true, zero(UInt))
|
||||
end
|
||||
end
|
||||
|
||||
elem_type(::Type{AutGroup{N}}) where N = Automorphism{N}
|
||||
parent_type(::Type{Automorphism{N}}) where N = AutGroup{N}
|
||||
|
||||
function AutGroup(G::FreeGroup; special=false)
|
||||
S = AutSymbol[]
|
||||
n = length(gens(G))
|
||||
n == 0 && return AutGroup{n}(G, S)
|
||||
|
||||
indexing = [[i,j] for i in 1:n for j in 1:n if i≠j]
|
||||
|
||||
rmuls = [ϱ(i,j) for (i,j) in indexing]
|
||||
lmuls = [λ(i,j) for (i,j) in indexing]
|
||||
|
||||
append!(S, [rmuls; lmuls])
|
||||
|
||||
if !special
|
||||
flips = [ε(i) for i in 1:n]
|
||||
syms = [σ(p) for p in SymmetricGroup(Int8(n))][2:end]
|
||||
|
||||
append!(S, [flips; syms])
|
||||
end
|
||||
return AutGroup{n}(G, S)
|
||||
end
|
||||
|
||||
Aut(G::Group) = AutGroup(G)
|
||||
SAut(G::Group) = AutGroup(G, special=true)
|
||||
|
||||
Automorphism{N}(s::AutSymbol) where N = Automorphism{N}(AutSymbol[s])
|
||||
|
||||
function (G::AutGroup{N})(f::AutSymbol) where N
|
||||
g = Automorphism{N}([f])
|
||||
setparent!(g, G)
|
||||
return g
|
||||
end
|
||||
|
||||
(G::AutGroup{N})(g::Automorphism{N}) where N = (setparent!(g, G); g)
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# AutSymbol defining functions && evaluation
|
||||
# NOTE: all automorphisms operate on a tuple of FreeWords INPLACE!
|
||||
#
|
||||
|
||||
function (ϱ::RTransvect)(v, pow::Integer=1)
|
||||
rmul!(v[ϱ.i], v[ϱ.j]^pow)
|
||||
return v
|
||||
end
|
||||
|
||||
function (λ::LTransvect)(v, pow::Integer=1)
|
||||
lmul!(v[λ.i], v[λ.j]^pow)
|
||||
return v
|
||||
end
|
||||
|
||||
function (σ::PermAut)(v, pow::Integer=1)
|
||||
w = deepcopy(v)
|
||||
s = (σ.perm^pow).d
|
||||
@inbounds for k in eachindex(v)
|
||||
v[k].symbols = w[s[k]].symbols
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
function (ɛ::FlipAut)(v, pow::Integer=1)
|
||||
@inbounds if isodd(pow)
|
||||
v[ɛ.i].symbols = inv(v[ɛ.i]).symbols
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
(::Identity)(v, pow::Integer=1) = v
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Functional call overloads for evaluation of AutSymbol and Automorphism
|
||||
#
|
||||
|
||||
(s::AutSymbol)(v::NTuple{N, T}) where {N, T} = s.fn(v, s.pow)::NTuple{N, T}
|
||||
|
||||
function (f::Automorphism{N})(v::NTuple{N, T}) where {N, T}
|
||||
for s in syllables(f)
|
||||
v = s(v)::NTuple{N, T}
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
function domain(G::AutGroup{N}) where N
|
||||
F = G.objectGroup
|
||||
return ntuple(i->F(F.gens[i]), N)
|
||||
end
|
||||
|
||||
evaluate(f::Automorphism) = f(domain(parent(f)))
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# hashing && equality
|
||||
#
|
||||
|
||||
function hash_internal(
|
||||
g::Automorphism,
|
||||
h::UInt = 0x7d28276b01874b19; # hash(Automorphism)
|
||||
# alternatively: 0xcbf29ce484222325 from FNV-1a algorithm
|
||||
images = compute_images(g),
|
||||
prime = 0x00000100000001b3, # prime from FNV-1a algorithm
|
||||
)
|
||||
return foldl((h,x) -> hash(x, h)*prime, images, init = hash(parent(g), h))
|
||||
end
|
||||
|
||||
function compute_images(g::Automorphism)
|
||||
images = evaluate(g)
|
||||
for im in images
|
||||
reduce!(im)
|
||||
end
|
||||
return images
|
||||
end
|
||||
|
||||
function (==)(g::Automorphism{N}, h::Automorphism{N}) where N
|
||||
syllables(g) == syllables(h) && return true
|
||||
img_computed, imh_computed = false, false
|
||||
|
||||
if ismodified(g)
|
||||
img = compute_images(g) # sets modified bit
|
||||
hash(g, images=img)
|
||||
img_computed = true
|
||||
end
|
||||
|
||||
if ismodified(h)
|
||||
imh = compute_images(h) # sets modified bit
|
||||
hash(h, images=imh)
|
||||
imh_computed = true
|
||||
end
|
||||
|
||||
@assert !ismodified(g) && !ismodified(h)
|
||||
# cheap
|
||||
# if hashes differ, images must have differed as well
|
||||
hash(g) != hash(h) && return false
|
||||
|
||||
# hashes equal, hence either equal elements, or a hash conflict
|
||||
begin
|
||||
if !img_computed
|
||||
img_task = Threads.@spawn img = compute_images(g)
|
||||
# img = compute_images(g)
|
||||
end
|
||||
if !imh_computed
|
||||
imh_task = Threads.@spawn imh = compute_images(h)
|
||||
# imh = compute_images(h)
|
||||
end
|
||||
!img_computed && fetch(img_task)
|
||||
!imh_computed && fetch(imh_task)
|
||||
end
|
||||
|
||||
img != imh && @warn "hash collision in == :" g h
|
||||
return img == imh
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# String I/O
|
||||
#
|
||||
|
||||
function show(io::IO, G::AutGroup)
|
||||
print(io, "Automorphism Group of $(G.objectGroup)\n")
|
||||
print(io, "Generated by $(gens(G))")
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Reduction
|
||||
#
|
||||
|
||||
getperm(s::AutSymbol) = s.fn.perm^s.pow
|
||||
|
||||
function simplifyperms!(::Type{Bool}, w::Automorphism{N}) where N
|
||||
reduced = true
|
||||
for i in 1:syllablelength(w)-1
|
||||
s, ns = syllables(w)[i], syllables(w)[i+1]
|
||||
if isone(s)
|
||||
continue
|
||||
elseif s.fn isa PermAut && ns.fn isa PermAut
|
||||
reduced = false
|
||||
setmodified!(w)
|
||||
syllables(w)[i+1] = AutSymbol(getperm(s)*getperm(ns))
|
||||
syllables(w)[i] = change_pow(s, 0)
|
||||
end
|
||||
end
|
||||
filter!(!isone, syllables(w))
|
||||
return reduced
|
||||
end
|
||||
|
||||
function reduce!(w::Automorphism)
|
||||
reduced = false
|
||||
while !reduced
|
||||
reduced = simplifyperms!(Bool, w) && freereduce!(Bool, w)
|
||||
end
|
||||
return w
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Abelianization (natural Representation to GL(N,Z))
|
||||
#
|
||||
|
||||
abelianize(A::Automorphism{N}) where N = image(A, abelianize; n=N)
|
||||
|
||||
# homomorphism definition
|
||||
abelianize(; n::Integer=1) = Matrix{Int}(I, n, n)
|
||||
abelianize(a::AutSymbol; n::Int=1) = abelianize(a.fn, n, a.pow)
|
||||
|
||||
function abelianize(a::Union{RTransvect, LTransvect}, n::Int, pow)
|
||||
x = Matrix{Int}(I, n, n)
|
||||
x[a.i,a.j] = pow
|
||||
return x
|
||||
end
|
||||
|
||||
function abelianize(a::FlipAut, n::Int, pow)
|
||||
x = Matrix{Int}(I, n, n)
|
||||
x[a.i,a.i] = -1
|
||||
return x
|
||||
end
|
||||
|
||||
abelianize(a::PermAut, n::Integer, pow) = Matrix{Int}(I, n, n)[(a.perm^pow).d, :]
|
||||
abelianize(a::Identity, n::Integer, pow) = abelianize(;n=n)
|
@ -1,192 +0,0 @@
|
||||
export DirectPowerGroup, DirectPowerGroupElem
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DirectPowerGroup / DirectPowerGroupElem Constructors
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
DirectPowerGroup(G::Group, n::Int) <: Group
|
||||
Return `n`-fold direct product of `G`. The group operation is
|
||||
`*` distributed component-wise, with component-wise identity as neutral element.
|
||||
"""
|
||||
struct DirectPowerGroup{N, T<:Group} <: Group
|
||||
group::T
|
||||
end
|
||||
|
||||
DirectPowerGroup(G::Gr, N::Int) where Gr<:Group = DirectPowerGroup{N,Gr}(G)
|
||||
|
||||
function DirectPower(G::Group, H::Group)
|
||||
G == H || throw(DomainError(
|
||||
"Direct Powers are defined only for the same groups"))
|
||||
return DirectPowerGroup(G,2)
|
||||
end
|
||||
|
||||
DirectPower(H::Group, G::DirectPowerGroup) = DirectPower(G,H)
|
||||
|
||||
function DirectPower(G::DirectPowerGroup{N}, H::Group) where N
|
||||
G.group == H || throw(DomainError(
|
||||
"Direct Powers are defined only for the same groups"))
|
||||
return DirectPowerGroup(G.group, N+1)
|
||||
end
|
||||
|
||||
struct DirectPowerGroupElem{N, T<:GroupElem} <: GroupElem
|
||||
elts::NTuple{N,T}
|
||||
end
|
||||
|
||||
function DirectPowerGroupElem(v::Vector{GrEl}) where GrEl<:GroupElem
|
||||
return DirectPowerGroupElem(tuple(v...))
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Type and parent object methods
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
elem_type(::Type{DirectPowerGroup{N,T}}) where {N,T} =
|
||||
DirectPowerGroupElem{N, elem_type(T)}
|
||||
|
||||
parent_type(::Type{DirectPowerGroupElem{N,T}}) where {N,T} =
|
||||
DirectPowerGroup{N, parent_type(T)}
|
||||
|
||||
parent(g::DirectPowerGroupElem{N, T}) where {N,T} =
|
||||
DirectPowerGroup(parent(first(g.elts)), N)
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# AbstractVector interface
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
Base.size(g::DirectPowerGroupElem{N}) where N = (N,)
|
||||
Base.IndexStyle(::Type{DirectPowerGroupElem}) = Base.LinearFast()
|
||||
Base.getindex(g::DirectPowerGroupElem, i::Int) = g.elts[i]
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Parent object call overloads
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
(G::DirectPowerGroup)(a::Vector, check::Bool=true)
|
||||
Constructs element of the `n`-fold direct product group `G` by coercing each
|
||||
element of vector `a` to `G.group`. If `check` flag is set to `false` neither
|
||||
check on the correctness nor coercion is performed.
|
||||
"""
|
||||
function (G::DirectPowerGroup{N})(a::Vector, check::Bool=true) where N
|
||||
if check
|
||||
N == length(a) || throw(DomainError(
|
||||
"Can not coerce to DirectPowerGroup: lengths differ"))
|
||||
a = (G.group).(a)
|
||||
end
|
||||
return DirectPowerGroupElem(a)
|
||||
end
|
||||
|
||||
function (G::DirectPowerGroup{N})(a::NTuple{N, GrEl}) where {N, GrEl}
|
||||
return DirectPowerGroupElem(G.group.(a))
|
||||
end
|
||||
|
||||
(G::DirectPowerGroup{N})(a::Vararg{GrEl, N}) where {N, GrEl} = DirectPowerGroupElem(G.group.(a))
|
||||
|
||||
function Base.one(G::DirectPowerGroup{N}) where N
|
||||
return DirectPowerGroupElem(ntuple(i->one(G.group),N))
|
||||
end
|
||||
|
||||
(G::DirectPowerGroup)(g::DirectPowerGroupElem) = G(g.elts)
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Basic manipulation
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
function hash(G::DirectPowerGroup{N}, h::UInt) where N
|
||||
return hash(G.group, hash(N, hash(DirectPowerGroup,h)))
|
||||
end
|
||||
|
||||
function hash(g::DirectPowerGroupElem, h::UInt)
|
||||
return hash(g.elts, hash(DirectPowerGroupElem, h))
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# String I/O
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
function show(io::IO, G::DirectPowerGroup{N}) where N
|
||||
print(io, "$(N)-fold direct product of $(G.group)")
|
||||
end
|
||||
|
||||
function show(io::IO, g::DirectPowerGroupElem)
|
||||
print(io, "[$(join(g.elts,","))]")
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Comparison
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
function (==)(G::DirectPowerGroup{N}, H::DirectPowerGroup{M}) where {N,M}
|
||||
N == M || return false
|
||||
G.group == H.group || return false
|
||||
return true
|
||||
end
|
||||
|
||||
(==)(g::DirectPowerGroupElem, h::DirectPowerGroupElem) = g.elts == h.elts
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Group operations
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
function *(g::DirectPowerGroupElem{N}, h::DirectPowerGroupElem{N}, check::Bool=true) where N
|
||||
if check
|
||||
parent(g) == parent(h) || throw(DomainError(
|
||||
"Can not multiply elements of different groups!"))
|
||||
end
|
||||
return DirectPowerGroupElem(ntuple(i-> g.elts[i]*h.elts[i], N))
|
||||
end
|
||||
|
||||
^(g::DirectPowerGroupElem, n::Integer) = Base.power_by_squaring(g, n)
|
||||
|
||||
function inv(g::DirectPowerGroupElem{N}) where {N}
|
||||
return DirectPowerGroupElem(ntuple(i-> inv(g.elts[i]), N))
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Misc
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
order(G::DirectPowerGroup{N}) where N = order(G.group)^N
|
||||
|
||||
function iterate(G::DirectPowerGroup{N}) where N
|
||||
elts = collect(G.group)
|
||||
|
||||
indices = CartesianIndices(ntuple(i -> order(G.group), N))
|
||||
idx, s = iterate(indices)
|
||||
g = DirectPowerGroupElem(ntuple(i -> elts[idx[i]], N))
|
||||
return g, (elts, indices, s)
|
||||
end
|
||||
|
||||
function iterate(G::DirectPowerGroup{N}, state) where N
|
||||
elts, indices, s = state
|
||||
res = iterate(indices, s)
|
||||
if res == nothing
|
||||
return nothing
|
||||
else
|
||||
idx, s = res
|
||||
end
|
||||
g = DirectPowerGroupElem(ntuple(i -> elts[idx[i]], N))
|
||||
return g, (elts, indices, s)
|
||||
end
|
||||
|
||||
eltype(::Type{DirectPowerGroup{N, G}}) where {N, G} = DirectPowerGroupElem{N, elem_type(G)}
|
||||
Base.length(G::DirectPowerGroup) = order(G)
|
135
src/FPGroups.jl
135
src/FPGroups.jl
@ -1,135 +0,0 @@
|
||||
###############################################################################
|
||||
#
|
||||
# FPSymbol/FPGroupElem/FPGroup definition
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
struct FPSymbol <: GSymbol
|
||||
id::Symbol
|
||||
pow::Int
|
||||
end
|
||||
|
||||
FPGroupElem = GroupWord{FPSymbol}
|
||||
|
||||
mutable struct FPGroup <: AbstractFPGroup
|
||||
gens::Vector{FPSymbol}
|
||||
rels::Dict{FreeGroupElem, FreeGroupElem}
|
||||
|
||||
function FPGroup(gens::Vector{T}, rels::Dict{FreeGroupElem, FreeGroupElem}) where {T<:GSymbol}
|
||||
G = new(gens)
|
||||
G.rels = Dict(G(k) => G(v) for (k,v) in rels)
|
||||
return G
|
||||
end
|
||||
end
|
||||
|
||||
export FPGroupElem, FPGroup
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Type and parent object methods
|
||||
#
|
||||
|
||||
AbstractAlgebra.elem_type(::Type{FPGroup}) = FPGroupElem
|
||||
AbstractAlgebra.parent_type(::Type{FPGroupElem}) = FPGroup
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# FPSymbol constructors
|
||||
#
|
||||
|
||||
FPSymbol(s::Symbol) = FPSymbol(s, 1)
|
||||
FPSymbol(s::String) = FPSymbol(Symbol(s))
|
||||
FPSymbol(s::GSymbol) = FPSymbol(s.id, s.pow)
|
||||
|
||||
FPGroup(n::Int, symbol::String="f") = FPGroup([Symbol(symbol,i) for i in 1:n])
|
||||
FPGroup(a::AbstractVector) = FPGroup([FPSymbol(i) for i in a])
|
||||
FPGroup(gens::Vector{FPSymbol}) = FPGroup(gens, Dict{FreeGroupElem, FreeGroupElem}())
|
||||
|
||||
FPGroup(H::FreeGroup) = FPGroup([FPSymbol(s) for s in H.gens])
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Parent object call overloads
|
||||
#
|
||||
|
||||
function (G::FPGroup)(w::GWord)
|
||||
if isempty(w)
|
||||
return one(G)
|
||||
end
|
||||
|
||||
@boundscheck for s in syllables(w)
|
||||
i = findfirst(g -> g.id == s.id, G.gens)
|
||||
i == 0 && throw(DomainError("Symbol $s does not belong to $G."))
|
||||
s.pow % G.gens[i].pow != 0 && throw(
|
||||
DomainError("Symbol $s doesn't belong to $G."))
|
||||
end
|
||||
|
||||
w = FPGroupElem(FPSymbol.(syllables(w)))
|
||||
setparent!(w, G)
|
||||
return reduce!(w)
|
||||
end
|
||||
|
||||
(G::FPGroup)(s::GSymbol) = G(FPGroupElem(s))
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# String I/O
|
||||
#
|
||||
|
||||
function show(io::IO, G::FPGroup)
|
||||
print(io, "FPgroup on $(length(G.gens)) generators ")
|
||||
strrels = join(G.rels, ", ")
|
||||
if length(strrels) > 200
|
||||
print(io, "⟨ ", join(G.gens, ", "), " | $(length(G.rels)) relation(s) ⟩.")
|
||||
else
|
||||
print(io, "⟨ ", join(G.gens, ", "), " | ", join(G.rels, ", "), " ⟩.")
|
||||
end
|
||||
end
|
||||
|
||||
function reduce!(W::FPGroupElem)
|
||||
reduced = false
|
||||
while !reduced
|
||||
W = replace(W, parent(W).rels)
|
||||
reduced = freereduce!(Bool, W)
|
||||
end
|
||||
return W
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Misc
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
freepreimage(G::FPGroup) = parent(first(keys(G.rels)))
|
||||
freepreimage(g::FPGroupElem) = freepreimage(parent(g))(syllables(g))
|
||||
|
||||
function add_rels!(G::FPGroup, newrels::Dict{FreeGroupElem,FreeGroupElem})
|
||||
for w in keys(newrels)
|
||||
haskey(G.rels, w) && continue
|
||||
G.rels[w] = newrels[w]
|
||||
end
|
||||
return G
|
||||
end
|
||||
|
||||
function Base.:/(G::FPGroup, newrels::Vector{FPGroupElem})
|
||||
for r in newrels
|
||||
parent(r) == G || throw(DomainError(
|
||||
"Can not form quotient group: $r is not an element of $G"))
|
||||
end
|
||||
H = deepcopy(G)
|
||||
F = freepreimage(H)
|
||||
newrels = Dict(freepreimage(r) => one(F) for r in newrels)
|
||||
add_rels!(H, newrels)
|
||||
return H
|
||||
end
|
||||
|
||||
function Base.:/(F::FreeGroup, rels::Vector{FreeGroupElem})
|
||||
for r in rels
|
||||
parent(r) == F || throw(DomainError(
|
||||
"Can not form quotient group: $r is not an element of $F"))
|
||||
end
|
||||
G = FPGroup(FPSymbol.(F.gens))
|
||||
G.rels = Dict(rel => one(F) for rel in unique(rels))
|
||||
return G
|
||||
end
|
@ -1,73 +0,0 @@
|
||||
###############################################################################
|
||||
#
|
||||
# FreeSymbol/FreeGroupElem/FreeGroup definition
|
||||
#
|
||||
|
||||
struct FreeSymbol <: GSymbol
|
||||
id::Symbol
|
||||
pow::Int
|
||||
end
|
||||
|
||||
FreeGroupElem = GroupWord{FreeSymbol}
|
||||
|
||||
mutable struct FreeGroup <: AbstractFPGroup
|
||||
gens::Vector{FreeSymbol}
|
||||
|
||||
function FreeGroup(gens::AbstractVector{T}) where {T<:GSymbol}
|
||||
G = new(gens)
|
||||
G.gens = gens
|
||||
return G
|
||||
end
|
||||
end
|
||||
|
||||
export FreeGroupElem, FreeGroup
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Type and parent object methods
|
||||
#
|
||||
|
||||
AbstractAlgebra.elem_type(::Type{FreeGroup}) = FreeGroupElem
|
||||
AbstractAlgebra.parent_type(::Type{FreeGroupElem}) = FreeGroup
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# FreeSymbol constructors
|
||||
#
|
||||
|
||||
FreeSymbol(s::Symbol) = FreeSymbol(s,1)
|
||||
FreeSymbol(s::AbstractString) = FreeSymbol(Symbol(s))
|
||||
FreeSymbol(s::GSymbol) = FreeSymbol(s.id, s.pow)
|
||||
|
||||
FreeGroup(n::Int, symbol::String="f") = FreeGroup([Symbol(symbol,i) for i in 1:n])
|
||||
FreeGroup(a::AbstractVector) = FreeGroup(FreeSymbol.(a))
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Parent object call overloads
|
||||
#
|
||||
|
||||
function (G::FreeGroup)(w::GroupWord{FreeSymbol})
|
||||
for s in syllables(w)
|
||||
i = findfirst(g -> g.id == s.id, G.gens)
|
||||
isnothing(i) && throw(DomainError(
|
||||
"Symbol $s does not belong to $G."))
|
||||
s.pow % G.gens[i].pow == 0 || throw(DomainError(
|
||||
"Symbol $s doesn't belong to $G."))
|
||||
end
|
||||
setparent!(w, G)
|
||||
return reduce!(w)
|
||||
end
|
||||
|
||||
(G::FreeGroup)(s::GSymbol) = G(FreeGroupElem(s))
|
||||
(G::FreeGroup)(v::AbstractVector{<:GSymbol}) = G(FreeGroupElem(FreeSymbol.(v)))
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# String I/O
|
||||
#
|
||||
|
||||
function show(io::IO, G::FreeGroup)
|
||||
print(io, "Free group on $(length(G.gens)) generators: ")
|
||||
join(io, G.gens, ", ")
|
||||
end
|
165
src/Groups.jl
165
src/Groups.jl
@ -1,162 +1,23 @@
|
||||
module Groups
|
||||
|
||||
using AbstractAlgebra
|
||||
import AbstractAlgebra: Group, GroupElem, Ring
|
||||
import AbstractAlgebra: parent, parent_type, elem_type
|
||||
import AbstractAlgebra: order, gens, matrix_repr
|
||||
|
||||
import Base: length, ==, hash, show, convert, eltype, iterate
|
||||
import Base: inv, reduce, *, ^, power_by_squaring
|
||||
import Base: findfirst, findnext, findlast, findprev, replace
|
||||
import Base: deepcopy_internal
|
||||
|
||||
using LinearAlgebra
|
||||
using GroupsCore
|
||||
using ThreadsX
|
||||
import KnuthBendix
|
||||
import KnuthBendix: AbstractWord, Alphabet, Word
|
||||
import KnuthBendix: alphabet
|
||||
import Random
|
||||
|
||||
export gens, FreeGroup, Aut, SAut
|
||||
import OrderedCollections: OrderedSet
|
||||
|
||||
export Alphabet, AutomorphismGroup, FreeGroup, FreeGroup, FPGroup, FPGroupElement, SpecialAutomorphismGroup
|
||||
export alphabet, evaluate, word
|
||||
|
||||
include("types.jl")
|
||||
|
||||
include("FreeGroup.jl")
|
||||
include("FPGroups.jl")
|
||||
include("AutGroup.jl")
|
||||
|
||||
include("symbols.jl")
|
||||
include("fallbacks.jl")
|
||||
include("words.jl")
|
||||
include("hashing.jl")
|
||||
include("freereduce.jl")
|
||||
include("arithmetic.jl")
|
||||
include("findreplace.jl")
|
||||
include("normalform.jl")
|
||||
include("autgroups.jl")
|
||||
|
||||
include("DirectPower.jl")
|
||||
include("WreathProducts.jl")
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# String I/O
|
||||
#
|
||||
|
||||
function Base.show(io::IO, W::GWord)
|
||||
if length(W) == 0
|
||||
print(io, "(id)")
|
||||
else
|
||||
join(io, (string(s) for s in syllables(W)), "*")
|
||||
end
|
||||
end
|
||||
|
||||
function Base.show(io::IO, s::T) where {T<:GSymbol}
|
||||
if s.pow == 1
|
||||
print(io, string(s.id))
|
||||
else
|
||||
print(io, "$(s.id)^$(s.pow)")
|
||||
end
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Misc
|
||||
#
|
||||
|
||||
"""
|
||||
gens(G::AbstractFPGroups)
|
||||
Return vector of generators of `G`, as its elements.
|
||||
"""
|
||||
AbstractAlgebra.gens(G::AbstractFPGroup) = G.(G.gens)
|
||||
|
||||
"""
|
||||
wlmetric_ball(S::AbstractVector{<:GroupElem}
|
||||
[, center=one(first(S)); radius=2, op=*])
|
||||
Compute metric ball as a list of elements of non-decreasing length, given the
|
||||
word-length metric on the group generated by `S`. The ball is centered at `center`
|
||||
(by default: the identity element). `radius` and `op` keywords specify the
|
||||
radius and multiplication operation to be used.
|
||||
"""
|
||||
function wlmetric_ball_serial(
|
||||
S::AbstractVector{T};
|
||||
radius = 2,
|
||||
op = *,
|
||||
) where {T<:Union{GroupElem,NCRingElem}}
|
||||
old = unique!([one(first(S)), S...])
|
||||
sizes = [1, length(old)]
|
||||
for i = 2:radius
|
||||
new = collect(op(o, s) for o in @view(old[sizes[end-1]:end]) for s in S)
|
||||
append!(old, new)
|
||||
resize!(new, 0)
|
||||
old = unique!(old)
|
||||
push!(sizes, length(old))
|
||||
end
|
||||
return old, sizes[2:end]
|
||||
end
|
||||
|
||||
function wlmetric_ball_thr(
|
||||
S::AbstractVector{T};
|
||||
radius = 2,
|
||||
op = *,
|
||||
) where {T<:Union{GroupElem,NCRingElem}}
|
||||
old = unique!([one(first(S)), S...])
|
||||
sizes = [1, length(old)]
|
||||
for r = 2:radius
|
||||
begin
|
||||
new = ThreadsX.collect(
|
||||
op(o, s) for o in @view(old[sizes[end-1]:end]) for s in S
|
||||
)
|
||||
ThreadsX.foreach(hash, new)
|
||||
end
|
||||
append!(old, new)
|
||||
resize!(new, 0)
|
||||
old = ThreadsX.unique(old)
|
||||
push!(sizes, length(old))
|
||||
end
|
||||
return old, sizes[2:end]
|
||||
end
|
||||
|
||||
function wlmetric_ball_serial(
|
||||
S::AbstractVector{T},
|
||||
center::T;
|
||||
radius = 2,
|
||||
op = *,
|
||||
) where {T<:Union{GroupElem,NCRingElem}}
|
||||
E, sizes = wlmetric_ball_serial(S, radius = radius, op = op)
|
||||
isone(center) && return E, sizes
|
||||
return c .* E, sizes
|
||||
end
|
||||
|
||||
function wlmetric_ball_thr(
|
||||
S::AbstractVector{T},
|
||||
center::T;
|
||||
radius = 2,
|
||||
op = *,
|
||||
) where {T<:Union{GroupElem,NCRingElem}}
|
||||
E, sizes = wlmetric_ball_thr(S, radius = radius, op = op)
|
||||
isone(center) && return E, sizes
|
||||
return c .* E, sizes
|
||||
end
|
||||
|
||||
function wlmetric_ball(
|
||||
S::AbstractVector{T},
|
||||
center::T = one(first(S));
|
||||
radius = 2,
|
||||
op = *,
|
||||
threading = true,
|
||||
) where {T<:Union{GroupElem,NCRingElem}}
|
||||
threading && return wlmetric_ball_thr(S, center, radius = radius, op = op)
|
||||
return return wlmetric_ball_serial(S, center, radius = radius, op = op)
|
||||
end
|
||||
|
||||
"""
|
||||
image(w::GWord, homomorphism; kwargs...)
|
||||
Evaluate homomorphism `homomorphism` on a group word (element) `w`.
|
||||
`homomorphism` needs to implement
|
||||
> `hom(w; kwargs...)`,
|
||||
where `hom(;kwargs...)` returns the value at the identity element.
|
||||
"""
|
||||
function image(w::GWord, hom; kwargs...)
|
||||
return reduce(
|
||||
*,
|
||||
(hom(s; kwargs...) for s in syllables(w)),
|
||||
init = hom(; kwargs...),
|
||||
)
|
||||
end
|
||||
include("groups/sautFn.jl")
|
||||
|
||||
include("wl_ball.jl")
|
||||
end # of module Groups
|
||||
|
@ -1,210 +0,0 @@
|
||||
export WreathProduct, WreathProductElem
|
||||
|
||||
import AbstractAlgebra: AbstractPermutationGroup, AbstractPerm
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# WreathProduct / WreathProductElem
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
WreathProduct(N, P) <: Group
|
||||
Return the wreath product of a group `N` by permutation group `P`, usually
|
||||
written as `N ≀ P`. The multiplication inside wreath product is defined as
|
||||
> `(n, σ) * (m, τ) = (n*σ(m), στ)`
|
||||
where `σ(m)` denotes the action (from the right) of the permutation group on
|
||||
`n-tuples` of elements from `N`
|
||||
|
||||
# Arguments:
|
||||
* `N::Group` : the single factor of the `DirectPower` group `N`
|
||||
* `P::AbstractPermutationGroup` acting on `DirectPower` of `N`
|
||||
"""
|
||||
struct WreathProduct{N, T<:Group, PG<:AbstractPermutationGroup} <: Group
|
||||
N::DirectPowerGroup{N, T}
|
||||
P::PG
|
||||
|
||||
function WreathProduct(G::Gr, P::PG) where
|
||||
{Gr <: Group, PG <: AbstractPermutationGroup}
|
||||
N = DirectPowerGroup(G, Int(P.n))
|
||||
return new{Int(P.n), Gr, PG}(N, P)
|
||||
end
|
||||
end
|
||||
|
||||
struct WreathProductElem{N, T<:GroupElem, P<:AbstractPerm} <: GroupElem
|
||||
n::DirectPowerGroupElem{N, T}
|
||||
p::P
|
||||
|
||||
function WreathProductElem(n::DirectPowerGroupElem{N,T}, p::P,
|
||||
check::Bool=true) where {N, T, P<:AbstractPerm}
|
||||
if check
|
||||
N == length(p.d) || throw(DomainError(
|
||||
"Can't form WreathProductElem: lengths differ"))
|
||||
end
|
||||
return new{N, T, P}(n, p)
|
||||
end
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Type and parent object methods
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
elem_type(::Type{WreathProduct{N, T, PG}}) where {N, T, PG} = WreathProductElem{N, elem_type(T), elem_type(PG)}
|
||||
|
||||
parent_type(::Type{WreathProductElem{N, T, P}}) where {N, T, P} =
|
||||
WreathProduct{N, parent_type(T), parent_type(P)}
|
||||
|
||||
parent(g::WreathProductElem) = WreathProduct(parent(g.n[1]), parent(g.p))
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Parent object call overloads
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
function (G::WreathProduct{N})(g::WreathProductElem{N}) where {N}
|
||||
n = G.N(g.n)
|
||||
p = G.P(g.p)
|
||||
return WreathProductElem(n, p)
|
||||
end
|
||||
|
||||
"""
|
||||
(G::WreathProduct)(n::DirectPowerGroupElem, p::Generic.Perm)
|
||||
Create an element of wreath product `G` by coercing `n` and `p` to `G.N` and
|
||||
`G.P`, respectively.
|
||||
"""
|
||||
(G::WreathProduct)(n::DirectPowerGroupElem, p::Generic.Perm) = WreathProductElem(n,p)
|
||||
|
||||
Base.one(G::WreathProduct) = WreathProductElem(one(G.N), one(G.P), false)
|
||||
|
||||
"""
|
||||
(G::WreathProduct)(p::Generic.Perm)
|
||||
Return the image of permutation `p` in `G` via embedding `p → (id,p)`.
|
||||
"""
|
||||
(G::WreathProduct)(p::Generic.Perm) = G(one(G.N), p)
|
||||
|
||||
"""
|
||||
(G::WreathProduct)(n::DirectPowerGroupElem)
|
||||
Return the image of `n` in `G` via embedding `n → (n, ())`. This is the
|
||||
embedding that makes the sequence `1 → N → G → P → 1` exact.
|
||||
"""
|
||||
(G::WreathProduct)(n::DirectPowerGroupElem) = G(n, one(G.P))
|
||||
|
||||
(G::WreathProduct)(n,p) = G(G.N(n), G.P(p))
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Basic manipulation
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
function hash(G::WreathProduct, h::UInt)
|
||||
return hash(G.N, hash(G.P, hash(WreathProduct, h)))
|
||||
end
|
||||
|
||||
function hash(g::WreathProductElem, h::UInt)
|
||||
return hash(g.n, hash(g.p, hash(WreathProductElem, h)))
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# String I/O
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
function show(io::IO, G::WreathProduct)
|
||||
print(io, "Wreath Product of $(G.N.group) by $(G.P)")
|
||||
end
|
||||
|
||||
function show(io::IO, g::WreathProductElem)
|
||||
print(io, "($(g.n)≀$(g.p))")
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Comparison
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
function (==)(G::WreathProduct, H::WreathProduct)
|
||||
G.N == H.N || return false
|
||||
G.P == H.P || return false
|
||||
return true
|
||||
end
|
||||
|
||||
function (==)(g::WreathProductElem, h::WreathProductElem)
|
||||
g.n == h.n || return false
|
||||
g.p == h.p || return false
|
||||
return true
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Group operations
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
(p::Generic.Perm)(n::DirectPowerGroupElem) = DirectPowerGroupElem(n.elts[p.d])
|
||||
|
||||
"""
|
||||
*(g::WreathProductElem, h::WreathProductElem)
|
||||
Return the group operation of wreath product elements, i.e.
|
||||
> `g*h = (g.n*g.p(h.n), g.p*h.p)`,
|
||||
where `g.p(h.n)` denotes the action of `g.p::Generic.Perm` on
|
||||
`h.n::DirectPowerGroupElem` via standard permutation of coordinates.
|
||||
"""
|
||||
function *(g::WreathProductElem, h::WreathProductElem)
|
||||
return WreathProductElem(g.n*g.p(h.n), g.p*h.p, false)
|
||||
end
|
||||
|
||||
^(g::WreathProductElem, n::Integer) = Base.power_by_squaring(g, n)
|
||||
|
||||
"""
|
||||
inv(g::WreathProductElem)
|
||||
Return the inverse of element of a wreath product, according to the formula
|
||||
> `g^-1 = (g.n, g.p)^-1 = (g.p^-1(g.n^-1), g.p^-1)`.
|
||||
"""
|
||||
function inv(g::WreathProductElem)
|
||||
pinv = inv(g.p)
|
||||
return WreathProductElem(pinv(inv(g.n)), pinv, false)
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Misc
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
matrix_repr(g::WreathProductElem) = Any[matrix_repr(g.p) g.n]
|
||||
|
||||
function iterate(G::WreathProduct)
|
||||
n, state_N = iterate(G.N)
|
||||
p, state_P = iterate(G.P)
|
||||
return G(n,p), (state_N, p, state_P)
|
||||
end
|
||||
|
||||
function iterate(G::WreathProduct, state)
|
||||
state_N, p, state_P = state
|
||||
res = iterate(G.N, state_N)
|
||||
|
||||
if res == nothing
|
||||
resP = iterate(G.P, state_P)
|
||||
if resP == nothing
|
||||
return nothing
|
||||
else
|
||||
n, state_N = iterate(G.N)
|
||||
p, state_P = resP
|
||||
end
|
||||
else
|
||||
n, state_N = res
|
||||
end
|
||||
|
||||
return G(n,p), (state_N, p, state_P)
|
||||
end
|
||||
|
||||
eltype(::Type{WreathProduct{N,G,PG}}) where {N,G,PG} = WreathProductElem{N, elem_type(G), elem_type(PG)}
|
||||
|
||||
order(G::WreathProduct) = order(G.P)*order(G.N)
|
||||
length(G::WreathProduct) = order(G)
|
@ -1,93 +0,0 @@
|
||||
function Base.inv(W::T) where T<:GWord
|
||||
length(W) == 0 && return one(W)
|
||||
G = parent(W)
|
||||
w = T([inv(s) for s in Iterators.reverse(syllables(W))])
|
||||
return setparent!(w, G)
|
||||
end
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Binary operators
|
||||
#
|
||||
|
||||
function Base.push!(w::GWord{T}, s::T) where T <: GSymbol
|
||||
push!(syllables(w), s)
|
||||
return w
|
||||
end
|
||||
|
||||
function Base.pushfirst!(w::GWord{T}, s::T) where T <: GSymbol
|
||||
pushfirst!(syllables(w), s)
|
||||
return w
|
||||
end
|
||||
|
||||
function Base.append!(w::T, v::T) where T <: GWord
|
||||
append!(syllables(w), syllables(v))
|
||||
return w
|
||||
end
|
||||
|
||||
function Base.prepend!(w::T, v::T) where T <: GWord
|
||||
prepend!(syllables(w), syllables(v))
|
||||
return w
|
||||
end
|
||||
|
||||
Base.append!(w::T, v::T, others::Vararg{T,N}) where {N,T <: GWord} =
|
||||
append!(append!(w, v), others...)
|
||||
|
||||
function rmul!(out::T, x::T, y::T) where T<: GWord
|
||||
if out === x
|
||||
out = deepcopy(out)
|
||||
return freereduce!(append!(out, y))
|
||||
elseif out === y
|
||||
out = deepcopy(out)
|
||||
return freereduce!(prepend!(out, x))
|
||||
else
|
||||
slenx = syllablelength(x)
|
||||
sleny = syllablelength(y)
|
||||
resize!(syllables(out), slenx+sleny)
|
||||
syllables(out)[1:slenx] .= syllables(x)
|
||||
syllables(out)[slenx+1:slenx+sleny] .= syllables(y)
|
||||
return freereduce!(out)
|
||||
end
|
||||
end
|
||||
|
||||
rmul!(out::T, v::T) where T<:GWord = freereduce!(append!(out, v))
|
||||
lmul!(out::T, v::T) where T<:GWord = freereduce!(prepend!(out, v))
|
||||
|
||||
lmul!(out::T, x::T, y::T) where T <: GWord = rmul!(out, y, x)
|
||||
|
||||
AbstractAlgebra.mul!(out::T, x::T, y::T) where T <: GWord = rmul!(out, x, y)
|
||||
|
||||
(*)(W::GW, Z::GW) where GW <: GWord = rmul!(deepcopy(W), W, Z)
|
||||
(*)(W::GWord, s::GSymbol) = freereduce!(push!(deepcopy(W), s))
|
||||
(*)(s::GSymbol, W::GWord) = freereduce!(pushfirst!(deepcopy(W), s))
|
||||
|
||||
function power_by_squaring(W::GWord, p::Integer)
|
||||
if p < 0
|
||||
return power_by_squaring(inv(W), -p)
|
||||
elseif p == 0
|
||||
return one(W)
|
||||
elseif p == 1
|
||||
return W
|
||||
elseif p == 2
|
||||
return W*W
|
||||
end
|
||||
W = deepcopy(W)
|
||||
t = trailing_zeros(p) + 1
|
||||
p >>= t
|
||||
while (t -= 1) > 0
|
||||
append!(W, W)
|
||||
end
|
||||
Z = deepcopy(W)
|
||||
while p > 0
|
||||
t = trailing_zeros(p) + 1
|
||||
p >>= t
|
||||
while (t -= 1) >= 0
|
||||
append!(W, W)
|
||||
end
|
||||
append!(Z, W)
|
||||
end
|
||||
|
||||
return freereduce!(Z)
|
||||
end
|
||||
|
||||
(^)(x::GWord, n::Integer) = power_by_squaring(x,n)
|
107
src/autgroups.jl
Normal file
107
src/autgroups.jl
Normal file
@ -0,0 +1,107 @@
|
||||
function KnuthBendix.Alphabet(S::AbstractVector{<:GSymbol})
|
||||
S = unique!([S; inv.(S)])
|
||||
inversions = [findfirst(==(inv(s)), S) for s in S]
|
||||
return Alphabet(S, inversions)
|
||||
end
|
||||
|
||||
struct AutomorphismGroup{G<:Group,T,R,S} <: AbstractFPGroup
|
||||
group::G
|
||||
gens::Vector{T}
|
||||
rws::R
|
||||
domain::S
|
||||
end
|
||||
|
||||
object(G::AutomorphismGroup) = G.group
|
||||
rewriting(G::AutomorphismGroup) = G.rws
|
||||
|
||||
function equality_data(f::FPGroupElement{<:AutomorphismGroup})
|
||||
imf = evaluate(f)
|
||||
# return normalform!.(imf)
|
||||
|
||||
tmp = one(first(imf))
|
||||
for g in imf
|
||||
normalform!(tmp, g)
|
||||
copyto!(g, tmp)
|
||||
end
|
||||
return imf
|
||||
end
|
||||
|
||||
function Base.:(==)(g::A, h::A) where {A<:FPGroupElement{<:AutomorphismGroup}}
|
||||
@assert parent(g) === parent(h)
|
||||
|
||||
if _isvalidhash(g) && _isvalidhash(h)
|
||||
hash(g) != hash(h) && return false
|
||||
end
|
||||
|
||||
length(word(g)) > 8 && normalform!(g)
|
||||
length(word(h)) > 8 && normalform!(h)
|
||||
|
||||
word(g) == word(h) && return true
|
||||
|
||||
img_computed, imh_computed = false, false
|
||||
|
||||
if !_isvalidhash(g)
|
||||
img = equality_data(g)
|
||||
_update_savedhash!(g, img)
|
||||
img_computed = true
|
||||
end
|
||||
if !_isvalidhash(h)
|
||||
imh = equality_data(h)
|
||||
_update_savedhash!(h, imh)
|
||||
imh_computed = true
|
||||
end
|
||||
|
||||
@assert _isvalidhash(g)
|
||||
@assert _isvalidhash(h)
|
||||
|
||||
hash(g) != hash(h) && return false
|
||||
|
||||
# words are different, but hashes agree
|
||||
if !img_computed
|
||||
img = equality_data(g)
|
||||
end
|
||||
if !imh_computed
|
||||
imh = equality_data(h)
|
||||
end
|
||||
|
||||
equal = img == imh
|
||||
equal || @warn "hash collision in == :" g h
|
||||
|
||||
return equal
|
||||
end
|
||||
|
||||
function Base.isone(g::FPGroupElement{<:AutomorphismGroup})
|
||||
if length(word(g)) > 8
|
||||
normalform!(g)
|
||||
end
|
||||
return evaluate(g) == parent(g).domain
|
||||
end
|
||||
|
||||
# eye-candy
|
||||
|
||||
Base.show(io::IO, ::Type{<:FPGroupElement{<:AutomorphismGroup{T}}}) where {T} =
|
||||
print(io, "Automorphism{$T,…}")
|
||||
|
||||
Base.show(io::IO, A::AutomorphismGroup) = print(io, "automorphism group of ", object(A))
|
||||
|
||||
## Automorphism Evaluation
|
||||
|
||||
domain(f::FPGroupElement{<:AutomorphismGroup}) = deepcopy(parent(f).domain)
|
||||
# tuple(gens(object(parent(f)))...)
|
||||
|
||||
evaluate(f::FPGroupElement{<:AutomorphismGroup}) = evaluate!(domain(f), f)
|
||||
|
||||
function evaluate!(
|
||||
t::NTuple{N,T},
|
||||
f::FPGroupElement{<:AutomorphismGroup{<:Group}},
|
||||
tmp = one(first(t)),
|
||||
) where {N, T}
|
||||
A = alphabet(f)
|
||||
AF = alphabet(object(parent(f)))
|
||||
for idx in word(f)
|
||||
t = @inbounds evaluate!(t, A[idx], AF, tmp)::NTuple{N,T}
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
evaluate!(t::NTuple{N, T}, s::GSymbol, A, tmp=one(first(t))) where {N, T} = throw("you need to implement `evaluate!(::$(typeof(t)), ::$(typeof(s)), ::Alphabet, tmp=one(first(t)))`")
|
@ -1,16 +0,0 @@
|
||||
# workarounds
|
||||
Base.one(G::Generic.SymmetricGroup) = Generic.Perm(G.n)
|
||||
|
||||
# fallback definitions
|
||||
# note: the user should implement those on type, when possible
|
||||
Base.eltype(w::GW) where GW<:GWord = eltype(GW)
|
||||
AbstractAlgebra.elem_type(G::Gr) where Gr <:AbstractFPGroup = elem_type(Gr)
|
||||
|
||||
AbstractAlgebra.parent_type(g::Gw) where Gw <:GWord = parent_type(parent(Gr))
|
||||
|
||||
function Base.one(G::Gr) where Gr <: AbstractFPGroup
|
||||
El = elem_type(G)
|
||||
id = El(eltype(El)[])
|
||||
id.parent = G
|
||||
return id
|
||||
end
|
@ -1,182 +0,0 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Replacement of symbols / sub-words
|
||||
#
|
||||
|
||||
issubsymbol(s::GSymbol, t::GSymbol) =
|
||||
s.id == t.id && (0 ≤ s.pow ≤ t.pow || 0 ≥ s.pow ≥ t.pow)
|
||||
|
||||
function issubsymbol(s::FreeSymbol, w::GWord, sindex::Integer)
|
||||
@boundscheck 1 ≤ sindex ≤ syllablelength(w) || throw(BoundsError(w, sindex))
|
||||
return issubsymbol(s, syllables(w)[sindex])
|
||||
end
|
||||
|
||||
function issubword(z::GWord, w::GWord, sindex::Integer)
|
||||
isempty(z) && return true
|
||||
@boundscheck 1 ≤ sindex ≤ syllablelength(w) || throw(BoundsError(w, sindex))
|
||||
n = syllablelength(z)
|
||||
n == 1 && return issubsymbol(first(syllables(z)), syllables(w)[sindex])
|
||||
|
||||
lastindex = sindex + n - 1
|
||||
lastindex > syllablelength(w) && return false
|
||||
|
||||
issubsymbol(first(z), syllables(w)[sindex]) || return false
|
||||
issubsymbol(syllables(z)[end], syllables(w)[lastindex]) || return false
|
||||
for (zidx, widx) in zip(2:n-1, sindex+1:lastindex-1)
|
||||
syllables(z)[zidx] == syllables(w)[widx] || return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
"""doc
|
||||
Find the first syllable index k>=i such that Z < syllables(W)[k:k+syllablelength(Z)-1]
|
||||
"""
|
||||
function findnext(subword::GWord, word::GWord, start::Integer)
|
||||
@boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start))
|
||||
isempty(subword) && return start
|
||||
stop = syllablelength(word) - syllablelength(subword) +1
|
||||
|
||||
for idx in start:1:stop
|
||||
issubword(subword, word, idx) && return idx
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
function findnext(s::FreeSymbol, word::GWord, start::Integer)
|
||||
@boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start))
|
||||
isone(s) && return start
|
||||
stop = syllablelength(word)
|
||||
|
||||
for idx in start:1:stop
|
||||
issubsymbol(s, word, idx) && return idx
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
function findprev(subword::GWord, word::GWord, start::Integer)
|
||||
@boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start))
|
||||
isempty(subword) && return start
|
||||
stop = 1
|
||||
|
||||
for idx in start:-1:1
|
||||
issubword(subword, word, idx) && return idx
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
function findprev(s::FreeSymbol, word::GWord, start::Integer)
|
||||
@boundscheck 1 ≤ start ≤ syllablelength(word) || throw(BoundsError(word, start))
|
||||
isone(s) && return start
|
||||
stop = 1
|
||||
|
||||
for idx in start:-1:stop
|
||||
issubsymbol(s, word, idx) && return idx
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
findfirst(subword::GWord, word::GWord) = findnext(subword, word, 1)
|
||||
findlast(subword::GWord, word::GWord) =
|
||||
findprev(subword, word, syllablelength(word)-syllablelength(subword)+1)
|
||||
|
||||
function replace!(out::GW, W::GW, lhs_rhs::Pair{GS, T}; count::Integer=typemax(Int)) where
|
||||
{GS<:GSymbol, T<:GWord, GW<:GWord}
|
||||
(count == 0 || isempty(W)) && return W
|
||||
count < 0 && throw(DomainError(count, "`count` must be non-negative."))
|
||||
|
||||
lhs, rhs = lhs_rhs
|
||||
|
||||
sW = syllables(W)
|
||||
sW_idx = 1
|
||||
r = something(findnext(lhs, W, sW_idx), 0)
|
||||
|
||||
sout = syllables(out)
|
||||
resize!(sout, 0)
|
||||
sizehint!(sout, syllablelength(W))
|
||||
|
||||
c = 0
|
||||
|
||||
while !iszero(r)
|
||||
append!(sout, view(sW, sW_idx:r-1))
|
||||
a, b = divrem(sW[r].pow, lhs.pow)
|
||||
|
||||
if b != 0
|
||||
push!(sout, change_pow(sW[r], b))
|
||||
end
|
||||
|
||||
append!(sout, repeat(syllables(rhs), a))
|
||||
|
||||
sW_idx = r+1
|
||||
sW_idx > syllablelength(W) && break
|
||||
|
||||
r = something(findnext(lhs, W, sW_idx), 0)
|
||||
c += 1
|
||||
c == count && break
|
||||
end
|
||||
append!(sout, sW[sW_idx:end])
|
||||
return freereduce!(out)
|
||||
end
|
||||
|
||||
function replace!(out::GW, W::GW, lhs_rhs::Pair{T, T}; count::Integer=typemax(Int)) where
|
||||
{GW<:GWord, T <: GWord}
|
||||
(count == 0 || isempty(W)) && return W
|
||||
count < 0 && throw(DomainError(count, "`count` must be non-negative."))
|
||||
|
||||
lhs, rhs = lhs_rhs
|
||||
lhs_slen = syllablelength(lhs)
|
||||
lhs_slen == 1 && return replace!(out, W, first(syllables(lhs))=>rhs; count=count)
|
||||
|
||||
sW = syllables(W)
|
||||
sW_idx = 1
|
||||
r = something(findnext(lhs, W, sW_idx), 0)
|
||||
|
||||
sout = syllables(out)
|
||||
resize!(sout, 0)
|
||||
sizehint!(sout, syllablelength(W))
|
||||
|
||||
c = 0
|
||||
|
||||
while !iszero(r)
|
||||
append!(sout, view(sW, sW_idx:r-1))
|
||||
|
||||
exp = sW[r].pow - first(syllables(lhs)).pow
|
||||
if exp != 0
|
||||
push!(sout, change_pow(sW[r], exp))
|
||||
end
|
||||
|
||||
append!(sout, syllables(rhs))
|
||||
|
||||
exp = sW[r+lhs_slen-1].pow - last(syllables(lhs)).pow
|
||||
if exp != 0
|
||||
push!(sout, change_pow(sW[r+lhs_slen-1], exp))
|
||||
end
|
||||
|
||||
sW_idx = r+lhs_slen
|
||||
sW_idx > syllablelength(W) && break
|
||||
|
||||
r = something(findnext(lhs, W, sW_idx), 0)
|
||||
c += 1
|
||||
c == count && break
|
||||
end
|
||||
|
||||
# copy the rest
|
||||
append!(sout, sW[sW_idx:end])
|
||||
return freereduce!(out)
|
||||
end
|
||||
|
||||
function replace(W::GW, lhs_rhs::Pair{T, T}; count::Integer=typemax(Int)) where
|
||||
{GW<:GWord, T <: GWord}
|
||||
return replace!(one(W), W, lhs_rhs; count=count)
|
||||
end
|
||||
|
||||
function replace(W::GW, subst_dict::Dict{T,T}) where {GW<:GWord, T<:GWord}
|
||||
out = W
|
||||
for toreplace in reverse!(sort!(collect(keys(subst_dict)), by=length))
|
||||
replacement = subst_dict[toreplace]
|
||||
if length(toreplace) > length(out)
|
||||
continue
|
||||
end
|
||||
out = replace(out, toreplace=>replacement)
|
||||
end
|
||||
return out
|
||||
end
|
@ -1,49 +0,0 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Naive reduction
|
||||
#
|
||||
|
||||
function freereduce!(::Type{Bool}, w::GWord)
|
||||
if syllablelength(w) == 1
|
||||
filter!(!isone, syllables(w))
|
||||
return syllablelength(w) == 1
|
||||
end
|
||||
|
||||
reduced = true
|
||||
@inbounds for i in 1:syllablelength(w)-1
|
||||
s, ns = syllables(w)[i], syllables(w)[i+1]
|
||||
if isone(s)
|
||||
continue
|
||||
elseif s.id === ns.id
|
||||
reduced = false
|
||||
p1 = s.pow
|
||||
p2 = ns.pow
|
||||
|
||||
syllables(w)[i+1] = change_pow(s, p1 + p2)
|
||||
syllables(w)[i] = change_pow(s, 0)
|
||||
end
|
||||
end
|
||||
if !reduced
|
||||
filter!(!isone, syllables(w))
|
||||
setmodified!(w)
|
||||
end
|
||||
return reduced
|
||||
end
|
||||
|
||||
function freereduce!(w::GWord)
|
||||
reduced = false
|
||||
while !reduced
|
||||
reduced = freereduce!(Bool, w)
|
||||
end
|
||||
return w
|
||||
end
|
||||
|
||||
reduce!(w::GWord) = freereduce!(w)
|
||||
|
||||
"""
|
||||
reduce(w::GWord)
|
||||
performs reduction/simplification of a group element (word in generators).
|
||||
The default reduction is the reduction in the free group reduction.
|
||||
More specific procedures should be dispatched on `GWord`s type parameter.
|
||||
"""
|
||||
reduce(w::GWord) = reduce!(deepcopy(w))
|
105
src/groups/gersten_relations.jl
Normal file
105
src/groups/gersten_relations.jl
Normal file
@ -0,0 +1,105 @@
|
||||
function gersten_alphabet(n::Integer; commutative::Bool = true)
|
||||
indexing = [(i, j) for i in 1:n for j in 1:n if i ≠ j]
|
||||
S = [ϱ(i, j) for (i, j) in indexing]
|
||||
|
||||
if !commutative
|
||||
append!(S, [λ(i, j) for (i, j) in indexing])
|
||||
end
|
||||
|
||||
return Alphabet(S)
|
||||
end
|
||||
|
||||
function _commutation_rule(
|
||||
::Type{W},
|
||||
A::Alphabet,
|
||||
x::S,
|
||||
y::S,
|
||||
) where {S,T,W<:AbstractWord{T}}
|
||||
return W(T[A[x], A[y]]) => W(T[A[y], A[x]])
|
||||
end
|
||||
|
||||
function _pentagonal_rule(
|
||||
::Type{W},
|
||||
A::Alphabet,
|
||||
x::S,
|
||||
y::S,
|
||||
z::S,
|
||||
) where {S,T,W<:AbstractWord{T}}
|
||||
# x·y·x⁻¹·y⁻¹ => z, i.e. z·y·x => x·y
|
||||
return W(T[A[z], A[y], A[x]]) => W(T[A[x], A[y]])
|
||||
end
|
||||
function _hexagonal_rule(
|
||||
::Type{W},
|
||||
A::Alphabet,
|
||||
x::S,
|
||||
y::S,
|
||||
z::S,
|
||||
w::S,
|
||||
) where {S,T,W<:AbstractWord{T}}
|
||||
# x·y⁻¹·z => z·w⁻¹·x
|
||||
return W(T[A[x], A[inv(y)], A[z]]) => W(T[A[z], A[w^-1], A[x]])
|
||||
end
|
||||
|
||||
gersten_relations(n::Integer; commutative) =
|
||||
gersten_relations(Word{UInt8}, n, commutative = commutative)
|
||||
|
||||
function gersten_relations(::Type{W}, n::Integer; commutative) where {W<:AbstractWord}
|
||||
@assert n > 1 "Gersten relations are defined only for n>1, got n=$n"
|
||||
A = gersten_alphabet(n, commutative = commutative)
|
||||
@assert length(A) <= KnuthBendix._max_alphabet_length(W) "Type $W can not represent words over alphabet with $(length(A)) letters."
|
||||
|
||||
rels = Pair{W,W}[]
|
||||
|
||||
for (i, j, k, l) in Iterators.product(1:n, 1:n, 1:n, 1:n)
|
||||
if i ≠ j && k ≠ l && k ≠ i && k ≠ j && l ≠ i
|
||||
push!(rels, _commutation_rule(W, A, ϱ(i, j), ϱ(k, l)))
|
||||
|
||||
commutative && continue
|
||||
|
||||
push!(rels, _commutation_rule(W, A, λ(i, j), λ(k, l)))
|
||||
end
|
||||
end
|
||||
|
||||
if !commutative
|
||||
for (i, j, k, l) in Iterators.product(1:n, 1:n, 1:n, 1:n)
|
||||
if (i ≠ j && k ≠ l && k ≠ j && l ≠ i)
|
||||
push!(rels, _commutation_rule(W, A, ϱ(i, j), λ(k, l)))
|
||||
push!(rels, _commutation_rule(W, A, λ(i, j), ϱ(k, l)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# pentagonal rule:
|
||||
# x*y*inv(x)*inv(y)=>z
|
||||
|
||||
for (i, j, k) in Iterators.product(1:n, 1:n, 1:n)
|
||||
if (i ≠ j && k ≠ i && k ≠ j)
|
||||
push!(rels, _pentagonal_rule(W, A, ϱ(i, j)^-1, ϱ(j, k)^-1, ϱ(i, k)^-1))
|
||||
push!(rels, _pentagonal_rule(W, A, ϱ(i, j)^-1, ϱ(j, k), ϱ(i, k)))
|
||||
|
||||
commutative && continue
|
||||
|
||||
push!(rels, _pentagonal_rule(W, A, ϱ(i, j), λ(j, k), ϱ(i, k)^-1))
|
||||
push!(rels, _pentagonal_rule(W, A, ϱ(i, j), λ(j, k)^-1, ϱ(i, k)))
|
||||
|
||||
# the same as above, but with ϱ ↔ λ:
|
||||
push!(rels, _pentagonal_rule(W, A, λ(i, j)^-1, λ(j, k)^-1, λ(i, k)^-1))
|
||||
push!(rels, _pentagonal_rule(W, A, λ(i, j)^-1, λ(j, k), λ(i, k)))
|
||||
|
||||
push!(rels, _pentagonal_rule(W, A, λ(i, j), ϱ(j, k), λ(i, k)^-1))
|
||||
push!(rels, _pentagonal_rule(W, A, λ(i, j), ϱ(j, k)^-1, λ(i, k)))
|
||||
end
|
||||
end
|
||||
|
||||
if !commutative
|
||||
for (i, j) in Iterators.product(1:n, 1:n)
|
||||
if i ≠ j
|
||||
push!(rels, _hexagonal_rule(W, A, ϱ(i, j), ϱ(j, i), λ(i, j), λ(j, i)))
|
||||
w = W([A[ϱ(i, j)], A[ϱ(j, i)^-1], A[λ(i, j)]])
|
||||
push!(rels, w^2 => inv(A, w)^2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return A, rels
|
||||
end
|
20
src/groups/sautFn.jl
Normal file
20
src/groups/sautFn.jl
Normal file
@ -0,0 +1,20 @@
|
||||
include("transvections.jl")
|
||||
include("gersten_relations.jl")
|
||||
|
||||
function SpecialAutomorphismGroup(F::FreeGroup; ordering = KnuthBendix.LenLex, kwargs...)
|
||||
|
||||
n = length(alphabet(F)) ÷ 2
|
||||
A, rels = gersten_relations(n, commutative = false)
|
||||
S = KnuthBendix.letters(A)[1:2(n^2-n)]
|
||||
|
||||
rws = KnuthBendix.RewritingSystem(rels, ordering(A))
|
||||
KnuthBendix.knuthbendix!(rws; kwargs...)
|
||||
return AutomorphismGroup(F, S, rws, ntuple(i -> gens(F, i), n))
|
||||
end
|
||||
|
||||
KnuthBendix.alphabet(G::AutomorphismGroup{<:FreeGroup}) = alphabet(rewriting(G))
|
||||
|
||||
function relations(G::AutomorphismGroup{<:FreeGroup})
|
||||
n = length(alphabet(object(G))) ÷ 2
|
||||
return last(gersten_relations(n, commutative = false))
|
||||
end
|
83
src/groups/transvections.jl
Normal file
83
src/groups/transvections.jl
Normal file
@ -0,0 +1,83 @@
|
||||
struct Transvection <: GSymbol
|
||||
id::Symbol
|
||||
ij::UInt8
|
||||
inv::Bool
|
||||
|
||||
function Transvection(id::Symbol, i::Integer, j::Integer, inv = false)
|
||||
@assert id in (:ϱ, :λ)
|
||||
return new(id, _indices(UInt8(i), UInt8(j)), inv)
|
||||
end
|
||||
end
|
||||
|
||||
ϱ(i, j) = Transvection(:ϱ, i, j)
|
||||
λ(i, j) = Transvection(:λ, i, j)
|
||||
|
||||
_indices(ij::UInt8) = (ij & 0xf0) >> 4, (ij & 0x0f)
|
||||
|
||||
function _indices(i::UInt8, j::UInt8)
|
||||
@boundscheck @assert i < typemax(i) ÷ 2
|
||||
@boundscheck @assert j < typemax(j) ÷ 2
|
||||
sizeof
|
||||
return (i << 4) + j
|
||||
end
|
||||
|
||||
indices(t::Transvection) = Int.(_indices(t.ij))
|
||||
|
||||
function Base.getproperty(t::Transvection, s::Symbol)
|
||||
s === :i && return first(indices(t))
|
||||
s === :j && return last(indices(t))
|
||||
return Core.getfield(t, s)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, t::Transvection)
|
||||
id = if t.id === :ϱ
|
||||
'ϱ'
|
||||
else # if t.id === :λ
|
||||
'λ'
|
||||
end
|
||||
print(io, id, subscriptify(t.i), '.', subscriptify(t.j))
|
||||
t.inv && print(io, "^-1")
|
||||
end
|
||||
|
||||
Base.inv(t::Transvection) = Transvection(t.id, _indices(t.ij)..., !t.inv)
|
||||
|
||||
Base.:(==)(t::Transvection, s::Transvection) =
|
||||
t.id === s.id && t.ij == s.ij && t.inv == s.inv
|
||||
Base.hash(t::Transvection, h::UInt) = hash(t.id, hash(t.ij, hash(t.inv, h)))
|
||||
|
||||
Base.@propagate_inbounds function evaluate!(v::NTuple{T, N}, t::Transvection, A::Alphabet, tmp=one(first(v))) where {T, N}
|
||||
i, j = indices(t)
|
||||
@assert i ≤ length(v) && j ≤ length(v)
|
||||
|
||||
@inbounds begin
|
||||
if t.id === :ϱ
|
||||
if !t.inv
|
||||
append!(word(v[i]), word(v[j]))
|
||||
else
|
||||
# append!(word(v[i]), inv(A, word(v[j])))
|
||||
for l in Iterators.reverse(word(v[j]))
|
||||
push!(word(v[i]), inv(A, l))
|
||||
end
|
||||
end
|
||||
else # if t.id === :λ
|
||||
if !t.inv
|
||||
# prepend!(word(v[i]), word(v[j]))
|
||||
for l in Iterators.reverse(word(v[j]))
|
||||
pushfirst!(word(v[i]), l)
|
||||
end
|
||||
else
|
||||
# prepend!(word(v[i]), inv(A, word(v[j])))
|
||||
for l in word(v[j])
|
||||
pushfirst!(word(v[i]), inv(A, l))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
_setnormalform!(v[i], false)
|
||||
_setvalidhash!(v[i], false)
|
||||
end
|
||||
normalform!(tmp, v[i])
|
||||
copyto!(v[i], tmp)
|
||||
|
||||
return v
|
||||
end
|
@ -1,34 +1,46 @@
|
||||
###############################################################################
|
||||
#
|
||||
# hashing, deepcopy and ==
|
||||
#
|
||||
## Hashing
|
||||
|
||||
function hash_internal(W::GWord)
|
||||
reduce!(W)
|
||||
h = hasparent(W) ? hash(parent(W)) : zero(UInt)
|
||||
return hash(syllables(W), hash(typeof(W), h))
|
||||
end
|
||||
equality_data(g::FPGroupElement) = (normalform!(g); word(g))
|
||||
|
||||
function hash(W::GWord, h::UInt=UInt(0); kwargs...)
|
||||
if ismodified(W)
|
||||
savehash!(W, hash_internal(W; kwargs...))
|
||||
unsetmodified!(W)
|
||||
end
|
||||
return xor(savedhash(W), h)
|
||||
end
|
||||
bitget(h::UInt, n::Int) = Bool((h & (1 << n)) >> n)
|
||||
bitclear(h::UInt, n::Int) = h & ~(1 << n)
|
||||
bitset(h::UInt, n::Int) = h | (1 << n)
|
||||
bitset(h::UInt, v::Bool, n::Int) = v ? bitset(h, n) : bitclear(h, n)
|
||||
|
||||
# WARNING: Due to specialised (constant) hash function of GWords this one is actually necessary!
|
||||
function Base.deepcopy_internal(W::T, dict::IdDict) where T<:GWord
|
||||
G = parent(W)
|
||||
g = T(deepcopy(syllables(W)))
|
||||
setparent!(g, G)
|
||||
# We store hash of a word in field `savedhash` to use it as cheap proxy to
|
||||
# determine non-equal words. Additionally bits of `savehash` store boolean
|
||||
# information as follows
|
||||
# * `savedhash & 1` (the first bit): is word in normal form?
|
||||
# * `savedhash & 2` (the second bit): is the hash valid?
|
||||
const __BITFLAGS_MASK = ~(~(UInt(0)) << 2)
|
||||
|
||||
isnormalform(g::FPGroupElement) = bitget(g.savedhash, 0)
|
||||
_isvalidhash(g::FPGroupElement) = bitget(g.savedhash, 1)
|
||||
|
||||
_setnormalform(h::UInt, v::Bool) = bitset(h, v, 0)
|
||||
_setvalidhash(h::UInt, v::Bool) = bitset(h, v, 1)
|
||||
|
||||
_setnormalform!(g::FPGroupElement, v::Bool) = g.savedhash = _setnormalform(g.savedhash, v)
|
||||
_setvalidhash!(g::FPGroupElement, v::Bool) = g.savedhash = _setvalidhash(g.savedhash, v)
|
||||
|
||||
# To update hash use this internal method, possibly only after computing the
|
||||
# normal form of `g`:
|
||||
function _update_savedhash!(g::FPGroupElement, data)
|
||||
h = hash(data, hash(parent(g)))
|
||||
h = (h << count_ones(__BITFLAGS_MASK)) | (__BITFLAGS_MASK & g.savedhash)
|
||||
g.savedhash = _setvalidhash(h, true)
|
||||
return g
|
||||
end
|
||||
|
||||
function (==)(W::T, Z::T) where T <: GWord
|
||||
hash(W) != hash(Z) && return false # distinguishes parent and parentless words
|
||||
if hasparent(W) && hasparent(Z)
|
||||
parent(W) != parent(Z) && return false
|
||||
end
|
||||
return syllables(W) == syllables(Z)
|
||||
function Base.hash(g::FPGroupElement, h::UInt)
|
||||
_isvalidhash(g) || _update_savedhash!(g, equality_data(g))
|
||||
return hash(g.savedhash >> count_ones(__BITFLAGS_MASK), h)
|
||||
end
|
||||
|
||||
function Base.copyto!(res::FPGroupElement, g::FPGroupElement)
|
||||
@assert parent(res) === parent(g)
|
||||
resize!(word(res), length(word(g)))
|
||||
copyto!(word(res), word(g))
|
||||
res.savedhash = g.savedhash
|
||||
return res
|
||||
end
|
||||
|
57
src/iteration.jl
Normal file
57
src/iteration.jl
Normal file
@ -0,0 +1,57 @@
|
||||
mutable struct FPGroupIter{S, T, GEl}
|
||||
seen::S
|
||||
seen_iter_state::T
|
||||
current::GEl
|
||||
gen_idx::Int
|
||||
u_tmp::GEl
|
||||
v_tmp::GEl
|
||||
end
|
||||
|
||||
function Base.iterate(G::AbstractFPGroup)
|
||||
seen = OrderedSet([one(G)])
|
||||
current, seen_state = iterate(seen)
|
||||
gr_iter = FPGroupIter(seen, seen_state, current, 1, one(G), one(G))
|
||||
return one(G), gr_iter
|
||||
end
|
||||
|
||||
function _next_elt!(itr::FPGroupIter)
|
||||
res = iterate(itr.seen, itr.seen_iter_state)
|
||||
res === nothing && return true
|
||||
|
||||
itr.current = first(res)
|
||||
itr.seen_iter_state = last(res)
|
||||
itr.gen_idx = 1
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function Base.iterate(G::AbstractFPGroup, state)
|
||||
if state.gen_idx > length(alphabet(G))
|
||||
finished = _next_elt!(state)
|
||||
finished && return nothing
|
||||
end
|
||||
|
||||
elt = let u = state.u_tmp, v = state.v_tmp, current = state.current
|
||||
copyto!(v, current)
|
||||
push!(word(v), state.gen_idx)
|
||||
_setnormalform!(v, false)
|
||||
_setvalidhash!(v, false)
|
||||
@assert !isnormalform(v)
|
||||
|
||||
resize!(word(u), 0)
|
||||
normalform!(u, v)
|
||||
end
|
||||
|
||||
state.gen_idx += 1
|
||||
|
||||
if elt in state.seen
|
||||
return iterate(G, state)
|
||||
else
|
||||
@assert isnormalform(elt)
|
||||
push!(state.seen, deepcopy(elt))
|
||||
return elt, state
|
||||
end
|
||||
end
|
||||
|
||||
# Groups.Core default:
|
||||
# Base.IteratorSize(::Type{<:AbstractFPGroup}) = Base.SizeUnknown()
|
46
src/normalform.jl
Normal file
46
src/normalform.jl
Normal file
@ -0,0 +1,46 @@
|
||||
"""
|
||||
normalform!(g::FPGroupElement)
|
||||
Compute the normal form of `g`, possibly modifying `g` in-place.
|
||||
"""
|
||||
@inline function normalform!(g::FPGroupElement)
|
||||
isnormalform(g) && return g
|
||||
|
||||
let w = one(word(g))
|
||||
w = normalform!(w, g)
|
||||
resize!(word(g), length(w))
|
||||
copyto!(word(g), w)
|
||||
end
|
||||
|
||||
_setnormalform!(g, true)
|
||||
_setvalidhash!(g, false)
|
||||
@assert isnormalform(g)
|
||||
return g
|
||||
end
|
||||
|
||||
"""
|
||||
normalform!(res::GEl, g::GEl) where GEl<:FPGroupElement
|
||||
Compute the normal fom of `g`, storing it in `res`.
|
||||
"""
|
||||
function normalform!(res::GEl, g::GEl) where {GEl<:FPGroupElement}
|
||||
@boundscheck @assert parent(res) === parent(g)
|
||||
if isnormalform(g)
|
||||
copyto!(res, g)
|
||||
else
|
||||
resize!(word(res), 0)
|
||||
normalform!(word(res), g)
|
||||
_setnormalform!(res, true)
|
||||
_setvalidhash!(res, false)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
"""
|
||||
normalform!(res::AbstractWord, g::FPGroupElement)
|
||||
Append the normal form of `g` to word `res`, modifying `res` in place.
|
||||
|
||||
Defaults to the rewriting in the free group.
|
||||
"""
|
||||
@inline function normalform!(res::AbstractWord, g::FPGroupElement)
|
||||
isone(res) && isnormalform(g) && return append!(res, word(g))
|
||||
return KnuthBendix.rewrite_from_left!(res, word(g), rewriting(parent(g)))
|
||||
end
|
@ -1,22 +0,0 @@
|
||||
change_pow(s::S, n::Integer) where S<:GSymbol = S(s.id, n)
|
||||
|
||||
function Base.iterate(s::GS, i=1) where GS<:GSymbol
|
||||
return i <= abs(s.pow) ? (change_pow(s, sign(s.pow)), i+1) : nothing
|
||||
end
|
||||
Base.size(s::GSymbol) = (abs(s.pow), )
|
||||
Base.length(s::GSymbol) = first(size(s))
|
||||
|
||||
Base.eltype(s::GS) where GS<:GSymbol = GS
|
||||
|
||||
Base.isone(s::GSymbol) = iszero(s.pow)
|
||||
Base.inv(s::GSymbol) = change_pow(s, -s.pow)
|
||||
Base.hash(s::S, h::UInt) where S<:GSymbol = hash(s.id, hash(s.pow, hash(S, h)))
|
||||
|
||||
function (==)(s::GSymbol, t::GSymbol)
|
||||
isone(s) && isone(t) && return true
|
||||
s.pow == t.pow && s.id == t.id && return true
|
||||
return false
|
||||
end
|
||||
|
||||
Base.convert(::Type{GS}, s::GSymbol) where GS<:GSymbol = GS(s.id, s.pow)
|
||||
Base.convert(::Type{GS}, s::GS) where GS<:GSymbol = s
|
239
src/types.jl
239
src/types.jl
@ -1,43 +1,210 @@
|
||||
abstract type AbstractFPGroup <: Group end
|
||||
## "Abstract" definitions
|
||||
|
||||
"""
|
||||
::GSymbol
|
||||
Represents a syllable. Abstract type which all group symbols of
|
||||
`AbstractFPGroups` should subtype. Each concrete subtype should implement fields:
|
||||
* `id` which is the `Symbol` representation/identification of a symbol
|
||||
* `pow` which is the (multiplicative) exponent of a symbol.
|
||||
AbstractFPGroup
|
||||
|
||||
An Abstract type representing finitely presented groups. Every instance `` must implement
|
||||
* `KnuthBendix.alphabet(G::MyFPGroup)`
|
||||
* `rewriting(G::MyFPGroup)` : return the rewriting object which must implement
|
||||
> `KnuthBendix.rewrite_from_left!(u, v, rewriting(G))`.
|
||||
By default `alphabet(G)` is returned, which amounts to free rewriting in `G`.
|
||||
* `relations(G::MyFPGroup)` : return a set of defining relations.
|
||||
|
||||
AbstractFPGroup may also override `word_type(::Type{MyFPGroup}) = Word{UInt16}`,
|
||||
which controls the word type used for group elements. If a group has more than `255` generators you need to define e.g.
|
||||
> `word_type(::Type{MyFPGroup}) = Word{UInt16}`
|
||||
"""
|
||||
abstract type GSymbol end
|
||||
abstract type AbstractFPGroup <: GroupsCore.Group end
|
||||
|
||||
abstract type GWord{T<:GSymbol} <: GroupElem end
|
||||
word_type(G::AbstractFPGroup) = word_type(typeof(G))
|
||||
# the default:
|
||||
word_type(::Type{<:AbstractFPGroup}) = Word{UInt8}
|
||||
|
||||
"""
|
||||
W::GroupWord{T} <: GWord{T<:GSymbol} <:GroupElem
|
||||
Basic representation of element of a finitely presented group.
|
||||
* `syllables(W)` return particular group syllables which multiplied constitute `W`
|
||||
group as a word in generators.
|
||||
* `parent(W)` return the parent group.
|
||||
# the default (results in free rewriting)
|
||||
rewriting(G::AbstractFPGroup) = alphabet(G)
|
||||
|
||||
As the reduction (inside the parent group) of word to normal form may be time
|
||||
consuming, we provide a shortcut that is useful in practice:
|
||||
`savehash!(W, h)` and `ismodified(W)` functions.
|
||||
When computing `hash(W)`, a reduction to normal form is performed and a
|
||||
persistent hash is stored inside `W`, setting `ismodified(W)` flag to `false`.
|
||||
This hash can be accessed by `savedhash(W)`.
|
||||
Future comparisons of `W` try not to perform reduction and use the stored hash as shortcut. Only when hashes collide reduction is performed. Whenever word `W` is
|
||||
changed, `ismodified(W)` returns `false` and stored hash is invalidated.
|
||||
"""
|
||||
|
||||
mutable struct GroupWord{T} <: GWord{T}
|
||||
symbols::Vector{T}
|
||||
modified::Bool
|
||||
savedhash::UInt
|
||||
parent::Group
|
||||
|
||||
function GroupWord{T}(symbols::AbstractVector{<:GSymbol}) where T
|
||||
return new{T}(symbols, true, zero(UInt))
|
||||
end
|
||||
GroupWord(v::AbstractVector{T}) where T<:GSymbol = GroupWord{T}(v)
|
||||
GroupWord{T}(s::GSymbol) where T<:GSymbol = GroupWord{T}(T[s])
|
||||
GroupWord(s::T) where T<:GSymbol = GroupWord{T}(s)
|
||||
Base.@propagate_inbounds function (G::AbstractFPGroup)(word::AbstractVector{<:Integer})
|
||||
@boundscheck @assert all(l -> 1 <= l <= length(KnuthBendix.alphabet(G)), word)
|
||||
return FPGroupElement(word_type(G)(word), G)
|
||||
end
|
||||
|
||||
## Group Interface
|
||||
|
||||
Base.one(G::AbstractFPGroup) = FPGroupElement(one(word_type(G)), G)
|
||||
|
||||
Base.eltype(::Type{FPG}) where {FPG<:AbstractFPGroup} = FPGroupElement{FPG,word_type(FPG)}
|
||||
|
||||
include("iteration.jl")
|
||||
|
||||
GroupsCore.ngens(G::AbstractFPGroup) = length(G.gens)
|
||||
|
||||
function GroupsCore.gens(G::AbstractFPGroup, i::Integer)
|
||||
@boundscheck 1 <= i <= GroupsCore.ngens(G)
|
||||
l = alphabet(G)[G.gens[i]]
|
||||
return FPGroupElement(word_type(G)([l]), G)
|
||||
end
|
||||
GroupsCore.gens(G::AbstractFPGroup) = [gens(G, i) for i in 1:GroupsCore.ngens(G)]
|
||||
|
||||
# TODO: ProductReplacementAlgorithm
|
||||
function Base.rand(rng::Random.AbstractRNG, rs::Random.SamplerTrivial{<:AbstractFPGroup})
|
||||
l = rand(10:100)
|
||||
G = rs[]
|
||||
nletters = length(alphabet(G))
|
||||
return FPGroupElement(word_type(G)(rand(1:nletters, l)), G)
|
||||
end
|
||||
|
||||
Base.isfinite(::AbstractFPGroup) = (@warn "using generic isfinite(::AbstractFPGroup): the returned `false` might be wrong"; false)
|
||||
|
||||
## FPGroupElement
|
||||
|
||||
mutable struct FPGroupElement{G<:AbstractFPGroup,W<:AbstractWord} <: GroupElement
|
||||
word::W
|
||||
savedhash::UInt
|
||||
parent::G
|
||||
|
||||
FPGroupElement(word::W, G::AbstractFPGroup) where {W<:AbstractWord} =
|
||||
new{typeof(G),W}(word, UInt(0), G)
|
||||
|
||||
FPGroupElement(word::W, hash::UInt, G::AbstractFPGroup) where {W<:AbstractWord} =
|
||||
new{typeof(G),W}(word, hash, G)
|
||||
end
|
||||
|
||||
word(f::FPGroupElement) = f.word
|
||||
|
||||
#convenience
|
||||
KnuthBendix.alphabet(g::FPGroupElement) = alphabet(parent(g))
|
||||
|
||||
function Base.show(io::IO, f::FPGroupElement)
|
||||
f = normalform!(f)
|
||||
KnuthBendix.print_repr(io, word(f), alphabet(f))
|
||||
end
|
||||
|
||||
## GroupElement Interface for FPGroupElement
|
||||
|
||||
Base.parent(f::FPGroupElement) = f.parent
|
||||
GroupsCore.parent_type(::Type{<:FPGroupElement{G}}) where {G} = G
|
||||
|
||||
function Base.:(==)(g::FPGroupElement, h::FPGroupElement)
|
||||
@boundscheck @assert parent(g) === parent(h)
|
||||
normalform!(g)
|
||||
normalform!(h)
|
||||
hash(g) != hash(h) && return false
|
||||
return word(g) == word(h)
|
||||
end
|
||||
|
||||
function Base.deepcopy_internal(g::FPGroupElement, stackdict::IdDict)
|
||||
return FPGroupElement(copy(word(g)), g.savedhash, parent(g))
|
||||
end
|
||||
|
||||
Base.inv(g::FPGroupElement) = (G = parent(g); FPGroupElement(inv(alphabet(G), word(g)), G))
|
||||
|
||||
function Base.:(*)(g::FPGroupElement, h::FPGroupElement)
|
||||
@boundscheck @assert parent(g) === parent(h)
|
||||
return FPGroupElement(word(g) * word(h), parent(g))
|
||||
end
|
||||
|
||||
GroupsCore.isfiniteorder(g::FPGroupElement) = isone(g) ? true : (@warn "using generic isfiniteorder(::FPGroupElement): the returned `false` might be wrong"; false)
|
||||
|
||||
# additional methods:
|
||||
Base.isone(g::FPGroupElement) = (normalform!(g); isempty(word(g)))
|
||||
|
||||
## Free Groups
|
||||
|
||||
struct FreeGroup{T} <: AbstractFPGroup
|
||||
gens::Vector{T}
|
||||
alphabet::KnuthBendix.Alphabet{T}
|
||||
|
||||
function FreeGroup(gens, A::KnuthBendix.Alphabet) where {W}
|
||||
@assert length(gens) == length(unique(gens))
|
||||
@assert all(l -> l in KnuthBendix.letters(A), gens)
|
||||
return new{eltype(gens)}(gens, A)
|
||||
end
|
||||
end
|
||||
|
||||
function FreeGroup(A::Alphabet)
|
||||
@boundscheck @assert all(KnuthBendix.hasinverse(l, A) for l in KnuthBendix.letters(A))
|
||||
ltrs = KnuthBendix.letters(A)
|
||||
gens = Vector{eltype(ltrs)}()
|
||||
invs = Vector{eltype(ltrs)}()
|
||||
for l in ltrs
|
||||
l ∈ invs && continue
|
||||
push!(gens, l)
|
||||
push!(invs, inv(A, l))
|
||||
end
|
||||
|
||||
return FreeGroup(gens, A)
|
||||
end
|
||||
|
||||
function FreeGroup(n::Integer)
|
||||
symbols = Symbol[]
|
||||
inverses = Int[]
|
||||
sizehint!(symbols, 2n)
|
||||
sizehint!(inverses, 2n)
|
||||
for i in 1:n
|
||||
push!(symbols, Symbol(:f, i), Symbol(:F, i))
|
||||
push!(inverses, 2i, 2i-1)
|
||||
end
|
||||
return FreeGroup(symbols[1:2:2n], Alphabet(symbols, inverses))
|
||||
end
|
||||
|
||||
Base.show(io::IO, F::FreeGroup) = print(io, "free group on $(ngens(F)) generators")
|
||||
|
||||
# mandatory methods:
|
||||
KnuthBendix.alphabet(F::FreeGroup) = F.alphabet
|
||||
relations(F::FreeGroup) = Pair{eltype(F)}[]
|
||||
|
||||
# GroupsCore interface:
|
||||
# these are mathematically correct
|
||||
Base.isfinite(::FreeGroup) = false
|
||||
|
||||
GroupsCore.isfiniteorder(g::FPGroupElement{<:FreeGroup}) = isone(g) ? true : false
|
||||
|
||||
## FP Groups
|
||||
|
||||
struct FPGroup{T,R,S} <: AbstractFPGroup
|
||||
gens::Vector{T}
|
||||
relations::Vector{Pair{S,S}}
|
||||
rws::R
|
||||
end
|
||||
|
||||
KnuthBendix.alphabet(G::FPGroup) = alphabet(rewriting(G))
|
||||
rewriting(G::FPGroup) = G.rws
|
||||
|
||||
relations(G::FPGroup) = G.relations
|
||||
|
||||
function FPGroup(
|
||||
G::AbstractFPGroup,
|
||||
rels::AbstractVector{<:Pair{GEl,GEl}};
|
||||
ordering = KnuthBendix.LenLex,
|
||||
kwargs...,
|
||||
) where {GEl<:FPGroupElement}
|
||||
|
||||
O = ordering(alphabet(G))
|
||||
for (lhs, rhs) in rels
|
||||
@assert parent(lhs) === parent(rhs) === G
|
||||
end
|
||||
word_rels = [word(lhs) => word(rhs) for (lhs, rhs) in [relations(G); rels]]
|
||||
rws = KnuthBendix.RewritingSystem(word_rels, O)
|
||||
|
||||
KnuthBendix.knuthbendix!(rws; kwargs...)
|
||||
|
||||
return FPGroup(G.gens, rels, rws)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, G::FPGroup)
|
||||
print(io, "⟨")
|
||||
join(io, gens(G), ", ")
|
||||
print(io, " | ")
|
||||
join(io, relations(G), ", ")
|
||||
print(io, "⟩")
|
||||
end
|
||||
|
||||
## GSymbol aka letter of alphabet
|
||||
|
||||
abstract type GSymbol end
|
||||
Base.literal_pow(::typeof(^), t::GSymbol, ::Val{-1}) = inv(t)
|
||||
|
||||
function subscriptify(n::Integer)
|
||||
subscript_0 = Int(0x2080) # Char(0x2080) -> subscript 0
|
||||
return join([Char(subscript_0 + i) for i in reverse(digits(n))], "")
|
||||
end
|
||||
|
62
src/wl_ball.jl
Normal file
62
src/wl_ball.jl
Normal file
@ -0,0 +1,62 @@
|
||||
"""
|
||||
wlmetric_ball(S::AbstractVector{<:GroupElem}
|
||||
[, center=one(first(S)); radius=2, op=*])
|
||||
Compute metric ball as a list of elements of non-decreasing length, given the
|
||||
word-length metric on the group generated by `S`. The ball is centered at `center`
|
||||
(by default: the identity element). `radius` and `op` keywords specify the
|
||||
radius and multiplication operation to be used.
|
||||
"""
|
||||
function wlmetric_ball_serial(S::AbstractVector{T}; radius = 2, op = *) where {T}
|
||||
@assert radius > 0
|
||||
old = unique!([one(first(S)), S...])
|
||||
sizes = [1, length(old)]
|
||||
for i in 2:radius
|
||||
new = collect(op(o, s) for o in @view(old[sizes[end-1]:end]) for s in S)
|
||||
append!(old, new)
|
||||
resize!(new, 0)
|
||||
old = unique!(old)
|
||||
push!(sizes, length(old))
|
||||
end
|
||||
return old, sizes[2:end]
|
||||
end
|
||||
|
||||
function wlmetric_ball_thr(S::AbstractVector{T}; radius = 2, op = *) where {T}
|
||||
@assert radius > 0
|
||||
old = unique!([one(first(S)), S...])
|
||||
sizes = [1, length(old)]
|
||||
for r in 2:radius
|
||||
begin
|
||||
new =
|
||||
ThreadsX.collect(op(o, s) for o in @view(old[sizes[end-1]:end]) for s in S)
|
||||
ThreadsX.foreach(hash, new)
|
||||
end
|
||||
append!(old, new)
|
||||
resize!(new, 0)
|
||||
old = ThreadsX.unique(old)
|
||||
push!(sizes, length(old))
|
||||
end
|
||||
return old, sizes[2:end]
|
||||
end
|
||||
|
||||
function wlmetric_ball_serial(S::AbstractVector{T}, center::T; radius = 2, op = *) where {T}
|
||||
E, sizes = wlmetric_ball_serial(S, radius = radius, op = op)
|
||||
isone(center) && return E, sizes
|
||||
return c .* E, sizes
|
||||
end
|
||||
|
||||
function wlmetric_ball_thr(S::AbstractVector{T}, center::T; radius = 2, op = *) where {T}
|
||||
E, sizes = wlmetric_ball_thr(S, radius = radius, op = op)
|
||||
isone(center) && return E, sizes
|
||||
return c .* E, sizes
|
||||
end
|
||||
|
||||
function wlmetric_ball(
|
||||
S::AbstractVector{T},
|
||||
center::T = one(first(S));
|
||||
radius = 2,
|
||||
op = *,
|
||||
threading = true,
|
||||
) where {T}
|
||||
threading && return wlmetric_ball_thr(S, center, radius = radius, op = op)
|
||||
return wlmetric_ball_serial(S, center, radius = radius, op = op)
|
||||
end
|
43
src/words.jl
43
src/words.jl
@ -1,43 +0,0 @@
|
||||
syllablelength(w::GWord) = length(w.symbols)
|
||||
syllables(w::GWord) = w.symbols
|
||||
ismodified(w::GWord) = w.modified
|
||||
setmodified!(w::GWord) = (w.modified = true; w)
|
||||
unsetmodified!(w::GWord) = (w.modified = false; w)
|
||||
savehash!(w::GWord, h::UInt) = (w.savedhash = h; w)
|
||||
savedhash(w::GWord) = w.savedhash
|
||||
parent(w::GWord) = w.parent
|
||||
hasparent(w::GWord) = isdefined(w, :parent)
|
||||
setparent!(w::GWord, G::AbstractFPGroup) = (w.parent = G; w)
|
||||
|
||||
Base.isempty(w::GWord) = isempty(syllables(w))
|
||||
Base.isone(w::GWord) = (freereduce!(w); isempty(w))
|
||||
Base.one(w::GWord) = one(parent(w))
|
||||
|
||||
function Base.iterate(w::GWord, state=(syllable=1, pow=1))
|
||||
state.syllable > syllablelength(w) && return nothing
|
||||
next = iterate(syllables(w)[state.syllable], state.pow)
|
||||
next === nothing && return iterate(w, (syllable=state.syllable+1, pow=1))
|
||||
return first(next), (syllable=state.syllable, pow=last(next))
|
||||
end
|
||||
|
||||
Base.eltype(::Type{<:GWord{T}}) where T = T
|
||||
Base.length(w::GWord) = isempty(w) ? 0 : sum(length, syllables(w))
|
||||
Base.size(w::GWord) = (length(w),)
|
||||
Base.lastindex(w::GWord) = length(w)
|
||||
|
||||
Base.@propagate_inbounds function Base.getindex(w::GWord, i::Integer)
|
||||
csum = 0
|
||||
idx = 0
|
||||
@boundscheck 0 < i <= length(w) || throw(BoundsError(w, i))
|
||||
while csum < i
|
||||
idx += 1
|
||||
csum += length(syllables(w)[idx])
|
||||
end
|
||||
return first(syllables(w)[idx])
|
||||
end
|
||||
|
||||
Base.@propagate_inbounds Base.getindex(w::GWord, itr) = [w[i] for i in itr]
|
||||
|
||||
# no setindex! for syllable based words
|
||||
|
||||
Base.convert(::Type{GW}, s::GSymbol) where GW <: GWord = GW(s)
|
189
test/AutFn.jl
Normal file
189
test/AutFn.jl
Normal file
@ -0,0 +1,189 @@
|
||||
@testset "Automorphisms" begin
|
||||
|
||||
@testset "Transvections" begin
|
||||
|
||||
@test Groups.Transvection(:ϱ, 1, 2) isa Groups.GSymbol
|
||||
@test Groups.Transvection(:ϱ, 1, 2) isa Groups.Transvection
|
||||
@test Groups.Transvection(:λ, 1, 2) isa Groups.GSymbol
|
||||
@test Groups.Transvection(:λ, 1, 2) isa Groups.Transvection
|
||||
t = Groups.Transvection(:ϱ, 1, 2)
|
||||
@test inv(t) isa Groups.GSymbol
|
||||
@test inv(t) isa Groups.Transvection
|
||||
|
||||
@test t != inv(t)
|
||||
|
||||
s = Groups.Transvection(:ϱ, 1, 2)
|
||||
@test t == s
|
||||
@test hash(t) == hash(s)
|
||||
|
||||
s_ = Groups.Transvection(:ϱ, 1, 3)
|
||||
@test s_ != s
|
||||
@test hash(s_) != hash(s)
|
||||
|
||||
@test Groups.gersten_alphabet(3) isa Alphabet
|
||||
A = Groups.gersten_alphabet(3)
|
||||
@test length(A) == 12
|
||||
|
||||
@test sprint(show, Groups.ϱ(1, 2)) == "ϱ₁.₂"
|
||||
@test sprint(show, Groups.λ(3, 2)) == "λ₃.₂"
|
||||
end
|
||||
|
||||
A4 = Alphabet(
|
||||
[:a,:A,:b,:B,:c,:C,:d,:D],
|
||||
[ 2, 1, 4, 3, 6, 5, 8, 7]
|
||||
)
|
||||
|
||||
A5 = Alphabet(
|
||||
[:a,:A,:b,:B,:c,:C,:d,:D,:e,:E],
|
||||
[ 2, 1, 4, 3, 6, 5, 8, 7,10, 9]
|
||||
)
|
||||
|
||||
F4 = FreeGroup([:a, :b, :c, :d], A4)
|
||||
a,b,c,d = gens(F4)
|
||||
D = ntuple(i->gens(F4, i), 4)
|
||||
|
||||
@testset "Transvection action correctness" begin
|
||||
i,j = 1,2
|
||||
r = Groups.Transvection(:ϱ,i,j)
|
||||
l = Groups.Transvection(:λ,i,j)
|
||||
|
||||
(t::Groups.Transvection)(v::Tuple) = Groups.evaluate!(v, t, A4)
|
||||
|
||||
@test r(deepcopy(D)) == (a*b, b, c, d)
|
||||
@test inv(r)(deepcopy(D)) == (a*b^-1,b, c, d)
|
||||
@test l(deepcopy(D)) == (b*a, b, c, d)
|
||||
@test inv(l)(deepcopy(D)) == (b^-1*a,b, c, d)
|
||||
|
||||
i,j = 3,1
|
||||
r = Groups.Transvection(:ϱ,i,j)
|
||||
l = Groups.Transvection(:λ,i,j)
|
||||
@test r(deepcopy(D)) == (a, b, c*a, d)
|
||||
@test inv(r)(deepcopy(D)) == (a, b, c*a^-1,d)
|
||||
@test l(deepcopy(D)) == (a, b, a*c, d)
|
||||
@test inv(l)(deepcopy(D)) == (a, b, a^-1*c,d)
|
||||
|
||||
i,j = 4,3
|
||||
r = Groups.Transvection(:ϱ,i,j)
|
||||
l = Groups.Transvection(:λ,i,j)
|
||||
@test r(deepcopy(D)) == (a, b, c, d*c)
|
||||
@test inv(r)(deepcopy(D)) == (a, b, c, d*c^-1)
|
||||
@test l(deepcopy(D)) == (a, b, c, c*d)
|
||||
@test inv(l)(deepcopy(D)) == (a, b, c, c^-1*d)
|
||||
|
||||
i,j = 2,4
|
||||
r = Groups.Transvection(:ϱ,i,j)
|
||||
l = Groups.Transvection(:λ,i,j)
|
||||
@test r(deepcopy(D)) == (a, b*d, c, d)
|
||||
@test inv(r)(deepcopy(D)) == (a, b*d^-1,c, d)
|
||||
@test l(deepcopy(D)) == (a, d*b, c, d)
|
||||
@test inv(l)(deepcopy(D)) == (a, d^-1*b,c, d)
|
||||
end
|
||||
|
||||
A = SpecialAutomorphismGroup(F4, maxrules=1000)
|
||||
|
||||
@testset "AutomorphismGroup constructors" begin
|
||||
@test A isa Groups.AbstractFPGroup
|
||||
@test A isa AutomorphismGroup
|
||||
@test alphabet(A) isa Alphabet
|
||||
@test Groups.relations(A) isa Vector{<:Pair}
|
||||
@test sprint(show, A) == "automorphism group of free group on 4 generators"
|
||||
end
|
||||
|
||||
@testset "Automorphisms: hash and evaluate" begin
|
||||
@test Groups.domain(gens(A, 1)) == D
|
||||
g, h = gens(A, 1), gens(A, 8)
|
||||
|
||||
@test evaluate(g*h) == evaluate(h*g)
|
||||
@test (g*h).savedhash == zero(UInt)
|
||||
|
||||
@test sprint(show, typeof(g)) == "Automorphism{FreeGroup{Symbol},…}"
|
||||
|
||||
a = g*h
|
||||
b = h*g
|
||||
@test hash(a) != zero(UInt)
|
||||
@test hash(a) == hash(b)
|
||||
@test a.savedhash == b.savedhash
|
||||
|
||||
@test length(unique([a,b])) == 1
|
||||
@test length(unique([g*h, h*g])) == 1
|
||||
|
||||
# Not so simple arithmetic: applying starting on the left:
|
||||
# ϱ₁₂*ϱ₂₁⁻¹*λ₁₂*ε₂ == σ₂₁₃₄
|
||||
|
||||
g = gens(A, 1)
|
||||
x1, x2, x3, x4 = Groups.domain(g)
|
||||
@test evaluate(g) == (x1*x2, x2, x3, x4)
|
||||
|
||||
g = g*inv(gens(A, 4)) # ϱ₂₁
|
||||
@test evaluate(g) == (x1*x2, x1^-1, x3, x4)
|
||||
|
||||
g = g*gens(A, 13)
|
||||
@test evaluate(g) == (x2, x1^-1, x3, x4)
|
||||
end
|
||||
|
||||
@testset "Automorphisms: SAut(F₄)" begin
|
||||
N = 4
|
||||
G = SpecialAutomorphismGroup(FreeGroup(N))
|
||||
|
||||
S = gens(G)
|
||||
@test S isa Vector{<:FPGroupElement{<:AutomorphismGroup{<:FreeGroup}}}
|
||||
|
||||
@test length(S) == 2*N*(N-1)
|
||||
@test length(unique(S)) == length(S)
|
||||
|
||||
S_sym = [S; inv.(S)]
|
||||
@test length(S_sym) == length(unique(S_sym))
|
||||
|
||||
pushfirst!(S_sym, one(G))
|
||||
|
||||
B_2 = [i*j for (i,j) in Base.product(S_sym, S_sym)]
|
||||
@test length(B_2) == 2401
|
||||
@test length(unique(B_2)) == 1777
|
||||
|
||||
@test all(g->isone(inv(g)*g), B_2)
|
||||
@test all(g->isone(g*inv(g)), B_2)
|
||||
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))
|
||||
|
||||
test_GroupElement_interface(g, h)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# using Random
|
||||
# using GroupsCore
|
||||
#
|
||||
# A = New.SpecialAutomorphismGroup(FreeGroup(4), maxrules=2000, ordering=KnuthBendix.RecursivePathOrder)
|
||||
#
|
||||
# # for seed in 1:1000
|
||||
# let seed = 68
|
||||
# N = 14
|
||||
# Random.seed!(seed)
|
||||
# g = A(rand(1:length(KnuthBendix.alphabet(A)), N))
|
||||
# h = A(rand(1:length(KnuthBendix.alphabet(A)), N))
|
||||
# @info "seed=$seed" g h
|
||||
# @time isone(g*inv(g))
|
||||
# @time isone(inv(g)*g)
|
||||
# @info "" length(word(New.normalform!(g*inv(g)))) length(word(New.normalform!(inv(g)*g)))
|
||||
# a = commutator(g, h, g)
|
||||
# b = conj(inv(g), h) * conj(conj(g, h), g)
|
||||
#
|
||||
# @info length(word(a))
|
||||
# @info length(word(b))
|
||||
#
|
||||
# w = a*inv(b)
|
||||
# @info length(word(w))
|
||||
# New.normalform!(w)
|
||||
# @info length(word(w))
|
||||
#
|
||||
#
|
||||
# #
|
||||
# # @time ima = evaluate(a)
|
||||
# # @time imb = evaluate(b)
|
||||
# # @info "" a b ima imb
|
||||
# # @time a == b
|
||||
# end
|
@ -1,286 +0,0 @@
|
||||
@testset "Automorphisms" begin
|
||||
|
||||
G = SymmetricGroup(Int8(4))
|
||||
|
||||
@testset "AutSymbol" begin
|
||||
@test_throws MethodError Groups.AutSymbol(:a)
|
||||
@test_throws MethodError Groups.AutSymbol(:a, 1)
|
||||
f = Groups.AutSymbol(:a, 1, Groups.FlipAut(2))
|
||||
@test f isa Groups.GSymbol
|
||||
@test f isa Groups.AutSymbol
|
||||
@test Groups.AutSymbol(perm"(4)") isa Groups.AutSymbol
|
||||
@test Groups.AutSymbol(perm"(1,2,3,4)") isa Groups.AutSymbol
|
||||
@test Groups.transvection_R(1,2) isa Groups.AutSymbol
|
||||
@test Groups.transvection_R(3,4) isa Groups.AutSymbol
|
||||
@test Groups.flip(3) isa Groups.AutSymbol
|
||||
|
||||
@test Groups.id_autsymbol() isa Groups.AutSymbol
|
||||
@test inv(Groups.id_autsymbol()) isa Groups.AutSymbol
|
||||
x = Groups.id_autsymbol()
|
||||
@test inv(x) == Groups.id_autsymbol()
|
||||
end
|
||||
|
||||
a,b,c,d = gens(FreeGroup(4))
|
||||
D = NTuple{4,FreeGroupElem}([a,b,c,d])
|
||||
|
||||
@testset "flip correctness" begin
|
||||
@test Groups.flip(1)(deepcopy(D)) == (a^-1, b,c,d)
|
||||
@test Groups.flip(2)(deepcopy(D)) == (a, b^-1,c,d)
|
||||
@test Groups.flip(3)(deepcopy(D)) == (a, b,c^-1,d)
|
||||
@test Groups.flip(4)(deepcopy(D)) == (a, b,c,d^-1)
|
||||
@test inv(Groups.flip(1))(deepcopy(D)) == (a^-1, b,c,d)
|
||||
@test inv(Groups.flip(2))(deepcopy(D)) == (a, b^-1,c,d)
|
||||
@test inv(Groups.flip(3))(deepcopy(D)) == (a, b,c^-1,d)
|
||||
@test inv(Groups.flip(4))(deepcopy(D)) == (a, b,c,d^-1)
|
||||
end
|
||||
|
||||
@testset "perm correctness" begin
|
||||
σ = Groups.AutSymbol(perm"(4)")
|
||||
@test σ(deepcopy(D)) == deepcopy(D)
|
||||
@test inv(σ)(deepcopy(D)) == deepcopy(D)
|
||||
|
||||
σ = Groups.AutSymbol(perm"(1,2,3,4)")
|
||||
@test σ(deepcopy(D)) == (b, c, d, a)
|
||||
@test inv(σ)(deepcopy(D)) == (d, a, b, c)
|
||||
|
||||
σ = Groups.AutSymbol(perm"(1,2)(4,3)")
|
||||
@test σ(deepcopy(D)) == (b, a, d, c)
|
||||
@test inv(σ)(deepcopy(D)) == (b, a, d, c)
|
||||
|
||||
σ = Groups.AutSymbol(perm"(1,2,3)(4)")
|
||||
@test σ(deepcopy(D)) == (b, c, a, d)
|
||||
@test inv(σ)(deepcopy(D)) == (c, a, b, d)
|
||||
end
|
||||
|
||||
@testset "rmul/transvection_R correctness" begin
|
||||
i,j = 1,2
|
||||
r = Groups.transvection_R(i,j)
|
||||
l = Groups.transvection_L(i,j)
|
||||
@test r(deepcopy(D)) == (a*b, b, c, d)
|
||||
@test inv(r)(deepcopy(D)) == (a*b^-1,b, c, d)
|
||||
@test l(deepcopy(D)) == (b*a, b, c, d)
|
||||
@test inv(l)(deepcopy(D)) == (b^-1*a,b, c, d)
|
||||
|
||||
i,j = 3,1
|
||||
r = Groups.transvection_R(i,j)
|
||||
l = Groups.transvection_L(i,j)
|
||||
@test r(deepcopy(D)) == (a, b, c*a, d)
|
||||
@test inv(r)(deepcopy(D)) == (a, b, c*a^-1,d)
|
||||
@test l(deepcopy(D)) == (a, b, a*c, d)
|
||||
@test inv(l)(deepcopy(D)) == (a, b, a^-1*c,d)
|
||||
|
||||
i,j = 4,3
|
||||
r = Groups.transvection_R(i,j)
|
||||
l = Groups.transvection_L(i,j)
|
||||
@test r(deepcopy(D)) == (a, b, c, d*c)
|
||||
@test inv(r)(deepcopy(D)) == (a, b, c, d*c^-1)
|
||||
@test l(deepcopy(D)) == (a, b, c, c*d)
|
||||
@test inv(l)(deepcopy(D)) == (a, b, c, c^-1*d)
|
||||
|
||||
i,j = 2,4
|
||||
r = Groups.transvection_R(i,j)
|
||||
l = Groups.transvection_L(i,j)
|
||||
@test r(deepcopy(D)) == (a, b*d, c, d)
|
||||
@test inv(r)(deepcopy(D)) == (a, b*d^-1,c, d)
|
||||
@test l(deepcopy(D)) == (a, d*b, c, d)
|
||||
@test inv(l)(deepcopy(D)) == (a, d^-1*b,c, d)
|
||||
end
|
||||
|
||||
@testset "AutGroup/Automorphism constructors" begin
|
||||
|
||||
f = Groups.AutSymbol(:a, 1, Groups.FlipAut(1))
|
||||
@test isa(Automorphism{3}(f), Groups.GWord)
|
||||
@test isa(Automorphism{3}(f), Automorphism)
|
||||
@test isa(AutGroup(FreeGroup(3)), AbstractAlgebra.Group)
|
||||
@test isa(AutGroup(FreeGroup(1)), Groups.AbstractFPGroup)
|
||||
|
||||
A = AutGroup(FreeGroup(1))
|
||||
@test Groups.gens(A) isa Vector{Automorphism{1}}
|
||||
@test length(Groups.gens(A)) == 1
|
||||
@test length(Groups.gens(Aut(FreeGroup(1)))) == 1
|
||||
@test Groups.gens(A) == [A(Groups.flip(1))]
|
||||
|
||||
A = AutGroup(FreeGroup(1), special=true)
|
||||
@test isempty(Groups.gens(A))
|
||||
@test Groups.gens(SAut(FreeGroup(1))) == Automorphism{1}[]
|
||||
|
||||
A = AutGroup(FreeGroup(2))
|
||||
@test length(Groups.gens(A)) == 7
|
||||
Agens = Groups.gens(A)
|
||||
@test A(first(Agens)) isa Automorphism
|
||||
|
||||
@test A(Groups.transvection_R(1,2)) isa Automorphism
|
||||
@test A(Groups.transvection_R(1,2)) in Agens
|
||||
|
||||
@test A(Groups.transvection_R(2,1)) isa Automorphism
|
||||
@test A(Groups.transvection_R(2,1)) in Agens
|
||||
|
||||
@test A(Groups.transvection_R(1,2)) isa Automorphism
|
||||
@test A(Groups.transvection_R(1,2)) in Agens
|
||||
|
||||
@test A(Groups.transvection_R(2,1)) isa Automorphism
|
||||
@test A(Groups.transvection_R(2,1)) in Agens
|
||||
|
||||
@test A(Groups.flip(1)) isa Automorphism
|
||||
@test A(Groups.flip(1)) in Agens
|
||||
|
||||
@test A(Groups.flip(2)) isa Automorphism
|
||||
@test A(Groups.flip(2)) in Agens
|
||||
|
||||
@test A(Groups.AutSymbol(perm"(1,2)")) isa Automorphism
|
||||
@test A(Groups.AutSymbol(perm"(1,2)")) in Agens
|
||||
|
||||
@test A(Groups.id_autsymbol()) isa Automorphism
|
||||
end
|
||||
|
||||
A = AutGroup(FreeGroup(4))
|
||||
|
||||
@testset "eltary functions" begin
|
||||
|
||||
f = Groups.AutSymbol(perm"(1,2,3,4)")
|
||||
@test (Groups.change_pow(f, 2)).pow == 1
|
||||
@test (Groups.change_pow(f, -2)).pow == 1
|
||||
@test (inv(f)).pow == 1
|
||||
|
||||
g = Groups.AutSymbol(perm"(1,2)(3,4)")
|
||||
@test isa(inv(g), Groups.AutSymbol)
|
||||
|
||||
@test_throws MethodError g*f
|
||||
|
||||
@test A(g)^-1 == A(inv(g))
|
||||
|
||||
h = Groups.transvection_R(1,2)
|
||||
|
||||
@test collect(A(g)*A(h)) == [g, h]
|
||||
@test collect(A(h)^2) == [h, h]
|
||||
end
|
||||
|
||||
@testset "reductions/arithmetic" begin
|
||||
f = Groups.AutSymbol(perm"(1,2,3,4)")
|
||||
|
||||
f² = push!(A(f), f)
|
||||
@test Groups.simplifyperms!(Bool, f²) == false
|
||||
@test f²^2 == one(A)
|
||||
@test !isone(f²)
|
||||
|
||||
a = A(Groups.λ(1,2))*Groups.ε(2)
|
||||
b = Groups.ε(2)*A(inv(Groups.λ(1,2)))
|
||||
@test a*b == b*a
|
||||
@test a^3 * b^3 == one(A)
|
||||
g,h = Groups.gens(A)[[1,8]] # (g, h) = (ϱ₁₂, ϱ₃₂)
|
||||
|
||||
@test Groups.domain(A) == NTuple{4, FreeGroupElem}(gens(A.objectGroup))
|
||||
|
||||
@test (g*h)(Groups.domain(A)) == (h*g)(Groups.domain(A))
|
||||
@test (g*h).savedhash == zero(UInt)
|
||||
@test (h*g).savedhash == zero(UInt)
|
||||
a = g*h
|
||||
b = h*g
|
||||
@test hash(a) != zero(UInt)
|
||||
@test hash(b) == hash(a)
|
||||
@test a.savedhash == b.savedhash
|
||||
@test length(unique([a,b])) == 1
|
||||
@test length(unique([g*h, h*g])) == 1
|
||||
|
||||
# Not so simple arithmetic: applying starting on the left:
|
||||
# ϱ₁₂*ϱ₂₁⁻¹*λ₁₂*ε₂ == σ₂₁₃₄
|
||||
|
||||
g = A(Groups.transvection_R(1,2))
|
||||
x1, x2, x3, x4 = Groups.domain(A)
|
||||
@test g(Groups.domain(A)) == (x1*x2, x2, x3, x4)
|
||||
g = g*inv(A(Groups.transvection_R(2,1)))
|
||||
@test g(Groups.domain(A)) == (x1*x2, x1^-1, x3, x4)
|
||||
g = g*A(Groups.transvection_L(1,2))
|
||||
@test g(Groups.domain(A)) == (x2, x1^-1, x3, x4)
|
||||
g = g*A(Groups.flip(2))
|
||||
@test g(Groups.domain(A)) == (x2, x1, x3, x4)
|
||||
|
||||
@test g(Groups.domain(A)) == A(Groups.AutSymbol(perm"(1,2)(4)"))(Groups.domain(A))
|
||||
|
||||
@test g == A(Groups.AutSymbol(perm"(1,2)(4)"))
|
||||
|
||||
g_im = g(Groups.domain(A))
|
||||
@test length.(g_im) == (1,1,1,1)
|
||||
|
||||
g = A(Groups.σ(perm"(1,2)(4)"))
|
||||
h = A(Groups.σ(perm"(2,3,4)"))
|
||||
@test g*h isa Groups.Automorphism{4}
|
||||
f = g*h
|
||||
Groups
|
||||
@test Groups.syllablelength(f) == 2
|
||||
@test Groups.reduce!(f) isa Groups.Automorphism{4}
|
||||
@test Groups.syllablelength(f) == 1
|
||||
end
|
||||
|
||||
@testset "specific Aut(F4) tests" begin
|
||||
N = 4
|
||||
G = AutGroup(FreeGroup(N))
|
||||
S = G.gens
|
||||
@test isa(S, Vector{Groups.AutSymbol})
|
||||
S = [G(s) for s in unique(S)]
|
||||
@test isa(S, Vector{Automorphism{N}})
|
||||
@test S == gens(G)
|
||||
@test length(S) == 51
|
||||
S_inv = [S..., [inv(s) for s in S]...]
|
||||
@test length(unique(S_inv)) == 75
|
||||
|
||||
G = AutGroup(FreeGroup(N), special=true)
|
||||
S = gens(G)
|
||||
S_inv = [one(G), S..., [inv(s) for s in S]...]
|
||||
S_inv = unique(S_inv)
|
||||
B_2 = [i*j for (i,j) in Base.product(S_inv, S_inv)]
|
||||
@test length(B_2) == 2401
|
||||
@test length(unique(B_2)) == 1777
|
||||
end
|
||||
|
||||
@testset "abelianization homomorphism" begin
|
||||
N = 4
|
||||
G = AutGroup(FreeGroup(N))
|
||||
S = unique([gens(G); inv.(gens(G))])
|
||||
R = 3
|
||||
|
||||
@test Groups.abelianize(one(G)) isa Matrix{Int}
|
||||
@test Groups.abelianize(one(G)) == Matrix{Int}(I, N, N)
|
||||
|
||||
M = Matrix{Int}(I, N, N)
|
||||
M[1,2] = 1
|
||||
ϱ₁₂ = G(Groups.ϱ(1,2))
|
||||
λ₁₂ = G(Groups.λ(1,2))
|
||||
|
||||
@test Groups.abelianize(ϱ₁₂) == M
|
||||
@test Groups.abelianize(λ₁₂) == M
|
||||
|
||||
M[1,2] = -1
|
||||
|
||||
@test Groups.abelianize(ϱ₁₂^-1) == M
|
||||
@test Groups.abelianize(λ₁₂^-1) == M
|
||||
|
||||
@test Groups.abelianize(ϱ₁₂*λ₁₂^-1) == Matrix{Int}(I, N, N)
|
||||
@test Groups.abelianize(λ₁₂^-1*ϱ₁₂) == Matrix{Int}(I, N, N)
|
||||
|
||||
M = Matrix{Int}(I, N, N)
|
||||
M[2,2] = -1
|
||||
ε₂ = G(Groups.flip(2))
|
||||
|
||||
@test Groups.abelianize(ε₂) == M
|
||||
@test Groups.abelianize(ε₂^2) == Matrix{Int}(I, N, N)
|
||||
|
||||
M = [0 1 0 0; 0 0 0 1; 0 0 1 0; 1 0 0 0]
|
||||
|
||||
σ = G(Groups.AutSymbol(perm"(1,2,4)"))
|
||||
@test Groups.abelianize(σ) == M
|
||||
@test Groups.abelianize(σ^3) == Matrix{Int}(I, N, N)
|
||||
@test Groups.abelianize(σ)^3 == Matrix{Int}(I, N, N)
|
||||
|
||||
@test Groups.abelianize(G(Groups.id_autsymbol())) == Matrix{Int}(I, N, N)
|
||||
|
||||
function test_homomorphism(S, r)
|
||||
for elts in Iterators.product([[g for g in S] for _ in 1:r]...)
|
||||
prod(Groups.abelianize.(elts)) == Groups.abelianize(prod(elts)) || error("linear representaton test failed at $elts")
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
@test test_homomorphism(S, R) == 0
|
||||
end
|
||||
end
|
@ -1,89 +0,0 @@
|
||||
@testset "DirectPowers" begin
|
||||
|
||||
×(a,b) = Groups.DirectPower(a,b)
|
||||
|
||||
@testset "Constructors" begin
|
||||
G = SymmetricGroup(3)
|
||||
|
||||
@test Groups.DirectPowerGroup(G,2) isa AbstractAlgebra.Group
|
||||
@test G×G isa AbstractAlgebra.Group
|
||||
@test Groups.DirectPowerGroup(G,2) isa Groups.DirectPowerGroup{2, Generic.SymmetricGroup{Int64}}
|
||||
|
||||
@test (G×G)×G == DirectPowerGroup(G, 3)
|
||||
@test (G×G)×G == (G×G)×G
|
||||
|
||||
GG = DirectPowerGroup(G,2)
|
||||
@test one(G×G) isa GroupElem
|
||||
@test (G×G)((one(G), one(G))) isa GroupElem
|
||||
@test (G×G)([one(G), one(G)]) isa GroupElem
|
||||
|
||||
@test Groups.DirectPowerGroupElem((one(G), one(G))) == one(G×G)
|
||||
@test GG(one(G), one(G)) == one(G×G)
|
||||
|
||||
g = perm"(1,2,3)"
|
||||
|
||||
@test GG(g, g^2) isa GroupElem
|
||||
@test GG(g, g^2) isa Groups.DirectPowerGroupElem{2, Generic.Perm{Int64}}
|
||||
|
||||
h = GG(g,g^2)
|
||||
|
||||
@test h == GG(h)
|
||||
|
||||
@test GG(g, g^2) isa GroupElem
|
||||
@test GG(g, g^2) isa Groups.DirectPowerGroupElem
|
||||
|
||||
@test_throws MethodError GG(g,g,g)
|
||||
@test GG(g,g^2) == h
|
||||
|
||||
@test h[1] == g
|
||||
@test h[2] == g^2
|
||||
h = GG(g, one(G))
|
||||
@test h == GG(g, one(G))
|
||||
end
|
||||
|
||||
@testset "Basic arithmetic" begin
|
||||
G = SymmetricGroup(3)
|
||||
GG = G×G
|
||||
i = perm"(1,3)"
|
||||
g = perm"(1,2,3)"
|
||||
|
||||
h = GG(g,g^2)
|
||||
k = GG(g^3, g^2)
|
||||
|
||||
@test h^2 == GG(g^2,g)
|
||||
@test h^6 == one(GG)
|
||||
|
||||
@test h*h == h^2
|
||||
@test h*k == GG(g,g)
|
||||
|
||||
@test h*inv(h) == one(G×G)
|
||||
|
||||
w = GG(g,i)*GG(i,g)
|
||||
@test w == GG(perm"(1,2)(3)", perm"(2,3)")
|
||||
@test w == inv(w)
|
||||
@test w^2 == w*w == one(GG)
|
||||
end
|
||||
|
||||
@testset "elem/parent_types" begin
|
||||
G = SymmetricGroup(3)
|
||||
g = perm"(1,2,3)"
|
||||
|
||||
@test elem_type(G×G) == DirectPowerGroupElem{2, elem_type(G)}
|
||||
@test elem_type(G×G×G) == DirectPowerGroupElem{3, elem_type(G)}
|
||||
@test parent_type(typeof((G×G)(g,g^2))) == Groups.DirectPowerGroup{2, typeof(G)}
|
||||
@test parent(DirectPowerGroupElem((g,g^2,g^3))) == DirectPowerGroup(G,3)
|
||||
end
|
||||
|
||||
@testset "Misc" begin
|
||||
G = SymmetricGroup(3)
|
||||
GG = Groups.DirectPowerGroup(G,3)
|
||||
@test order(GG) == 216
|
||||
|
||||
@test isa(collect(GG), Vector{Groups.DirectPowerGroupElem{3, elem_type(G)}})
|
||||
elts = vec(collect(GG))
|
||||
|
||||
@test length(elts) == 216
|
||||
@test all([g*inv(g) == one(GG) for g in elts])
|
||||
@test all(inv(g*h) == inv(h)*inv(g) for g in elts for h in elts)
|
||||
end
|
||||
end
|
@ -1,18 +0,0 @@
|
||||
@testset "FPGroups definitions" begin
|
||||
F = FreeGroup(["a", "b", "c"])
|
||||
a,b,c = Groups.gens(F)
|
||||
R = [a^2, a*b*a, c*b*a]
|
||||
@test F/R isa FPGroup
|
||||
@test F isa FreeGroup
|
||||
G = F/R
|
||||
A,B,C = Groups.gens(G)
|
||||
|
||||
@test Groups.reduce!(A^2) == one(G)
|
||||
@test Groups.reduce!(A*B*A*A) == A
|
||||
@test Groups.reduce!(A*A*B*A) == A
|
||||
|
||||
@test Groups.freepreimage(G) == F
|
||||
@test Groups.freepreimage(B^2) == b^2
|
||||
|
||||
@test G/[B^2, C*B*C] isa FPGroup
|
||||
end
|
@ -1,188 +0,0 @@
|
||||
@testset "Groups.FreeSymbols" begin
|
||||
s = Groups.FreeSymbol(:s)
|
||||
t = Groups.FreeSymbol(:t)
|
||||
|
||||
@testset "constructors" begin
|
||||
@test isa(Groups.FreeSymbol(:aaaaaaaaaaaaaaaa), Groups.GSymbol)
|
||||
@test Groups.FreeSymbol(:abc).pow == 1
|
||||
@test isa(s, Groups.FreeSymbol)
|
||||
@test isa(t, Groups.FreeSymbol)
|
||||
end
|
||||
@testset "eltary functions" begin
|
||||
@test length(s) == 1
|
||||
@test Groups.change_pow(s, 0) == Groups.change_pow(t, 0)
|
||||
@test length(Groups.change_pow(s, 0)) == 0
|
||||
@test inv(s).pow == -1
|
||||
@test Groups.FreeSymbol(:s, 3) == Groups.change_pow(s, 3)
|
||||
@test Groups.FreeSymbol(:s, 3) != Groups.FreeSymbol(:t, 3)
|
||||
@test Groups.change_pow(inv(s), -3) == inv(Groups.change_pow(s, 3))
|
||||
end
|
||||
@testset "powers" begin
|
||||
s⁴ = Groups.change_pow(s,4)
|
||||
@test s⁴.pow == 4
|
||||
@test Groups.change_pow(s, 4) == Groups.FreeSymbol(:s, 4)
|
||||
end
|
||||
end
|
||||
|
||||
@testset "FreeGroupSymbols manipulation" begin
|
||||
s = Groups.FreeSymbol("s")
|
||||
t = Groups.FreeSymbol(:t, -2)
|
||||
|
||||
@test isa(Groups.GroupWord(s), Groups.GWord{Groups.FreeSymbol})
|
||||
@test isa(Groups.GroupWord(s), FreeGroupElem)
|
||||
@test isa(FreeGroupElem(s), Groups.GWord)
|
||||
@test isa(convert(FreeGroupElem, s), Groups.GWord)
|
||||
@test isa(convert(FreeGroupElem, s), FreeGroupElem)
|
||||
@test isa(Vector{FreeGroupElem}([s,t]), Vector{FreeGroupElem})
|
||||
@test length(FreeGroupElem(s)) == 1
|
||||
@test length(FreeGroupElem(t)) == 2
|
||||
@test Groups.FreeSymbol(:s, 1) != Groups.FreeSymbol(:s, 2)
|
||||
@test Groups.FreeSymbol(:s, 1) != Groups.FreeSymbol(:t, 1)
|
||||
@test collect(Groups.FreeSymbol(:s, 2)) == [i for i in Groups.FreeSymbol(:s, 2)] == [s, s]
|
||||
end
|
||||
|
||||
@testset "FreeGroup" begin
|
||||
@test isa(FreeGroup(["s", "t"]), AbstractAlgebra.Group)
|
||||
G = FreeGroup(["s", "t"])
|
||||
s, t = gens(G)
|
||||
|
||||
@testset "elements constructors" begin
|
||||
@test isa(one(G), FreeGroupElem)
|
||||
@test eltype(G.gens) == Groups.FreeSymbol
|
||||
@test length(G.gens) == 2
|
||||
@test eltype(gens(G)) == FreeGroupElem
|
||||
@test length(gens(G)) == 2
|
||||
|
||||
tt, ss = Groups.FreeSymbol(:t), Groups.FreeSymbol(:s)
|
||||
@test Groups.GroupWord([tt, inv(tt)]) isa FreeGroupElem
|
||||
|
||||
@test collect(s*t) == Groups.syllables(s*t)
|
||||
@test collect(t^2) == [tt, tt]
|
||||
ttinv = Groups.FreeSymbol(:t, -1)
|
||||
w = t^-2*s^3*t^2
|
||||
@test collect(w) == [inv(tt), inv(tt), ss, ss, ss, tt, tt]
|
||||
@test w[1] == inv(tt)
|
||||
@test w[2] == inv(tt)
|
||||
@test w[3] == ss
|
||||
@test w[3:5] == [ss, ss, ss]
|
||||
@test w[end] == tt
|
||||
|
||||
@test collect(ttinv) == [ttinv]
|
||||
|
||||
@test isone(t^0)
|
||||
@test !isone(t^1)
|
||||
end
|
||||
|
||||
@testset "internal arithmetic" begin
|
||||
|
||||
@test (s*s).symbols == (s^2).symbols
|
||||
@test hash([t^1,s^1]) == hash([t^2*inv(t),s*inv(s)*s])
|
||||
|
||||
t_symb = Groups.FreeSymbol(:t)
|
||||
tt = deepcopy(t)
|
||||
@test string(Groups.rmul!(tt, tt, inv(tt))) == "(id)"
|
||||
tt = deepcopy(t)
|
||||
@test string(Groups.lmul!(tt, tt, inv(tt))) == "(id)"
|
||||
|
||||
w = deepcopy(t)
|
||||
@test length(Groups.rmul!(w, t)) == 2
|
||||
@test length(Groups.lmul!(w, inv(t))) == 1
|
||||
w = AbstractAlgebra.mul!(w, w, s)
|
||||
@test length(w) == 2
|
||||
@test length(Groups.lmul!(w, inv(s))) == 3
|
||||
|
||||
tt = deepcopy(t)
|
||||
push!(tt, inv(t_symb))
|
||||
@test string(tt) == "t*t^-1"
|
||||
tt = deepcopy(t)
|
||||
pushfirst!(tt, inv(t_symb))
|
||||
@test string(tt) == "t^-1*t"
|
||||
|
||||
tt = deepcopy(t)
|
||||
append!(tt, inv(t))
|
||||
@test string(tt) == "t*t^-1"
|
||||
|
||||
tt = deepcopy(t)
|
||||
prepend!(tt, inv(t))
|
||||
@test string(tt) == "t^-1*t"
|
||||
|
||||
tt = deepcopy(t)
|
||||
append!(tt, s, inv(t))
|
||||
@test string(tt) == "t*s*t^-1"
|
||||
|
||||
o = one(t)
|
||||
o_inv = inv(o)
|
||||
@test o == o_inv
|
||||
@test o !== o_inv
|
||||
Groups.rmul!(o, t)
|
||||
@test o != o_inv
|
||||
end
|
||||
|
||||
@testset "reductions" begin
|
||||
@test length(one(G).symbols) == 0
|
||||
@test length((one(G)*one(G)).symbols) == 0
|
||||
@test one(G) == one(G)*one(G)
|
||||
w = deepcopy(s)
|
||||
push!(Groups.syllables(w), (s^-1).symbols[1])
|
||||
@test Groups.reduce!(w) == one(parent(w))
|
||||
o = (t*s)^3
|
||||
@test o == t*s*t*s*t*s
|
||||
p = (t*s)^-3
|
||||
@test p == s^-1*t^-1*s^-1*t^-1*s^-1*t^-1
|
||||
@test o*p == one(parent(o*p))
|
||||
w = FreeGroupElem([o.symbols..., p.symbols...])
|
||||
w.parent = G
|
||||
@test Groups.syllables(Groups.reduce(w)) == Vector{Groups.FreeSymbol}([])
|
||||
end
|
||||
|
||||
@testset "Group operations" begin
|
||||
@test parent(s) == G
|
||||
@test parent(s) === parent(deepcopy(s))
|
||||
@test isa(s*t, FreeGroupElem)
|
||||
@test parent(s*t) == parent(s^2)
|
||||
@test s*s == s^2
|
||||
@test inv(s*s) == inv(s^2)
|
||||
@test inv(s)^2 == inv(s^2)
|
||||
@test inv(s)*inv(s) == inv(s^2)
|
||||
@test inv(s*t) == inv(t)*inv(s)
|
||||
w = s*t*s^-1
|
||||
@test inv(w) == s*t^-1*s^-1
|
||||
@test (t*s*t^-1)^10 == t*s^10*t^-1
|
||||
@test (t*s*t^-1)^-10 == t*s^-10*t^-1
|
||||
end
|
||||
|
||||
@testset "replacements" begin
|
||||
a = Groups.FreeSymbol(:a)
|
||||
b = Groups.FreeSymbol(:b)
|
||||
@test Groups.issubsymbol(a, Groups.change_pow(a,2)) == true
|
||||
@test Groups.issubsymbol(a, Groups.change_pow(a,-2)) == false
|
||||
@test Groups.issubsymbol(b, Groups.change_pow(a,-2)) == false
|
||||
@test Groups.issubsymbol(inv(b), Groups.change_pow(b,-2)) == true
|
||||
|
||||
c = s*t*s^-1*t^-1
|
||||
@test findfirst(s^-1*t^-1, c) == 3
|
||||
@test findnext(s^-1*t^-1, c*s^-1,3) == 3
|
||||
@test findnext(s^-1*t^-1, c*s^-1*t^-1, 4) == 5
|
||||
@test findfirst(c, c*t) === nothing
|
||||
|
||||
@test findlast(s^-1*t^-1, c) == 3
|
||||
@test findprev(s, s*t*s*t, 4) == 3
|
||||
@test findprev(s*t, s*t*s*t, 2) == 1
|
||||
@test findprev(Groups.FreeSymbol(:t, 2), c, 4) === nothing
|
||||
|
||||
w = s*t*s^-1
|
||||
subst = Dict{FreeGroupElem, FreeGroupElem}(w => s^1, s*t^-1 => t^4)
|
||||
@test Groups.replace(c, s*t=>one(G)) == s^-1*t^-1
|
||||
@test Groups.replace(c, w=>subst[w]) == s*t^-1
|
||||
@test Groups.replace(s*c*t^-1, w=>subst[w]) == s^2*t^-2
|
||||
@test Groups.replace(t*c*t, w=>subst[w]) == t*s
|
||||
@test Groups.replace(s*c*s*c*s, subst) == s*t^4*s*t^4*s
|
||||
|
||||
G = FreeGroup(["x", "y"])
|
||||
x,y = gens(G)
|
||||
|
||||
@test Groups.replace(x*y^9, y^2=>y) == x*y^5
|
||||
@test Groups.replace(x^3, x^2=>y) == x*y
|
||||
@test Groups.replace(y*x^3*y, x^2=>y) == y*x*y^2
|
||||
end
|
||||
end
|
@ -1,98 +0,0 @@
|
||||
@testset "WreathProducts" begin
|
||||
S_3 = SymmetricGroup(3)
|
||||
S_2 = SymmetricGroup(2)
|
||||
b = perm"(1,2,3)"
|
||||
a = perm"(1,2)"
|
||||
|
||||
@testset "Constructors" begin
|
||||
@test Groups.WreathProduct(S_2, S_3) isa AbstractAlgebra.Group
|
||||
B3 = Groups.WreathProduct(S_2, S_3)
|
||||
@test B3 isa Groups.WreathProduct
|
||||
@test B3 isa WreathProduct{3, Generic.SymmetricGroup{Int}, Generic.SymmetricGroup{Int}}
|
||||
|
||||
aa = Groups.DirectPowerGroupElem((a^0 ,a, a^2))
|
||||
|
||||
@test Groups.WreathProductElem(aa, b) isa AbstractAlgebra.GroupElem
|
||||
x = Groups.WreathProductElem(aa, b)
|
||||
@test x isa Groups.WreathProductElem
|
||||
@test x isa
|
||||
Groups.WreathProductElem{3, Generic.Perm{Int}, Generic.Perm{Int}}
|
||||
|
||||
@test B3.N == Groups.DirectPowerGroup(S_2, 3)
|
||||
@test B3.P == S_3
|
||||
|
||||
@test B3(aa, b) == Groups.WreathProductElem(aa, b)
|
||||
w = B3(aa, b)
|
||||
@test B3(w) == w
|
||||
@test B3(b) == Groups.WreathProductElem(one(B3.N), b)
|
||||
@test B3(aa) == Groups.WreathProductElem(aa, one(S_3))
|
||||
|
||||
@test B3((a^0 ,a, a^2), b) isa WreathProductElem
|
||||
|
||||
@test B3((a^0 ,a, a^2), b) == B3(aa, b)
|
||||
end
|
||||
|
||||
@testset "Types" begin
|
||||
B3 = Groups.WreathProduct(S_2, S_3)
|
||||
|
||||
@test elem_type(B3) == Groups.WreathProductElem{3, Generic.Perm{Int}, Generic.Perm{Int}}
|
||||
|
||||
@test parent_type(typeof(one(B3))) == Groups.WreathProduct{3, parent_type(typeof(one(B3.N.group))), Generic.SymmetricGroup{Int}}
|
||||
|
||||
@test parent(one(B3)) == Groups.WreathProduct(S_2,S_3)
|
||||
@test parent(one(B3)) == B3
|
||||
end
|
||||
|
||||
@testset "Basic operations on WreathProductElem" begin
|
||||
aa = Groups.DirectPowerGroupElem((a^0 ,a, a^2))
|
||||
B3 = Groups.WreathProduct(S_2, S_3)
|
||||
g = B3(aa, b)
|
||||
|
||||
@test g.p == b
|
||||
@test g.n == DirectPowerGroupElem(aa.elts)
|
||||
|
||||
h = deepcopy(g)
|
||||
@test h == g
|
||||
@test !(g === h)
|
||||
|
||||
g = B3(Groups.DirectPowerGroupElem((a ,a, a^2)), g.p)
|
||||
|
||||
@test g.n[1] == parent(g.n[1])(a)
|
||||
@test g != h
|
||||
|
||||
@test hash(g) != hash(h)
|
||||
end
|
||||
|
||||
@testset "Group arithmetic" begin
|
||||
B4 = Groups.WreathProduct(SymmetricGroup(3), SymmetricGroup(4))
|
||||
|
||||
id, a, b = perm"(3)", perm"(1,2)(3)", perm"(1,2,3)"
|
||||
|
||||
x = B4((id,a,b,id), perm"(1,2,3)(4)")
|
||||
@test inv(x) == B4((inv(b),id, a,id), perm"(1,3,2)(4)")
|
||||
|
||||
y = B4((a,id,a,b), perm"(1,4)(2,3)")
|
||||
@test inv(y) == B4((inv(b), a,id, a), perm"(1,4)(2,3)")
|
||||
|
||||
@test x*y == B4((id,id,b*a,b), perm"(1,3,4)(2)")
|
||||
@test y*x == B4(( a, b, id,b), perm"(1,4,2)(3)")
|
||||
|
||||
@test inv(x)*y == B4((inv(b)*a,a,a,b), perm"(1,2,4)(3)")
|
||||
@test y*inv(x) == B4((a,a,a,id), perm"(1,4,3)(2)")
|
||||
|
||||
@test (x*y)^6 == ((x*y)^2)^3
|
||||
end
|
||||
|
||||
@testset "Iteration" begin
|
||||
Wr = WreathProduct(SymmetricGroup(2),SymmetricGroup(4))
|
||||
|
||||
elts = collect(Wr)
|
||||
@test elts isa Vector{Groups.WreathProductElem{4, Generic.Perm{Int}, Generic.Perm{Int}}}
|
||||
@test order(Wr) == 2^4*factorial(4)
|
||||
|
||||
@test length(elts) == order(Wr)
|
||||
@test all((g*inv(g) == one(Wr) for g in elts))
|
||||
@test all(inv(g*h) == inv(h)*inv(g) for g in elts for h in elts)
|
||||
end
|
||||
|
||||
end
|
72
test/benchmarks.jl
Normal file
72
test/benchmarks.jl
Normal file
@ -0,0 +1,72 @@
|
||||
using BenchmarkTools
|
||||
using Test
|
||||
|
||||
using Groups
|
||||
using Groups.New
|
||||
|
||||
function wl_ball(F; radius::Integer)
|
||||
g, state = iterate(F)
|
||||
while length(word(g)) <= radius
|
||||
res = iterate(F, state)
|
||||
isnothing(res) && break
|
||||
g, state = res
|
||||
end
|
||||
elts = collect(state.seen)
|
||||
elts = resize!(elts, length(elts)-1)
|
||||
return elts
|
||||
end
|
||||
|
||||
@testset "Benchmarks" begin
|
||||
N = 4
|
||||
|
||||
@testset "iteration: FreeGroup" begin
|
||||
FN = FreeGroup(N)
|
||||
R = 8
|
||||
|
||||
let G = FN
|
||||
S = unique([gens(G); inv.(gens(G))])
|
||||
|
||||
sizes1 = last(Groups.wlmetric_ball(S, radius=R, threading=false))
|
||||
sizes2 = last(Groups.wlmetric_ball(S, radius=R, threading=true))
|
||||
|
||||
l = length(wl_ball(G, radius=R))
|
||||
|
||||
@test sizes1 == sizes2
|
||||
@test last(sizes1) == l
|
||||
|
||||
@info "Ball of radius $R in $(parent(first(S)))" sizes=sizes1
|
||||
@info "serial"
|
||||
@time Groups.wlmetric_ball(S, radius=R, threading=false)
|
||||
@info "threaded"
|
||||
@time Groups.wlmetric_ball(S, radius=R, threading=true)
|
||||
@info "iteration"
|
||||
@time wl_ball(G, radius=R)
|
||||
end
|
||||
end
|
||||
|
||||
@testset "iteration: SAut(F_n)" begin
|
||||
R = 4
|
||||
FN = FreeGroup(N)
|
||||
SAutFN = New.SpecialAutomorphismGroup(FN)
|
||||
|
||||
let G = SAutFN
|
||||
S = unique([gens(G); inv.(gens(G))])
|
||||
|
||||
sizes1 = last(Groups.wlmetric_ball(S, radius=R, threading=false))
|
||||
sizes2 = last(Groups.wlmetric_ball(S, radius=R, threading=true))
|
||||
|
||||
l = length(wl_ball(G, radius=R))
|
||||
|
||||
@test sizes1 == sizes2
|
||||
@test last(sizes1) == l
|
||||
|
||||
@info "Ball of radius $R in $(parent(first(S)))" sizes=sizes1
|
||||
@info "serial"
|
||||
@time Groups.wlmetric_ball(S, radius=R, threading=false)
|
||||
@info "threaded"
|
||||
@time Groups.wlmetric_ball(S, radius=R, threading=true)
|
||||
@info "iteration"
|
||||
@time wl_ball(G, radius=R)
|
||||
end
|
||||
end
|
||||
end
|
59
test/fp_groups.jl
Normal file
59
test/fp_groups.jl
Normal file
@ -0,0 +1,59 @@
|
||||
@testset "FPGroups" begin
|
||||
A = Alphabet([:a, :A, :b, :B, :c, :C], [2,1,4,3,6,5])
|
||||
|
||||
@test FreeGroup(A) isa FreeGroup
|
||||
@test sprint(show, FreeGroup(A)) == "free group on 3 generators"
|
||||
|
||||
F = FreeGroup([:a, :b, :c], A)
|
||||
@test sprint(show, F) == "free group on 3 generators"
|
||||
|
||||
a,b,c = gens(F)
|
||||
@test c*b*a isa FPGroupElement
|
||||
|
||||
# quotient of F:
|
||||
G = FPGroup(F, [a*b=>b*a, a*c=>c*a, b*c=>c*b])
|
||||
|
||||
@test G isa FPGroup
|
||||
@test sprint(show, G) == "⟨a, b, c | a*b => b*a, a*c => c*a, b*c => c*b⟩"
|
||||
@test rand(G) isa FPGroupElement
|
||||
|
||||
f = a*c*b
|
||||
@test word(f) isa Word{UInt8}
|
||||
|
||||
aG,bG,cG = gens(G)
|
||||
|
||||
@test aG isa FPGroupElement
|
||||
@test_throws AssertionError aG == a
|
||||
@test word(aG) == word(a)
|
||||
|
||||
g = aG*cG*bG
|
||||
|
||||
@test_throws AssertionError f == g
|
||||
@test word(f) == word(g)
|
||||
@test word(g) == [1, 5, 3]
|
||||
Groups.normalform!(g)
|
||||
@test word(g) == [1, 3, 5]
|
||||
|
||||
let g = aG*cG*bG
|
||||
# test that we normalize g before printing
|
||||
@test sprint(show, g) == "a*b*c"
|
||||
end
|
||||
|
||||
# quotient of G
|
||||
H = FPGroup(G, [aG^2=>cG, bG*cG=>aG], maxrules=200)
|
||||
|
||||
h = H(word(g))
|
||||
|
||||
@test h isa FPGroupElement
|
||||
@test_throws AssertionError h == g
|
||||
@test_throws AssertionError h*g
|
||||
|
||||
Groups.normalform!(h)
|
||||
@test h == H([5])
|
||||
|
||||
@testset "GroupsCore conformance: H" begin
|
||||
test_Group_interface(H)
|
||||
test_GroupElement_interface(rand(H, 2)...)
|
||||
end
|
||||
|
||||
end
|
71
test/free_groups.jl
Normal file
71
test/free_groups.jl
Normal file
@ -0,0 +1,71 @@
|
||||
@testset "FreeGroup" begin
|
||||
|
||||
A3 = Alphabet([:a, :b, :c, :A, :B, :C], [4,5,6,1,2,3])
|
||||
F3 = FreeGroup([:a, :b, :c], A3)
|
||||
@test F3 isa FreeGroup
|
||||
|
||||
@test gens(F3) isa Vector
|
||||
|
||||
@test eltype(F3) <: FPGroupElement{<:FreeGroup}
|
||||
|
||||
w = F3([1,2,3,4])
|
||||
W = inv(w)
|
||||
@test deepcopy(w) !== w
|
||||
@test deepcopy(w).word !== w.word
|
||||
|
||||
@test isone(w*W)
|
||||
|
||||
@test alphabet(w) == A3
|
||||
|
||||
@testset "generic iteration" begin
|
||||
w, s = iterate(F3)
|
||||
@test isone(w)
|
||||
w, s = iterate(F3, s)
|
||||
@test w == gens(F3, 1)
|
||||
|
||||
a,b,c = gens(F3)
|
||||
|
||||
function test_iteration(F, n=1000)
|
||||
w, s = iterate(F)
|
||||
for i in 1:n
|
||||
w, s = iterate(F, s)
|
||||
end
|
||||
return w
|
||||
end
|
||||
|
||||
k = test_iteration(F3, 10)
|
||||
@test k == a*b^-1
|
||||
|
||||
@time k = test_iteration(F3, 1000)
|
||||
@test k == (a^2)*c^2*a^-1
|
||||
end
|
||||
|
||||
@testset "wl_ball" begin
|
||||
function wl_ball(F; radius::Integer)
|
||||
g, state = iterate(F)
|
||||
while length(word(g)) <= radius
|
||||
res = iterate(F, state)
|
||||
isnothing(res) && break
|
||||
g, state = res
|
||||
end
|
||||
elts = collect(state.seen)
|
||||
elts = resize!(elts, length(elts)-1)
|
||||
return elts
|
||||
end
|
||||
|
||||
E4 = wl_ball(F3, radius=4)
|
||||
@test length(E4) == 937
|
||||
@test word(last(E4)) == Word([6])^4
|
||||
|
||||
E8, t, _ = @timed wl_ball(F3, radius=8)
|
||||
@test length(E8) == 585937
|
||||
@test word(last(E8)) == Word([6])^8
|
||||
@test t/10^9 < 1
|
||||
end
|
||||
|
||||
@testset "GroupsCore conformance" begin
|
||||
test_Group_interface(F3)
|
||||
test_GroupElement_interface(rand(F3, 2)...)
|
||||
end
|
||||
|
||||
end
|
@ -1,27 +1,33 @@
|
||||
using Test
|
||||
using AbstractAlgebra
|
||||
import AbstractAlgebra
|
||||
using Groups
|
||||
|
||||
using LinearAlgebra
|
||||
import KnuthBendix: Word
|
||||
|
||||
using GroupsCore
|
||||
include(joinpath(pathof(GroupsCore), "..", "..", "test", "conformance_test.jl"))
|
||||
|
||||
@testset "Groups" begin
|
||||
|
||||
@testset "wlmetric_ball" begin
|
||||
M = MatrixAlgebra(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;
|
||||
@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;
|
||||
|
||||
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]
|
||||
end
|
||||
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]
|
||||
end
|
||||
|
||||
include("FreeGroup-tests.jl")
|
||||
include("AutGroup-tests.jl")
|
||||
include("DirectPower-tests.jl")
|
||||
include("WreathProd-tests.jl")
|
||||
include("FPGroup-tests.jl")
|
||||
include("free_groups.jl")
|
||||
include("fp_groups.jl")
|
||||
|
||||
include("AutFn.jl")
|
||||
|
||||
# if !haskey(ENV, "CI")
|
||||
# include("benchmarks.jl")
|
||||
# end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user