function KnuthBendix.Alphabet(S::AbstractVector{<:GSymbol}) S = unique!([S; inv.(S)]) inversions = [findfirst(==(inv(s)), S) for s in S] return Alphabet(S, inversions) end struct AutomorphismGroup{G<:Group, T, R, S} <: AbstractFPGroup group::G gens::Vector{T} rws::R domain::S end object(G::AutomorphismGroup) = G.group function SpecialAutomorphismGroup(F::FreeGroup; ordering=KnuthBendix.LenLex, kwargs...) n = length(KnuthBendix.alphabet(F))÷2 A, rels = gersten_relations(n, commutative=false) S = KnuthBendix.letters(A)[1:2(n^2 - n)] rws = KnuthBendix.RewritingSystem(rels, ordering(A)) KnuthBendix.knuthbendix!(rws; kwargs...) return AutomorphismGroup(F, S, rws, ntuple(i->gens(F, i), n)) end KnuthBendix.alphabet(G::AutomorphismGroup{<:FreeGroup}) = alphabet(rewriting(G)) rewriting(G::AutomorphismGroup) = G.rws function relations(G::AutomorphismGroup) n = length(KnuthBendix.alphabet(object(G)))÷2 return last(gersten_relations(n, commutative=false)) end equality_data(f::FPGroupElement{<:AutomorphismGroup}) = normalform!.(evaluate(f)) function Base.:(==)(g::A, h::A) where A<:FPGroupElement{<:AutomorphismGroup} @assert parent(g) === parent(h) if _isvalidhash(g) && _isvalidhash(h) hash(g) != hash(h) && return false end length(word(g)) > 8 && normalform!(g) length(word(h)) > 8 && normalform!(h) word(g) == word(h) && return true img_computed, imh_computed = false, false if !_isvalidhash(g) img = equality_data(g) _update_savedhash!(g, img) img_computed = true end if !_isvalidhash(h) imh = equality_data(h) _update_savedhash!(h, imh) imh_computed = true end @assert _isvalidhash(g) @assert _isvalidhash(h) hash(g) != hash(h) && return false # words are different, but hashes agree if !img_computed img = equality_data(g) end if !imh_computed imh = equality_data(h) end equal = img == imh equal || @warn "hash collision in == :" g h return equal end # eye-candy Base.show(io::IO, ::Type{<:FPGroupElement{<:AutomorphismGroup{T}}}) where T <: FreeGroup = print(io, "Automorphism{$T}") ## Automorphism Evaluation domain(f::FPGroupElement{<:AutomorphismGroup}) = deepcopy(parent(f).domain) # tuple(gens(object(parent(f)))...) evaluate(f::FPGroupElement{<:AutomorphismGroup{<:FreeGroup}}) = evaluate!(domain(f), f) function evaluate!( t::NTuple{N,T}, f::FPGroupElement{<:AutomorphismGroup{<:FreeGroup}}, ) where {T<:FPGroupElement,N} A = alphabet(f) for idx in word(f) t = evaluate!(t, A[idx], alphabet(object(parent(f))))::NTuple{N,T} end return t end