From c3cc804e8ba26b43c12284fc02c1cac74dbf05b0 Mon Sep 17 00:00:00 2001 From: Dante Luber <82584641+danteluber@users.noreply.github.com> Date: Fri, 6 Sep 2024 22:28:15 +0200 Subject: [PATCH 1/4] change variable labeling for default grassmann pluecker ideal (Related to issue #4018) (#4074) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * change variable labeling for default grassmann pluecker ideal * Update src/Rings/mpoly-ideals.jl --------- Co-authored-by: Lars Göttgens Co-authored-by: Lars Göttgens --- src/Rings/mpoly-ideals.jl | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Rings/mpoly-ideals.jl b/src/Rings/mpoly-ideals.jl index 5a6fd7ae7451..b3443766cb82 100644 --- a/src/Rings/mpoly-ideals.jl +++ b/src/Rings/mpoly-ideals.jl @@ -2063,23 +2063,25 @@ small_generating_set(I::MPolyIdeal{<:MPolyDecRingElem}; algorithm::Symbol=:simpl ################################################################################ #returns Pluecker ideal in ring with standard grading function grassmann_pluecker_ideal(subspace_dimension::Int, ambient_dimension::Int) - I = convert(MPolyIdeal{QQMPolyRingElem}, - Polymake.ideal.pluecker_ideal(subspace_dimension, ambient_dimension)) - base, _ = grade(base_ring(I)) - o = degrevlex(base) - - return ideal(IdealGens(base,base.(gens(I)), o; - keep_ordering=true, - isReduced=true, - isGB=true)) + I = convert(MPolyIdeal{QQMPolyRingElem}, + Polymake.ideal.pluecker_ideal(subspace_dimension, ambient_dimension)) + base = base_ring(I) + base2, x = graded_polynomial_ring(coefficient_ring(base), :x=> sort(subsets(ambient_dimension, subspace_dimension))) + h = hom(base, base2, x) + o = degrevlex(base2) + return ideal(IdealGens(base2, h.(gens(I)), o; + keep_ordering=true, + isReduced=true, + isGB=true)) end @doc raw""" grassmann_pluecker_ideal([ring::MPolyRing,] subspace_dimension::Int, ambient_dimension::Int) -Given a (possibly graded) ring, an ambient dimension, and a subspace dimension, return the ideal in the ring +Given a (possibly graded) ring, an ambient dimension $n$ and a subspace dimension $d$, return the ideal in the ring generated by the Plücker relations. If the input ring is not graded, return the ideal in the ring with the standard grading. -If the ring is not specified return the ideal in a multivariate polynomial ring over the rationals with the standard grading. +If the ring is not specified return the ideal in a multivariate polynomial ring over the rationals with variables indexed by +elements of ${[n]\choose d}$ with the standard grading. The Grassmann-Plücker ideal is the homogeneous ideal generated by the relations defined by the Plücker Embedding of the Grassmannian. That is given Gr$(k, n)$ the Moduli @@ -2091,7 +2093,7 @@ are given by all $d \times d$ minors of a $d \times n$ matrix. For the algorithm ```jldoctest julia> grassmann_pluecker_ideal(2, 4) Ideal generated by - x[1]*x[6] - x[2]*x[5] + x[3]*x[4] + x[[1, 2]]*x[[3, 4]] - x[[1, 3]]*x[[2, 4]] + x[[1, 4]]*x[[2, 3]] julia> R, x = polynomial_ring(residue_ring(ZZ, 7)[1], "x" => (1:2, 1:3)) (Multivariate polynomial ring in 6 variables over ZZ/(7), zzModMPolyRingElem[x[1, 1] x[1, 2] x[1, 3]; x[2, 1] x[2, 2] x[2, 3]]) From 9759a7773e53c0898d60b2145a9d680288bdb88e Mon Sep 17 00:00:00 2001 From: Tommy Hofmann Date: Sat, 7 Sep 2024 23:18:40 +0200 Subject: [PATCH 2/4] fix: remove debug statement (#4076) --- experimental/GaloisGrp/src/Subfields.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experimental/GaloisGrp/src/Subfields.jl b/experimental/GaloisGrp/src/Subfields.jl index 945bb3b37b1c..ea89e4e62e0b 100644 --- a/experimental/GaloisGrp/src/Subfields.jl +++ b/experimental/GaloisGrp/src/Subfields.jl @@ -75,7 +75,7 @@ function block_system(G::GaloisCtx, a::SimpleNumFieldElem) end pr *= 2 # error("adada") - @show bs, pr + #@show bs, pr if pr > 100 error("too bad") end @@ -297,7 +297,7 @@ end function frob_test(E::SubfieldLatticeElem, si::PermGroupElem) #test if the (tentative) block system in E makes sense - @show bs = E.b + bs = E.b L = E.p #the lattice for i=3:length(L) x = intersect(bs, L[i].b) From 56eaf4a52a51aeebdf9fdbdaf7efb43247042819 Mon Sep 17 00:00:00 2001 From: MarieKaltoft <151847984+MarieKaltoft@users.noreply.github.com> Date: Sun, 8 Sep 2024 15:48:19 +0200 Subject: [PATCH 3/4] Documentation: `vertices_and_rays` and `maximal_polyhedra` (tropical variety) (#3918) --- docs/src/TropicalGeometry/hypersurface.md | 77 +++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/docs/src/TropicalGeometry/hypersurface.md b/docs/src/TropicalGeometry/hypersurface.md index 5eb64153a764..259c7d8bc5f5 100644 --- a/docs/src/TropicalGeometry/hypersurface.md +++ b/docs/src/TropicalGeometry/hypersurface.md @@ -1,3 +1,7 @@ +```@meta +CurrentModule = Oscar +DocTestSetup = Oscar.doctestsetup() +``` # Tropical hypersurfaces ## Introduction @@ -26,3 +30,76 @@ algebraic_polynomial(TropH::TropicalHypersurface) tropical_polynomial(TropH::TropicalHypersurface) dual_subdivision(TropH::TropicalHypersurface) ``` + +## Example +The following code sets up an example and prints the vertices and rays of the tropical hypersurface (in no particular order). +```jldoctest exampleHypersurface +julia> T = tropical_semiring(); + +julia> Tx,(x1,x2) = polynomial_ring(T, 2); + +julia> g = 1 + 2*x1^2 + 2*x2^2 + 1*x1*x2; + +julia> TropH = tropical_hypersurface(g); + +julia> vertRays = vertices_and_rays(TropH) +5-element SubObjectIterator{Union{PointVector{QQFieldElem}, RayVector{QQFieldElem}}}: + [-1, -1] + [1, 0] + [0, 1] + [-1//2, 1//2] + [1//2, -1//2] +``` +By broadcasting the `typeof()` command, we can see, which are vertices, and which are rays. +```jldoctest exampleHypersurface +julia> typeof.(vertRays) +5-element Vector{DataType}: + RayVector{QQFieldElem} + RayVector{QQFieldElem} + RayVector{QQFieldElem} + PointVector{QQFieldElem} + PointVector{QQFieldElem} +``` +The maximal polyhedra of our tropical hypersurface is simply the edges (both bounded and unbounded). The command `maximal_polyhedra()` gives us a list of these edges (in no particular order). +```jldoctest exampleHypersurface +julia> maxPol = maximal_polyhedra(TropH) +5-element SubObjectIterator{Polyhedron{QQFieldElem}}: + Polyhedron in ambient dimension 2 + Polyhedron in ambient dimension 2 + Polyhedron in ambient dimension 2 + Polytope in ambient dimension 2 + Polyhedron in ambient dimension 2 +``` +The polyhedrons are the unbounded edges, and the polytopes are the bounded edges. + +The incidence matrix of the maximal polyhedra is a list of the relations between the elements of `vertices_and_rays(TropH)`. +From these relations, we can draw the hypersurface. However, one should be careful, as there is no distinction between vertices and rays in the incidence matrix. +```jldoctest exampleHypersurface +julia> IncidenceMatrix(maxPol) +5×5 IncidenceMatrix +[1, 4] +[1, 5] +[3, 4] +[4, 5] +[2, 5] +``` +This is made clearer if we ask for the vertices of each of the maximal polyhedra (the bounded edges have a vertex at each end, while the unbounded only have one vertex). +```jldoctest exampleHypersurface +julia> vertices.(maxPol) +5-element Vector{SubObjectIterator{PointVector{QQFieldElem}}}: + [[-1//2, 1//2]] + [[1//2, -1//2]] + [[-1//2, 1//2]] + [[-1//2, 1//2], [1//2, -1//2]] + [[1//2, -1//2]] +``` +Instead of being between two vertices, the unbounded edges are defined by a vertex and a ray. These rays can be seen in the following way. +```jldoctest exampleHypersurface +julia> rays.(maxPol) +5-element Vector{SubObjectIterator{RayVector{QQFieldElem}}}: + [[-1, -1]] + [[-1, -1]] + [[0, 1]] + 0-element SubObjectIterator{RayVector{QQFieldElem}} + [[1, 0]] +``` From f7775260e5168425180a1dcfc02db74343ebe04f Mon Sep 17 00:00:00 2001 From: Thomas Breuer Date: Mon, 9 Sep 2024 10:57:36 +0200 Subject: [PATCH 4/4] support group extensions of matrix groups (#4013) * support group extensions of matrix groups - admit `Oscar.GAPGroupElem` not only `Oscar.BasicGAPGroupElem` in evaluating 2-cochains (we need in particular `Oscar.MatrixGroupElem`) - add a `isomorphism(::Type{FPGroup}, A::AbstractAlgebra.Generic.FreeModule)` method, which is needed by `extension` if we want to support G-modules created from matrix groups acting on vector spaces - add tests * add a test in order to increase coverage * `natural_gmodule` and `regular_gmodule` - rename `natural_gmodule` to `regular_gmodule`, extend its documentation, export it - introduce `natural_module`, export it - add tests for `natural_gmodule` and `regular_gmodule` * add `elementary_abelian_group` and support `is_elementary_abelian` for `FinGenAbGroup` * next iteration, motivated by the comments - generalize `isomorphism(FPGroup, M::...; on_gens=true)` and `isomorphism(PcGroup, M::...: on_gens=true)` to `M::AbstractAlgebra.Generic.FreeModule{FqFieldElem}` as in the functions `fp_group_with_isomorphism`, `pc_group_with_isomorphism` in `experimental/GModule`, delegate from the latter to the former - remove the variant `fp_group_with_isomorphism(g::Vector{<:Oscar.GAPGroupElem})`, since it did in fact not depend on `g` but on `parent(g[1])`. - move the general `relators(G::GAPGroup)` methods to `src/Groups`, changed `relations` to delegate to `relators` - change `free_group(G::FPGroup)` to return `G` if `G` is free - move `underlying_word(g::FPGroupElem)` to `src/Groups`, changed it to return `g` if its parent is free - add `isomorphism(T, G::T)` methods that return an identity map - improve the `isomorphism(::Type{FPGroup}, A::FinGenAbGroup)` in the sense that only commutator relators for one half of the matrix are needed - add some tests - removed some tests: Due to the fact that `relators` is now defined for more types of groups, one may get fewer errors or different errors than before (and it may take longer until one gets these errors) * fix an `elementary_abelian_group` method --- docs/src/Groups/pcgroup.md | 1 + experimental/GModule/src/Cohomology.jl | 164 ++++-------------------- experimental/GModule/src/GModule.jl | 54 ++++++-- experimental/GModule/test/runtests.jl | 57 ++++++++- src/Groups/GAPGroups.jl | 23 ++++ src/Groups/group_constructors.jl | 65 +++++++++- src/Groups/homomorphisms.jl | 167 ++++++++++++++++++++----- src/exports.jl | 2 + test/Groups/GrpAb.jl | 1 + test/Groups/constructors.jl | 15 +++ test/Groups/elements.jl | 8 ++ test/Groups/homomorphisms.jl | 14 +++ test/Groups/quotients.jl | 36 +++++- 13 files changed, 422 insertions(+), 185 deletions(-) diff --git a/docs/src/Groups/pcgroup.md b/docs/src/Groups/pcgroup.md index 28a912395e31..e81197a18436 100644 --- a/docs/src/Groups/pcgroup.md +++ b/docs/src/Groups/pcgroup.md @@ -14,6 +14,7 @@ map_word(g::Union{PcGroupElem, SubPcGroupElem}, genimgs::Vector; genimgs_inv::Ve Julia has the following functions that allow to generate polycyclic groups: ```@docs abelian_group(::Type{T}, v::Vector{Int}) where T <: GAPGroup +elementary_abelian_group cyclic_group dihedral_group quaternion_group diff --git a/experimental/GModule/src/Cohomology.jl b/experimental/GModule/src/Cohomology.jl index fa51bce924a4..60d4a313ff19 100644 --- a/experimental/GModule/src/Cohomology.jl +++ b/experimental/GModule/src/Cohomology.jl @@ -184,22 +184,9 @@ function inv_action(C::GModule) end function fp_group_with_isomorphism(C::GModule) - #TODO: better for PcGroup!!! - G = group(C) if !isdefined(C, :F) - if (!isa(G, FPGroup)) && is_trivial(G) - C.F = free_group(0) - C.mF = hom(G, C.F, elem_type(G)[], elem_type(C.F)[]) - elseif isa(group(C), PcGroup) && GAP.Globals.GeneratorsOfGroup(G.X) == GAP.Globals.Pcgs(G.X) - X = GAPWrap.IsomorphismFpGroupByPcgs(GAP.Globals.InducedPcgsWrtFamilyPcgs(G.X), GAP.julia_to_gap("a")) - - C.F = FPGroup(GAPWrap.Range(X)) - C.mF = GAPGroupHomomorphism(G, C.F, X) - return C.F, C.mF - else - C.F, C.mF = fp_group_with_isomorphism(gens(G)) -# C.mF = GAPGroupHomomorphism(C.F, group(C), GAP.Globals.InverseGeneralMapping(C.mF.map)) - end + iso = isomorphism(FPGroup, group(C), on_gens=true) + C.F, C.mF = codomain(iso), iso end return C.F, C.mF end @@ -550,17 +537,6 @@ Oscar.group(C::GModule) = C.G # To be moved and revised eventually ########################################################### -""" -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) - iso = isomorphism(FPGroup, G, on_gens=true) - return codomain(iso), iso -end - """ For an element of an fp-group, return a corresponding word as a sequence of integers. A positive integers indicates the corresponding generator, @@ -572,39 +548,18 @@ function word(y::FPGroupElem) end """ -The relations defining 'F' as an array of pairs. +The relations defining 'G' as an array of pairs. """ -function _relations_by_generators(G::Oscar.GAPGroup) - f = GAPWrap.IsomorphismFpGroupByGenerators(GapObj(G), GAPWrap.GeneratorsOfGroup(GapObj(G))) - @req f != GAP.Globals.fail "Could not convert group into a group of type FPGroup" - H = FPGroup(GAPWrap.Image(f)) - return relations(H) -end - -Oscar.relations(G::Oscar.GAPGroup) = _relations_by_generators(G) - -function Oscar.relations(F::Union{FPGroup, SubFPGroup}) - Oscar._is_full_fp_group(GapObj(F)) || return _relations_by_generators(F) - R = relators(F) - z = one(free_group(F)) - return [(x, z) for x = R] -end - -function Oscar.relators(F::PcGroup) - #TODO: do it properly!!!! - return [x[1] for x = relations(F)] +function Oscar.relations(G::Oscar.GAPGroup) + rels = relators(G) + if length(rels) == 0 + T = eltype(rels) + return Tuple{T,T}[] + end + z = one(parent(rels[1])) + return [(x, z) for x in rels] end -function Oscar.relations(G::PcGroup) - # Call `GAPWrap.IsomorphismFpGroupByPcgs` only if `gens(G)` is a pcgs. - Ggens = GAPWrap.GeneratorsOfGroup(GapObj(G)) - Gpcgs = GAPWrap.Pcgs(GapObj(G)) - Ggens == Gpcgs || return _relations_by_generators(G) - f = GAPWrap.IsomorphismFpGroupByPcgs(Gpcgs, GAP.Obj("g")) - @req f != GAP.Globals.fail "Could not convert group into a group of type FPGroup" - H = FPGroup(GAPWrap.Image(f)) - return relations(H) -end ###################################################### # @@ -759,14 +714,14 @@ end Evaluate a 2-cochain, a 2-cochain is a map from pairs of group elements into the module """ -function (C::CoChain{2})(g::Oscar.BasicGAPGroupElem, h::Oscar.BasicGAPGroupElem) +function (C::CoChain{2})(g::Oscar.GAPGroupElem, h::Oscar.GAPGroupElem) if haskey(C.d, (g,h)) return C.d[(g,h)] end @assert isdefined(C, :D) return C.d[(g,h)] = C.D((g, h)) end -(C::CoChain{2})(g::NTuple{2, <:Oscar.BasicGAPGroupElem}) = C(g[1], g[2]) +(C::CoChain{2})(g::NTuple{2, <:Oscar.GAPGroupElem}) = C(g[1], g[2]) #TODO: re-write to get the maps! To support Q/Z as well """ @@ -1705,10 +1660,10 @@ returns (I, q), (hom(Z[G], C), B) function dimension_shift(C::GModule) G = C.G if isa(C.M, FinGenAbGroup) - zg, ac, em = Oscar.GModuleFromGap.natural_gmodule(FinGenAbGroup, G, ZZ) + zg, ac, em = regular_gmodule(FinGenAbGroup, G, ZZ) Z = Hecke.zero_obj(zg.M) elseif isa(C.M, AbstractAlgebra.FPModule{<:FieldElem}) - zg, ac, em = Oscar.GModuleFromGap.natural_gmodule(G, base_ring(C)) + zg, ac, em = regular_gmodule(G, base_ring(C)) Z = free_module(base_ring(C), 0) else error("unsupported module") @@ -1751,10 +1706,10 @@ end function dimension_shift_left(C::GModule) G = C.G if isa(C.M, FinGenAbGroup) - zg, ac, em = Oscar.GModuleFromGap.natural_gmodule(FinGenAbGroup, G, ZZ) + zg, ac, em = regular_gmodule(FinGenAbGroup, G, ZZ) Z = Hecke.zero_obj(zg.M) elseif isa(C.M, AbstractAlgebra.FPModule{<:FieldElem}) - zg, ac, em = Oscar.GModuleFromGap.natural_gmodule(G, base_ring(C)) + zg, ac, em = regular_gmodule(G, base_ring(C)) Z = free_module(base_ring(C), 0) else error("unsupported module") @@ -1853,13 +1808,13 @@ 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` +# better use `isomorphism` directly 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), mf*mp + iso = isomorphism(FPGroup, M, on_gens=true) + return codomain(iso), iso end + ######################################################### function Oscar.matrix(M::FreeModuleHom{FreeMod{QQAbFieldElem}, FreeMod{QQAbFieldElem}}) return M.matrix @@ -1973,82 +1928,13 @@ function pc_group_with_isomorphism(M::FinGenAbGroup; refine::Bool = true) x->image(mM, gap_to_julia(GapObj(x)))) end +# `refine` is irrelevant because `M` is elementary abelian. function pc_group_with_isomorphism(M::AbstractAlgebra.FPModule{<:FinFieldElem}; refine::Bool = true) - k = base_ring(M) - p = characteristic(k) - - G = free_group(degree(k)*dim(M)) - - C = GAP.Globals.CombinatorialCollector(GapObj(G), - GAP.Obj([p for i=1:ngens(G)]; recursive = true)) - F = GAP.Globals.FamilyObj(GAP.Globals.Identity(GapObj(G))) - - # 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(GapObj(B))) - - function Julia_to_gap(a::AbstractAlgebra.FPModuleElem{<:Union{fpFieldElem, FpFieldElem, FqFieldElem}}) - F = base_ring(parent(a)) - @assert absolute_degree(F) == 1 - r = ZZRingElem[] - for i=1:ngens(M) - if !iszero(a[i]) - push!(r, i) - push!(r, lift(ZZ, a[i])) - end - end - g = GAP.Globals.ObjByExtRep(FB, GAP.Obj(r; recursive = true)) - return g - end - - function Julia_to_gap(a::AbstractAlgebra.FPModuleElem{<:Union{FqPolyRepFieldElem, fqPolyRepFieldElem}}) - r = ZZRingElem[] - for i=1:ngens(M) - if !iszero(a[i]) - for j=0:degree(k)-1 - if !iszero(coeff(a[i], j)) - push!(r, (i-1)*degree(k)+j+1) - push!(r, ZZ(coeff(a[i], j))) - end - end - end - end - g = GAP.Globals.ObjByExtRep(FB, GAP.Obj(r; recursive = true)) - return g - end - - - gap_to_julia = function(a::GapObj) - e = GAPWrap.ExtRepOfObj(a) - z = zeros(ZZRingElem, ngens(M)*degree(k)) - for i=1:2:length(e) - if !iszero(e[i+1]) - z[e[i]] = e[i+1] - end - end - c = elem_type(k)[] - for i=1:dim(M) - push!(c, k(z[(i-1)*degree(k)+1:i*degree(k)])) - end - return M(c) - end - - return B, MapFromFunc( - M, B, - y->PcGroupElem(B, Julia_to_gap(y)), - x->gap_to_julia(GapObj(x))) + iso = isomorphism(PcGroup, M, on_gens=true) + return codomain(iso), iso end -function underlying_word(g::FPGroupElem) - return FPGroupElem(free_group(parent(g)), GAPWrap.UnderlyingElement(GapObj(g))) -end - """ Given a 2-cocycle, return the corresponding group extension, ie. the large group, the injection of the abelian group and the quotient as well as a map @@ -2061,7 +1947,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, _ = fp_group_with_isomorphism(gens(G)) + F = codomain(isomorphism(FPGroup, G, on_gens=true)) M = Module(C) ac = action(C) iac = inv_action(C) diff --git a/experimental/GModule/src/GModule.jl b/experimental/GModule/src/GModule.jl index 2b37b2c4305d..cb2ef40cd73a 100644 --- a/experimental/GModule/src/GModule.jl +++ b/experimental/GModule/src/GModule.jl @@ -813,15 +813,19 @@ gmodule(k::fpField, C::GModule{<:Any, <:AbstractAlgebra.FPModule{fpFieldElem}}) end """ -Return Z[G] and a function f that, when applied to a G-module M will return -a map representing the action of Z[G] on M: - -f(C) yields the extension of g -> action(C, g) - -The third value returned is a Map between group elements and the index of the -corresponding module generator. + regular_gmodule(G::GAPGroup, R::Ring) + +Return `(R[G], f, g)`, where +- `R[G]` is the group ring of `G` over `R`, as a G-module object, +- `f` is a function that, when applied to a `G`-module `M` over `R`, + will return a function `F` representing the action of `R[G]` on `M` + in the sense that for a vector `x` of length `order(G)` over `R`, + `F(x)` is the module homomorphism on `M` induced by the action of the + element of `R[G]` with coefficient vector `x`, and +- `g` is a bijective map between the elements of `G` and the + indices of the corresponding module generators. """ -function natural_gmodule(G::Oscar.GAPGroup, R::Ring) +function regular_gmodule(G::Oscar.GAPGroup, R::Ring) M = free_module(R, Int(order(G))) ge = collect(G) ZG = gmodule(G, [hom(M, M, [M[findfirst(isequal(ge[i]*g), ge)] for i=1:length(ge)]) for g = gens(G)]) @@ -830,7 +834,7 @@ function natural_gmodule(G::Oscar.GAPGroup, R::Ring) y->ge[Int(y)]) end -function natural_gmodule(::Type{FinGenAbGroup}, G::Oscar.GAPGroup, ::ZZRing) +function regular_gmodule(::Type{FinGenAbGroup}, G::Oscar.GAPGroup, ::ZZRing) M = free_abelian_group(order(Int, G)) ge = collect(G) ZG = gmodule(G, [hom(M, M, [M[findfirst(isequal(ge[i]*g), ge)] for i=1:length(ge)]) for g = gens(G)]) @@ -839,6 +843,32 @@ function natural_gmodule(::Type{FinGenAbGroup}, G::Oscar.GAPGroup, ::ZZRing) y->ge[Int(y)]) end +""" + natural_gmodule(G::PermGroup, R::Ring) + +Return the G-module of dimension `degree(G)` over `R` +that is induced by the permutation action of `G` on the basis of the module. +""" +function natural_gmodule(G::PermGroup, R::Ring) + M = free_module(R, degree(G)) + return GModule(M, G, [hom(M, M, permutation_matrix(R, a)) for a in gens(G)]) +#TODO: We do not really want to write down these matrices. +# What is the appropriate way to construct a module homomorphism +# without storing a matrix? +end + +""" + natural_gmodule(G::MatrixGroup) + +Return the G-module of dimension `degree(G)` over `base_ring(G)` +that is induced by the action of `G` via right multiplication. +""" +function natural_gmodule(G::MatrixGroup) + R = base_ring(G) + M = free_module(R, degree(G)) + return GModule(M, G, [hom(M, M, matrix(a)) for a in gens(G)]) +end + Oscar.character_field(C::GModule{<:Any, <:AbstractAlgebra.FPModule{QQFieldElem}}) = QQ @attr Any function _character_field(C::GModule{<:Any, <:AbstractAlgebra.FPModule{AbsSimpleNumFieldElem}}) @@ -1507,7 +1537,7 @@ A = abelian_group([3, 3]) C = gmodule(G, [hom(A, A, [A[1], A[1]+A[2]])]) is_consistent(C) -zg, ac = Oscar.GModuleFromGap.natural_gmodule(G, ZZ) +zg, ac = regular_gmodule(G, ZZ) zg = gmodule(FinGenAbGroup, zg) H, mH = Oscar.GModuleFromGap.ghom(zg, C) inj = hom(C.M, H.M, [preimage(mH, hom(zg.M, C.M, [ac(C)(g)(c) for g = gens(zg.M)])) for c = gens(C.M)]) @@ -1931,6 +1961,8 @@ export is_decomposable export is_G_hom export restriction_of_scalars export trivial_gmodule +export natural_gmodule +export regular_gmodule export gmodule_minimal_field export gmodule_over @@ -1987,6 +2019,8 @@ export is_decomposable export is_G_hom export restriction_of_scalars export trivial_gmodule +export natural_gmodule +export regular_gmodule export gmodule_minimal_field export gmodule_over diff --git a/experimental/GModule/test/runtests.jl b/experimental/GModule/test/runtests.jl index ab527e504f0c..ced5c36ae5d0 100644 --- a/experimental/GModule/test/runtests.jl +++ b/experimental/GModule/test/runtests.jl @@ -11,6 +11,7 @@ F = FPGroup(G) @test testrels(F) # full f.p. group @test testrels(center(F)[1]) # subgroup of full f.p. group + @test testrels(free_group(2)) # free group (empty relations) @test testrels(PermGroup(G)) # nothing of the above end @@ -76,11 +77,65 @@ end z = irreducible_modules(ZZ, G) @test length(Oscar.GModuleFromGap.invariant_lattice_classes(z[3])) == 2 - G = Oscar.GrpCoh.fp_group_with_isomorphism(gens(G))[1] + G = codomain(isomorphism(FPGroup, G, on_gens=true)) q, mq = maximal_abelian_quotient(PcGroup, G) @test length(Oscar.RepPc.brueckner(mq)) == 6 end +@testset "Experimental.gmodule natural G-modules" begin + # for permutation groups + G = symmetric_group(3) + M = natural_gmodule(G, GF(2)) + cf = composition_factors_with_multiplicity(M) + @test sort([dim(x[1]) for x in cf]) == [1, 2] + + # for matrix groups + G = SL(2, 3) + M = natural_gmodule(G) + cf = composition_factors_with_multiplicity(M) + @test length(cf) == 1 && cf[1][2] == 1 +end + +@testset "Experimental.gmodule regular G-modules" begin + # for permutation groups + G = symmetric_group(3) + R = GF(2) + M, f, g = regular_gmodule(G, R) + cf = composition_factors_with_multiplicity(M) + @test sort([dim(x[1]) for x in cf]) == [1, 2] + @test [x[2] for x in cf] == [2, 2] + N = natural_gmodule(G, R) + F = f(N) + V = M.M + @test all(i -> F(gen(V, Int(g(gen(G, i))))).matrix == matrix(N.ac[i]), 1:ngens(G)) + + @test map(g, collect(G)) == 1:order(G) + @test [preimage(g, i) for i in 1:order(G)] == collect(G) + + # for pc groups + G = dihedral_group(8) + M, f, g = regular_gmodule(G, GF(3)) + cf = composition_factors_with_multiplicity(M) + @test sort([dim(x[1]) for x in cf]) == [1, 1, 1, 1, 2] + @test all(x -> dim(x[1]) == x[2], cf) + @test map(g, collect(G)) == 1:order(G) + @test [preimage(g, i) for i in 1:order(G)] == collect(G) +end + +@testset "Experimental.gmodule extension for matrix group" begin + G = SL(2,3) + F = GF(3) + m = free_module(F, 2) + M = GModule(m, G, [hom(m, m, matrix(a)) for a in gens(G)]) + H2 = Oscar.GrpCoh.cohomology_group(M, 2) + x = collect(H2[1])[1] + c = H2[2](x) + e = extension(c) + GG = e[1] + @test order(GG) == 216 + @test GG isa FPGroup +end + @testset "Experimental.gmodule SL(2,5)" begin G = SL(2, 5) T = character_table(G) diff --git a/src/Groups/GAPGroups.jl b/src/Groups/GAPGroups.jl index a0bcefe8efc3..93be60411a22 100644 --- a/src/Groups/GAPGroups.jl +++ b/src/Groups/GAPGroups.jl @@ -1939,6 +1939,29 @@ function relators(G::FPGroup) return [group_element(F, L[i]::GapObj) for i in 1:length(L)] end +function relators(G::PcGroup) + gapG = GapObj(G) + Ggens = GAPWrap.GeneratorsOfGroup(gapG) + Gpcgs = GAPWrap.Pcgs(gapG) + if Ggens == Gpcgs + # The generators form a pcgs, compute w.r.t. this pcgs. + f = GAPWrap.IsomorphismFpGroupByPcgs(Gpcgs, GapObj("g")) + @req f != GAP.Globals.fail "Could not convert group into a group of type FPGroup" + return relators(FPGroup(GAPWrap.Image(f))) + else + return _relators_by_generators(FPGroup(GAPWrap.Image(f))) + end +end + +relators(G::GAPGroup) = _relators_by_generators(G) + +function _relators_by_generators(G::GAPGroup) + gapG = GapObj(G) + f = GAPWrap.IsomorphismFpGroupByGenerators(gapG, GAPWrap.GeneratorsOfGroup(gapG)) + @req f != GAP.Globals.fail "Could not convert group into a group of type FPGroup" + return relators(FPGroup(GAPWrap.Image(f))) +end + @doc raw""" map_word(g::Union{FPGroupElem, SubFPGroupElem}, genimgs::Vector; genimgs_inv::Vector = Vector(undef, length(genimgs)), init = nothing) diff --git a/src/Groups/group_constructors.jl b/src/Groups/group_constructors.jl index 6139f80fd4e3..8768a319d133 100644 --- a/src/Groups/group_constructors.jl +++ b/src/Groups/group_constructors.jl @@ -220,6 +220,45 @@ that is, $x*y = y*x$ holds for all elements $x, y$ in `G`. """ @gapattribute is_abelian(G::GAPGroup) = GAP.Globals.IsAbelian(GapObj(G))::Bool + +@doc raw""" + elementary_abelian_group(::Type{T} = PcGroup, n::S) where T <: Group where S <: IntegerUnion + +Return the elementary abelian group group of order `n`, as an instance of `T`. +Here, `T` must be one of `PermGroup`, `FPGroup`, `SubFPGroup`, `PcGroup`, +`SubPcGroup`, or `FinGenAbGroup`, +and `n` must be a prime power or 1. + +The `gens` vector of the result has minimal length. + +# Examples +```jldoctest +julia> g = elementary_abelian_group(27) +Pc group of order 27 + +julia> g = elementary_abelian_group(PermGroup, 27) +Permutation group of degree 9 and order 27 +``` +""" +elementary_abelian_group(n::IntegerUnion) = elementary_abelian_group(PcGroup, n) + +function elementary_abelian_group(::Type{T}, n::S) where T <: GAPGroup where S <: IntegerUnion + @req (n == 1 || is_prime_power_with_data(n)[1]) "n must be a prime power or 1" + return T(GAP.Globals.ElementaryAbelianGroup(_gap_filter(T), GAP.Obj(n))::GapObj) +end + +# Delegating to the GAP constructor via `_gap_filter` does not work here. +function elementary_abelian_group(::Type{T}, n::S) where T <: Union{PcGroup, SubPcGroup} where S <: IntegerUnion + @req (n == 1 || is_prime_power_with_data(n)[1]) "n must be a prime power or 1" + return T(GAP.Globals.ElementaryAbelianGroup(GAP.Globals.IsPcGroup, GAP.Obj(n))::GapObj) +end + +function elementary_abelian_group(::Type{FinGenAbGroup}, n::S) where S <: IntegerUnion + flag, e, p = is_prime_power_with_data(n) + @req (n == 1 || flag) "n must be a prime power or 1" + return abelian_group(fill(p, e)) +end + @doc raw""" is_elementary_abelian(G::Group) @@ -242,6 +281,12 @@ false """ @gapattribute is_elementary_abelian(G::GAPGroup) = GAP.Globals.IsElementaryAbelian(GapObj(G))::Bool +function is_elementary_abelian(G::FinGenAbGroup) + e = exponent(G) + return e == 1 || is_prime(e) +end + + function mathieu_group(n::Int) @req 9 <= n <= 12 || 21 <= n <= 24 "n must be a 9-12 or 21-24" return PermGroup(GAP.Globals.MathieuGroup(n), n) @@ -673,9 +718,27 @@ end # for the definition of group modulo relations, see the quo function in the sub.jl section function free_group(G::FPGroup) - return FPGroup(GAPWrap.FreeGroupOfFpGroup(GapObj(G))::GapObj) + gapG = GapObj(G)::GapObj + gapF = GAPWrap.FreeGroupOfFpGroup(gapG) + if gapF === gapG + # G is itself a free group + return G + else + return FPGroup(gapF) + end end +function underlying_word(g::FPGroupElem) + G = parent(g) + F = free_group(G) + if F === G + return g + else + return FPGroupElem(F, GAPWrap.UnderlyingElement(GapObj(g))) + end +end + + ################################################################################ # # end FpGroups diff --git a/src/Groups/homomorphisms.jl b/src/Groups/homomorphisms.jl index f512b27a6081..d05863351595 100644 --- a/src/Groups/homomorphisms.jl +++ b/src/Groups/homomorphisms.jl @@ -479,6 +479,35 @@ end # ################################################################################ +function _isomorphism_same_type(G::T, on_gens::Bool) where T <: GAPGroup + # 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, (T, on_gens)) do + return id_hom(G) + end::GAPGroupHomomorphism{T, T} +end + +for T in [PermGroup, SubFPGroup, SubPcGroup] + @eval begin + isomorphism(::Type{$T}, G::$T) = _isomorphism_same_type(G, false) + end +end + +for T in [FPGroup, PcGroup] + @eval begin + isomorphism(::Type{$T}, G::$T; on_gens::Bool = false) = _isomorphism_same_type(G, on_gens) + end +end + +function isomorphism(::Type{FinGenAbGroup}, A::FinGenAbGroup) + # Known isomorphisms are cached in the attribute `:isomorphisms`. + 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 + + _get_iso_function(::Type{PermGroup}) = GAP.Globals.IsomorphismPermGroup # We use `GAP.Globals.IsomorphismPcGroup` as the `_get_iso_function` value @@ -542,30 +571,28 @@ 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 - Ggens = GAPWrap.GeneratorsOfGroup(GapObj(G)) - if length(Ggens) == 0 + if is_trivial(G) # TODO: remove this special treatment as soon as the change from # https://github.com/gap-system/gap/pull/5700 is available in Oscar # (not yet in GAP 4.13.0) - f = GAP.Globals.GroupHomomorphismByImages(GapObj(G), GAP.Globals.FreeGroup(0), GAP.Obj([]), GAP.Obj([])) - GAP.Globals.SetIsBijective(f, true) - else - # The computations are easy if `Ggens` is a pcgs, - # otherwise GAP will call `CoKernel`. - if GAP.Globals.HasFamilyPcgs(GapObj(G)) - pcgs = GAP.Globals.InducedPcgsWrtFamilyPcgs(GapObj(G)) - if pcgs == Ggens - # `pcgs` fits *and* is an object in `GAP.Globals.IsPcgs`, - # for which a special `GAPWrap.IsomorphismFpGroupByGenerators` - # method is applicable. - # (Currently the alternative is a cokernel computation. - # It might be useful to improve this on the GAP side.) - Ggens = pcgs - end + f = GAP.Globals.GroupHomomorphismByImages(GapObj(G), GAP.Globals.FreeGroup(0), GAP.Obj([]), GAP.Obj([])) + GAP.Globals.SetIsBijective(f, true) + elseif on_gens + Ggens = GAPWrap.GeneratorsOfGroup(GapObj(G)) + # The computations are easy if `Ggens` is a pcgs, + # otherwise GAP will call `CoKernel`. + if GAP.Globals.HasFamilyPcgs(GapObj(G)) + pcgs = GAP.Globals.InducedPcgsWrtFamilyPcgs(GapObj(G)) + if pcgs == Ggens + # `pcgs` fits *and* is an object in `GAP.Globals.IsPcgs`, + # for which a special `GAPWrap.IsomorphismFpGroupByGenerators` + # method is applicable. + # (Currently the alternative is a cokernel computation. + # It might be useful to improve this on the GAP side.) + Ggens = pcgs end - f = GAPWrap.IsomorphismFpGroupByGenerators(GapObj(G), Ggens) end + f = GAPWrap.IsomorphismFpGroupByGenerators(GapObj(G), Ggens) else f = GAPWrap.IsomorphismFpGroup(GapObj(G)) end @@ -850,35 +877,113 @@ end #### -function isomorphism(::Type{FinGenAbGroup}, A::FinGenAbGroup) - # Known isomorphisms are cached in the attribute `:isomorphisms`. - 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 - # We need not find independent generators in order to create # 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{Tuple{Type, Bool}, Any}, A, :isomorphisms)::Dict{Tuple{Type, Bool}, Any} return get!(isos, (FPGroup, false)) do - G = free_group(ngens(A); eltype = :syllable) + n = ngens(A) + G = free_group(n; 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], - elem_type(G)[prod([gen(G, i)^R[j,i] for i=1:ngens(A) if !iszero(R[j,i])], init = one(G)) for j=1:nrows(R)]) + gG = gens(G) + gGi = map(inv, gG) + s = vcat(elem_type(G)[gG[i]*gG[j]*gGi[i]*gGi[j] for i in 1:n for j in (i+1):n], + elem_type(G)[prod([gen(G, i)^R[j,i] for i=1:n if !iszero(R[j,i])], init = one(G)) for j=1:nrows(R)]) F, mF = quo(G, s) set_is_abelian(F, true) set_is_finite(F, is_finite(A)) is_finite(A) && set_order(F, order(A)) return MapFromFunc( A, F, - y->F([i => y[i] for i=1:ngens(A)]), + y->F([i => y[i] for i=1:n]), x->sum([w.second*gen(A, w.first) for w = syllables(x)], init = zero(A))) end::MapFromFunc{FinGenAbGroup, FPGroup} end + +# We need not find independent generators in order to create +# a presentation of a `FPModule` over a finite field. +# Note that additively, the given module is an elementary abelian p-group +# where p is the characteristic. +# The function guarantees always a correspondence of `gens(M)` and the `gens` +# value of the codomain of the result, +# that is, the value of `on_gens` is irrelevant. +# (This method is needed in the construction of group extensions +# from information computed by `cohomology_group`.) +function isomorphism(::Type{T}, M::S; on_gens::Bool=true) where T <: Union{FPGroup, PcGroup} where S <: AbstractAlgebra.FPModule{<:FinFieldElem} + # Known isomorphisms are cached in the attribute `:isomorphisms`. + isos = get_attribute!(Dict{Tuple{Type, Bool}, Any}, M, :isomorphisms)::Dict{Tuple{Type, Bool}, Any} + return get!(isos, (T, false)) do + k = base_ring(M) + p = characteristic(k) + B = elementary_abelian_group(T, order(M)) + FB = GAPWrap.ElementsFamily(GAPWrap.FamilyObj(GapObj(B))) + + # module to group + function Julia_to_gap(a::AbstractAlgebra.FPModuleElem{<:Union{fpFieldElem, FpFieldElem}}) + F = base_ring(parent(a)) + @assert absolute_degree(F) == 1 + r = ZZRingElem[] + for i=1:ngens(M) + if !iszero(a[i]) + push!(r, i) + push!(r, lift(ZZ, a[i])) + end + end + return GapObj(r; recursive = true) + end + + function Julia_to_gap(a::AbstractAlgebra.FPModuleElem{<:Union{FqPolyRepFieldElem, fqPolyRepFieldElem, FqFieldElem}}) + r = ZZRingElem[] + for i=1:ngens(M) + if !iszero(a[i]) + for j=0:degree(k)-1 + if !iszero(coeff(a[i], j)) + push!(r, (i-1)*degree(k)+j+1) + push!(r, lift(ZZ, coeff(a[i], j))) + end + end + end + end + return GapObj(r; recursive = true) + end + + # group to module + gap_to_julia = function(a::GapObj) + e = GAPWrap.ExtRepOfObj(a) + z = zeros(ZZRingElem, ngens(M)*degree(k)) + for i=1:2:length(e) + if !iszero(e[i+1]) + z[e[i]] = e[i+1] + end + end + c = elem_type(k)[] + for i=1:dim(M) + push!(c, k(z[(i-1)*degree(k)+1:i*degree(k)])) + end + return M(c) + end + + if T === PcGroup + return MapFromFunc( + M, B, + y -> PcGroupElem(B, GAPWrap.ObjByExtRep(FB, Julia_to_gap(y))), + x -> gap_to_julia(GapObj(x))) + else + # We need an indirection: First create the word in the free group, + # then wrap it into an element of the f.p. group. + FR = GAPWrap.ElementsFamily(GAPWrap.FamilyObj(GAPWrap.FreeGroupOfFpGroup(GapObj(B)))) + + return MapFromFunc( + M, B, + y -> FPGroupElem(B, GAPWrap.ElementOfFpGroup(FB, GAPWrap.ObjByExtRep(FR, Julia_to_gap(y)))), + x -> gap_to_julia(GapObj(x))) + end + end::MapFromFunc{S, T} +end + + """ FPGroup(G::T) where T <: Union{GAPGroup, FinGenAbGroup} fp_group(G::T) where T <: Union{GAPGroup, FinGenAbGroup} diff --git a/src/exports.jl b/src/exports.jl index b27a7f2553a0..8846c6c27405 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -498,6 +498,7 @@ export dwarfed_product_polygons export edges export ehrhart_polynomial export element_to_homomorphism +export elementary_abelian_group export elementary_symmetric export elements export eliminate @@ -1502,6 +1503,7 @@ export twist export two_sided_ideal export underlying_gluing export underlying_quotient +export underlying_word export uniform_matroid export unit export unitary_group diff --git a/test/Groups/GrpAb.jl b/test/Groups/GrpAb.jl index 7e3d2b30cd1e..c601e80d8835 100644 --- a/test/Groups/GrpAb.jl +++ b/test/Groups/GrpAb.jl @@ -39,6 +39,7 @@ end # properties @test is_abelian(G1) == is_abelian(G2) + @test is_elementary_abelian(G1) == is_elementary_abelian(G2) @test is_finite(G1) == is_finite(G2) @test is_finitely_generated(G1) == is_finitely_generated(G2) @test is_perfect(G1) == is_perfect(G2) diff --git a/test/Groups/constructors.jl b/test/Groups/constructors.jl index 210328d5c58b..af2212361bd5 100644 --- a/test/Groups/constructors.jl +++ b/test/Groups/constructors.jl @@ -78,9 +78,16 @@ end @test_throws ArgumentError cyclic_generator(symmetric_group(3)) + @test isa(elementary_abelian_group(27), PcGroup) + @test isa(elementary_abelian_group(PermGroup, 27), PermGroup) + @test isa(elementary_abelian_group(FinGenAbGroup, 27), FinGenAbGroup) + @test_throws ArgumentError elementary_abelian_group(6) + @test_throws ArgumentError elementary_abelian_group(PermGroup, 6) + for p in [1, next_prime(2^62), next_prime(ZZRingElem(2)^66)] g = cyclic_group(p) @test is_cyclic(g) + @test is_elementary_abelian(g) @test order(cyclic_generator(g)) == order(g) @test !is_dihedral_group(g) @test is_finite(g) @@ -94,6 +101,14 @@ end #@test is_dihedral_group(g) @test is_finite(g) @test order(g) == n + + n = p^2 + g = elementary_abelian_group(n) + @test is_abelian(g) + @test is_finite(g) + @test !is_cyclic(g) + @test exponent(g) == p + @test order(g) == n end g = cyclic_group(PosInf()) diff --git a/test/Groups/elements.jl b/test/Groups/elements.jl index 0ef98e74ca60..6af01c001453 100644 --- a/test/Groups/elements.jl +++ b/test/Groups/elements.jl @@ -199,4 +199,12 @@ end g1 = codomain(isomorphism(FPGroup, L[1])) g2 = codomain(isomorphism(FPGroup, L[2])) @test_throws ArgumentError one(g1) * one(g2) + + g = free_group(2) + x, y = gens(g) + f, epi = quo(g, [x, y^2]) + @test free_group(g) === g + @test free_group(f) == g + @test underlying_word(x) === x + @test underlying_word(epi(x)) == x end diff --git a/test/Groups/homomorphisms.jl b/test/Groups/homomorphisms.jl index 53553ee01dd9..6e1a7848b98d 100644 --- a/test/Groups/homomorphisms.jl +++ b/test/Groups/homomorphisms.jl @@ -301,6 +301,20 @@ end iso = @inferred isomorphism(FinGenAbGroup, A) end + @testset "Vector space to FPGroup or PcGroup" begin + for (F, n) in [(GF(2), 0), (GF(3), 2), (GF(4), 2), + (Nemo.Native.GF(2), 0), + (Nemo.Native.GF(3), 2)] + V = free_module(F, n) + for T in [FPGroup, PcGroup] + iso = @inferred isomorphism(T, V) + for x in [zero(V), rand(V)] + @test preimage(iso, iso(x)) == x + end + end + end + end + @testset "MultTableGroup to GAPGroups" begin for G in [Hecke.small_group(64, 14, DB = Hecke.DefaultSmallGroupDB()), Hecke.small_group(20, 3, DB = Hecke.DefaultSmallGroupDB())] diff --git a/test/Groups/quotients.jl b/test/Groups/quotients.jl index b9d733dff7e9..76693e0a3ec5 100644 --- a/test/Groups/quotients.jl +++ b/test/Groups/quotients.jl @@ -87,6 +87,38 @@ end @test abelian_invariants(small_group(8, 5)) == [2, 2, 2] end +@testset "Relators" begin + # free group + F = free_group(2) + @test relators(F) == [] + + # f.p. group + x, y = gens(F) + rels = [x^3, y^2, comm(x, y)] + G, epi = quo(F, rels) + @test relators(G) == rels + + # sub-f.p. group + G = sylow_subgroup(G, 2)[1] + rels = relators(G) + @test is_isomorphic(G, quo(parent(rels[1]), rels)[1]) + + # pc group + G = dihedral_group(12) + rels = relators(G) + @test is_isomorphic(G, quo(parent(rels[1]), rels)[1]) + + # sub-pc group + G = sylow_subgroup(G, 2)[1] + rels = relators(G) + @test is_isomorphic(G, quo(parent(rels[1]), rels)[1]) + + # perm. group + G = symmetric_group(3) + rels = relators(G) + @test is_isomorphic(G, quo(parent(rels[1]), rels)[1]) +end + @testset "Finitely presented groups" begin F = free_group(2) x,y = gens(F) @@ -96,8 +128,7 @@ end @test relators(F) == FPGroupElem[] S = sub(F, [gen(F, 1)])[1] @test ! Oscar._is_full_fp_group(GapObj(S)) - @test_throws MethodError relators(S) - + n=5 G,f = quo(F, [x^2,y^n,(x*y)^2] ) # dihedral group D(2n) @test is_finite(G) @@ -125,7 +156,6 @@ end @test relators(G)==[x^n,y^n,comm(x,y)] S = sub(G, [gen(G, 1)])[1] @test ! Oscar._is_full_fp_group(GapObj(S)) - @test_throws MethodError relators(S) @test_throws ArgumentError quo(S, gens(S)) @test G([1 => 2, 2 => -3]) == G[1]^2 * G[2]^-3