remove the old implementation

This commit is contained in:
Marek Kaluba 2021-06-21 18:16:04 +02:00
parent 7d5c7ac623
commit 26781bad5c
No known key found for this signature in database
GPG Key ID: 8BF1A3855328FC15
17 changed files with 85 additions and 1712 deletions

View File

@ -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::AbstractAlgebra.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::AbstractAlgebra.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::AbstractAlgebra.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
Base.eltype(::Type{AutGroup{N}}) where N = Automorphism{N}
GroupsCore.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 AbstractAlgebra.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 Base.:(==)(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 Base.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)

View File

@ -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
#
Base.eltype(::Type{FPGroup}) = FPGroupElem
GroupsCore.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 Base.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

View File

@ -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
#
Base.eltype(::Type{FreeGroup}) = FreeGroupElem
GroupsCore.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 Base.show(io::IO, G::FreeGroup)
print(io, "Free group on $(length(G.gens)) generators: ")
join(io, G.gens, ", ")
end

View File

@ -1,30 +1,12 @@
module Groups
using GroupsCore
using LinearAlgebra
using ThreadsX
import AbstractAlgebra
import KnuthBendix
import OrderedCollections: OrderedSet
export gens, FreeGroup, Aut, SAut
include("types.jl")
include("FreeGroup.jl")
include("FPGroups.jl")
include("AutGroup.jl")
include("symbols.jl")
include("words.jl")
include("hashing.jl")
include("freereduce.jl")
include("arithmetic.jl")
include("findreplace.jl")
module New
import OrderedCollections: OrderedSet
include("new_types.jl")
include("new_hashing.jl")
include("normalform.jl")
@ -32,112 +14,5 @@ include("new_autgroups.jl")
include("groups/sautFn.jl")
end # module New
###############################################################################
#
# 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
#
GroupsCore.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}
@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
"""
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("wl_ball.jl")
end # of module Groups

View File

@ -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)
GroupsCore.mul!(out::T, x::T, y::T) where T <: GWord = rmul!(out, x, y)
Base.:(*)(W::GW, Z::GW) where GW <: GWord = rmul!(deepcopy(W), W, Z)
Base.:(*)(W::GWord, s::GSymbol) = freereduce!(push!(deepcopy(W), s))
Base.:(*)(s::GSymbol, W::GWord) = freereduce!(pushfirst!(deepcopy(W), s))
function Base.power_by_squaring(W::GWord, p::Integer)
if p < 0
return Base.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
Base.:(^)(x::GWord, n::Integer) = Base.power_by_squaring(x,n)

View File

@ -1,183 +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
"""
Find the first syllable index k>=i such that Z < syllables(W)[k:k+syllablelength(Z)-1]
"""
function Base.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 Base.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 Base.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 Base.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
Base.findfirst(subword::GWord, word::GWord) = findnext(subword, word, 1)
Base.findlast(subword::GWord, word::GWord) =
findprev(subword, word, syllablelength(word)-syllablelength(subword)+1)
function Base.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 Base.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 Base.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 Base.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

View File

@ -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.
"""
Base.reduce(w::GWord) = reduce!(deepcopy(w))

View File

@ -1,34 +0,0 @@
###############################################################################
#
# 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 Base.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
# 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 Base.:(==)(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

View File

@ -1,23 +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.literal_pow(::typeof(^), s::Groups.GSymbol, ::Val{-1}) = inv(s)
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 Base.:(==)(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

View File

@ -1,50 +0,0 @@
abstract type AbstractFPGroup <: GroupsCore.Group end
function Base.one(G::Gr) where Gr <: AbstractFPGroup
El = eltype(G)
id = El(eltype(El)[])
id.parent = G
return id
end
"""
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} <: GroupsCore.GroupElement end
"""
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.
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)
end

62
src/wl_ball.jl Normal file
View 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

View File

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

View File

@ -1,288 +0,0 @@
import AbstractAlgebra.@perm_str
@testset "Automorphisms" begin
G = AbstractAlgebra.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)), GroupsCore.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)")
= push!(A(f), f)
@test Groups.simplifyperms!(Bool, ) == false
@test ^2 == one(A)
@test !isone()
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

View File

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

View File

@ -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"]), GroupsCore.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 = GroupsCore.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

View File

@ -2,42 +2,32 @@ using Test
import AbstractAlgebra
using Groups
include("symmetric.jl")
using LinearAlgebra
using KnuthBendix
using GroupsCore
include(joinpath(pathof(GroupsCore), "..", "..", "test", "conformance_test.jl"))
@testset "Groups" begin
@testset "wlmetric_ball" begin
M = AbstractAlgebra.MatrixAlgebra(AbstractAlgebra.zz, 3)
w = one(M); w[1,2] = 1;
r = one(M); r[2,3] = -3;
s = one(M); s[1,3] = 2; s[3,2] = -1;
@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("FPGroup-tests.jl")
include("free_groups.jl")
include("fp_groups.jl")
@testset "New FPGroups" begin
using Groups.New
using KnuthBendix
include("AutFn.jl")
using GroupsCore
include(joinpath(pathof(GroupsCore), "..", "..", "test", "conformance_test.jl"))
include("free_groups.jl")
include("fp_groups.jl")
include("AutFn.jl")
if !haskey(ENV, "CI")
include("benchmarks.jl")
end
end
# if !haskey(ENV, "CI")
# include("benchmarks.jl")
# end
end

View File

@ -1,31 +0,0 @@
import AbstractAlgebra
using GroupsCore
# disambiguation
GroupsCore.order(
::Type{I},
G::AbstractAlgebra.Generic.SymmetricGroup,
) where {I<:Integer} = I(factorial(G.n))
# disambiguation
GroupsCore.order(
::Type{I},
g::AbstractAlgebra.Generic.Perm,
) where {I<:Integer} =
I(foldl(lcm, length(c) for c in AbstractAlgebra.cycles(g)))
# correct the AA length:
Base.length(G::AbstractAlgebra.Generic.SymmetricGroup) = order(Int, G)
# genuinely new methods:
Base.IteratorSize(::Type{<:AbstractAlgebra.AbstractPermutationGroup}) = Base.HasLength()
function GroupsCore.gens(G::AbstractAlgebra.Generic.SymmetricGroup{I}) where {I}
a, b = one(G), one(G)
circshift!(a.d, b.d, -1)
b.d[1], b.d[2] = 2, 1
return [a, b]
end
Base.deepcopy_internal(g::AbstractAlgebra.Generic.Perm, ::IdDict) =
AbstractAlgebra.Generic.Perm(deepcopy(g.d), false)