1
0
mirror of https://github.com/kalmarek/Groups.jl.git synced 2024-12-26 02:20:30 +01:00

Merge pull request #6 from kalmarek/rework_replace

Rework replace
This commit is contained in:
kalmarek 2020-03-28 15:47:07 +01:00 committed by GitHub
commit c3eb34eee8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 898 additions and 959 deletions

View File

@ -1,18 +1,18 @@
name = "Groups" name = "Groups"
uuid = "5d8bd718-bd84-11e8-3b40-ad14f4a32557" uuid = "5d8bd718-bd84-11e8-3b40-ad14f4a32557"
authors = ["Marek Kaluba <kalmar@amu.edu.pl>"] authors = ["Marek Kaluba <kalmar@amu.edu.pl>"]
version = "0.3.0" version = "0.4.0"
[deps] [deps]
AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
[compat]
AbstractAlgebra = "^0.9.0"
[extras] [extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets] [targets]
test = ["Test"] test = ["Test"]
[compat]
AbstractAlgebra = "^0.7.0"

View File

@ -1,8 +1,9 @@
export Automorphism, AutGroup, Aut, SAut
############################################################################### ###############################################################################
# #
# AutSymbol/ AutGroup / Automorphism # AutSymbol/ AutGroup / Automorphism
# #
###############################################################################
struct RTransvect struct RTransvect
i::Int8 i::Int8
@ -30,6 +31,77 @@ struct AutSymbol <: GSymbol
fn::Union{LTransvect, RTransvect, PermAut, FlipAut, Identity} fn::Union{LTransvect, RTransvect, PermAut, FlipAut, Identity}
end 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
@assert 0 <= n <= 9
return Char(subscript_0 + n)
# return [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)
id = Symbol("ϱ", subscriptify(i), subscriptify(j))
return AutSymbol(id, pow, RTransvect(i, j))
end
function transvection_L(i::Integer, j::Integer, pow::Integer=1)
id = Symbol("λ", subscriptify(i), subscriptify(j))
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("σ", "", [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)
if n == zero(n)
return id_autsymbol()
end
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 s
else
throw(DomainError("Unknown type of AutSymbol: $s"))
end
end
###############################################################################
#
# AutGroup / Automorphism
#
mutable struct AutGroup{N} <: AbstractFPGroup mutable struct AutGroup{N} <: AbstractFPGroup
objectGroup::FreeGroup objectGroup::FreeGroup
gens::Vector{AutSymbol} gens::Vector{AutSymbol}
@ -46,46 +118,67 @@ mutable struct Automorphism{N} <: GWord{AutSymbol}
end end
end end
export Automorphism, AutGroup, Aut, SAut 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)
############################################################################### ###############################################################################
# #
# Type and parent object methods # AutSymbol defining functions && evaluation
# NOTE: all automorphisms operate on a tuple of FreeWords INPLACE!
# #
###############################################################################
elem_type(::AutGroup{N}) where N = Automorphism{N}
parent_type(::Automorphism{N}) where N = AutGroup{N}
###############################################################################
#
# AutSymbol defining functions
#
###############################################################################
function (ϱ::RTransvect)(v, pow::Integer=1) function (ϱ::RTransvect)(v, pow::Integer=1)
@inbounds Groups.r_multiply!(v[ϱ.i], (v[ϱ.j]^pow).symbols, reduced=false) append!(v[ϱ.i], v[ϱ.j]^pow)
freereduce!(v[ϱ.i])
return v return v
end end
function (λ::LTransvect)(v, pow::Integer=1) function (λ::LTransvect)(v, pow::Integer=1)
@inbounds Groups.l_multiply!(v[λ.i], (v[λ.j]^pow).symbols, reduced=false) prepend!(v[λ.i], v[λ.j]^pow)
freereduce!(v[λ.i])
return v return v
end end
function (σ::PermAut)(v, pow::Integer=1) function (σ::PermAut)(v, pow::Integer=1)
w = deepcopy(v) w = deepcopy(v)
if pow == 1
@inbounds for k in eachindex(v)
v[k].symbols = w[σ.perm.d[k]].symbols
end
else
s = (σ.perm^pow).d s = (σ.perm^pow).d
@inbounds for k in eachindex(v) @inbounds for k in eachindex(v)
v[k].symbols = w[s[k]].symbols v[k].symbols = w[s[k]].symbols
end end
end
return v return v
end end
@ -98,220 +191,74 @@ end
(::Identity)(v, pow::Integer=1) = v (::Identity)(v, pow::Integer=1) = v
# 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
@assert 0 <= n <= 9
return Char(subscript_0 + n)
# return [Char(subscript_0 + i) for i in reverse(digits(n))])
end
function id_autsymbol()
return AutSymbol(Symbol("(id)"), 0, Identity())
end
function rmul_autsymbol(i::Integer, j::Integer; pow::Integer=1)
id = Symbol("ϱ", subscriptify(i), subscriptify(j))
return AutSymbol(id, pow, RTransvect(i, j))
end
function lmul_autsymbol(i::Integer, j::Integer; pow::Integer=1)
id = Symbol("λ", subscriptify(i), subscriptify(j))
return AutSymbol(id, pow, LTransvect(i, j))
end
function flip_autsymbol(i::Integer; pow::Integer=1)
if iseven(pow)
return id_autsymbol()
else
id = Symbol("ɛ", subscriptify(i))
return AutSymbol(id, 1, FlipAut(i))
end
end
function perm_autsymbol(p::Generic.Perm{I}; pow::Integer=one(I)) where I<:Integer
if pow != 1
p = p^pow
end
for i in eachindex(p.d)
if p.d[i] != i
id = Symbol("σ", [subscriptify(i) for i in p.d]...)
return AutSymbol(id, 1, PermAut(p))
end
end
return id_autsymbol()
end
function perm_autsymbol(a::Vector{<:Integer})
return perm_autsymbol(Generic.Perm(Vector{Int8}(a), false))
end
function domain(G::AutGroup{N}) where N
F = G.objectGroup
gg = gens(F)
return ntuple(i->gg[i], N)
end
###############################################################################
#
# AutGroup / Automorphism constructors
#
###############################################################################
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 = [rmul_autsymbol(i,j) for (i,j) in indexing]
lmuls = [lmul_autsymbol(i,j) for (i,j) in indexing]
append!(S, [rmuls; lmuls])
if !special
flips = [flip_autsymbol(i) for i in 1:n]
syms = [perm_autsymbol(p) for p in PermutationGroup(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)
###############################################################################
#
# Types call overloads
#
###############################################################################
Automorphism{N}(s::AutSymbol) where N = Automorphism{N}(AutSymbol[s])
function Base.one(G::AutGroup{N}) where N
id = Automorphism{N}(id_autsymbol())
id.parent = G
return id
end
function (G::AutGroup{N})(f::AutSymbol) where N
g = Automorphism{N}([f])
g.parent = G
return g
end
function (G::AutGroup{N})(g::Automorphism{N}) where N
g.parent = G
return g
end
############################################################################### ###############################################################################
# #
# Functional call overloads for evaluation of AutSymbol and Automorphism # Functional call overloads for evaluation of AutSymbol and Automorphism
# #
###############################################################################
function (s::AutSymbol)(v::NTuple{N, T}) where {N, T} (s::AutSymbol)(v::NTuple{N, T}) where {N, T} = s.fn(v, s.pow)::NTuple{N, T}
if s.pow != 0
v = 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 end
return v return v
end end
function (f::Automorphism{N})(v::NTuple{N, T}) where {N, T} function domain(G::AutGroup{N}) where N
for (i, s) in enumerate(f.symbols) F = G.objectGroup
v = s(v)::NTuple{N, T} return ntuple(i->F(F.gens[i]), N)
if i % 5 == 0
freereduce!.(v)
end
end
return v
end end
evaluate(f::Automorphism) = f(domain(parent(f))) evaluate(f::Automorphism) = f(domain(parent(f)))
############################################################################### ###############################################################################
# #
# Comparison # hashing && equality
# #
###############################################################################
const HASHINGCONST = 0x7d28276b01874b19 # hash(Automorphism) function hash_internal(g::Automorphism, images = freereduce!.(evaluate(g)),
h::UInt = 0x7d28276b01874b19) # hash(Automorphism)
hash(s::AutSymbol, h::UInt) = hash(s.id, hash(s.pow, hash(:AutSymbol, h))) return hash(images, hash(parent(g), h))
function hash(g::Automorphism{N}, images, h::UInt=HASHINGCONST) where N
return hash(images, hash(parent(g), hash(Automorphism{N}, h)))
end end
function hash(g::Automorphism, h::UInt) function compute_images(g::Automorphism)
if g.modified images = reduce!.(evaluate(g))
g_im = reduce!.(evaluate(g)) savehash!(g, hash_internal(g, images))
g.savedhash = hash(g, g_im) unsetmodified!(g)
g.modified = false return images
end
return xor(g.savedhash, h)
end end
function (==)(g::Automorphism{N}, h::Automorphism{N}) where N function (==)(g::Automorphism{N}, h::Automorphism{N}) where N
parent(g) == parent(h) || return false img_c, imh_c = false, false
if !g.modified && !h.modified if ismodified(g)
if g.savedhash != h.savedhash img = compute_images(g)
return false img_c = true
end
end end
# expensive: if ismodified(h)
g_im = reduce!.(evaluate(g)) imh = compute_images(h)
h_im = reduce!.(evaluate(h)) imh_c = true
# cheap: end
g.savedhash = hash(g, g_im)
g.modified = false
h.savedhash = hash(h, h_im)
h.modified = false
return g_im == h_im @assert !ismodified(g) && !ismodified(h)
# cheap
hash(g) != hash(h) && return false # hashes differ, so images must have differed as well
# equal elements, or possibly hash conflict
if !img_c
img = compute_images(g)
end
if !imh_c
imh = compute_images(h)
end
return img == imh
end end
###############################################################################
#
# Basic manipulation
#
###############################################################################
function change_pow(s::AutSymbol, n::Integer)
if n == zero(n)
return id_autsymbol()
end
symbol = s.fn
if symbol isa FlipAut
return flip_autsymbol(symbol.i, pow=n)
elseif symbol isa PermAut
return perm_autsymbol(symbol.perm, pow=n)
elseif symbol isa RTransvect
return rmul_autsymbol(symbol.i, symbol.j, pow=n)
elseif symbol isa LTransvect
return lmul_autsymbol(symbol.i, symbol.j, pow=n)
elseif symbol isa Identity
return s
else
warn("Changing power of an unknown type of symbol! $s")
return AutSymbol(s.id, n, s.fn)
end
end
length(s::AutSymbol) = abs(s.pow)
############################################################################### ###############################################################################
# #
# String I/O # String I/O
# #
###############################################################################
function show(io::IO, G::AutGroup) function show(io::IO, G::AutGroup)
print(io, "Automorphism Group of $(G.objectGroup)\n") print(io, "Automorphism Group of $(G.objectGroup)\n")
@ -320,87 +267,58 @@ end
############################################################################### ###############################################################################
# #
# Binary operators # Reduction
# #
###############################################################################
############################################################################### getperm(s::AutSymbol) = s.fn.perm^s.pow
#
# Inversion
#
###############################################################################
inv(f::AutSymbol) = change_pow(f, -f.pow) function simplifyperms!(::Type{Bool}, w::Automorphism{N}) where N
###############################################################################
#
# Misc
#
###############################################################################
function getperm(s::AutSymbol)
if s.pow != 1
@warn("Power for perm_symbol should be never 0!")
return s.fn.perm^s.pow
else
return s.fn.perm
end
end
function simplifyperms!(W::Automorphism{N}) where N
reduced = true reduced = true
to_delete = Int[] for i in 1:syllablelength(w)-1
for i in 1:length(W.symbols)-1 s, ns = syllables(w)[i], syllables(w)[i+1]
if W.symbols[i].pow == 0 if isone(s)
continue continue
elseif W.symbols[i].fn isa PermAut && W.symbols[i+1].fn isa PermAut elseif s.fn isa PermAut && ns.fn isa PermAut
reduced = false reduced = false
c = W.symbols[i] setmodified!(w)
n = W.symbols[i+1] syllables(w)[i+1] = AutSymbol(getperm(s)*getperm(ns))
W.symbols[i+1] = perm_autsymbol(getperm(c)*getperm(n)) syllables(w)[i] = change_pow(s, 0)
push!(to_delete, i)
end end
end end
deleteat!(W.symbols, to_delete) filter!(!isone, syllables(w))
deleteids!(W)
return reduced return reduced
end end
function reduce!(W::Automorphism) function reduce!(w::Automorphism)
if length(W) == 0
return W
elseif length(W.symbols) == 1
deleteids!(W)
else
reduced = false reduced = false
while !reduced while !reduced
reduced = simplifyperms!(W) && freereduce!(W) reduced = simplifyperms!(Bool, w) && freereduce!(Bool, w)
end end
end return w
W.modified = true
return W
end end
function linear_repr(A::Automorphism{N}, hom=matrix_repr) where N ###############################################################################
return reduce(*, linear_repr.(A.symbols, N, hom), init=hom(Identity(),N,1)) #
end # Abelianization (natural Representation to GL(N,Z))
#
linear_repr(a::AutSymbol, n::Int, hom) = hom(a.fn, n, a.pow) abelianize(A::Automorphism{N}) where N = image(A, abelianize; n=N)
function matrix_repr(a::Union{RTransvect, LTransvect}, n::Int, pow) # 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 = Matrix{Int}(I, n, n)
x[a.i,a.j] = pow x[a.i,a.j] = pow
return x return x
end end
function matrix_repr(a::FlipAut, n::Int, pow) function abelianize(a::FlipAut, n::Int, pow)
x = Matrix{Int}(I, n, n) x = Matrix{Int}(I, n, n)
x[a.i,a.i] = -1^pow x[a.i,a.i] = -1
return x return x
end end
matrix_repr(a::PermAut, n::Int, pow) = Matrix{Int}(I, n, n)[(a.perm^pow).d, :] abelianize(a::PermAut, n::Integer, pow) = Matrix{Int}(I, n, n)[(a.perm^pow).d, :]
abelianize(a::Identity, n::Integer, pow) = abelianize(;n=n)
matrix_repr(a::Identity, n::Int, pow) = Matrix{Int}(I, n, n)

