diff --git a/src/autgroups.jl b/src/autgroups.jl index 0361116..53db353 100644 --- a/src/autgroups.jl +++ b/src/autgroups.jl @@ -115,3 +115,61 @@ function evaluate!( end evaluate!(t::NTuple{N, T}, s::GSymbol, A, tmp=one(first(t))) where {N, T} = throw("you need to implement `evaluate!(::$(typeof(t)), ::$(typeof(s)), ::Alphabet, tmp=one(first(t)))`") + +# forward evaluate by substitution + +struct LettersMap{T, A} + indices_map::Dict{Int, T} + A::A +end + +function LettersMap(a::FPGroupElement{<:AutomorphismGroup}) + dom = domain(a) + @assert all(isone ∘ length ∘ word, dom) + A = alphabet(first(dom)) + first_letters = first.(word.(dom)) + img = evaluate!(dom, a) + + # (dom[i] → img[i] is a map from domain to images) + # we need a map from alphabet indices → (gens, gens⁻¹) → images + # here we do it for elements of the domai + # (trusting it's a set of generators that define a) + @assert length(dom) == length(img) + + indices_map = Dict(A[A[fl]] => word(im) for (fl, im) in zip(first_letters, img)) + # inverses of generators are dealt lazily in getindex + + return LettersMap(indices_map, A) +end + + +function Base.getindex(lm::LettersMap, i::Integer) + # here i is an index of an alphabet + @boundscheck 1 ≤ i ≤ length(KnuthBendix.letters(lm.A)) + + if !haskey(lm.indices_map, i) + img = if haskey(lm.indices_map, inv(lm.A, i)) + inv(lm.A, lm.indices_map[inv(lm.A, i)]) + else + @warn "LetterMap: neither $i nor its inverse has assigned value" + one(valtype(lm.indices_map)) + end + lm.indices_map[i] = img + end + return lm.indices_map[i] +end + +function (a::FPGroupElement{<:AutomorphismGroup})(g::FPGroupElement) + @assert object(parent(a)) === parent(g) + img_w = evaluate(word(g), LettersMap(a)) + return parent(g)(img_w) +end + +evaluate(w::AbstractWord, lm::LettersMap) = evaluate!(one(w), w, lm) + +function evaluate!(res::AbstractWord, w::AbstractWord, lm::LettersMap) + for i in w + append!(res, lm[i]) + end + return res +end diff --git a/test/AutFn.jl b/test/AutFn.jl index a33dc65..a269163 100644 --- a/test/AutFn.jl +++ b/test/AutFn.jl @@ -144,6 +144,36 @@ @test all(g->isone(g*inv(g)), B_2) end + @testset "Forward evaluate" begin + N = 3 + F = FreeGroup(N) + G = SpecialAutomorphismGroup(F) + + a = gens(G, 1) # ϱ₁₂ + + f = gens(F) + + @test a(f[1]) == f[1]*f[2] + @test all(a(f[i]) == f[i] for i in 2:length(f)) + + S = let s = gens(G) + [s; inv.(s)] + end + + @test all( + map(first(Groups.wlmetric_ball(S, radius=2))) do g + lm = Groups.LettersMap(g) + img = evaluate(g) + + fimg = [F(lm[first(word(s))]) for s in gens(F)] + + succeeded = all(img .== fimg) + @assert succeeded "forward evaluation of $(word(g)) failed: \n img=$img\n fimg=$(tuple(fimg...))" + succeeded + end + ) + end + @testset "GroupsCore conformance" begin test_Group_interface(A) g = A(rand(1:length(alphabet(A)), 10)) @@ -153,37 +183,3 @@ end end - -# using Random -# using GroupsCore -# -# A = New.SpecialAutomorphismGroup(FreeGroup(4), maxrules=2000, ordering=KnuthBendix.RecursivePathOrder) -# -# # for seed in 1:1000 -# let seed = 68 -# N = 14 -# Random.seed!(seed) -# g = A(rand(1:length(KnuthBendix.alphabet(A)), N)) -# h = A(rand(1:length(KnuthBendix.alphabet(A)), N)) -# @info "seed=$seed" g h -# @time isone(g*inv(g)) -# @time isone(inv(g)*g) -# @info "" length(word(New.normalform!(g*inv(g)))) length(word(New.normalform!(inv(g)*g))) -# a = commutator(g, h, g) -# b = conj(inv(g), h) * conj(conj(g, h), g) -# -# @info length(word(a)) -# @info length(word(b)) -# -# w = a*inv(b) -# @info length(word(w)) -# New.normalform!(w) -# @info length(word(w)) -# -# -# # -# # @time ima = evaluate(a) -# # @time imb = evaluate(b) -# # @info "" a b ima imb -# # @time a == b -# end