From b9a504532b6d22c68c5dcf7a0564be11581331c8 Mon Sep 17 00:00:00 2001 From: Thomas Breuer Date: Tue, 9 Apr 2024 20:27:31 +0200 Subject: [PATCH] change `fp_group_with_isomorphism`, `pc_group_with_isomorphism` (#3563) - add optional boolean `on_gens` for `isomorphism(FPGroup, G)` (changes the internal structure of the `:isomorphisms` attribute) - reverse the direction of the maps returned by `fp_group_with_isomorphism` and `pc_group_with_isomorphism` - do not export `fp_group_with_isomorphism`, `pc_group_with_isomorphism` - create a collector for a pc group in Oscar not in GAP - fix code using sparse matrices (add a test for the case that did not work before) --- docs/src/Groups/grouphom.md | 2 +- experimental/GModule/Cohomology.jl | 119 ++++++++++++-------------- experimental/GModule/test/runtests.jl | 8 ++ src/Groups/homomorphisms.jl | 58 +++++++++---- test/Groups/homomorphisms.jl | 11 ++- 5 files changed, 115 insertions(+), 83 deletions(-) diff --git a/docs/src/Groups/grouphom.md b/docs/src/Groups/grouphom.md index deea2f1a5019..1f71f1c068c9 100644 --- a/docs/src/Groups/grouphom.md +++ b/docs/src/Groups/grouphom.md @@ -157,7 +157,7 @@ isomorphism(G::GAPGroup, H::GAPGroup) ``` ```@docs -isomorphism(::Type{T}, G::GAPGroup) where T <: Union{FPGroup, PcGroup, PermGroup} +isomorphism(::Type{T}, G::GAPGroup) where T <: Union{PcGroup, PermGroup} isomorphism(::Type{FinGenAbGroup}, G::GAPGroup) simplified_fp_group(G::FPGroup) ``` diff --git a/experimental/GModule/Cohomology.jl b/experimental/GModule/Cohomology.jl index a5dd87480ec8..aa32aae0ae39 100644 --- a/experimental/GModule/Cohomology.jl +++ b/experimental/GModule/Cohomology.jl @@ -100,7 +100,7 @@ Base.hash(a::MultGrpElem, u::UInt = UInt(1235)) = hash(a.data. u) end F::Group # G as an Fp-group (if set) - mF::GAPGroupHomomorphism # F -> G, maps F[i] to G[i] + mF::GAPGroupHomomorphism # G -> F, maps G[i] to F[i] iac::Vector{Map} # the inverses of ac end @@ -150,14 +150,14 @@ Checks if the action maps satisfy the same relations as the generators of `G`. """ function is_consistent(M::GModule) - G, mG = fp_group_with_isomorphism(M) + G, mG = fp_group_with_isomorphism(M) # mG: group(M) -> G V = Module(M) R = relators(G) for r = R w = word(r) - a = action(M, mG(w[1]< 0 ? inv(gen(G, -w[1])) : gen(G, w[1]))) + a = action(M, preimage(mG, w[1]< 0 ? inv(gen(G, -w[1])) : gen(G, w[1]))) for i=2:length(w) - a = a* action(M, mG(w[i]< 0 ? inv(gen(G, -w[i])) : gen(G, w[i]))) + a = a* action(M, preimage(mG, w[i]< 0 ? inv(gen(G, -w[i])) : gen(G, w[i]))) end all(x->a(x) == x, gens(V)) || (@show r; return false) end @@ -186,7 +186,7 @@ function fp_group_with_isomorphism(C::GModule) if !isdefined(C, :F) if (!isa(group(C), FPGroup)) && is_trivial(group(C)) C.F = free_group(0) - C.mF = hom(C.F, group(C), gens(C.F), elem_type(group(C))[]) + C.mF = hom(group(C), C.F, gens(group(C)), elem_type(C.F)[]) else C.F, C.mF = fp_group_with_isomorphism(gens(group(C))) end @@ -215,7 +215,7 @@ function action(C::GModule, g, v::Array) end F, mF = fp_group_with_isomorphism(C) - for i = word(preimage(mF, g)) + for i = word(mF(g)) if i > 0 v = map(ac[i], v) else @@ -261,7 +261,7 @@ function action(C::GModule, g) F, mF = fp_group_with_isomorphism(C) h = id_hom(C.M) - for i = word(preimage(mF, g)) + for i = word(mF(g)) if i > 0 h = h*ac[i] # v = map(ac[i], v) @@ -297,7 +297,9 @@ function induce(C::GModule{<:Oscar.GAPGroup, FinGenAbGroup}, h::Map, D = nothing iU = image(h)[1] # ra = right_coset_action(G, image(h)[1]) # will not always match -# the transversal, so cannot use. There is a PR in Gap to return "both" +# the transversal, so cannot use. +# See https://github.com/gap-system/gap/issues/5337 +# for a discussion whether to return both transversal and action on it. g = right_transversal(G, iU) S = symmetric_group(length(g)) ra = hom(G, S, [S([findfirst(x->x*inv(z*y) in iU, g) for z = g]) for y in gens(G)]) @@ -483,8 +485,8 @@ function Oscar.inflate(C::GModule, h) return gmodule(G, [action(C, h(g)) for g = gens(G)]) end -export GModule, gmodule, word, fp_group_with_isomorphism, confluent_fp_group -export action, cohomology_group, extension, pc_group_with_isomorphism +export GModule, gmodule, word, confluent_fp_group +export action, cohomology_group, extension export induce, is_consistent, istwo_cocycle, all_extensions export split_extension, extension_with_abelian_kernel @@ -502,16 +504,14 @@ Oscar.group(C::GModule) = C.G ########################################################### """ -Compute an fp-presentation of the group generated by 'g' -and returns both the group and the map from the new group to the -parent of the generators. +Compute an fp-presentation of the common parent 'G' of 'g' +and return both the group and the map from 'G' to the new group. """ function fp_group_with_isomorphism(g::Vector{<:Oscar.GAPGroupElem}) G = parent(g[1]) @assert all(x->parent(x) == G, g) - X = GAPWrap.IsomorphismFpGroupByGenerators(G.X, GAPWrap.GeneratorsOfGroup(G.X)) - F = FPGroup(GAPWrap.Range(X)) - return F, GAPGroupHomomorphism(F, G, GAP.Globals.InverseGeneralMapping(X)) + iso = isomorphism(FPGroup, G, on_gens=true) + return codomain(iso), iso end """ @@ -647,8 +647,8 @@ function (C::CoChain{1})(g::Oscar.BasicGAPGroupElem) G = parent(g) @assert G == group(C.C) @assert ngens(F) == ngens(G) - @assert all(i->mF(gen(F, i)) == gen(G, i), 1:ngens(G)) - w = word(preimage(mF, g)) + @assert all(i-> preimage(mF, gen(F, i)) == gen(G, i), 1:ngens(G)) + w = word(mF(g)) t = zero(Module(C.C)) ac = action(C.C) iac = inv_action(C.C) @@ -804,6 +804,7 @@ function H_one_maps(C::GModule; task::Symbol = :maps) F, mF = fp_group_with_isomorphism(C) @assert ngens(F) == ngens(G) @assert all(i->mF(gen(F, i)) == gen(G, i), 1:ngens(G)) + @assert all(i->preimage(mF, gen(F, i)) == gen(G, i), 1:ngens(G)) R = relators(F) # @assert G == F @@ -866,7 +867,7 @@ function H_one(C::GModule) return domain(z), z end - g, gg, pro, inj, mF = H_one_maps(C, task = :all) + g, gg, pro, inj, _ = H_one_maps(C, task = :all) K = kernel(gg)[1] D = domain(gg) @@ -1076,7 +1077,7 @@ function H_two(C::GModule; force_rws::Bool = false, redo::Bool = false, lazy::Bo @vprint :GroupCohomology 1 "starting H^2 for group of size $(order(G)) and module with $(ngens(M)) gens\n" id = hom(M, M, gens(M), check = false) - F, mF = fp_group_with_isomorphism(C) #mF: F -> G + F, _ = fp_group_with_isomorphism(C) if !force_rws && (isa(G, PcGroup) || is_solvable(G)) @vprint :GroupCohomology 2 "using pc-presentation ...\n" @@ -1736,10 +1737,11 @@ function cohomology_group(C::GModule, i::Int; Tate::Bool = false) error("only H^0, H^1 and H^2 are supported") end +# return an f.p. group `F` and an isomorphism `M -> F` function fp_group_with_isomorphism(M::AbstractAlgebra.FPModule{<:FinFieldElem}) p, mp = pc_group_with_isomorphism(M, refine = false) mf = isomorphism(FPGroup, p) - return codomain(mf), inv(mf)*mp + return codomain(mf), mf*mp end ######################################################### @@ -1809,6 +1811,7 @@ end function Oscar.id_hom(A::AbstractAlgebra.FPModule) return Generic.ModuleHomomorphism(A, A, identity_matrix(base_ring(A), ngens(A))) end + ########################################################### #= @@ -1819,7 +1822,8 @@ end =# """ -Compute an isomorphic pc-group (and the isomorphism). If `refine` is true, +Compute an isomorphic pc-group `G` (and the isomorphism from `M` to `G`). +If `refine` is true, the pc-generators will all have prime relative order, thus the group should be safe to use. If `refine` is false, then the relative orders are just used from the hnf @@ -1857,8 +1861,8 @@ function pc_group_with_isomorphism(M::FinGenAbGroup; refine::Bool = true) for i=1:nrows(h) for j=i+1:ncols(h) if !iszero(h[i,j]) - push!(r.rows[gp[i]], gp[j]) - push!(r.values(gp[i], h[i,j])) + push!(r.rows[gp[i]].pos, gp[j]) + push!(r.rows[gp[i]].values, h[i,j]) end end end @@ -1874,21 +1878,16 @@ function pc_group_with_isomorphism(M::FinGenAbGroup; refine::Bool = true) h = rels(M) @assert !any(x->h[x,x] == 1, 1:ncols(h)) - C = GAP.Globals.SingleCollector(G.X, GAP.Obj([h[i,i] for i=1:nrows(h)], recursive = true)) - F = GAP.Globals.FamilyObj(GAP.Globals.Identity(G.X)) - - for i=1:ngens(M)-1 - r = ZZRingElem[] - for j=i+1:ngens(M) - push!(r, j) - push!(r, -h[i, j]) - GAP.Globals.SetConjugate(C, j, i, gen(G, j).X) + C = collector(nrows(h)) + set_relative_orders!(C, [h[i,i] for i in 1:nrows(h)]) + for i in 1:ngens(M) + r = Pair{Int, ZZRingElem}[] + for j in i+1:ngens(M) + push!(r, j => -h[i, j]) end - rr = GAP.Globals.ObjByExtRep(F, GAP.Obj(r, recursive = true)) - GAP.Globals.SetPower(C, i, rr) + set_power!(C, i, r) end - - B = PcGroup(GAP.Globals.GroupByRws(C)) + B = pc_group(C) FB = GAP.Globals.FamilyObj(GAP.Globals.Identity(B.X)) Julia_to_gap = function(a::FinGenAbGroupElem) @@ -1916,9 +1915,9 @@ function pc_group_with_isomorphism(M::FinGenAbGroup; refine::Bool = true) @assert is_isomorphic(B, fp_group(M)) return B, MapFromFunc( - B, codomain(mM), - x->image(mM, gap_to_julia(x.X)), - y->PcGroupElem(B, Julia_to_gap(preimage(mM, y)))) + codomain(mM), B, + y->PcGroupElem(B, Julia_to_gap(preimage(mM, y))), + x->image(mM, gap_to_julia(x.X))) end function (k::Nemo.fpField)(a::Vector) @@ -1944,7 +1943,13 @@ function pc_group_with_isomorphism(M::AbstractAlgebra.FPModule{<:FinFieldElem}; GAP.Obj([p for i=1:ngens(G)], recursive = true)) F = GAP.Globals.FamilyObj(GAP.Globals.Identity(G.X)) + # Note that we have specified all relative orders as `p`. + # Missing commutator and power relators are interpreted as trivial, + # thus `C` describes an elementary abelian group. B = PcGroup(GAP.Globals.GroupByRws(C)) + @assert is_abelian(B) + @assert order(B) == order(M) + FB = GAP.Globals.FamilyObj(GAP.Globals.Identity(B.X)) function Julia_to_gap(a::AbstractAlgebra.FPModuleElem{<:Union{fpFieldElem, FpFieldElem, FqFieldElem}}) @@ -1993,20 +1998,10 @@ function pc_group_with_isomorphism(M::AbstractAlgebra.FPModule{<:FinFieldElem}; return M(c) end - for i=1:ngens(M)-1 - r = ZZRingElem[] - for j=i+1:ngens(M) - GAP.Globals.SetConjugate(C, j, i, gen(G, j).X) - end - GAP.Globals.SetPower(C, i, GAP.Globals.Identity(F)) - end - @assert is_abelian(B) - @assert order(B) == order(M) - return B, MapFromFunc( - B, M, - x->gap_to_julia(x.X), - y->PcGroupElem(B, Julia_to_gap(y))) + M, B, + y->PcGroupElem(B, Julia_to_gap(y)), + x->gap_to_julia(x.X)) end @@ -2026,7 +2021,7 @@ If the gmodule is defined via a pc-group and the 1st argument is the function extension(c::CoChain{2,<:Oscar.GAPGroupElem}) C = c.C G = Group(C) - F, mF = fp_group_with_isomorphism(gens(G)) + F, _ = fp_group_with_isomorphism(gens(G)) M = Module(C) ac = action(C) iac = inv_action(C) @@ -2154,7 +2149,7 @@ function extension(::Type{PcGroup}, c::CoChain{2,<:Oscar.PcGroupElem}) end end return t - return fMtoN(preimage(mfM, t)) + return fMtoN(mfM(t)) end #to lift the pc-relations: @@ -2172,16 +2167,16 @@ function extension(::Type{PcGroup}, c::CoChain{2,<:Oscar.PcGroupElem}) for i=1:ngens(G) p = Gp[i]^Go[i] pp = GAP.Globals.ObjByExtRep(FN, GAPWrap.ExtRepOfObj(p)) - m = fMtoN(preimage(mfM, word_to_elem([i for k=1:Go[i]])-word_to_elem(word(p)))) + m = fMtoN(mfM(word_to_elem([i for k=1:Go[i]])-word_to_elem(word(p)))) GAP.Globals.SetPower(CN, i, pp*m) for j=i+1:ngens(G) p = Gp[j]^Gp[i] - m = fMtoN(preimage(mfM, word_to_elem([-i, j, i])-word_to_elem(word(p)))) + m = fMtoN(mfM(word_to_elem([-i, j, i])-word_to_elem(word(p)))) pp = GAP.Globals.ObjByExtRep(FN, GAPWrap.ExtRepOfObj(p)) GAP.Globals.SetConjugate(CN, j, i, pp*m) end for j=1:ngens(fM) - m = fMtoN(preimage(mfM, action(C, gen(G, i), mfM(gen(fM, j))))) + m = fMtoN(mfM(action(C, gen(G, i), preimage(mfM, gen(fM, j))))) GAP.Globals.SetConjugate(CN, j+ngens(G), i, m) end end @@ -2200,8 +2195,8 @@ function extension(::Type{PcGroup}, c::CoChain{2,<:Oscar.PcGroupElem}) @assert ngens(Q) == ngens(N) MtoQ = hom(fM, Q, gens(fM), gens(Q)[ngens(G)+1:end]) QtoG = hom(Q, G, gens(Q), vcat(gens(G), [one(G) for i=1:ngens(fM)])) - @assert domain(mfM) ==fM - @assert codomain(mfM) == M + @assert domain(mfM) == M + @assert codomain(mfM) == fM # @assert is_surjective(QtoG) # @assert is_injective(MtoQ) @@ -2209,7 +2204,7 @@ function extension(::Type{PcGroup}, c::CoChain{2,<:Oscar.PcGroupElem}) mffM = epimorphism_from_free_group(fM) function GMtoQ(wg, m) - wm = GAP.gap_to_julia(GAPWrap.ExtRepOfObj(preimage(mffM, preimage(mfM, m)).X)) + wm = GAP.gap_to_julia(GAPWrap.ExtRepOfObj(preimage(mffM, mfM(m)).X)) for i=1:2:length(wm) push!(wg, wm[i]+ngens(G)) push!(wg, wm[i+1]) @@ -2217,7 +2212,7 @@ function extension(::Type{PcGroup}, c::CoChain{2,<:Oscar.PcGroupElem}) return mQ(FPGroupElem(N, GAP.Globals.ObjByExtRep(FN, GAP.Obj(wg)))) end - return Q, inv(mfM)*MtoQ, QtoG, GMtoQ + return Q, mfM*MtoQ, QtoG, GMtoQ end """ diff --git a/experimental/GModule/test/runtests.jl b/experimental/GModule/test/runtests.jl index e71825ae8a18..c379e59525ff 100644 --- a/experimental/GModule/test/runtests.jl +++ b/experimental/GModule/test/runtests.jl @@ -15,6 +15,14 @@ end +@testset "Experimental.gmodule: pc group of FinGenAbGroup" begin + M = abelian_group([2 6 9; 1 5 3; 1 1 0]) + G1, _ = Oscar.GrpCoh.pc_group_with_isomorphism(M) + G2 = codomain(isomorphism(PcGroup, M)) + @test describe(G1) == describe(G2) +end + + @testset "Experimental.gmodule" begin G = small_group(7*3, 1) diff --git a/src/Groups/homomorphisms.jl b/src/Groups/homomorphisms.jl index 01eaae9c149e..c790e9546911 100644 --- a/src/Groups/homomorphisms.jl +++ b/src/Groups/homomorphisms.jl @@ -480,18 +480,21 @@ end ################################################################################ _get_iso_function(::Type{PermGroup}) = GAP.Globals.IsomorphismPermGroup -_get_iso_function(::Type{FPGroup}) = GAPWrap.IsomorphismFpGroup _get_iso_function(::Type{PcGroup}) = GAP.Globals.IsomorphismPcGroup """ - isomorphism(::Type{T}, G::GAPGroup) where T <: Union{FPGroup, PcGroup, PermGroup} + isomorphism(::Type{T}, G::GAPGroup) where T <: Union{PcGroup, PermGroup} + isomorphism(::Type{T}, G::GAPGroup; on_gens=false) where T = FPGroup -Return an isomorphism from `G` to a group of type `T`. +Return an isomorphism from `G` to a group `H` of type `T`. An exception is thrown if no such isomorphism exists. +If `on_gens` is `true` then `gens(G)` is guaranteed to correspond to +`gens(H)`. + Isomorphisms are cached in `G`, subsequent calls of `isomorphism` with the -same `T` yield identical results. +same `T` (and the same value of `on_gens`) yield identical results. If only the image of such an isomorphism is needed, use `T(G)`. @@ -512,21 +515,38 @@ julia> codomain(iso) === ans true ``` """ -function isomorphism(::Type{T}, G::GAPGroup) where T <: Union{FPGroup, PcGroup, PermGroup} +function isomorphism(::Type{T}, G::GAPGroup) where T <: Union{PcGroup, PermGroup} # Known isomorphisms are cached in the attribute `:isomorphisms`. - isos = get_attribute!(Dict{Type, Any}, G, :isomorphisms)::Dict{Type, Any} - return get!(isos, T) do + isos = get_attribute!(Dict{Tuple{Type, Bool}, Any}, G, :isomorphisms)::Dict{Tuple{Type, Bool}, Any} + return get!(isos, (T, false)) do fun = _get_iso_function(T) f = fun(G.X)::GapObj @req f !== GAP.Globals.fail "Could not convert group into a group of type $T" H = T(GAP.Globals.ImagesSource(f)::GapObj) - # TODO: remove the next line once https://github.com/gap-system/gap/pull/5660 - # is deployed to Oscar +# TODO: remove the next line once GAP 4.13.0 is available in Oscar GAP.Globals.UseIsomorphismRelation(G.X, H.X) return GAPGroupHomomorphism(G, H, f) end::GAPGroupHomomorphism{typeof(G), T} end +function isomorphism(::Type{FPGroup}, G::GAPGroup; on_gens::Bool=false) + # Known isomorphisms are cached in the attribute `:isomorphisms`. + isos = get_attribute!(Dict{Tuple{Type, Bool}, Any}, G, :isomorphisms)::Dict{Tuple{Type, Bool}, Any} + return get!(isos, (FPGroup, on_gens)) do + if on_gens + f = GAPWrap.IsomorphismFpGroupByGenerators(G.X, + GAP.Globals.GeneratorsOfGroup(G.X)) + else + f = GAPWrap.IsomorphismFpGroup(G.X) + end + @req f !== GAP.Globals.fail "Could not convert group into a group of type FPGroup" + H = FPGroup(GAP.Globals.ImagesSource(f)::GapObj) +# TODO: remove the next line once GAP 4.13.0 is available in Oscar + GAP.Globals.UseIsomorphismRelation(G.X, H.X) + return GAPGroupHomomorphism(G, H, f) + end::GAPGroupHomomorphism{typeof(G), FPGroup} +end + """ isomorphism(::Type{FinGenAbGroup}, G::GAPGroup) @@ -536,8 +556,8 @@ An exception is thrown if `G` is not abelian or not finite. """ function isomorphism(::Type{FinGenAbGroup}, G::GAPGroup) # Known isomorphisms are cached in the attribute `:isomorphisms`. - isos = get_attribute!(Dict{Type, Any}, G, :isomorphisms)::Dict{Type, Any} - return get!(isos, FinGenAbGroup) do + isos = get_attribute!(Dict{Tuple{Type, Bool}, Any}, G, :isomorphisms)::Dict{Tuple{Type, Bool}, Any} + return get!(isos, (FinGenAbGroup, false)) do @req is_abelian(G) "the group is not abelian" @req is_finite(G) "the group is not finite" #T this restriction is not nice @@ -569,8 +589,8 @@ An exception is thrown if no such isomorphism exists or if `A` is not finite. """ function isomorphism(::Type{T}, A::FinGenAbGroup) where T <: GAPGroup # Known isomorphisms are cached in the attribute `:isomorphisms`. - isos = get_attribute!(Dict{Type, Any}, A, :isomorphisms)::Dict{Type, Any} - return get!(isos, T) do + isos = get_attribute!(Dict{Tuple{Type, Bool}, Any}, A, :isomorphisms)::Dict{Tuple{Type, Bool}, Any} + return get!(isos, (T, false)) do # find independent generators if is_diagonal(rels(A)) exponents = diagonal(rels(A)) @@ -719,8 +739,8 @@ end function isomorphism(::Type{FinGenAbGroup}, A::FinGenAbGroup) # Known isomorphisms are cached in the attribute `:isomorphisms`. - isos = get_attribute!(Dict{Type, Any}, A, :isomorphisms)::Dict{Type, Any} - return get!(isos, FinGenAbGroup) do + isos = get_attribute!(Dict{Tuple{Type, Bool}, Any}, A, :isomorphisms)::Dict{Tuple{Type, Bool}, Any} + return get!(isos, (FinGenAbGroup, false)) do return identity_map(A) end::AbstractAlgebra.Generic.IdentityMap{FinGenAbGroup} end @@ -729,8 +749,8 @@ end # a presentation of a fin. gen. abelian group. function isomorphism(::Type{FPGroup}, A::FinGenAbGroup) # Known isomorphisms are cached in the attribute `:isomorphisms`. - isos = get_attribute!(Dict{Type, Any}, A, :isomorphisms)::Dict{Type, Any} - return get!(isos, FPGroup) do + isos = get_attribute!(Dict{Tuple{Type, Bool}, Any}, A, :isomorphisms)::Dict{Tuple{Type, Bool}, Any} + return get!(isos, (FPGroup, false)) do G = free_group(ngens(A); eltype = :syllable) R = rels(A) s = vcat(elem_type(G)[i*j*inv(i)*inv(j) for i = gens(G) for j = gens(G) if i != j], @@ -779,8 +799,8 @@ permutation_group(G::T) where {T <: Union{FinGenAbGroup, GAPGroup, MultTableGrou function isomorphism(::Type{T}, A::MultTableGroup) where T <: GAPGroup # Known isomorphisms are cached in the attribute `:isomorphisms`. - isos = get_attribute!(Dict{Type, Any}, A, :isomorphisms)::Dict{Type, Any} - return get!(isos, T) do + isos = get_attribute!(Dict{Tuple{Type, Bool}, Any}, A, :isomorphisms)::Dict{Tuple{Type, Bool}, Any} + return get!(isos, (T, false)) do S = symmetric_group(order(A)) gensA = gens(A) newgens = elem_type(S)[] diff --git a/test/Groups/homomorphisms.jl b/test/Groups/homomorphisms.jl index 1ab4f24ba33b..74f7420501fd 100644 --- a/test/Groups/homomorphisms.jl +++ b/test/Groups/homomorphisms.jl @@ -410,17 +410,26 @@ end @test is_injective(f) @test is_surjective(f) + G = symmetric_group(5) f = @inferred isomorphism(FPGroup, G) @test codomain(f) isa FPGroup @test domain(f) == G @test is_injective(f) @test is_surjective(f) + f2 = @inferred isomorphism(FPGroup, G, on_gens=true) + @test codomain(f2) isa FPGroup + @test domain(f2) == G + @test is_injective(f2) + @test is_surjective(f2) + @test [preimage(f2, x) for x in gens(codomain(f2))] == gens(G) + @test [preimage(f, x) for x in gens(codomain(f))] != gens(G) + G = abelian_group(PermGroup, [2, 2]) f = @inferred isomorphism(FinGenAbGroup, G) @test codomain(f) isa FinGenAbGroup @test domain(f) == G - # @test is_injective(f) + # @test is_injective(f) # no method for GroupIsomorphismFromFunc # @test is_surjective(f) @test_throws ArgumentError isomorphism(FinGenAbGroup, symmetric_group(5))