View File

@ -13,9 +13,9 @@ FPGroupElem = GroupWord{FPSymbol}
mutable struct FPGroup <: AbstractFPGroup mutable struct FPGroup <: AbstractFPGroup
gens::Vector{FPSymbol} gens::Vector{FPSymbol}
rels::Dict{FPGroupElem, FPGroupElem} rels::Dict{FreeGroupElem, FreeGroupElem}
function FPGroup(gens::Vector{T}, rels::Dict{FPGroupElem, FPGroupElem}) where {T<:GSymbol} function FPGroup(gens::Vector{T}, rels::Dict{FreeGroupElem, FreeGroupElem}) where {T<:GSymbol}
G = new(gens) G = new(gens)
G.rels = Dict(G(k) => G(v) for (k,v) in rels) G.rels = Dict(G(k) => G(v) for (k,v) in rels)
return G return G
@ -28,131 +28,70 @@ export FPGroupElem, FPGroup
# #
# Type and parent object methods # Type and parent object methods
# #
###############################################################################
parent_type(::Type{FPGroupElem}) = FPGroup AbstractAlgebra.elem_type(::Type{FPGroup}) = FPGroupElem
AbstractAlgebra.parent_type(::Type{FPGroupElem}) = FPGroup
elem_type(::FPGroup) = FPGroupElem
############################################################################### ###############################################################################
# #
# FPSymbol constructors # FPSymbol constructors
# #
###############################################################################
FPSymbol(s::Symbol) = FPSymbol(s, 1) FPSymbol(s::Symbol) = FPSymbol(s, 1)
FPSymbol(s::String) = FPSymbol(Symbol(s)) FPSymbol(s::String) = FPSymbol(Symbol(s))
FPSymbol(s::GSymbol) = FPSymbol(s.id, s.pow) FPSymbol(s::GSymbol) = FPSymbol(s.id, s.pow)
convert(::Type{FPSymbol}, s::FreeSymbol) = 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(gens::Vector{FPSymbol}) = FPGroup(gens, Dict{FPGroupElem, FPGroupElem}())
FPGroup(a::Vector{String}) = FPGroup([FPSymbol(i) for i in a])
FPGroup(n::Int, symbol::String="f") = FPGroup(["$symbol$i" for i in 1:n])
FPGroup(H::FreeGroup) = FPGroup([FPSymbol(s) for s in H.gens]) FPGroup(H::FreeGroup) = FPGroup([FPSymbol(s) for s in H.gens])
############################################################################### ###############################################################################
# #
# Parent object call overloads # Parent object call overloads
# #
###############################################################################
function Base.one(G::FPGroup)
id = FPGroupElem(FPSymbol[])
id.parent = G
return id
end
function (G::FPGroup)(w::GWord) function (G::FPGroup)(w::GWord)
if length(w) == 0 if isempty(w)
return one(G) return one(G)
end end
if eltype(w.symbols) == FreeSymbol @boundscheck for s in syllables(w)
w = FPGroupElem(FPSymbol.(w.symbols)) 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 end
if eltype(w.symbols) == FPSymbol w = FPGroupElem(FPSymbol.(syllables(w)))
for s in w.symbols setparent!(w, G)
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
end
w.parent = G
return reduce!(w) return reduce!(w)
end end
(G::FPGroup)(s::FPSymbol) = G(FPGroupElem(s)) (G::FPGroup)(s::GSymbol) = G(FPGroupElem(s))
###############################################################################
#
# Basic manipulation
#
###############################################################################
hash(s::FPSymbol, h::UInt) = hash(s.id, hash(s.pow, hash(FPSymbol, h)))
change_pow(s::FPSymbol, n::Int) = FPSymbol(s.id, n)
length(s::FPSymbol) = abs(s.pow)
############################################################################### ###############################################################################
# #
# String I/O # String I/O
# #
###############################################################################
function show(io::IO, G::FPGroup) function show(io::IO, G::FPGroup)
print(io, "FPgroup on $(length(G.gens)) generators ") print(io, "FPgroup on $(length(G.gens)) generators ")
strrels = join(G.rels, ", ") strrels = join(G.rels, ", ")
if length(strrels) > 300 if length(strrels) > 200
print(io, "", join(G.gens, ", "), " | $(length(G.rels)) relation(s) ⟩.") print(io, "", join(G.gens, ", "), " | $(length(G.rels)) relation(s) ⟩.")
else else
print(io, "", join(G.gens, ", "), " | ", join(G.rels, ", "), " ⟩.") print(io, "", join(G.gens, ", "), " | ", join(G.rels, ", "), " ⟩.")
end end
end end
###############################################################################
#
# Comparison
#
###############################################################################
###############################################################################
#
# Inversion
#
###############################################################################
inv(s::FPSymbol) = change_pow(s, -s.pow)
###############################################################################
#
# Binary operations
#
###############################################################################
(*)(W::FPGroupElem, Z::FPGroupElem) = r_multiply(W, Z.symbols)
(*)(W::FPGroupElem, s::FPSymbol) = r_multiply(W, [s])
(*)(s::FPSymbol, W::FPGroupElem) = l_multiply(W, [s])
function reduce!(W::FPGroupElem) function reduce!(W::FPGroupElem)
if length(W) < 2
deleteat!(W.symbols, findall(x -> x.pow == 0, W.symbols))
else
reduced = false reduced = false
while !reduced while !reduced
reduced = freereduce!(W) || replace_all!(W, parent(W).rels) W = replace(W, parent(W).rels)
reduced = freereduce!(Bool, W)
end end
end
W.savedhash = hash(W.symbols, hash(typeof(W)))
W.modified = false
return W return W
end end
@ -162,12 +101,15 @@ end
# #
############################################################################### ###############################################################################
function add_rels!(G::FPGroup, newrels::Dict{FPGroupElem,FPGroupElem}) 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) for w in keys(newrels)
if !(w in keys(G.rels)) haskey(G.rels, w) && continue
G.rels[w] = G(newrels[w]) G.rels[w] = newrels[w]
end
end end
return G
end end
function Base.:/(G::FPGroup, newrels::Vector{FPGroupElem}) function Base.:/(G::FPGroup, newrels::Vector{FPGroupElem})
@ -176,17 +118,18 @@ function Base.:/(G::FPGroup, newrels::Vector{FPGroupElem})
"Can not form quotient group: $r is not an element of $G")) "Can not form quotient group: $r is not an element of $G"))
end end
H = deepcopy(G) H = deepcopy(G)
newrels = Dict(H(r) => one(H) for r in newrels) F = freepreimage(H)
newrels = Dict(freepreimage(r) => one(F) for r in newrels)
add_rels!(H, newrels) add_rels!(H, newrels)
return H return H
end end
function Base.:/(G::FreeGroup, rels::Vector{FreeGroupElem}) function Base.:/(F::FreeGroup, rels::Vector{FreeGroupElem})
for r in rels for r in rels
parent(r) == G || throw(DomainError( parent(r) == F || throw(DomainError(
"Can not form quotient group: $r is not an element of $G")) "Can not form quotient group: $r is not an element of $F"))
end end
H = FPGroup(deepcopy(G)) G = FPGroup(FPSymbol.(F.gens))
H.rels = Dict(H(rel) => one(H) for rel in unique(rels)) G.rels = Dict(rel => one(F) for rel in unique(rels))
return H return G
end end

