diff --git a/docs/src/Groups/pcgroup.md b/docs/src/Groups/pcgroup.md index 912f357ebab8..28a912395e31 100644 --- a/docs/src/Groups/pcgroup.md +++ b/docs/src/Groups/pcgroup.md @@ -8,7 +8,7 @@ DocTestSetup = Oscar.doctestsetup() ```@docs PcGroup PcGroupElem -map_word(g::PcGroupElem, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) +map_word(g::Union{PcGroupElem, SubPcGroupElem}, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) ``` Julia has the following functions that allow to generate polycyclic groups: diff --git a/src/Groups/GAPGroups.jl b/src/Groups/GAPGroups.jl index 82a3e9593167..54360a489ff3 100644 --- a/src/Groups/GAPGroups.jl +++ b/src/Groups/GAPGroups.jl @@ -1930,7 +1930,7 @@ end @doc raw""" - map_word(g::FPGroupElem, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) + map_word(g::Union{FPGroupElem, SubFPGroupElem}, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) map_word(v::Vector{Union{Int, Pair{Int, Int}}}, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) Return the product $R_1 R_2 \cdots R_n$ @@ -1944,7 +1944,9 @@ and $R_j =$ `imgs[`$i_j$`]`$^{e_j}$. If `g` is an element of (a subgroup of) a finitely presented group then the result is defined as `map_word` applied to a representing element -of the underlying free group. +of the underlying free group of `full_group(parent(g))`. +In particular, `genimgs` are interpreted as the images of the generators +of this free group, not of `gens(parent(g))`. If the first argument is a vector `v` of integers $k_i$ or pairs `k_i => e_i`, respectively, @@ -1957,9 +1959,13 @@ to be the inverses of the corresponding entries in `genimgs`, and the function will use (and set) these entries in order to avoid calling `inv` (more than once) for entries of `genimgs`. +If `init` is different from `nothing` then the product gets initialized with +`init`. + If `v` has length zero then `init` is returned if also `genimgs` has length zero, otherwise `one(genimgs[1])` is returned. -In all other cases, `init` is ignored. +Thus the intended value for the empty word must be specified as `init` +whenever it is possible that the elements in `genimgs` do not support `one`. # Examples ```jldoctest @@ -1979,6 +1985,9 @@ julia> map_word(F2, imgs) julia> map_word(one(F), imgs) () +julia> map_word(one(F), imgs, init = imgs[1]) +(1,2,3,4) + julia> invs = Vector(undef, 2); julia> map_word(F1^-2*F2, imgs, genimgs_inv = invs) @@ -1988,7 +1997,6 @@ julia> invs 2-element Vector{Any}: (1,4,3,2) #undef - ``` """ function map_word(g::Union{FPGroupElem, SubFPGroupElem}, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) @@ -2021,13 +2029,13 @@ end @doc raw""" - map_word(g::PcGroupElem, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) + map_word(g::Union{PcGroupElem, SubPcGroupElem}, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) Return the product $R_1 R_2 \cdots R_n$ that is described by `g`, which is a product of the form $g_{i_1}^{e_1} g_{i_2}^{e_2} \cdots g_{i_n}^{e_n}$ where $g_i$ is the $i$-th entry in the defining polycyclic generating sequence -of $G$ and the $e_i$ are nonzero integers, +of `full_group(parent(g))` and the $e_i$ are nonzero integers, and $R_j =$ `imgs[`$i_j$`]`$^{e_j}$. # Examples @@ -2042,7 +2050,7 @@ julia> map_word(g, gens(free_group(:x, :y))) x*y^4 ``` """ -function map_word(g::PcGroupElem, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) +function map_word(g::Union{PcGroupElem, SubPcGroupElem}, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) G = parent(g) Ggens = gens(G) if length(Ggens) == 0 @@ -2061,11 +2069,38 @@ function map_word(g::PcGroupElem, genimgs::Vector; genimgs_inv::Vector = Vector( end function map_word(v::Union{Vector{Int}, Vector{Pair{Int, Int}}, Vector{Any}}, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) - length(genimgs) == 0 && (@assert length(v) == 0; return init) - length(v) == 0 && return one(genimgs[1]) - return prod(i -> _map_word_syllable(i, genimgs, genimgs_inv), v) + if length(v) == 0 + # If `init` is given then return it. + init !== nothing && return init + # Otherwise try the `one` of one of the `genimgs` + @req length(genimgs) != 0 "no `init` given in `map_word` without generators" + return one(genimgs[1]) + end + res = prod(i -> _map_word_syllable(i, genimgs, genimgs_inv), v) + if init !== nothing + res = init * res + end + return res end +# Support mapping to `FinGenAbGroupElem`: +# We use `+` instead of `*`, and scalar multiplication instead of powering. +function map_word(v::Union{Vector{Int}, Vector{Pair{Int, Int}}, Vector{Any}}, genimgs::Vector{FinGenAbGroupElem}; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) + if length(v) == 0 + # If `init` is given then return it. + init !== nothing && return init + # Otherwise use the `zero` of one of the `genimgs`. + @req length(genimgs) != 0 "no `init` given in `map_word` without generators" + return zero(parent(genimgs[1])) + end + res = sum(i -> _map_word_syllable_additive(i, genimgs, genimgs_inv), v) + if init !== nothing + res = init + res + end + return res +end + + function _map_word_syllable(vi::Int, genimgs::Vector, genimgs_inv::Vector) vi > 0 && (@assert vi <= length(genimgs); return genimgs[vi]) vi = -vi @@ -2090,6 +2125,30 @@ function _map_word_syllable(vi::Pair{Int, Int}, genimgs::Vector, genimgs_inv::Ve end +function _map_word_syllable_additive(vi::Int, genimgs::Vector, genimgs_inv::Vector) + vi > 0 && (@assert vi <= length(genimgs); return genimgs[vi]) + vi = -vi + @assert vi <= length(genimgs) + isassigned(genimgs_inv, vi) && return genimgs_inv[vi] + res = -genimgs[vi] + genimgs_inv[vi] = res + return res +end + +function _map_word_syllable_additive(vi::Pair{Int, Int}, genimgs::Vector, genimgs_inv::Vector) + x = vi[1] + @assert (x > 0 && x <= length(genimgs)) + e = vi[2] + e > 1 && return e * genimgs[x] + e == 1 && return genimgs[x] + isassigned(genimgs_inv, x) && return (-e) * genimgs_inv[x] + res = -genimgs[x] + genimgs_inv[x] = res + e == -1 && return res + return (-e) * res +end + + @doc raw""" syllables(g::Union{FPGroupElem, SubFPGroupElem}) diff --git a/test/Groups/homomorphisms.jl b/test/Groups/homomorphisms.jl index afc3d6d53c1d..53553ee01dd9 100644 --- a/test/Groups/homomorphisms.jl +++ b/test/Groups/homomorphisms.jl @@ -145,27 +145,41 @@ end rels = [F1^2, F2^2, comm(F1, F2)] FP = quo(F, rels)[1] - # map an element of a free group or a f.p. group ... + # map an element of a (subgroup of a) free group or a f.p. group ... for f in [F, FP] f1, f2 = gens(f) of = one(f) + s1 = gen(sub(f, [f1])[1], 1) - # ... to an element of a permutation group or to a rational number - for imgs in [gens(symmetric_group(4)), QQFieldElem[2, 3]] + # ... to an element of a permutation group or to a rational number ... + for imgs in [gens(symmetric_group(4)), + QQFieldElem[2, 3]] g1 = imgs[1] g2 = imgs[2] - for (x, w) in [(f1, g1), (f2, g2), (f2^2*f1^-3, g2^2*g1^-3)] + for (x, w) in [(f1, g1), (f2, g2), (f2^2*f1^-3, g2^2*g1^-3), + (s1^2, g1^2)] @test map_word(x, imgs) == w + @test map_word(x, imgs, init = g1) == g1*w # `init` is used end - @test map_word(of, imgs, init = 0) == one(g1) # `init` is ignored + @test map_word(of, imgs, init = 0) == 0 # `init` is returned end + + # ... or to an element in an (additive) abelian group + imgs = gens(abelian_group(3, 5)) + g1 = imgs[1] + g2 = imgs[2] + for (x, w) in [(f1, g1), (f2, g2), (f2^2*f1^-3, 2*g2-3*g1)] + @test map_word(x, imgs) == w + @test map_word(x, imgs, init = g1) == g1+w # `init` is used + end + @test map_word(of, imgs, init = 0) == 0 # `init` is returned end # empty list of generators T = free_group(0) - @test_throws ArgumentError map_word(one(T), Int[]) # no `init` - @test map_word(one(T), [], init = 1) == 1 # `init` is returned - @test map_word(one(F), gens(F), init = 1) == one(F) # `init` is ignored + @test_throws ArgumentError map_word(one(T), Int[]) # no `init` + @test map_word(one(T), [], init = 1) == 1 # `init` is returned + @test map_word(one(F), gens(F)) == one(F) # works without `init` # wrong number of images @test_throws AssertionError map_word(one(F), []) @@ -196,7 +210,10 @@ end # empty list of generators @test map_word([], [], init = 0) == 0 # `init` is returned - @test map_word([], [2, 3], init = 0) == 1 # `init` is ignored + @test map_word([], [2, 3], init = 0) == 0 # `init` is returned + @test map_word([], [2, 3]) == 1 # no `init` given, try `one` + G = abelian_group(2) + @test map_word([], gens(G)) == zero(G) # wrong number of images @test_throws AssertionError map_word([3], []) @@ -204,18 +221,21 @@ end @test_throws AssertionError map_word([3 => 1], [2, 3]) end -@testset "map_word for pc groups" begin +@testset "map_word for (sub) pc groups" begin for G in [ PcGroup(symmetric_group(4)), # GAP Pc Group # abelian_group(PcGroup, [2, 3, 4]), # problem with gens vs. pcgs abelian_group(PcGroup, [0, 3, 4]) ] # GAP Pcp group n = number_of_generators(G) F = free_group(n) - for x in [one(G), rand(G)] - img = map_word(x, gens(F)) - @test x == map_word(img, gens(G)) - invs = Vector(undef, n) - img = map_word(x, gens(F), genimgs_inv = invs) - @test x == map_word(img, gens(G)) + S = sub(G, gens(G))[1] + for g in [G, S] + for x in [one(g), rand(g)] + img = map_word(x, gens(F)) + @test x == map_word(img, gens(g)) + invs = Vector(undef, n) + img = map_word(x, gens(F), genimgs_inv = invs) + @test x == map_word(img, gens(g)) + end end end end