diff --git a/Project.toml b/Project.toml index 5b2d400..58a8539 100644 --- a/Project.toml +++ b/Project.toml @@ -1,18 +1,18 @@ name = "Groups" uuid = "5d8bd718-bd84-11e8-3b40-ad14f4a32557" authors = ["Marek Kaluba "] -version = "0.3.0" +version = "0.4.0" [deps] AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +[compat] +AbstractAlgebra = "^0.9.0" + [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Test"] - -[compat] -AbstractAlgebra = "^0.7.0" diff --git a/src/AutGroup.jl b/src/AutGroup.jl index 7695278..232d98d 100644 --- a/src/AutGroup.jl +++ b/src/AutGroup.jl @@ -1,8 +1,9 @@ +export Automorphism, AutGroup, Aut, SAut + ############################################################################### # # AutSymbol/ AutGroup / Automorphism # -############################################################################### struct RTransvect i::Int8 @@ -25,79 +26,11 @@ end struct Identity end struct AutSymbol <: GSymbol - id::Symbol - pow::Int8 - fn::Union{LTransvect, RTransvect, PermAut, FlipAut, Identity} + id::Symbol + pow::Int8 + fn::Union{LTransvect, RTransvect, PermAut, FlipAut, Identity} end -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 - -export Automorphism, AutGroup, Aut, SAut - -############################################################################### -# -# Type and parent object methods -# -############################################################################### - -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) - @inbounds Groups.r_multiply!(v[ϱ.i], (v[ϱ.j]^pow).symbols, reduced=false) - return v -end - -function (λ::LTransvect)(v, pow::Integer=1) - @inbounds Groups.l_multiply!(v[λ.i], (v[λ.j]^pow).symbols, reduced=false) - return v -end - -function (σ::PermAut)(v, pow::Integer=1) - 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 - @inbounds for k in eachindex(v) - v[k].symbols = w[s[k]].symbols - end - 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 - # taken from ValidatedNumerics, under under the MIT "Expat" License: # https://github.com/JuliaIntervals/ValidatedNumerics.jl/blob/master/LICENSE.md function subscriptify(n::Integer) @@ -111,53 +44,82 @@ function id_autsymbol() return AutSymbol(Symbol("(id)"), 0, Identity()) end -function rmul_autsymbol(i::Integer, j::Integer; pow::Integer=1) +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 lmul_autsymbol(i::Integer, j::Integer; pow::Integer=1) +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_autsymbol(i::Integer; pow::Integer=1) - if iseven(pow) - return id_autsymbol() - else - id = Symbol("ɛ", subscriptify(i)) - return AutSymbol(id, 1, FlipAut(i)) - 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 perm_autsymbol(p::Generic.Perm{I}; pow::Integer=one(I)) where I<:Integer +function AutSymbol(p::Generic.Perm, pow::Integer=1) 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 + + 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 -function perm_autsymbol(a::Vector{<:Integer}) - return perm_autsymbol(Generic.Perm(Vector{Int8}(a), false)) -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 domain(G::AutGroup{N}) where N - F = G.objectGroup - gg = gens(F) - return ntuple(i->gg[i], N) +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 constructors +# AutGroup / Automorphism # -############################################################################### + +mutable struct AutGroup{N} <: AbstractFPGroup + objectGroup::FreeGroup + gens::Vector{AutSymbol} +end + +mutable struct Automorphism{N} <: GWord{AutSymbol} + symbols::Vector{AutSymbol} + modified::Bool + savedhash::UInt + parent::AutGroup{N} + + function Automorphism{N}(f::Vector{AutSymbol}) where {N} + return new{N}(f, true, zero(UInt)) + end +end + +elem_type(::Type{AutGroup{N}}) where N = Automorphism{N} +parent_type(::Type{Automorphism{N}}) where N = AutGroup{N} function AutGroup(G::FreeGroup; special=false) S = AutSymbol[] @@ -166,17 +128,16 @@ function AutGroup(G::FreeGroup; special=false) 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] + rmuls = [ϱ(i,j) for (i,j) in indexing] + lmuls = [λ(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] + 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 @@ -184,134 +145,120 @@ 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 + setparent!(g, G) return g end -function (G::AutGroup{N})(g::Automorphism{N}) where N - g.parent = G - return g +(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) + append!(v[ϱ.i], v[ϱ.j]^pow) + freereduce!(v[ϱ.i]) + return v end +function (λ::LTransvect)(v, pow::Integer=1) + prepend!(v[λ.i], v[λ.j]^pow) + freereduce!(v[λ.i]) + 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 # -############################################################################### -function (s::AutSymbol)(v::NTuple{N, T}) where {N, T} - if s.pow != 0 - v = s.fn(v, s.pow)::NTuple{N, T} - end - return v -end +(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 (i, s) in enumerate(f.symbols) + for s in syllables(f) v = s(v)::NTuple{N, T} - if i % 5 == 0 - freereduce!.(v) - end 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))) ############################################################################### # -# Comparison +# hashing && equality # -############################################################################### -const HASHINGCONST = 0x7d28276b01874b19 # hash(Automorphism) - -hash(s::AutSymbol, h::UInt) = hash(s.id, hash(s.pow, hash(:AutSymbol, h))) - -function hash(g::Automorphism{N}, images, h::UInt=HASHINGCONST) where N - return hash(images, hash(parent(g), hash(Automorphism{N}, h))) +function hash_internal(g::Automorphism, images = freereduce!.(evaluate(g)), + h::UInt = 0x7d28276b01874b19) # hash(Automorphism) + return hash(images, hash(parent(g), h)) end -function hash(g::Automorphism, h::UInt) - if g.modified - g_im = reduce!.(evaluate(g)) - g.savedhash = hash(g, g_im) - g.modified = false - end - return xor(g.savedhash, h) +function compute_images(g::Automorphism) + images = reduce!.(evaluate(g)) + savehash!(g, hash_internal(g, images)) + unsetmodified!(g) + return images end 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 g.savedhash != h.savedhash - return false - end + if ismodified(g) + img = compute_images(g) + img_c = true end - # expensive: - g_im = reduce!.(evaluate(g)) - h_im = reduce!.(evaluate(h)) - # cheap: - g.savedhash = hash(g, g_im) - g.modified = false - h.savedhash = hash(h, h_im) - h.modified = false + if ismodified(h) + imh = compute_images(h) + imh_c = true + end - 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 -############################################################################### -# -# 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 # -############################################################################### function show(io::IO, G::AutGroup) print(io, "Automorphism Group of $(G.objectGroup)\n") @@ -320,87 +267,58 @@ end ############################################################################### # -# Binary operators +# Reduction # -############################################################################### -############################################################################### -# -# Inversion -# -############################################################################### +getperm(s::AutSymbol) = s.fn.perm^s.pow -inv(f::AutSymbol) = change_pow(f, -f.pow) - -############################################################################### -# -# 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 +function simplifyperms!(::Type{Bool}, w::Automorphism{N}) where N reduced = true - to_delete = Int[] - for i in 1:length(W.symbols)-1 - if W.symbols[i].pow == 0 + for i in 1:syllablelength(w)-1 + s, ns = syllables(w)[i], syllables(w)[i+1] + if isone(s) 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 - c = W.symbols[i] - n = W.symbols[i+1] - W.symbols[i+1] = perm_autsymbol(getperm(c)*getperm(n)) - push!(to_delete, i) + setmodified!(w) + syllables(w)[i+1] = AutSymbol(getperm(s)*getperm(ns)) + syllables(w)[i] = change_pow(s, 0) end end - deleteat!(W.symbols, to_delete) - deleteids!(W) + filter!(!isone, syllables(w)) return reduced end -function reduce!(W::Automorphism) - if length(W) == 0 - return W - elseif length(W.symbols) == 1 - deleteids!(W) - else - reduced = false - while !reduced - reduced = simplifyperms!(W) && freereduce!(W) - end +function reduce!(w::Automorphism) + reduced = false + while !reduced + reduced = simplifyperms!(Bool, w) && freereduce!(Bool, w) end - - W.modified = true - - return W + return w 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[a.i,a.j] = pow return x end -function matrix_repr(a::FlipAut, n::Int, pow) +function abelianize(a::FlipAut, n::Int, pow) x = Matrix{Int}(I, n, n) - x[a.i,a.i] = -1^pow + x[a.i,a.i] = -1 return x end -matrix_repr(a::PermAut, n::Int, pow) = Matrix{Int}(I, n, n)[(a.perm^pow).d, :] - -matrix_repr(a::Identity, n::Int, pow) = Matrix{Int}(I, n, n) +abelianize(a::PermAut, n::Integer, pow) = Matrix{Int}(I, n, n)[(a.perm^pow).d, :] +abelianize(a::Identity, n::Integer, pow) = abelianize(;n=n) diff --git a/src/FPGroups.jl b/src/FPGroups.jl index e7aeb84..ff25389 100644 --- a/src/FPGroups.jl +++ b/src/FPGroups.jl @@ -12,14 +12,14 @@ end FPGroupElem = GroupWord{FPSymbol} mutable struct FPGroup <: AbstractFPGroup - gens::Vector{FPSymbol} - rels::Dict{FPGroupElem, FPGroupElem} + gens::Vector{FPSymbol} + rels::Dict{FreeGroupElem, FreeGroupElem} - function FPGroup(gens::Vector{T}, rels::Dict{FPGroupElem, FPGroupElem}) where {T<:GSymbol} - G = new(gens) - G.rels = Dict(G(k) => G(v) for (k,v) in rels) - return G - end + 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 @@ -28,131 +28,70 @@ export FPGroupElem, FPGroup # # Type and parent object methods # -############################################################################### -parent_type(::Type{FPGroupElem}) = FPGroup - -elem_type(::FPGroup) = FPGroupElem +AbstractAlgebra.elem_type(::Type{FPGroup}) = FPGroupElem +AbstractAlgebra.parent_type(::Type{FPGroupElem}) = FPGroup ############################################################################### # # FPSymbol constructors # -############################################################################### FPSymbol(s::Symbol) = FPSymbol(s, 1) FPSymbol(s::String) = FPSymbol(Symbol(s)) FPSymbol(s::GSymbol) = FPSymbol(s.id, s.pow) -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]) ############################################################################### # # Parent object call overloads # -############################################################################### - -function Base.one(G::FPGroup) - id = FPGroupElem(FPSymbol[]) - id.parent = G - return id -end function (G::FPGroup)(w::GWord) - if length(w) == 0 - return one(G) - end + if isempty(w) + return one(G) + end - if eltype(w.symbols) == FreeSymbol - w = FPGroupElem(FPSymbol.(w.symbols)) - 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 - if eltype(w.symbols) == FPSymbol - for s in 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 - w.parent = G - return reduce!(w) + w = FPGroupElem(FPSymbol.(syllables(w))) + setparent!(w, G) + return reduce!(w) end -(G::FPGroup)(s::FPSymbol) = 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) +(G::FPGroup)(s::GSymbol) = G(FPGroupElem(s)) ############################################################################### # # String I/O # -############################################################################### function show(io::IO, G::FPGroup) - print(io, "FPgroup on $(length(G.gens)) generators ") - strrels = join(G.rels, ", ") - if length(strrels) > 300 - print(io, "⟨ ", join(G.gens, ", "), " | $(length(G.rels)) relation(s) ⟩.") - else - print(io, "⟨ ", join(G.gens, ", "), " | ", join(G.rels, ", "), " ⟩.") - end + 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 -############################################################################### -# -# 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) - if length(W) < 2 - deleteat!(W.symbols, findall(x -> x.pow == 0, W.symbols)) - else - reduced = false - while !reduced - reduced = freereduce!(W) || replace_all!(W, parent(W).rels) - end + reduced = false + while !reduced + W = replace(W, parent(W).rels) + reduced = freereduce!(Bool, W) end - - W.savedhash = hash(W.symbols, hash(typeof(W))) - W.modified = false return W end @@ -162,31 +101,35 @@ end # ############################################################################### -function add_rels!(G::FPGroup, newrels::Dict{FPGroupElem,FPGroupElem}) - for w in keys(newrels) - if !(w in keys(G.rels)) - G.rels[w] = G(newrels[w]) - end - end +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) - newrels = Dict(H(r) => one(H) for r in newrels) - add_rels!(H, newrels) - return H + 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.:/(G::FreeGroup, rels::Vector{FreeGroupElem}) - for r in rels - parent(r) == G || throw(DomainError( - "Can not form quotient group: $r is not an element of $G")) - end - H = FPGroup(deepcopy(G)) - H.rels = Dict(H(rel) => one(H) for rel in unique(rels)) - return H +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 diff --git a/src/FreeGroup.jl b/src/FreeGroup.jl index 9e3709e..9800d62 100644 --- a/src/FreeGroup.jl +++ b/src/FreeGroup.jl @@ -2,7 +2,6 @@ # # FreeSymbol/FreeGroupElem/FreeGroup definition # -############################################################################### struct FreeSymbol <: GSymbol id::Symbol @@ -14,7 +13,7 @@ FreeGroupElem = GroupWord{FreeSymbol} mutable struct FreeGroup <: AbstractFPGroup gens::Vector{FreeSymbol} - function FreeGroup(gens::Vector{T}) where {T<:GSymbol} + function FreeGroup(gens::AbstractVector{T}) where {T<:GSymbol} G = new(gens) G.gens = gens return G @@ -27,86 +26,48 @@ export FreeGroupElem, FreeGroup # # Type and parent object methods # -############################################################################### -elem_type(::Type{FreeGroup}) = FreeGroupElem - -parent_type(::Type{FreeGroupElem}) = FreeGroup +AbstractAlgebra.elem_type(::Type{FreeGroup}) = FreeGroupElem +AbstractAlgebra.parent_type(::Type{FreeGroupElem}) = FreeGroup ############################################################################### # # FreeSymbol constructors # -############################################################################### 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(a::Vector) = FreeGroup(FreeSymbol.(a)) +FreeGroup(a::AbstractVector) = FreeGroup(FreeSymbol.(a)) ############################################################################### # # Parent object call overloads # -############################################################################### - -function Base.one(G::FreeGroup) - id = FreeGroupElem(FreeSymbol[]) - id.parent = G - return id -end function (G::FreeGroup)(w::GroupWord{FreeSymbol}) - if length(w) > 0 - for s in 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 + 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 - w.parent = G - return w + setparent!(w, G) + return reduce!(w) end -(G::FreeGroup)(s::FreeSymbol) = G(FreeGroupElem(s)) - -############################################################################### -# -# 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) +(G::FreeGroup)(s::GSymbol) = G(FreeGroupElem(s)) +(G::FreeGroup)(v::AbstractVector{<:GSymbol}) = G(FreeGroupElem(FreeSymbol.(v))) ############################################################################### # # String I/O # -############################################################################### function show(io::IO, G::FreeGroup) print(io, "Free group on $(length(G.gens)) generators: ") join(io, G.gens, ", ") end - -############################################################################### -# -# Comparison -# -############################################################################### - -############################################################################### -# -# Inversion -# -############################################################################### - -inv(s::FreeSymbol) = change_pow(s, -s.pow) diff --git a/src/Groups.jl b/src/Groups.jl index ee021e8..9dc67cb 100644 --- a/src/Groups.jl +++ b/src/Groups.jl @@ -7,463 +7,100 @@ import AbstractAlgebra: order, gens, matrix_repr import Base: length, ==, hash, show, convert, eltype, iterate import Base: inv, reduce, *, ^, power_by_squaring -import Base: findfirst, findnext +import Base: findfirst, findnext, replace import Base: deepcopy_internal -export elements - using LinearAlgebra using Markdown -Base.one(G::Generic.PermGroup) = G(collect(1:G.n), false) +export gens, FreeGroup, Aut, SAut -############################################################################### -# -# 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("types.jl") include("FreeGroup.jl") include("FPGroups.jl") include("AutGroup.jl") +include("symbols.jl") +include("fallbacks.jl") +include("words.jl") +include("hashing.jl") +include("freereduce.jl") +include("arithmetic.jl") +include("findreplace.jl") + include("DirectPower.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 # -############################################################################### @doc doc""" show(io::IO, W::GWord) > 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 print(io, "(id)") else - join(io, [string(s) for s in W.symbols], "*") + join(io, (string(s) for s in syllables(W)), "*") end end -function show(io::IO, s::T) where {T<:GSymbol} - if s.pow == 1 - print(io, string(s.id)) - else - print(io, string((s.id))*"^$(s.pow)") - 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 +function Base.show(io::IO, s::T) where {T<:GSymbol} + if s.pow == 1 + print(io, string(s.id)) else - G = parent(W) - w = T(reverse([inv(s) for s in W.symbols])) - w.modified = true - return G(w) + print(io, "$(s.id)^$(s.pow)") 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 # -############################################################################### -function generate_balls(S::AbstractVector{T}, Id::T=one(parent(first(S))); - radius=2, op=*) where T<:GroupElem +@doc doc""" + 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[] - B = [Id] + B = [one(first(S))] for i in 1:radius BB = [op(i,j) for (i,j) in Base.product(B,S)] B = unique([B; vec(BB)]) push!(sizes, length(B)) end - return B, sizes + isone(center) && return B, sizes + return c.*B, sizes end -function generate_balls(S::AbstractVector{T}, Id::T=one(parent(first(S))); - radius=2, op=*) where {T<:NCRingElem} - sizes = Int[] - B = [Id] - for i in 1:radius - BB = [op(i,j) for (i,j) in Base.product(B,S)] - B = unique([B; vec(BB)]) - push!(sizes, length(B)) - end - return B, sizes +@doc doc""" + image(A::GWord, homomorphism; kwargs...) +Evaluate homomorphism `homomorphism` on a GWord `A`. +`homomorphism` needs implement + > `hom(s; kwargs...)`, +where `hom(;kwargs...)` evaluates 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 -########### 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 diff --git a/src/WreathProducts.jl b/src/WreathProducts.jl index 18f0961..3754892 100644 --- a/src/WreathProducts.jl +++ b/src/WreathProducts.jl @@ -1,5 +1,7 @@ export WreathProduct, WreathProductElem +import AbstractAlgebra: AbstractPermutationGroup, AbstractPerm + ############################################################################### # # WreathProduct / WreathProductElem @@ -19,22 +21,23 @@ export WreathProduct, WreathProductElem * `N::Group` : the single factor of the group $N$ * `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} P::PG - function WreathProduct(Gr::T, P::PG) where {T, PG<:Generic.PermGroup} - N = DirectPowerGroup(Gr, Int(P.n)) - return new{Int(P.n), T, PG}(N, P) + function WreathProduct(G::Gr, P::PG) where + {Gr <: Group, PG <: AbstractPermutationGroup} + N = DirectPowerGroup(G, Int(P.n)) + return new{Int(P.n), Gr, PG}(N, P) end end -struct WreathProductElem{N, T<:GroupElem, P<:Generic.Perm} <: GroupElem +struct WreathProductElem{N, T<:GroupElem, P<:AbstractPerm} <: GroupElem n::DirectPowerGroupElem{N, T} p::P function WreathProductElem(n::DirectPowerGroupElem{N,T}, p::P, - check::Bool=true) where {N, T, P<:Generic.Perm} + check::Bool=true) where {N, T, P<:AbstractPerm} if check N == length(p.d) || throw(DomainError( "Can't form WreathProductElem: lengths differ")) diff --git a/src/arithmetic.jl b/src/arithmetic.jl new file mode 100644 index 0000000..933c5ec --- /dev/null +++ b/src/arithmetic.jl @@ -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) diff --git a/src/fallbacks.jl b/src/fallbacks.jl new file mode 100644 index 0000000..8fccc98 --- /dev/null +++ b/src/fallbacks.jl @@ -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 diff --git a/src/findreplace.jl b/src/findreplace.jl new file mode 100644 index 0000000..50fc358 --- /dev/null +++ b/src/findreplace.jl @@ -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 diff --git a/src/freereduce.jl b/src/freereduce.jl new file mode 100644 index 0000000..ef56c2c --- /dev/null +++ b/src/freereduce.jl @@ -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)) diff --git a/src/hashing.jl b/src/hashing.jl new file mode 100644 index 0000000..ff799eb --- /dev/null +++ b/src/hashing.jl @@ -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 diff --git a/src/symbols.jl b/src/symbols.jl new file mode 100644 index 0000000..dd766ff --- /dev/null +++ b/src/symbols.jl @@ -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 diff --git a/src/types.jl b/src/types.jl new file mode 100644 index 0000000..1de0a34 --- /dev/null +++ b/src/types.jl @@ -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 diff --git a/src/words.jl b/src/words.jl new file mode 100644 index 0000000..9ef7383 --- /dev/null +++ b/src/words.jl @@ -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) diff --git a/test/AutGroup-tests.jl b/test/AutGroup-tests.jl index 8a0c9b9..7b29949 100644 --- a/test/AutGroup-tests.jl +++ b/test/AutGroup-tests.jl @@ -1,6 +1,6 @@ @testset "Automorphisms" begin - G = PermutationGroup(Int8(4)) + G = SymmetricGroup(Int8(4)) @testset "AutSymbol" begin @test_throws MethodError Groups.AutSymbol(:a) @@ -8,72 +8,73 @@ f = Groups.AutSymbol(:a, 1, Groups.FlipAut(2)) @test isa(f, Groups.GSymbol) @test isa(f, Groups.AutSymbol) - @test isa(Groups.perm_autsymbol(Int8.([1,2,3,4])), Groups.AutSymbol) - @test isa(Groups.rmul_autsymbol(1,2), Groups.AutSymbol) - @test isa(Groups.lmul_autsymbol(3,4), Groups.AutSymbol) - @test isa(Groups.flip_autsymbol(3), Groups.AutSymbol) + @test isa(Groups.AutSymbol(perm"(4)"), Groups.AutSymbol) + @test isa(Groups.AutSymbol(perm"(1,2,3,4)"), Groups.AutSymbol) + @test isa(Groups.transvection_R(1,2), Groups.AutSymbol) + @test isa(Groups.transvection_R(3,4), Groups.AutSymbol) + @test isa(Groups.flip(3), Groups.AutSymbol) end a,b,c,d = gens(FreeGroup(4)) D = NTuple{4,FreeGroupElem}([a,b,c,d]) - @testset "flip_autsymbol correctness" begin - @test Groups.flip_autsymbol(1)(deepcopy(D)) == (a^-1, b,c,d) - @test Groups.flip_autsymbol(2)(deepcopy(D)) == (a, b^-1,c,d) - @test Groups.flip_autsymbol(3)(deepcopy(D)) == (a, b,c^-1,d) - @test Groups.flip_autsymbol(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_autsymbol(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_autsymbol(4))(deepcopy(D)) == (a, b,c,d^-1) + @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_autsymbol correctness" begin - σ = Groups.perm_autsymbol([1,2,3,4]) + @testset "perm correctness" begin + σ = Groups.AutSymbol(perm"(4)") @test σ(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 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 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 inv(σ)(deepcopy(D)) == (c, a, b, d) end - @testset "rmul/lmul_autsymbol correctness" begin + @testset "rmul/transvection_R correctness" begin i,j = 1,2 - r = Groups.rmul_autsymbol(i,j) - l = Groups.lmul_autsymbol(i,j) + 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.rmul_autsymbol(i,j) - l = Groups.lmul_autsymbol(i,j) + 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.rmul_autsymbol(i,j) - l = Groups.lmul_autsymbol(i,j) + 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.rmul_autsymbol(i,j) - l = Groups.lmul_autsymbol(i,j) + 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) @@ -94,40 +95,40 @@ @test length(Groups.gens(A)) == 0 A = AutGroup(FreeGroup(2)) @test length(Groups.gens(A)) == 7 - gens = Groups.gens(A) + Agens = Groups.gens(A) - @test isa(A(Groups.rmul_autsymbol(1,2)), Automorphism) - @test A(Groups.rmul_autsymbol(1,2)) in gens + @test isa(A(Groups.transvection_R(1,2)), Automorphism) + @test A(Groups.transvection_R(1,2)) in Agens - @test isa(A(Groups.rmul_autsymbol(2,1)), Automorphism) - @test A(Groups.rmul_autsymbol(2,1)) in gens + @test isa(A(Groups.transvection_R(2,1)), Automorphism) + @test A(Groups.transvection_R(2,1)) in Agens - @test isa(A(Groups.lmul_autsymbol(1,2)), Automorphism) - @test A(Groups.lmul_autsymbol(1,2)) in gens + @test isa(A(Groups.transvection_R(1,2)), Automorphism) + @test A(Groups.transvection_R(1,2)) in Agens - @test isa(A(Groups.lmul_autsymbol(2,1)), Automorphism) - @test A(Groups.lmul_autsymbol(2,1)) in gens + @test isa(A(Groups.transvection_R(2,1)), Automorphism) + @test A(Groups.transvection_R(2,1)) in Agens - @test isa(A(Groups.flip_autsymbol(1)), Automorphism) - @test A(Groups.flip_autsymbol(1)) in gens + @test isa(A(Groups.flip(1)), Automorphism) + @test A(Groups.flip(1)) in Agens - @test isa(A(Groups.flip_autsymbol(2)), Automorphism) - @test A(Groups.flip_autsymbol(2)) in gens + @test isa(A(Groups.flip(2)), Automorphism) + @test A(Groups.flip(2)) in Agens - @test isa(A(Groups.perm_autsymbol([2,1])), Automorphism) - @test A(Groups.perm_autsymbol([2,1])) in gens + @test isa(A(Groups.AutSymbol(perm"(1,2)")), Automorphism) + @test A(Groups.AutSymbol(perm"(1,2)")) in Agens end A = AutGroup(FreeGroup(4)) @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 (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_throws MethodError f*f @@ -136,14 +137,15 @@ end @testset "reductions/arithmetic" begin - f = Groups.perm_autsymbol([2,3,4,1]) + f = Groups.AutSymbol(perm"(1,2,3,4)") - f² = Groups.r_multiply(A(f), [f], reduced=false) - @test Groups.simplifyperms!(f²) == false + f² = append!(A(f), [f]) + @test Groups.simplifyperms!(Bool, f²) == false @test f²^2 == one(A) + @test !isone(f²) - a = A(Groups.rmul_autsymbol(1,2))*Groups.flip_autsymbol(2) - b = Groups.flip_autsymbol(2)*A(inv(Groups.rmul_autsymbol(1,2))) + a = A(Groups.transvection_L(1,2))*Groups.flip(2) + b = Groups.flip(2)*A(inv(Groups.transvection_L(1,2))) @test a*b == b*a @test a^3 * b^3 == one(A) g,h = Groups.gens(A)[[1,8]] # (g, h) = (ϱ₁₂, ϱ₃₂) @@ -164,27 +166,31 @@ # 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) @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) - 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) - 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)) == 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)) - @test length(g_im[1]) == 5 - @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) + @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 @@ -208,48 +214,48 @@ @test length(unique(B_2)) == 1777 end - @testset "linear_repr tests" begin - N = 3 + @testset "abelianization homomorphism" begin + N = 4 G = AutGroup(FreeGroup(N)) S = unique([gens(G); inv.(gens(G))]) R = 3 - @test Groups.linear_repr(one(G)) isa Matrix{Int} - @test Groups.linear_repr(one(G)) == Matrix{Int}(I, N, N) + @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.rmul_autsymbol(1,2)) - λ₁₂ = G(Groups.rmul_autsymbol(1,2)) + ϱ₁₂ = G(Groups.ϱ(1,2)) + λ₁₂ = G(Groups.λ(1,2)) - @test Groups.linear_repr(ϱ₁₂) == M - @test Groups.linear_repr(λ₁₂) == M + @test Groups.abelianize(ϱ₁₂) == M + @test Groups.abelianize(λ₁₂) == M M[1,2] = -1 - @test Groups.linear_repr(ϱ₁₂^-1) == M - @test Groups.linear_repr(λ₁₂^-1) == M + @test Groups.abelianize(ϱ₁₂^-1) == M + @test Groups.abelianize(λ₁₂^-1) == M - @test Groups.linear_repr(ϱ₁₂*λ₁₂^-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) + @test Groups.abelianize(λ₁₂^-1*ϱ₁₂) == Matrix{Int}(I, N, N) M = Matrix{Int}(I, N, N) M[2,2] = -1 - ε₂ = G(Groups.flip_autsymbol(2)) + ε₂ = G(Groups.flip(2)) - @test Groups.linear_repr(ε₂) == M - @test Groups.linear_repr(ε₂^2) == Matrix{Int}(I, N, N) + @test Groups.abelianize(ε₂) == M + @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])) - @test Groups.linear_repr(σ) == M - @test Groups.linear_repr(σ^3) == Matrix{Int}(I, 3, 3) - @test Groups.linear_repr(σ)^3 == Matrix{Int}(I, 3, 3) + σ = 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) function test_homomorphism(S, 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 return 0 end diff --git a/test/DirectPower-tests.jl b/test/DirectPower-tests.jl index 5eb4a80..65722f5 100644 --- a/test/DirectPower-tests.jl +++ b/test/DirectPower-tests.jl @@ -3,11 +3,11 @@ ×(a,b) = Groups.DirectPower(a,b) @testset "Constructors" begin - G = PermutationGroup(3) + G = SymmetricGroup(3) @test Groups.DirectPowerGroup(G,2) isa AbstractAlgebra.Group @test G×G isa AbstractAlgebra.Group - @test Groups.DirectPowerGroup(G,2) isa Groups.DirectPowerGroup{2, Generic.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 == (G×G)×G @@ -42,7 +42,7 @@ end @testset "Basic arithmetic" begin - G = PermutationGroup(3) + G = SymmetricGroup(3) GG = G×G i = perm"(1,3)" g = perm"(1,2,3)" @@ -65,7 +65,7 @@ end @testset "elem/parent_types" begin - G = PermutationGroup(3) + G = SymmetricGroup(3) g = perm"(1,2,3)" @test elem_type(G×G) == DirectPowerGroupElem{2, elem_type(G)} @@ -75,7 +75,7 @@ end @testset "Misc" begin - G = PermutationGroup(3) + G = SymmetricGroup(3) GG = Groups.DirectPowerGroup(G,3) @test order(GG) == 216 diff --git a/test/FPGroup-tests.jl b/test/FPGroup-tests.jl index 831b78e..ddf29e9 100644 --- a/test/FPGroup-tests.jl +++ b/test/FPGroup-tests.jl @@ -1,15 +1,18 @@ @testset "FPGroups definitions" begin 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] @test F/R isa FPGroup @test F isa FreeGroup G = F/R - A,B,C = gens(G) + A,B,C = Groups.gens(G) - @test A^2 == one(G) - @test A*B*A*A == A - @test A*A*B*A == B*A + @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 diff --git a/test/FreeGroup-tests.jl b/test/FreeGroup-tests.jl index cd0df27..da39e75 100644 --- a/test/FreeGroup-tests.jl +++ b/test/FreeGroup-tests.jl @@ -55,23 +55,18 @@ end @testset "internal arithmetic" begin - @test Vector{Groups.FreeGroupElem}([s,t]) == [Groups.GroupWord(s), Groups.GroupWord(t)] @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.r_multiply!(tt,[inv(t_symb)]; reduced=true)) == - "(id)" + @test string(Groups.rmul!(tt, tt, inv(t_symb))) == "(id)" tt = deepcopy(t) - @test string(Groups.r_multiply!(tt,[inv(t_symb)]; reduced=false)) == - "t*t^-1" + @test string(append!(tt, [inv(t_symb)])) == "t*t^-1" tt = deepcopy(t) - @test string(Groups.l_multiply!(tt,[inv(t_symb)]; reduced=true)) == - "(id)" + @test string(Groups.lmul!(tt, tt, inv(t_symb))) == "(id)" tt = deepcopy(t) - @test string(Groups.l_multiply!(tt,[inv(t_symb)]; reduced=false)) == - "t^-1*t" + @test string(prepend!(tt, [inv(t_symb)])) == "t^-1*t" end @testset "reductions" begin @@ -79,7 +74,7 @@ end @test length((one(G)*one(G)).symbols) == 0 @test one(G) == one(G)*one(G) 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)) o = (t*s)^3 @test o == t*s*t*s*t*s @@ -116,23 +111,23 @@ end @test Groups.issubsymbol(inv(b), Groups.change_pow(b,-2)) == true c = s*t*s^-1*t^-1 - @test findfirst(c, s^-1*t^-1) == 3 - @test findnext(c*s^-1, s^-1*t^-1,3) == 3 - @test findnext(c*s^-1*t^-1, s^-1*t^-1,4) == 5 - @test findfirst(c*t, c) == 0 + @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 w = s*t*s^-1 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, 1, w, subst[w]) == s*t^-1 - @test Groups.replace(s*c*t^-1, 1, w, subst[w]) == s^2*t^-2 - @test Groups.replace(t*c*t, 2, 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(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, 2, y^2, y) == x*y^8 - @test Groups.replace(x^3, 1, x^2, y) == x*y - @test Groups.replace(y*x^3*y, 2, x^2, y) == y*x*y^2 + @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 diff --git a/test/WreathProd-tests.jl b/test/WreathProd-tests.jl index 33aafb7..158e056 100644 --- a/test/WreathProd-tests.jl +++ b/test/WreathProd-tests.jl @@ -1,6 +1,6 @@ @testset "WreathProducts" begin - S_3 = PermutationGroup(3) - S_2 = PermutationGroup(2) + S_3 = SymmetricGroup(3) + S_2 = SymmetricGroup(2) b = perm"(1,2,3)" a = perm"(1,2)" @@ -8,7 +8,7 @@ @test Groups.WreathProduct(S_2, S_3) isa AbstractAlgebra.Group B3 = Groups.WreathProduct(S_2, S_3) @test B3 isa Groups.WreathProduct - @test B3 isa WreathProduct{3, Generic.PermGroup{Int}, Generic.PermGroup{Int}} + @test B3 isa WreathProduct{3, Generic.SymmetricGroup{Int}, Generic.SymmetricGroup{Int}} 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 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)) == B3 @@ -64,7 +64,7 @@ end @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)" @@ -84,7 +84,7 @@ end @testset "Iteration" begin - Wr = WreathProduct(PermutationGroup(2),PermutationGroup(4)) + Wr = WreathProduct(SymmetricGroup(2),SymmetricGroup(4)) elts = collect(Wr) @test elts isa Vector{Groups.WreathProductElem{4, Generic.Perm{Int}, Generic.Perm{Int}}}