View File

@ -2,7 +2,6 @@
# #
# FreeSymbol/FreeGroupElem/FreeGroup definition # FreeSymbol/FreeGroupElem/FreeGroup definition
# #
###############################################################################
struct FreeSymbol <: GSymbol struct FreeSymbol <: GSymbol
id::Symbol id::Symbol
@ -14,7 +13,7 @@ FreeGroupElem = GroupWord{FreeSymbol}
mutable struct FreeGroup <: AbstractFPGroup mutable struct FreeGroup <: AbstractFPGroup
gens::Vector{FreeSymbol} gens::Vector{FreeSymbol}
function FreeGroup(gens::Vector{T}) where {T<:GSymbol} function FreeGroup(gens::AbstractVector{T}) where {T<:GSymbol}
G = new(gens) G = new(gens)
G.gens = gens G.gens = gens
return G return G
@ -27,86 +26,48 @@ export FreeGroupElem, FreeGroup
# #
# Type and parent object methods # Type and parent object methods
# #
###############################################################################
elem_type(::Type{FreeGroup}) = FreeGroupElem AbstractAlgebra.elem_type(::Type{FreeGroup}) = FreeGroupElem
AbstractAlgebra.parent_type(::Type{FreeGroupElem}) = FreeGroup
parent_type(::Type{FreeGroupElem}) = FreeGroup
############################################################################### ###############################################################################
# #
# FreeSymbol constructors # FreeSymbol constructors
# #
###############################################################################
FreeSymbol(s::Symbol) = FreeSymbol(s,1) FreeSymbol(s::Symbol) = FreeSymbol(s,1)
FreeSymbol(s::String) = FreeSymbol(Symbol(s)) 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(n::Int, symbol::String="f") = FreeGroup([Symbol(symbol,i) for i in 1:n])
FreeGroup(a::AbstractVector) = FreeGroup(FreeSymbol.(a))
FreeGroup(a::Vector) = FreeGroup(FreeSymbol.(a))
############################################################################### ###############################################################################
# #
# Parent object call overloads # Parent object call overloads
# #
###############################################################################
function Base.one(G::FreeGroup)
id = FreeGroupElem(FreeSymbol[])
id.parent = G
return id
end
function (G::FreeGroup)(w::GroupWord{FreeSymbol}) function (G::FreeGroup)(w::GroupWord{FreeSymbol})
if length(w) > 0 for s in syllables(w)
for s in w.symbols
i = findfirst(g -> g.id == s.id, G.gens) i = findfirst(g -> g.id == s.id, G.gens)
i == 0 && throw(DomainError( isnothing(i) && throw(DomainError(
"Symbol $s does not belong to $G.")) "Symbol $s does not belong to $G."))
s.pow % G.gens[i].pow == 0 || throw(DomainError( s.pow % G.gens[i].pow == 0 || throw(DomainError(
"Symbol $s doesn't belong to $G.")) "Symbol $s doesn't belong to $G."))
end end
end setparent!(w, G)
w.parent = G return reduce!(w)
return w
end end
(G::FreeGroup)(s::FreeSymbol) = G(FreeGroupElem(s)) (G::FreeGroup)(s::GSymbol) = G(FreeGroupElem(s))
(G::FreeGroup)(v::AbstractVector{<:GSymbol}) = G(FreeGroupElem(FreeSymbol.(v)))
###############################################################################
#
# Basic manipulation
#
###############################################################################
hash(s::FreeSymbol, h::UInt) = hash(s.id, hash(s.pow, hash(FreeSymbol, h)))
change_pow(s::FreeSymbol, n::Int) = FreeSymbol(s.id, n)
length(s::FreeSymbol) = abs(s.pow)
############################################################################### ###############################################################################
# #
# String I/O # String I/O
# #
###############################################################################
function show(io::IO, G::FreeGroup) function show(io::IO, G::FreeGroup)
print(io, "Free group on $(length(G.gens)) generators: ") print(io, "Free group on $(length(G.gens)) generators: ")
join(io, G.gens, ", ") join(io, G.gens, ", ")
end end
###############################################################################
#
# Comparison
#
###############################################################################
###############################################################################
#
# Inversion
#
###############################################################################
inv(s::FreeSymbol) = change_pow(s, -s.pow)

View File

