mirror of
https://github.com/kalmarek/Groups.jl.git
synced 2024-11-19 14:35:28 +01:00
first try on the new FPGroups
This commit is contained in:
parent
711988b98a
commit
9d7596acf1
@ -21,6 +21,12 @@ include("freereduce.jl")
|
|||||||
include("arithmetic.jl")
|
include("arithmetic.jl")
|
||||||
include("findreplace.jl")
|
include("findreplace.jl")
|
||||||
|
|
||||||
|
module New
|
||||||
|
include("new_types.jl")
|
||||||
|
include("normalform.jl")
|
||||||
|
|
||||||
|
end # module New
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
#
|
#
|
||||||
# String I/O
|
# String I/O
|
||||||
|
211
src/new_types.jl
Normal file
211
src/new_types.jl
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
using GroupsCore
|
||||||
|
# using Groups
|
||||||
|
# import Groups.AbstractFPGroup
|
||||||
|
import KnuthBendix
|
||||||
|
import KnuthBendix: AbstractWord, Alphabet, Word, RewritingSystem
|
||||||
|
import KnuthBendix: alphabet
|
||||||
|
using Random
|
||||||
|
|
||||||
|
## "Abstract" definitions
|
||||||
|
|
||||||
|
"""
|
||||||
|
AbstractFPGroup
|
||||||
|
|
||||||
|
An Abstract type representing finitely presented groups. Every instance `` must implement
|
||||||
|
* `KnuthBendix.alphabet(G::MyFPGroup)`
|
||||||
|
|
||||||
|
By default `word_type(::Type{MyFPGroup}) = Word{UInt16}` returns the default
|
||||||
|
word type used for group elements. You may wish to override this if e.g. to
|
||||||
|
> `word_type(::Type{MyFPGroup}) = Word{UInt8}`
|
||||||
|
if your group has less than `255` generators.
|
||||||
|
"""
|
||||||
|
abstract type AbstractFPGroup <: GroupsCore.Group end
|
||||||
|
|
||||||
|
word_type(G::AbstractFPGroup) = word_type(typeof(G))
|
||||||
|
# the default:
|
||||||
|
word_type(::Type{<:AbstractFPGroup}) = Word{UInt16}
|
||||||
|
|
||||||
|
function (G::AbstractFPGroup)(word::AbstractVector{<:Integer})
|
||||||
|
@boundscheck @assert all(l -> 1<= l <=length(KnuthBendix.alphabet(G)), word)
|
||||||
|
return FPGroupElement(word_type(G)(word), G)
|
||||||
|
end
|
||||||
|
|
||||||
|
## Group Interface
|
||||||
|
|
||||||
|
Base.one(G::AbstractFPGroup) = FPGroupElement(one(word_type(G)), G)
|
||||||
|
|
||||||
|
Base.eltype(::Type{FPG}) where {FPG<:AbstractFPGroup} =
|
||||||
|
FPGroupElement{FPG, word_type(FPG)}
|
||||||
|
|
||||||
|
struct FPGroupIter{GEl}
|
||||||
|
elts::Vector{GEl}
|
||||||
|
seen::Set{GEl}
|
||||||
|
u::GEl
|
||||||
|
v::GEl
|
||||||
|
end
|
||||||
|
|
||||||
|
FPGroupIter(G::AbstractFPGroup) =
|
||||||
|
FPGroupIter([one(G)], Set([one(G)]), one(G), one(G))
|
||||||
|
|
||||||
|
Base.iterate(G::AbstractFPGroup) = one(G), (FPGroupIter(G), 1, 1)
|
||||||
|
@inline function Base.iterate(G::AbstractFPGroup, state)
|
||||||
|
iter, elt_idx, gen_idx = state
|
||||||
|
|
||||||
|
if gen_idx > length(alphabet(G))
|
||||||
|
elt_idx == length(iter.elts) && return nothing
|
||||||
|
gen_idx = 1
|
||||||
|
elt_idx += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
res = let (u, v) = (iter.u, iter.v), elt = iter.elts[elt_idx]
|
||||||
|
copyto!(v, elt) # this invalidates normalform of v
|
||||||
|
@assert !isnormalform(v)
|
||||||
|
push!(word(v), gen_idx)
|
||||||
|
resize!(word(u), 0)
|
||||||
|
|
||||||
|
normalform!(u, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
if res in iter.seen
|
||||||
|
return iterate(G, (iter, elt_idx, gen_idx+1))
|
||||||
|
else
|
||||||
|
w = deepcopy(res)
|
||||||
|
@assert isnormalform(w)
|
||||||
|
push!(iter.elts, w)
|
||||||
|
push!(iter.seen, w)
|
||||||
|
state = (iter, elt_idx, gen_idx+1)
|
||||||
|
return w, state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# the default:
|
||||||
|
# Base.IteratorSize(::Type{<:AbstractFPGroup}) = Base.SizeUnknown()
|
||||||
|
|
||||||
|
GroupsCore.ngens(G::AbstractFPGroup) = length(G.gens)
|
||||||
|
|
||||||
|
function GroupsCore.gens(G::AbstractFPGroup, i::Integer)
|
||||||
|
@boundscheck 1<=i<=GroupsCore.ngens(G)
|
||||||
|
return FPGroupElement(word_type(G)([i]), G)
|
||||||
|
end
|
||||||
|
GroupsCore.gens(G::AbstractFPGroup) = [gens(G, i) for i in 1:GroupsCore.ngens(G)]
|
||||||
|
|
||||||
|
# TODO: ProductReplacementAlgorithm
|
||||||
|
function Base.rand(
|
||||||
|
rng::Random.AbstractRNG,
|
||||||
|
rs::Random.SamplerTrivial{<:AbstractFPGroup},
|
||||||
|
)
|
||||||
|
l = rand(10:100)
|
||||||
|
G = rs[]
|
||||||
|
symmgens_len = length(alphabet(F))
|
||||||
|
return FPGroupElement(word_type(G)(rand(1:symmgens_len, l)), G)
|
||||||
|
end
|
||||||
|
|
||||||
|
## FPGroupElement
|
||||||
|
|
||||||
|
mutable struct FPGroupElement{G<:AbstractFPGroup, W<:AbstractWord} <: GroupElement
|
||||||
|
word::W
|
||||||
|
savedhash::UInt
|
||||||
|
parent::G
|
||||||
|
|
||||||
|
FPGroupElement(word::W, G::AbstractFPGroup) where W<:AbstractWord =
|
||||||
|
new{typeof(G), W}(word, UInt(0), G)
|
||||||
|
|
||||||
|
FPGroupElement(word::W, hash::UInt, G::AbstractFPGroup) where W<:AbstractWord =
|
||||||
|
new{typeof(G), W}(word, hash, G)
|
||||||
|
end
|
||||||
|
|
||||||
|
word(f::FPGroupElement) = f.word
|
||||||
|
|
||||||
|
#convenience
|
||||||
|
KnuthBendix.alphabet(g::FPGroupElement) = alphabet(parent(g))
|
||||||
|
|
||||||
|
function Base.show(io::IO, f::FPGroupElement)
|
||||||
|
f = normalform!(f)
|
||||||
|
print(io, KnuthBendix.string_repr(word(f), alphabet(f)))
|
||||||
|
end
|
||||||
|
|
||||||
|
## Hashing
|
||||||
|
|
||||||
|
# We store hash of a word in field `savedhash` to use it as cheap replacement
|
||||||
|
# for equality checking. In the last bit of `savehash` we store the information
|
||||||
|
# whether the word is already reduced (in normal form) or not. Hence
|
||||||
|
isnormalform(g::FPGroupElement) = Bool(g.savedhash & 1)
|
||||||
|
|
||||||
|
# To update hash use this internal method, possibly only after computing the
|
||||||
|
# normal form of `g`:
|
||||||
|
_update_savedhash!(g::FPGroupElement, h = hash(word(g))) =
|
||||||
|
(g.savedhash = h | 1; return g)
|
||||||
|
|
||||||
|
# If `g` has been mutated (e.g. `word(g)` has been modified, `_invalidate_hash`
|
||||||
|
# must be called.
|
||||||
|
_invalidate_hash!(g::FPGroupElement) = g.savedhash = g.savedhash & 0
|
||||||
|
|
||||||
|
# Accessor for the saved hash
|
||||||
|
@inline _savedhash(f::FPGroupElement) = f.savedhash
|
||||||
|
|
||||||
|
function Base.hash(g::FPGroupElement, h::UInt)
|
||||||
|
normalform!(g)
|
||||||
|
# compute the best approximation of the normal form
|
||||||
|
return hash(parent(g), _savedhash(g) ⊻ h)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Base.copyto!(res::FPGroupElement, g::FPGroupElement)
|
||||||
|
resize!(word(res), length(word(g)))
|
||||||
|
copyto!(word(res), word(g))
|
||||||
|
_invalidate_hash!(res)
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
## GroupElement Interface for FPGroupElement
|
||||||
|
|
||||||
|
Base.parent(f::FPGroupElement) = f.parent
|
||||||
|
GroupsCore.parent_type(::Type{<:FPGroupElement{G}}) where G = G
|
||||||
|
|
||||||
|
function Base.:(==)(g::FPGroupElement, h::FPGroupElement)
|
||||||
|
@boundscheck @assert parent(g) === parent(h)
|
||||||
|
hash(g) != hash(h) && return false
|
||||||
|
# hash reduces to normal form behind the scenes
|
||||||
|
return word(g) == word(h)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Base.deepcopy_internal(g::FPGroupElement, stackdict::IdDict)
|
||||||
|
return FPGroupElement(copy(word(g)), _savedhash(g), parent(g))
|
||||||
|
end
|
||||||
|
|
||||||
|
Base.inv(g::FPGroupElement) =
|
||||||
|
(G = parent(g); FPGroupElement(inv(alphabet(G), word(g)), G))
|
||||||
|
|
||||||
|
function Base.:(*)(g::FPGroupElement, h::FPGroupElement)
|
||||||
|
@boundscheck @assert parent(g) === parent(h)
|
||||||
|
return FPGroupElement(word(g)*word(h), parent(g))
|
||||||
|
end
|
||||||
|
|
||||||
|
GroupsCore.isfiniteorder(g::FPGroupElement) = isone(g) ? true : throw("Not Implemented")
|
||||||
|
|
||||||
|
# additional methods:
|
||||||
|
Base.isone(g::FPGroupElement) = (normalform!(g); isempty(word(g)))
|
||||||
|
|
||||||
|
## Free Groups
|
||||||
|
|
||||||
|
struct FreeGroup{T} <: AbstractFPGroup
|
||||||
|
gens::Vector{T}
|
||||||
|
alphabet::KnuthBendix.Alphabet{T}
|
||||||
|
|
||||||
|
function FreeGroup(gens, A::KnuthBendix.Alphabet) where W
|
||||||
|
@assert length(gens) == length(unique(gens))
|
||||||
|
@assert all(l->l in KnuthBendix.letters(A), gens)
|
||||||
|
return new{eltype(gens)}(gens, A)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function FreeGroup(a::Alphabet)
|
||||||
|
@boundscheck @assert all(KnuthBendix.hasinverse(l, A)
|
||||||
|
for l in KnuthBendix.letters(a))
|
||||||
|
return FreeGroup(KnuthBendix.letters(a), a)
|
||||||
|
end
|
||||||
|
|
||||||
|
Base.show(io::IO, F::FreeGroup) = print(io, "free group on $(ngens(F)) generators")
|
||||||
|
|
||||||
|
# mandatory methods:
|
||||||
|
KnuthBendix.alphabet(F::FreeGroup) = F.alphabet
|
76
src/normalform.jl
Normal file
76
src/normalform.jl
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
"""
|
||||||
|
normalform!(g::FPGroupElement)
|
||||||
|
Compute the normal form of `g`, possibly modifying `g` in-place.
|
||||||
|
"""
|
||||||
|
@inline function normalform!(g::FPGroupElement)
|
||||||
|
isnormalform(g) && return g
|
||||||
|
|
||||||
|
let w = one(word(g))
|
||||||
|
w = normalform!(w, g)
|
||||||
|
resize!(word(g), length(w))
|
||||||
|
copyto!(word(g), w)
|
||||||
|
end
|
||||||
|
|
||||||
|
g = _update_savedhash!(g)
|
||||||
|
@assert isnormalform(g)
|
||||||
|
return g
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
normalform!(res::GEl, g::GEl) where GEl<:FPGroupElement
|
||||||
|
Compute the normal fom of `g`, storing it in `res`.
|
||||||
|
"""
|
||||||
|
function normalform!(res::GEl, g::GEl) where GEl<:FPGroupElement
|
||||||
|
@boundscheck @assert parent(res) === parent(g)
|
||||||
|
if isnormalform(g)
|
||||||
|
copyto!(res, g)
|
||||||
|
_update_savedhash!(res, g.savedhash)
|
||||||
|
else
|
||||||
|
resize!(word(res), 0)
|
||||||
|
normalform!(word(res), g)
|
||||||
|
res = _update_savedhash!(res)
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
normalform!(res::AbstractWord, g::FPGroupElement)
|
||||||
|
Append the normal form of `g` to word `res`, modifying `res` in place.
|
||||||
|
|
||||||
|
Defaults to the rewriting in the free group.
|
||||||
|
"""
|
||||||
|
@inline function normalform!(res::AbstractWord, g::FPGroupElement)
|
||||||
|
isone(res) && isnormalform(g) && return append!(res, word(g))
|
||||||
|
if isnormalform(g) && inv(alphabet(g), last(out)) != first(word(g))
|
||||||
|
return append!(res, word(g))
|
||||||
|
end
|
||||||
|
return free_rewrite!(res, word(g), alphabet(g))
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
free_rewrite!(v::AbstractWord, w::AbstractWord, A::Alphabet)
|
||||||
|
Append `w` to `v` applying free reductions as defined by the inverses of `A`.
|
||||||
|
"""
|
||||||
|
free_rewrite!(v::AbstractWord, w::AbstractWord, A::Alphabet) =
|
||||||
|
KnuthBendix.rewrite_from_left!(v, w, A)
|
||||||
|
|
||||||
|
function KnuthBendix.rewrite_from_left!(
|
||||||
|
v::AbstractWord,
|
||||||
|
w::AbstractWord,
|
||||||
|
A::Alphabet
|
||||||
|
)
|
||||||
|
while !isone(w)
|
||||||
|
if isone(v)
|
||||||
|
push!(v, popfirst!(w))
|
||||||
|
else
|
||||||
|
# the first check is for monoids only
|
||||||
|
if KnuthBendix.hasinverse(last(v), A) && inv(A, last(v)) == first(w)
|
||||||
|
pop!(v)
|
||||||
|
popfirst!(w)
|
||||||
|
else
|
||||||
|
push!(v, popfirst!(w))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return v
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user