@ -7,463 +7,100 @@ import AbstractAlgebra: order, gens, matrix_repr
import Base: length, ==, hash, show, convert, eltype, iterate import Base: length, ==, hash, show, convert, eltype, iterate
import Base: inv, reduce, *, ^, power_by_squaring import Base: inv, reduce, *, ^, power_by_squaring
import Base: findfirst, findnext import Base: findfirst, findnext, replace
import Base: deepcopy_internal import Base: deepcopy_internal
export elements
using LinearAlgebra using LinearAlgebra
using Markdown using Markdown
Base.one(G::Generic.PermGroup) = G(collect(1:G.n), false) export gens, FreeGroup, Aut, SAut
############################################################################### include("types.jl")
#
# ParentType / ObjectType definition
#
###############################################################################
@doc doc"""
::GSymbol
> 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.
"""
abstract type GSymbol end
abstract type GWord{T<:GSymbol} <:GroupElem end
@doc doc"""
W::GroupWord{T} <: GWord{T<:GSymbol} <:GroupElem
> Basic representation of element of a finitely presented group. `W.symbols`
> fieldname contains particular group symbols which multiplied constitute a
> group element, i.e. a word in generators.
> As reduction (inside group) of such word may be time consuming we provide
> `savedhash` and `modified` fields as well:
> hash (used e.g. in the `unique` function) is calculated by reducing the word,
> setting `modified` flag to `false` and computing the hash which is stored in
> `savedhash` field.
> whenever word `W` is changed `W.modified` is set to `false`;
> Future comparisons don't perform reduction (and use `savedhash`) as long as
> `modified` flag remains `false`.
"""
mutable struct GroupWord{T} <: GWord{T}
symbols::Vector{T}
savedhash::UInt
modified::Bool
parent::Group
function GroupWord{T}(symbols::Vector{T}) where {T}
return new{T}(symbols, hash(symbols), true)
end
end
abstract type AbstractFPGroup <: Group end
###############################################################################
#
# Includes
#
###############################################################################
include("FreeGroup.jl") include("FreeGroup.jl")
include("FPGroups.jl") include("FPGroups.jl")
include("AutGroup.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("DirectPower.jl") include("DirectPower.jl")
include("WreathProducts.jl") include("WreathProducts.jl")
###############################################################################
#
# Type and parent object methods
#
###############################################################################
parent(w::GWord{T}) where {T<:GSymbol} = w.parent
###############################################################################
#
# ParentType / ObjectType constructors
#
###############################################################################
GroupWord(s::T) where {T<:GSymbol} = GroupWord{T}(T[s])
GroupWord{T}(s::T) where {T<:GSymbol} = GroupWord{T}(T[s])
GroupWord(w::GroupWord{T}) where {T<:GSymbol} = w
convert(::Type{GroupWord{T}}, s::T) where {T<:GSymbol} = GroupWord{T}(T[s])
###############################################################################
#
# Basic manipulation
#
###############################################################################
function hash(W::GWord, h::UInt)
W.modified && reduce!(W)
return xor(W.savedhash, h)
end
# WARNING: Due to specialised (constant) hash function of GWords this one is actually necessary!
function deepcopy_internal(W::T, dict::IdDict) where {T<:GWord}
G = parent(W)
return G(T(deepcopy(W.symbols)))
end
length(W::GWord) = sum([length(s) for s in W.symbols])
function deleteids!(W::GWord)
to_delete = Int[]
for i in 1:length(W.symbols)
if W.symbols[i].pow == 0
push!(to_delete, i)
end
end
deleteat!(W.symbols, to_delete)
end
function freereduce!(W::GWord)
reduced = true
for i in 1:length(W.symbols) - 1
if W.symbols[i].pow == 0
continue
elseif W.symbols[i].id == W.symbols[i+1].id
reduced = false
p1 = W.symbols[i].pow
p2 = W.symbols[i+1].pow
W.symbols[i+1] = change_pow(W.symbols[i], p1 + p2)
W.symbols[i] = change_pow(W.symbols[i], 0)
end
end
deleteids!(W)
return reduced
end
function reduce!(W::GWord)
if length(W) < 2
deleteids!(W)
else
reduced = false
while !reduced
reduced = freereduce!(W)
end
end
W.savedhash = hash(W.symbols, hash(typeof(W), hash(parent(W), zero(UInt))))
W.modified = false
return W
end
@doc doc"""
reduce(W::GWord)
> performs reduction/simplification of a group element (word in generators).
> The default reduction is the free group reduction, i.e. consists of
> multiplying adjacent symbols with the same `id` identifier and deleting the
> identity elements from `W.symbols`.
> More specific procedures should be dispatched on `GWord`s type parameter.
"""
reduce(W::GWord) = reduce!(deepcopy(W))
@doc doc"""
gens(G::AbstractFPGroups)
> returns vector of generators of `G`, as its elements.
"""
gens(G::AbstractFPGroup) = [G(g) for g in G.gens]
############################################################################### ###############################################################################
# #
# String I/O # String I/O
# #
###############################################################################
@doc doc""" @doc doc"""
show(io::IO, W::GWord) show(io::IO, W::GWord)
> The actual string produced by show depends on the eltype of `W.symbols`. > The actual string produced by show depends on the eltype of `W.symbols`.
""" """
function show(io::IO, W::GWord) function Base.show(io::IO, W::GWord)
if length(W) == 0 if length(W) == 0
print(io, "(id)") print(io, "(id)")
else else
join(io, [string(s) for s in W.symbols], "*") join(io, (string(s) for s in syllables(W)), "*")
end end
end end
function show(io::IO, s::T) where {T<:GSymbol} function Base.show(io::IO, s::T) where {T<:GSymbol}
if s.pow == 1 if s.pow == 1
print(io, string(s.id)) print(io, string(s.id))
else else
print(io, string((s.id))*"^$(s.pow)") print(io, "$(s.id)^$(s.pow)")
end end
end end
###############################################################################
#
# Comparison
#
###############################################################################
function (==)(W::GWord, Z::GWord)
parent(W) == parent(Z) || return false
W.modified && reduce!(W)
Z.modified && reduce!(Z)
if W.savedhash != Z.savedhash
return false
end
return W.symbols == Z.symbols
end
function (==)(s::GSymbol, t::GSymbol)
s.pow == t.pow || return false
s.pow == 0 && return true
s.id == t.id || return false
return true
end
###############################################################################
#
# Binary operators
#
###############################################################################
function AbstractAlgebra.mul!(out::GWord, x::GWord, y::GWord; reduced::Bool=true)
resize!(out.symbols, length(x.symbols)+length(y.symbols))
for i in eachindex(x.symbols)
out.symbols[i] = x.symbols[i]
end
for i in eachindex(y.symbols)
out.symbols[length(x.symbols)+i] = y.symbols[i]
end
if reduced
reduce!(out)
end
return out
end
function r_multiply!(W::GWord, x; reduced::Bool=true)
if length(x) > 0
append!(W.symbols, x)
end
if reduced
reduce!(W)
end
return W
end
function l_multiply!(W::GWord, x; reduced::Bool=true)
if length(x) > 0
prepend!(W.symbols, x)
end
if reduced
reduce!(W)
end
return W
end
r_multiply(W::GWord, x; reduced=true) =
r_multiply!(deepcopy(W),x, reduced=reduced)
l_multiply(W::GWord, x; reduced=true) =
l_multiply!(deepcopy(W),x, reduced=reduced)
(*)(W::GWord, Z::GWord) = r_multiply(W, Z.symbols)
(*)(W::GWord, s::GSymbol) = r_multiply(W, [s])
(*)(s::GSymbol, W::GWord) = l_multiply(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(parent(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
r_multiply!(W, W.symbols)
end
Z = deepcopy(W)
while p > 0
t = trailing_zeros(p) + 1
p >>= t
while (t -= 1) >= 0
r_multiply!(W, W.symbols)
end
r_multiply!(Z, W.symbols)
end
return Z
end
(^)(x::GWord, n::Integer) = power_by_squaring(x,n)
###############################################################################
#
# Inversion
#
###############################################################################
function inv(W::T) where {T<:GWord}
if length(W) == 0
return W
else
G = parent(W)
w = T(reverse([inv(s) for s in W.symbols]))
w.modified = true
return G(w)
end
end
###############################################################################
#
# Replacement of symbols / sub-words
#
###############################################################################
issubsymbol(s::GSymbol, t::GSymbol) =
s.id == t.id && (0 s.pow t.pow || 0 s.pow t.pow)
"""doc
Find the first linear index k>=i such that Z < W.symbols[k:k+length(Z)-1]
"""
function findnext(W::GWord, Z::GWord, i::Int)
n = length(Z.symbols)
if n == 0
return 0
elseif n == 1
for idx in i:lastindex(W.symbols)
if issubsymbol(Z.symbols[1], W.symbols[idx])
return idx
end
end
return 0
else
for idx in i:lastindex(W.symbols) - n + 1
foundfirst = issubsymbol(Z.symbols[1], W.symbols[idx])
lastmatch = issubsymbol(Z.symbols[end], W.symbols[idx+n-1])
if foundfirst && lastmatch
# middles match:
if view(Z.symbols, 2:n-1) == view(W.symbols, idx+1:idx+n-2)
return idx
end
end
end
end
return 0
end
findfirst(W::GWord, Z::GWord) = findnext(W, Z, 1)
function replace!(W::GWord, index, toreplace::GWord, replacement::GWord; check=true)
n = length(toreplace.symbols)
if n == 0
return reduce!(W)
elseif n == 1
if check
@assert issubsymbol(toreplace.symbols[1], W.symbols[index])
end
first = change_pow(W.symbols[index],
W.symbols[index].pow - toreplace.symbols[1].pow)
last = change_pow(W.symbols[index], 0)
else
if check
@assert issubsymbol(toreplace.symbols[1], W.symbols[index])
@assert W.symbols[index+1:index+n-2] == toreplace.symbols[2:end-1]
@assert issubsymbol(toreplace.symbols[end], W.symbols[index+n-1])
end
first = change_pow(W.symbols[index],
W.symbols[index].pow - toreplace.symbols[1].pow)
last = change_pow(W.symbols[index+n-1],
W.symbols[index+n-1].pow - toreplace.symbols[end].pow)
end
replacement = first * replacement * last
splice!(W.symbols, index:index+n-1, replacement.symbols)
return reduce!(W)
end
function replace(W::GWord, index, toreplace::GWord, replacement::GWord)
replace!(deepcopy(W), index, toreplace, replacement)
end
function replace_all!(W::T,subst_dict::Dict{T,T}) where {T<:GWord}
modified = false
for toreplace in reverse!(sort!(collect(keys(subst_dict)), by=length))
replacement = subst_dict[toreplace]
i = findfirst(W, toreplace)
while i 0
modified = true
replace!(W,i,toreplace, replacement)
i = findnext(W, toreplace, i)
end
end
return modified
end
function replace_all(W::T, subst_dict::Dict{T,T}) where {T<:GWord}
W = deepcopy(W)
replace_all!(W, subst_dict)
return W
end
############################################################################### ###############################################################################
# #
# Misc # Misc
# #
###############################################################################
function generate_balls(S::AbstractVector{T}, Id::T=one(parent(first(S))); @doc doc"""
radius=2, op=*) where T<:GroupElem gens(G::AbstractFPGroups)
> returns vector of generators of `G`, as its elements.
"""
AbstractAlgebra.gens(G::AbstractFPGroup) = G.(G.gens)
@doc doc"""
metric_ball(S::Vector{GroupElem}, center=Id; radius=2, op=*)
Compute metric ball as a list of elements of non-decreasing length, given the
word-length metric on 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 generate_balls(S::AbstractVector{T}, center::T=one(first(S));
radius=2, op=*) where T<:Union{GroupElem, NCRingElem}
sizes = Int[] sizes = Int[]
B = [Id] B = [one(first(S))]
for i in 1:radius for i in 1:radius
BB = [op(i,j) for (i,j) in Base.product(B,S)] BB = [op(i,j) for (i,j) in Base.product(B,S)]
B = unique([B; vec(BB)]) B = unique([B; vec(BB)])
push!(sizes, length(B)) push!(sizes, length(B))
end end
return B, sizes isone(center) && return B, sizes
return c.*B, sizes
end end
function generate_balls(S::AbstractVector{T}, Id::T=one(parent(first(S))); @doc doc"""
radius=2, op=*) where {T<:NCRingElem} image(A::GWord, homomorphism; kwargs...)
sizes = Int[] Evaluate homomorphism `homomorphism` on a GWord `A`.
B = [Id] `homomorphism` needs implement
for i in 1:radius > `hom(s; kwargs...)`,
BB = [op(i,j) for (i,j) in Base.product(B,S)] where `hom(;kwargs...)` evaluates the value at the identity element.
B = unique([B; vec(BB)]) """
push!(sizes, length(B)) function image(w::GWord, hom; kwargs...)
end return reduce(*,
return B, sizes (hom(s; kwargs...) for s in syllables(w)),
init = hom(;kwargs...))
end end
########### iteration for GFField
length(F::AbstractAlgebra.GFField) = order(F)
function iterate(F::AbstractAlgebra.GFField, s=0)
if s >= order(F)
return nothing
else
return F(s), s+1
end
end
eltype(::Type{AbstractAlgebra.GFField{I}}) where I = AbstractAlgebra.gfelem{I}
end # of module Groups end # of module Groups

View File

@ -1,5 +1,7 @@
export WreathProduct, WreathProductElem export WreathProduct, WreathProductElem
import AbstractAlgebra: AbstractPermutationGroup, AbstractPerm
############################################################################### ###############################################################################
# #
# WreathProduct / WreathProductElem # WreathProduct / WreathProductElem
@ -19,22 +21,23 @@ export WreathProduct, WreathProductElem
* `N::Group` : the single factor of the group $N$ * `N::Group` : the single factor of the group $N$
* `P::Generic.PermGroup` : full `PermutationGroup` * `P::Generic.PermGroup` : full `PermutationGroup`
""" """
struct WreathProduct{N, T<:Group, PG<:Generic.PermGroup} <: Group struct WreathProduct{N, T<:Group, PG<:AbstractPermutationGroup} <: Group
N::DirectPowerGroup{N, T} N::DirectPowerGroup{N, T}
P::PG P::PG
function WreathProduct(Gr::T, P::PG) where {T, PG<:Generic.PermGroup} function WreathProduct(G::Gr, P::PG) where
N = DirectPowerGroup(Gr, Int(P.n)) {Gr <: Group, PG <: AbstractPermutationGroup}
return new{Int(P.n), T, PG}(N, P) N = DirectPowerGroup(G, Int(P.n))
return new{Int(P.n), Gr, PG}(N, P)
end end
end end
struct WreathProductElem{N, T<:GroupElem, P<:Generic.Perm} <: GroupElem struct WreathProductElem{N, T<:GroupElem, P<:AbstractPerm} <: GroupElem
n::DirectPowerGroupElem{N, T} n::DirectPowerGroupElem{N, T}
p::P p::P
function WreathProductElem(n::DirectPowerGroupElem{N,T}, p::P, function WreathProductElem(n::DirectPowerGroupElem{N,T}, p::P,
check::Bool=true) where {N, T, P<:Generic.Perm} check::Bool=true) where {N, T, P<:AbstractPerm}
if check if check
N == length(p.d) || throw(DomainError( N == length(p.d) || throw(DomainError(
"Can't form WreathProductElem: lengths differ")) "Can't form WreathProductElem: lengths differ"))

93
src/arithmetic.jl Normal file
View File

@ -0,0 +1,93 @@
function Base.inv(W::T) where T<:GWord
length(W) == 0 && return W
G = parent(W)
w = T([inv(s) for s in Iterators.reverse(syllables(W))])
return setparent!(w, G)
end
###############################################################################
#
# Binary operators
#
function Base.append!(w::GWord{T}, v::AbstractVector{T}) where T
append!(syllables(w), v)
return w
end
function Base.prepend!(w::GWord{T}, v::AbstractVector{T}) where T
prepend!(syllables(w), v)
return w
end
Base.append!(w::T, v::T) where T <: GWord = append!(w, syllables(v))
Base.prepend!(w::T, v::T) where T <: GWord = prepend!(w, syllables(v))
for (mul, f) in ((:rmul!, :push!), (:lmul!, :pushfirst!))
@eval begin
function $mul(out::T, w::T, s::GSymbol) where T <:GWord
resize!(syllables(out), syllablelength(w))
syllables(out) .= syllables(w)
$f(syllables(out), s)
return freereduce!(out)
end
end
end
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
lmul!(out::T, x::T, y::T) where T <: GWord = rmul!(out, y, x)
function AbstractAlgebra.mul!(out::T, x::T, y::T) where T <: GWord
return rmul!(out, x, y)
end
(*)(W::GW, Z::GW) where GW <: GWord = rmul!(deepcopy(W), W, Z)
(*)(W::GWord, s::GSymbol) = rmul!(deepcopy(W), W, s)
(*)(s::GSymbol, W::GWord) = lmul!(deepcopy(W), 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)

16
src/fallbacks.jl Normal file
View File

@ -0,0 +1,16 @@
# 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

182
src/findreplace.jl Normal file
View File

@ -0,0 +1,182 @@
###############################################################################
#
# 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

43
src/freereduce.jl Normal file
View File

@ -0,0 +1,43 @@
###############################################################################
#
# Naive reduction
#
function freereduce!(::Type{Bool}, w::GWord)
reduced = true
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
setmodified!(w)
p1 = s.pow
p2 = ns.pow
syllables(w)[i+1] = change_pow(s, p1 + p2)
syllables(w)[i] = change_pow(s, 0)
end
end
filter!(!isone, syllables(w))
return reduced
end
function freereduce!(w::GWord)
reduced = false
while !reduced
reduced = freereduce!(Bool, w)
end
return w
end
reduce!(w::GWord) = freereduce!(w)
@doc doc"""
reduce(w::GWord)
> performs reduction/simplification of a group element (word in generators).
> The default reduction is the free group reduction
> More specific procedures should be dispatched on `GWord`s type parameter.
"""
reduce(w::GWord) = reduce!(deepcopy(w))

34
src/hashing.jl Normal file
View File

@ -0,0 +1,34 @@
###############################################################################
#
# hashing, deepcopy and ==
#
function hash_internal(W::GWord)
reduce!(W)
h = hasparent(W) ? hash(parent(W)) : zero(UInt)
return hash(syllables(W), hash(typeof(W), h))
end
function hash(W::GWord, h::UInt)
if ismodified(W)
savehash!(W, hash_internal(W))
unsetmodified!(W)
end
return xor(savedhash(W), h)
end
# 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)
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)
end

21
src/symbols.jl Normal file
View File

@ -0,0 +1,21 @@
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) ? (GS(s.id, sign(s.pow)), i+1) : nothing
end
Base.length(s::GSymbol) = abs(s.pow)
Base.size(s::GSymbol) = (length(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

43
src/types.jl Normal file
View File

@ -0,0 +1,43 @@
abstract type AbstractFPGroup <: Group end
@doc doc"""
::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.
"""
abstract type GSymbol end
abstract type GWord{T<:GSymbol} <: GroupElem end
@doc doc"""
W::GroupWord{T} <: GWord{T<:GSymbol} <:GroupElem
> Basic representation of element of a finitely presented group. `W.symbols`
> fieldname contains particular group symbols which multiplied constitute a
> group element, i.e. a word in generators.
> As reduction (inside group) of such word may be time consuming we provide
> `savedhash` and `modified` fields as well:
> hash (used e.g. in the `unique` function) is calculated by reducing the word,
> setting `modified` flag to `false` and computing the hash which is stored in
> `savedhash` field.
> whenever word `W` is changed `W.modified` is set to `false`;
> Future comparisons don't perform reduction (and use `savedhash`) as long as
> `modified` flag remains `false`.
"""
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)
end

41
src/words.jl Normal file
View File

@ -0,0 +1,41 @@
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
# no setindex! for syllable based words
Base.convert(::Type{GW}, s::GSymbol) where GW <: GWord = GW(s)

View File

@ -1,6 +1,6 @@
@testset "Automorphisms" begin @testset "Automorphisms" begin
G = PermutationGroup(Int8(4)) G = SymmetricGroup(Int8(4))
@testset "AutSymbol" begin @testset "AutSymbol" begin
@test_throws MethodError Groups.AutSymbol(:a) @test_throws MethodError Groups.AutSymbol(:a)
@ -8,72 +8,73 @@
f = Groups.AutSymbol(:a, 1, Groups.FlipAut(2)) f = Groups.AutSymbol(:a, 1, Groups.FlipAut(2))
@test isa(f, Groups.GSymbol) @test isa(f, Groups.GSymbol)
@test isa(f, Groups.AutSymbol) @test isa(f, Groups.AutSymbol)
@test isa(Groups.perm_autsymbol(Int8.([1,2,3,4])), Groups.AutSymbol) @test isa(Groups.AutSymbol(perm"(4)"), Groups.AutSymbol)
@test isa(Groups.rmul_autsymbol(1,2), Groups.AutSymbol) @test isa(Groups.AutSymbol(perm"(1,2,3,4)"), Groups.AutSymbol)
@test isa(Groups.lmul_autsymbol(3,4), Groups.AutSymbol) @test isa(Groups.transvection_R(1,2), Groups.AutSymbol)
@test isa(Groups.flip_autsymbol(3), Groups.AutSymbol) @test isa(Groups.transvection_R(3,4), Groups.AutSymbol)
@test isa(Groups.flip(3), Groups.AutSymbol)
end end
a,b,c,d = gens(FreeGroup(4)) a,b,c,d = gens(FreeGroup(4))
D = NTuple{4,FreeGroupElem}([a,b,c,d]) D = NTuple{4,FreeGroupElem}([a,b,c,d])
@testset "flip_autsymbol correctness" begin @testset "flip correctness" begin
@test Groups.flip_autsymbol(1)(deepcopy(D)) == (a^-1, b,c,d) @test Groups.flip(1)(deepcopy(D)) == (a^-1, b,c,d)
@test Groups.flip_autsymbol(2)(deepcopy(D)) == (a, b^-1,c,d) @test Groups.flip(2)(deepcopy(D)) == (a, b^-1,c,d)
@test Groups.flip_autsymbol(3)(deepcopy(D)) == (a, b,c^-1,d) @test Groups.flip(3)(deepcopy(D)) == (a, b,c^-1,d)
@test Groups.flip_autsymbol(4)(deepcopy(D)) == (a, b,c,d^-1) @test Groups.flip(4)(deepcopy(D)) == (a, b,c,d^-1)
@test inv(Groups.flip_autsymbol(1))(deepcopy(D)) == (a^-1, b,c,d) @test inv(Groups.flip(1))(deepcopy(D)) == (a^-1, b,c,d)
@test inv(Groups.flip_autsymbol(2))(deepcopy(D)) == (a, b^-1,c,d) @test inv(Groups.flip(2))(deepcopy(D)) == (a, b^-1,c,d)
@test inv(Groups.flip_autsymbol(3))(deepcopy(D)) == (a, b,c^-1,d) @test inv(Groups.flip(3))(deepcopy(D)) == (a, b,c^-1,d)
@test inv(Groups.flip_autsymbol(4))(deepcopy(D)) == (a, b,c,d^-1) @test inv(Groups.flip(4))(deepcopy(D)) == (a, b,c,d^-1)
end end
@testset "perm_autsymbol correctness" begin @testset "perm correctness" begin
σ = Groups.perm_autsymbol([1,2,3,4]) σ = Groups.AutSymbol(perm"(4)")
@test σ(deepcopy(D)) == deepcopy(D) @test σ(deepcopy(D)) == deepcopy(D)
@test inv(σ)(deepcopy(D)) == deepcopy(D) @test inv(σ)(deepcopy(D)) == deepcopy(D)
σ = Groups.perm_autsymbol([2,3,4,1]) σ = Groups.AutSymbol(perm"(1,2,3,4)")
@test σ(deepcopy(D)) == (b, c, d, a) @test σ(deepcopy(D)) == (b, c, d, a)
@test inv(σ)(deepcopy(D)) == (d, a, b, c) @test inv(σ)(deepcopy(D)) == (d, a, b, c)
σ = Groups.perm_autsymbol([2,1,4,3]) σ = Groups.AutSymbol(perm"(1,2)(4,3)")
@test σ(deepcopy(D)) == (b, a, d, c) @test σ(deepcopy(D)) == (b, a, d, c)
@test inv(σ)(deepcopy(D)) == (b, a, d, c) @test inv(σ)(deepcopy(D)) == (b, a, d, c)
σ = Groups.perm_autsymbol([2,3,1,4]) σ = Groups.AutSymbol(perm"(1,2,3)(4)")
@test σ(deepcopy(D)) == (b, c, a, d) @test σ(deepcopy(D)) == (b, c, a, d)
@test inv(σ)(deepcopy(D)) == (c, a, b, d) @test inv(σ)(deepcopy(D)) == (c, a, b, d)
end end
@testset "rmul/lmul_autsymbol correctness" begin @testset "rmul/transvection_R correctness" begin
i,j = 1,2 i,j = 1,2
r = Groups.rmul_autsymbol(i,j) r = Groups.transvection_R(i,j)
l = Groups.lmul_autsymbol(i,j) l = Groups.transvection_L(i,j)
@test r(deepcopy(D)) == (a*b, b, c, d) @test r(deepcopy(D)) == (a*b, b, c, d)
@test inv(r)(deepcopy(D)) == (a*b^-1,b, c, d) @test inv(r)(deepcopy(D)) == (a*b^-1,b, c, d)
@test l(deepcopy(D)) == (b*a, b, c, d) @test l(deepcopy(D)) == (b*a, b, c, d)
@test inv(l)(deepcopy(D)) == (b^-1*a,b, c, d) @test inv(l)(deepcopy(D)) == (b^-1*a,b, c, d)
i,j = 3,1 i,j = 3,1
r = Groups.rmul_autsymbol(i,j) r = Groups.transvection_R(i,j)
l = Groups.lmul_autsymbol(i,j) l = Groups.transvection_L(i,j)
@test r(deepcopy(D)) == (a, b, c*a, d) @test r(deepcopy(D)) == (a, b, c*a, d)
@test inv(r)(deepcopy(D)) == (a, b, c*a^-1,d) @test inv(r)(deepcopy(D)) == (a, b, c*a^-1,d)
@test l(deepcopy(D)) == (a, b, a*c, d) @test l(deepcopy(D)) == (a, b, a*c, d)
@test inv(l)(deepcopy(D)) == (a, b, a^-1*c,d) @test inv(l)(deepcopy(D)) == (a, b, a^-1*c,d)
i,j = 4,3 i,j = 4,3
r = Groups.rmul_autsymbol(i,j) r = Groups.transvection_R(i,j)
l = Groups.lmul_autsymbol(i,j) l = Groups.transvection_L(i,j)
@test r(deepcopy(D)) == (a, b, c, d*c) @test r(deepcopy(D)) == (a, b, c, d*c)
@test inv(r)(deepcopy(D)) == (a, b, c, d*c^-1) @test inv(r)(deepcopy(D)) == (a, b, c, d*c^-1)
@test l(deepcopy(D)) == (a, b, c, c*d) @test l(deepcopy(D)) == (a, b, c, c*d)
@test inv(l)(deepcopy(D)) == (a, b, c, c^-1*d) @test inv(l)(deepcopy(D)) == (a, b, c, c^-1*d)
i,j = 2,4 i,j = 2,4
r = Groups.rmul_autsymbol(i,j) r = Groups.transvection_R(i,j)
l = Groups.lmul_autsymbol(i,j) l = Groups.transvection_L(i,j)
@test r(deepcopy(D)) == (a, b*d, c, d) @test r(deepcopy(D)) == (a, b*d, c, d)
@test inv(r)(deepcopy(D)) == (a, b*d^-1,c, d) @test inv(r)(deepcopy(D)) == (a, b*d^-1,c, d)
@test l(deepcopy(D)) == (a, d*b, c, d) @test l(deepcopy(D)) == (a, d*b, c, d)
@ -94,40 +95,40 @@
@test length(Groups.gens(A)) == 0 @test length(Groups.gens(A)) == 0
A = AutGroup(FreeGroup(2)) A = AutGroup(FreeGroup(2))
@test length(Groups.gens(A)) == 7 @test length(Groups.gens(A)) == 7
gens = Groups.gens(A) Agens = Groups.gens(A)
@test isa(A(Groups.rmul_autsymbol(1,2)), Automorphism) @test isa(A(Groups.transvection_R(1,2)), Automorphism)
@test A(Groups.rmul_autsymbol(1,2)) in gens @test A(Groups.transvection_R(1,2)) in Agens
@test isa(A(Groups.rmul_autsymbol(2,1)), Automorphism) @test isa(A(Groups.transvection_R(2,1)), Automorphism)
@test A(Groups.rmul_autsymbol(2,1)) in gens @test A(Groups.transvection_R(2,1)) in Agens
@test isa(A(Groups.lmul_autsymbol(1,2)), Automorphism) @test isa(A(Groups.transvection_R(1,2)), Automorphism)
@test A(Groups.lmul_autsymbol(1,2)) in gens @test A(Groups.transvection_R(1,2)) in Agens
@test isa(A(Groups.lmul_autsymbol(2,1)), Automorphism) @test isa(A(Groups.transvection_R(2,1)), Automorphism)
@test A(Groups.lmul_autsymbol(2,1)) in gens @test A(Groups.transvection_R(2,1)) in Agens
@test isa(A(Groups.flip_autsymbol(1)), Automorphism) @test isa(A(Groups.flip(1)), Automorphism)
@test A(Groups.flip_autsymbol(1)) in gens @test A(Groups.flip(1)) in Agens
@test isa(A(Groups.flip_autsymbol(2)), Automorphism) @test isa(A(Groups.flip(2)), Automorphism)
@test A(Groups.flip_autsymbol(2)) in gens @test A(Groups.flip(2)) in Agens
@test isa(A(Groups.perm_autsymbol([2,1])), Automorphism) @test isa(A(Groups.AutSymbol(perm"(1,2)")), Automorphism)
@test A(Groups.perm_autsymbol([2,1])) in gens @test A(Groups.AutSymbol(perm"(1,2)")) in Agens
end end
A = AutGroup(FreeGroup(4)) A = AutGroup(FreeGroup(4))
@testset "eltary functions" begin @testset "eltary functions" begin
f = Groups.perm_autsymbol([2,3,4,1]) 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 (Groups.change_pow(f, -2)).pow == 1 @test (Groups.change_pow(f, -2)).pow == 1
@test (inv(f)).pow == 1 @test (inv(f)).pow == 1
f = Groups.perm_autsymbol([2,1,4,3]) f = Groups.AutSymbol(perm"(1,2)(3,4)")
@test isa(inv(f), Groups.AutSymbol) @test isa(inv(f), Groups.AutSymbol)
@test_throws MethodError f*f @test_throws MethodError f*f
@ -136,14 +137,15 @@
end end
@testset "reductions/arithmetic" begin @testset "reductions/arithmetic" begin
f = Groups.perm_autsymbol([2,3,4,1]) f = Groups.AutSymbol(perm"(1,2,3,4)")
= Groups.r_multiply(A(f), [f], reduced=false) = append!(A(f), [f])
@test Groups.simplifyperms!() == false @test Groups.simplifyperms!(Bool, ) == false
@test ^2 == one(A) @test ^2 == one(A)
@test !isone()
a = A(Groups.rmul_autsymbol(1,2))*Groups.flip_autsymbol(2) a = A(Groups.transvection_L(1,2))*Groups.flip(2)
b = Groups.flip_autsymbol(2)*A(inv(Groups.rmul_autsymbol(1,2))) b = Groups.flip(2)*A(inv(Groups.transvection_L(1,2)))
@test a*b == b*a @test a*b == b*a
@test a^3 * b^3 == one(A) @test a^3 * b^3 == one(A)
g,h = Groups.gens(A)[[1,8]] # (g, h) = (ϱ₁₂, ϱ₃₂) g,h = Groups.gens(A)[[1,8]] # (g, h) = (ϱ₁₂, ϱ₃₂)
@ -164,27 +166,31 @@
# Not so simple arithmetic: applying starting on the left: # Not so simple arithmetic: applying starting on the left:
# ϱ₁₂*ϱ₂₁⁻¹*λ₁₂*ε₂ == σ₂₁₃₄ # ϱ₁₂*ϱ₂₁⁻¹*λ₁₂*ε₂ == σ₂₁₃₄
g = A(Groups.rmul_autsymbol(1,2)) g = A(Groups.transvection_R(1,2))
x1, x2, x3, x4 = Groups.domain(A) x1, x2, x3, x4 = Groups.domain(A)
@test g(Groups.domain(A)) == (x1*x2, x2, x3, x4) @test g(Groups.domain(A)) == (x1*x2, x2, x3, x4)
g = g*inv(A(Groups.rmul_autsymbol(2,1))) g = g*inv(A(Groups.transvection_R(2,1)))
@test g(Groups.domain(A)) == (x1*x2, x1^-1, x3, x4) @test g(Groups.domain(A)) == (x1*x2, x1^-1, x3, x4)
g = g*A(Groups.lmul_autsymbol(1,2)) g = g*A(Groups.transvection_L(1,2))
@test g(Groups.domain(A)) == (x2, x1^-1, x3, x4) @test g(Groups.domain(A)) == (x2, x1^-1, x3, x4)
g = g*A(Groups.flip_autsymbol(2)) g = g*A(Groups.flip(2))
@test g(Groups.domain(A)) == (x2, x1, x3, x4) @test g(Groups.domain(A)) == (x2, x1, x3, x4)
@test g(Groups.domain(A)) == A(Groups.perm_autsymbol([2,1,3,4]))(Groups.domain(A)) @test g(Groups.domain(A)) == A(Groups.AutSymbol(perm"(1,2)(4)"))(Groups.domain(A))
@test g == A(Groups.perm_autsymbol([2,1,3,4])) @test g == A(Groups.AutSymbol(perm"(1,2)(4)"))
g_im = g(Groups.domain(A)) g_im = g(Groups.domain(A))
@test length(g_im[1]) == 5 @test length.(g_im) == (1,1,1,1)
@test length(g_im[2]) == 3
@test length(g_im[3]) == 1
@test length(g_im[4]) == 1
@test length.(Groups.reduce!.(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 end
@testset "specific Aut(F4) tests" begin @testset "specific Aut(F4) tests" begin
@ -208,48 +214,48 @@
@test length(unique(B_2)) == 1777 @test length(unique(B_2)) == 1777
end end
@testset "linear_repr tests" begin @testset "abelianization homomorphism" begin
N = 3 N = 4
G = AutGroup(FreeGroup(N)) G = AutGroup(FreeGroup(N))
S = unique([gens(G); inv.(gens(G))]) S = unique([gens(G); inv.(gens(G))])
R = 3 R = 3
@test Groups.linear_repr(one(G)) isa Matrix{Int} @test Groups.abelianize(one(G)) isa Matrix{Int}
@test Groups.linear_repr(one(G)) == Matrix{Int}(I, N, N) @test Groups.abelianize(one(G)) == Matrix{Int}(I, N, N)
M = Matrix{Int}(I, N, N) M = Matrix{Int}(I, N, N)
M[1,2] = 1 M[1,2] = 1
ϱ₁₂ = G(Groups.rmul_autsymbol(1,2)) ϱ₁₂ = G(Groups.ϱ(1,2))
λ₁₂ = G(Groups.rmul_autsymbol(1,2)) λ₁₂ = G(Groups.λ(1,2))
@test Groups.linear_repr(ϱ₁₂) == M @test Groups.abelianize(ϱ₁₂) == M
@test Groups.linear_repr(λ₁₂) == M @test Groups.abelianize(λ₁₂) == M
M[1,2] = -1 M[1,2] = -1
@test Groups.linear_repr(ϱ₁₂^-1) == M @test Groups.abelianize(ϱ₁₂^-1) == M
@test Groups.linear_repr(λ₁₂^-1) == M @test Groups.abelianize(λ₁₂^-1) == M
@test Groups.linear_repr(ϱ₁₂*λ₁₂^-1) == Matrix{Int}(I, N, N) @test Groups.abelianize(ϱ₁₂*λ₁₂^-1) == Matrix{Int}(I, N, N)
@test Groups.linear_repr(λ₁₂^-1*ϱ₁₂) == Matrix{Int}(I, N, N) @test Groups.abelianize(λ₁₂^-1*ϱ₁₂) == Matrix{Int}(I, N, N)
M = Matrix{Int}(I, N, N) M = Matrix{Int}(I, N, N)
M[2,2] = -1 M[2,2] = -1
ε₂ = G(Groups.flip_autsymbol(2)) ε₂ = G(Groups.flip(2))
@test Groups.linear_repr(ε₂) == M @test Groups.abelianize(ε₂) == M
@test Groups.linear_repr(ε₂^2) == Matrix{Int}(I, N, N) @test Groups.abelianize(ε₂^2) == Matrix{Int}(I, N, N)
M = [0 1 0; 0 0 1; 1 0 0] M = [0 1 0 0; 0 0 0 1; 0 0 1 0; 1 0 0 0]
σ = G(Groups.perm_autsymbol([2,3,1])) σ = G(Groups.AutSymbol(perm"(1,2,4)"))
@test Groups.linear_repr(σ) == M @test Groups.abelianize(σ) == M
@test Groups.linear_repr(σ^3) == Matrix{Int}(I, 3, 3) @test Groups.abelianize(σ^3) == Matrix{Int}(I, N, N)
@test Groups.linear_repr(σ)^3 == Matrix{Int}(I, 3, 3) @test Groups.abelianize(σ)^3 == Matrix{Int}(I, N, N)
function test_homomorphism(S, r) function test_homomorphism(S, r)
for elts in Iterators.product([[g for g in S] for _ in 1:r]...) for elts in Iterators.product([[g for g in S] for _ in 1:r]...)
prod(Groups.linear_repr.(elts)) == Groups.linear_repr(prod(elts)) || error("linear representaton test failed at $elts") prod(Groups.abelianize.(elts)) == Groups.abelianize(prod(elts)) || error("linear representaton test failed at $elts")
end end
return 0 return 0
end end

View File

@ -3,11 +3,11 @@
×(a,b) = Groups.DirectPower(a,b) ×(a,b) = Groups.DirectPower(a,b)
@testset "Constructors" begin @testset "Constructors" begin
G = PermutationGroup(3) G = SymmetricGroup(3)
@test Groups.DirectPowerGroup(G,2) isa AbstractAlgebra.Group @test Groups.DirectPowerGroup(G,2) isa AbstractAlgebra.Group
@test G×G isa AbstractAlgebra.Group @test G×G isa AbstractAlgebra.Group
@test Groups.DirectPowerGroup(G,2) isa Groups.DirectPowerGroup{2, Generic.PermGroup{Int64}} @test Groups.DirectPowerGroup(G,2) isa Groups.DirectPowerGroup{2, Generic.SymmetricGroup{Int64}}
@test (G×G)×G == DirectPowerGroup(G, 3) @test (G×G)×G == DirectPowerGroup(G, 3)
@test (G×G)×G == (G×G)×G @test (G×G)×G == (G×G)×G
@ -42,7 +42,7 @@
end end
@testset "Basic arithmetic" begin @testset "Basic arithmetic" begin
G = PermutationGroup(3) G = SymmetricGroup(3)
GG = G×G GG = G×G
i = perm"(1,3)" i = perm"(1,3)"
g = perm"(1,2,3)" g = perm"(1,2,3)"
@ -65,7 +65,7 @@
end end
@testset "elem/parent_types" begin @testset "elem/parent_types" begin
G = PermutationGroup(3) G = SymmetricGroup(3)
g = perm"(1,2,3)" g = perm"(1,2,3)"
@test elem_type(G×G) == DirectPowerGroupElem{2, elem_type(G)} @test elem_type(G×G) == DirectPowerGroupElem{2, elem_type(G)}
@ -75,7 +75,7 @@
end end
@testset "Misc" begin @testset "Misc" begin
G = PermutationGroup(3) G = SymmetricGroup(3)
GG = Groups.DirectPowerGroup(G,3) GG = Groups.DirectPowerGroup(G,3)
@test order(GG) == 216 @test order(GG) == 216

View File

@ -1,15 +1,18 @@
@testset "FPGroups definitions" begin @testset "FPGroups definitions" begin
F = FreeGroup(["a", "b", "c"]) F = FreeGroup(["a", "b", "c"])
a,b,c = gens(F) a,b,c = Groups.gens(F)
R = [a^2, a*b*a, c*b*a] R = [a^2, a*b*a, c*b*a]
@test F/R isa FPGroup @test F/R isa FPGroup
@test F isa FreeGroup @test F isa FreeGroup
G = F/R G = F/R
A,B,C = gens(G) A,B,C = Groups.gens(G)
@test A^2 == one(G) @test Groups.reduce!(A^2) == one(G)
@test A*B*A*A == A @test Groups.reduce!(A*B*A*A) == A
@test A*A*B*A == B*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 @test G/[B^2, C*B*C] isa FPGroup
end end

View File

@ -55,23 +55,18 @@ end
@testset "internal arithmetic" begin @testset "internal arithmetic" begin
@test Vector{Groups.FreeGroupElem}([s,t]) == [Groups.GroupWord(s), Groups.GroupWord(t)]
@test (s*s).symbols == (s^2).symbols @test (s*s).symbols == (s^2).symbols
@test hash([t^1,s^1]) == hash([t^2*inv(t),s*inv(s)*s]) @test hash([t^1,s^1]) == hash([t^2*inv(t),s*inv(s)*s])
t_symb = Groups.FreeSymbol(:t) t_symb = Groups.FreeSymbol(:t)
tt = deepcopy(t) tt = deepcopy(t)
@test string(Groups.r_multiply!(tt,[inv(t_symb)]; reduced=true)) == @test string(Groups.rmul!(tt, tt, inv(t_symb))) == "(id)"
"(id)"
tt = deepcopy(t) tt = deepcopy(t)
@test string(Groups.r_multiply!(tt,[inv(t_symb)]; reduced=false)) == @test string(append!(tt, [inv(t_symb)])) == "t*t^-1"
"t*t^-1"
tt = deepcopy(t) tt = deepcopy(t)
@test string(Groups.l_multiply!(tt,[inv(t_symb)]; reduced=true)) == @test string(Groups.lmul!(tt, tt, inv(t_symb))) == "(id)"
"(id)"
tt = deepcopy(t) tt = deepcopy(t)
@test string(Groups.l_multiply!(tt,[inv(t_symb)]; reduced=false)) == @test string(prepend!(tt, [inv(t_symb)])) == "t^-1*t"
"t^-1*t"
end end
@testset "reductions" begin @testset "reductions" begin
@ -79,7 +74,7 @@ end
@test length((one(G)*one(G)).symbols) == 0 @test length((one(G)*one(G)).symbols) == 0
@test one(G) == one(G)*one(G) @test one(G) == one(G)*one(G)
w = deepcopy(s) w = deepcopy(s)
push!(w.symbols, (s^-1).symbols[1]) push!(Groups.syllables(w), (s^-1).symbols[1])
@test Groups.reduce!(w) == one(parent(w)) @test Groups.reduce!(w) == one(parent(w))
o = (t*s)^3 o = (t*s)^3
@test o == t*s*t*s*t*s @test o == t*s*t*s*t*s
@ -116,23 +111,23 @@ end
@test Groups.issubsymbol(inv(b), Groups.change_pow(b,-2)) == true @test Groups.issubsymbol(inv(b), Groups.change_pow(b,-2)) == true
c = s*t*s^-1*t^-1 c = s*t*s^-1*t^-1
@test findfirst(c, s^-1*t^-1) == 3 @test findfirst(s^-1*t^-1, c) == 3
@test findnext(c*s^-1, s^-1*t^-1,3) == 3 @test findnext(s^-1*t^-1, c*s^-1,3) == 3
@test findnext(c*s^-1*t^-1, s^-1*t^-1,4) == 5 @test findnext(s^-1*t^-1, c*s^-1*t^-1,4) == 5
@test findfirst(c*t, c) == 0 @test findfirst(c, c*t) === nothing
w = s*t*s^-1 w = s*t*s^-1
subst = Dict{FreeGroupElem, FreeGroupElem}(w => s^1, s*t^-1 => t^4) subst = Dict{FreeGroupElem, FreeGroupElem}(w => s^1, s*t^-1 => t^4)
@test Groups.replace(c, 1, s*t, one(G)) == s^-1*t^-1 @test Groups.replace(c, s*t=>one(G)) == s^-1*t^-1
@test Groups.replace(c, 1, w, subst[w]) == s*t^-1 @test Groups.replace(c, w=>subst[w]) == s*t^-1
@test Groups.replace(s*c*t^-1, 1, w, subst[w]) == s^2*t^-2 @test Groups.replace(s*c*t^-1, w=>subst[w]) == s^2*t^-2
@test Groups.replace(t*c*t, 2, w, subst[w]) == t*s @test Groups.replace(t*c*t, w=>subst[w]) == t*s
@test Groups.replace_all(s*c*s*c*s, subst) == s*t^4*s*t^4*s @test Groups.replace(s*c*s*c*s, subst) == s*t^4*s*t^4*s
G = FreeGroup(["x", "y"]) G = FreeGroup(["x", "y"])
x,y = gens(G) x,y = gens(G)
@test Groups.replace(x*y^9, 2, y^2, y) == x*y^8 @test Groups.replace(x*y^9, y^2=>y) == x*y^5
@test Groups.replace(x^3, 1, x^2, y) == x*y @test Groups.replace(x^3, x^2=>y) == x*y
@test Groups.replace(y*x^3*y, 2, x^2, y) == y*x*y^2 @test Groups.replace(y*x^3*y, x^2=>y) == y*x*y^2
end end
end end

View File

@ -1,6 +1,6 @@
@testset "WreathProducts" begin @testset "WreathProducts" begin
S_3 = PermutationGroup(3) S_3 = SymmetricGroup(3)
S_2 = PermutationGroup(2) S_2 = SymmetricGroup(2)
b = perm"(1,2,3)" b = perm"(1,2,3)"
a = perm"(1,2)" a = perm"(1,2)"
@ -8,7 +8,7 @@
@test Groups.WreathProduct(S_2, S_3) isa AbstractAlgebra.Group @test Groups.WreathProduct(S_2, S_3) isa AbstractAlgebra.Group
B3 = Groups.WreathProduct(S_2, S_3) B3 = Groups.WreathProduct(S_2, S_3)
@test B3 isa Groups.WreathProduct @test B3 isa Groups.WreathProduct
@test B3 isa WreathProduct{3, Generic.PermGroup{Int}, Generic.PermGroup{Int}} @test B3 isa WreathProduct{3, Generic.SymmetricGroup{Int}, Generic.SymmetricGroup{Int}}
aa = Groups.DirectPowerGroupElem((a^0 ,a, a^2)) aa = Groups.DirectPowerGroupElem((a^0 ,a, a^2))
@ -37,7 +37,7 @@
@test elem_type(B3) == Groups.WreathProductElem{3, Generic.Perm{Int}, Generic.Perm{Int}} @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.PermGroup{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)) == Groups.WreathProduct(S_2,S_3)
@test parent(one(B3)) == B3 @test parent(one(B3)) == B3
@ -64,7 +64,7 @@
end end
@testset "Group arithmetic" begin @testset "Group arithmetic" begin
B4 = Groups.WreathProduct(PermutationGroup(3), PermutationGroup(4)) B4 = Groups.WreathProduct(SymmetricGroup(3), SymmetricGroup(4))
id, a, b = perm"(3)", perm"(1,2)(3)", perm"(1,2,3)" id, a, b = perm"(3)", perm"(1,2)(3)", perm"(1,2,3)"
@ -84,7 +84,7 @@
end end
@testset "Iteration" begin @testset "Iteration" begin
Wr = WreathProduct(PermutationGroup(2),PermutationGroup(4)) Wr = WreathProduct(SymmetricGroup(2),SymmetricGroup(4))
elts = collect(Wr) elts = collect(Wr)
@test elts isa Vector{Groups.WreathProductElem{4, Generic.Perm{Int}, Generic.Perm{Int}}} @test elts isa Vector{Groups.WreathProductElem{4, Generic.Perm{Int}, Generic.Perm{Int}}}