Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve describe(::GAPGroup) and other group related changes #1137

Merged
merged 7 commits into from
Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/src/Groups/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ one(x::GAPGroup)
one(x::GAPGroupElem)
isfiniteorder(x::GAPGroupElem)
gens(::GAPGroup)
hasgens(::GAPGroup)
ngens(G::GAPGroup)
gen(::GAPGroup, i::Int)
Base.rand(G::GAPGroup)
Expand All @@ -45,7 +46,7 @@ f1=G[1]; f2=G[2]; f3=G[3];
For a group `G` that has been created as a subgroup of another group,
generated by a list `L` of elements, `gens(G)` is equal to `L`.

## Operations
## Operations on group elements

Oscar supports the following operations and functions on group elements.

Expand All @@ -67,7 +68,6 @@ Oscar supports the following operations and functions on group elements.

```@docs
comm(x::GAPGroupElem, y::GAPGroupElem)
nilpotency_class(G::GAPGroup)
```

## Properties of groups
Expand All @@ -82,6 +82,7 @@ issolvable
isperfect
issimple
isalmostsimple
isfinitelygenerated
```


Expand All @@ -91,4 +92,5 @@ isalmostsimple
order(::Type{T}, x::Union{GAPGroupElem, GAPGroup}) where T <: IntegerUnion
exponent(x::GAPGroup)
describe(x::GAPGroup)
nilpotency_class(G::GAPGroup)
```
1 change: 1 addition & 0 deletions docs/src/Groups/grouphom.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,5 @@ preimage(f::GAPGroupHomomorphism{S, T}, H::T) where S <: GAPGroup where T <: GAP
isomorphic_perm_group(G::GAPGroup)
isomorphic_pc_group(G::GAPGroup)
isomorphic_fp_group(G::GAPGroup)
simplified_fp_group(G::FPGroup)
```
210 changes: 175 additions & 35 deletions src/Groups/GAPGroups.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ export
frattini_subgroup, hasfrattini_subgroup, setfrattini_subgroup,
gap_perm, # HACK
gen,
gens,
gens, hasgens,
hall_subgroup,
hall_subgroups_representatives,
hall_system, hashall_system, sethall_system,
inv!,
isalmostsimple, hasisalmostsimple, setisalmostsimple,
isconjugate,
isfinite, hasisfinite, setisfinite,
isfinitelygenerated, hasisfinitelygenerated, setisfinitelygenerated,
isfiniteorder,
isperfect, hasisperfect, setisperfect,
ispgroup,
Expand Down Expand Up @@ -368,6 +369,28 @@ function gens(G::GAPGroup)
return res
end

"""
hasgens(G::Group)

Return whether generators for the group `G` are known.

# Examples
```jldoctest
julia> F = free_group(2)
<free group on the generators [ f1, f2 ]>

julia> hasgens(F)
true

julia> H = derived_subgroup(F)[1]
Group(<free, no generators known>)

julia> hasgens(H)
false
```
"""
hasgens(G::GAPGroup) = GAP.Globals.HasGeneratorsOfGroup(G.X)::Bool

"""
gen(G::GAPGroup, i::Int)

Expand Down Expand Up @@ -889,6 +912,29 @@ function ispgroup(G::GAPGroup)
return false, nothing
end

"""
isfinitelygenerated(G)

Return whether `G` is a finitely generated group.

# Examples
```jldoctest
julia> F = free_group(2)
<free group on the generators [ f1, f2 ]>

julia> isfinitelygenerated(F)
true

julia> H = derived_subgroup(F)[1]
Group(<free, no generators known>)

julia> isfinitelygenerated(H)
false
```
"""
@gapattribute isfinitelygenerated(G::GAPGroup) = GAP.Globals.IsFinitelyGeneratedGroup(G.X)::Bool


@doc Markdown.doc"""
relators(G::FPGroup)

Expand Down Expand Up @@ -920,45 +966,40 @@ end

Return a string that describes some aspects of the structure of `G`.

This works well if `G` is an abelian group or a finite simple group
or a group in one of the following series:
symmetric, dihedral, quasidihedral, generalized quaternion,
general linear, special linear.

For other groups, the function tries to decompose `G` as a direct product
or a semidirect product,
and if this is not possible as a non-splitting extension of
a normal subgroup $N$ with the factor group `G`$/N$,
where $N$ is the center or the derived subgroup or the Frattini subgroup
of `G`.

Note that
- there is in general no "nice" decomposition of `G`,
- there may be several decompositions of `G`,
- nonisomorphic groups may yield the same `describe` result,
- isomorphic groups may yield different `describe` results,
- the computations can take a long time (for example in the case of
large $p$-groups), and the results are still often not very helpful.
For finite groups, the function works well if `G` is an abelian group or a
finite simple group or a group in one of the following series: symmetric,
dihedral, quasidihedral, generalized quaternion, general linear, special
linear.

# Examples
```jldoctest
julia> g = symmetric_group(6);

julia> describe(g)
"S6"
For other finite groups, the function tries to decompose `G` as a direct
product or a semidirect product, and if this is not possible as a
non-splitting extension of a normal subgroup $N$ with the factor group
`G`$/N$, where $N$ is the center or the derived subgroup or the Frattini
subgroup of `G`.

julia> describe(sylow_subgroup(g,2)[1])
"C2 x D8"
For infinite groups, if the group is known to be finitely generated and
abelian or free, a reasonable description is printed.

julia> describe(sylow_subgroup(g, 3)[1])
"C3 x C3"
For general infinite groups, or groups for which finiteness is not (yet) known,
not much if anything can be done. In particular we avoid potentially expensive
checks such as computing the size of the group or whether it is abelian.
While we do attempt a few limited fast checks for finiteness and
commutativity, these will not detect all finite or commutative groups.

julia> g = symmetric_group(10);
Thus calling `describe` again on the same group after additional information
about it becomes known to Oscar may yield different outputs.

julia> describe(sylow_subgroup(g,2)[1])
"C2 x ((((C2 x C2 x C2 x C2) : C2) : C2) : C2)"
!!! note
- for finitely presented groups, even deciding if the group is trivial
is impossible in general; the same holds for most other properties,
like whether the group is finite, abelian, etc.,
- there is in general no "nice" decomposition of `G`,
- there may be several decompositions of `G`,
- nonisomorphic groups may yield the same `describe` result,
- isomorphic groups may yield different `describe` results,
- the computations can take a long time (for example in the case of
large $p$-groups), and the results are still often not very helpful.

```
The following notation is used in the returned string.

| Description | Syntax |
Expand Down Expand Up @@ -989,5 +1030,104 @@ The following notation is used in the returned string.
| direct product | A x B |
| semidirect product | N : H |
| non-split extension | Z(G) . G/Z(G) = G' . G/G', Phi(G) . G/Phi(G) |

# Examples
```jldoctest
julia> g = symmetric_group(6);

julia> describe(g)
"S6"

julia> describe(sylow_subgroup(g,2)[1])
"C2 x D8"

julia> describe(sylow_subgroup(g, 3)[1])
"C3 x C3"

julia> g = symmetric_group(10);

julia> describe(sylow_subgroup(g,2)[1])
"C2 x ((((C2 x C2 x C2 x C2) : C2) : C2) : C2)"

```
"""
describe(G::GAPGroup) = String(GAP.Globals.StructureDescription(G.X))
function describe(G::GAPGroup)
isfinitelygenerated(G) || return "a non-finitely generated group"

# handle groups whose finiteness is known
if hasisfinite(G)
# finite groups: pass them to GAP
if isfinite(G)
return String(GAP.Globals.StructureDescription(G.X)::GapObj)
end

# infinite groups known to be abelian can still be dealt with by GAP
if hasisabelian(G) && isabelian(G)
return String(GAP.Globals.StructureDescription(G.X)::GapObj)
end

return "an infinite group"
end

return "a group"
end

function describe(G::FPGroup)
# despite the name, there are non-finitely generated (and hence non-finitely presented)
# FPGroup instances
isfinitelygenerated(G) || return "a non-finitely generated group"

if GAP.Globals.IsFreeGroup(G.X)::Bool
r = GAP.Globals.RankOfFreeGroup(G.X)::GapInt
r >= 2 && return "a free group of rank $(r)"
r == 1 && return "Z"
r == 0 && return "1"
end

# check for free groups in disguise
isempty(relators(G)) && return describe(free_group(G))

# attempt to simplify presentation
H = simplified_fp_group(G)[1]
ngens(H) < ngens(G) && return describe(H)

# abelian groups can be dealt with by GAP
extra = ""
if !hasisabelian(G)
if isobviouslyabelian(G)
setisabelian(G, true) # TODO: Claus won't like this...
return String(GAP.Globals.StructureDescription(G.X)::GapObj)
end
elseif isabelian(G)
return String(GAP.Globals.StructureDescription(G.X)::GapObj)
else
extra *= " non-abelian"
end

if !hasisfinite(G)
# try to obtain an isomorphic permutation group, but don't try too hard
iso = GAP.Globals.IsomorphismPermGroupOrFailFpGroup(G.X, 100000)::GapObj
iso != GAP.Globals.fail && return describe(PermGroup(GAP.Globals.Range(iso)))
elseif isfinite(G)
return describe(isomorphic_perm_group(G)[1])
else
extra *= " infinite"
end

return "a finitely presented$(extra) group"

end

function isobviouslyabelian(G::FPGroup)
rels = relators(G)
fgens = gens(free_group(G))
signs = [(e1,e2,e3) for e1 in (-1,1) for e2 in (-1,1) for e3 in (-1,1)]
for i in 1:length(fgens)
a = fgens[i]
for j in i+1:length(fgens)
b = fgens[j]
any(t -> comm(a^t[1],b^t[2])^t[3] in rels, signs) || return false
end
end
return true
end
30 changes: 30 additions & 0 deletions src/Groups/homomorphisms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export
order,
restrict_automorphism,
restrict_homomorphism,
simplified_fp_group,
trivial_morphism

function Base.show(io::IO, x::GAPGroupHomomorphism)
Expand Down Expand Up @@ -396,6 +397,35 @@ function isomorphic_fp_group(G::GAPGroup)
return H, GAPGroupHomomorphism(G,H,f)
end

"""
simplified_fp_group(G::FPGroup)

Return a group `H` of type `FPGroup` and an isomorphism `f` from `G` to `H`, where
the presentation of `H` was obtained from the presentation of `G` by applying Tietze
transformations in order to reduce it with respect to the number of
generators, the number of relators, and the relator lengths.

# Examples
```jldoctest
julia> F = free_group(3)
<free group on the generators [ f1, f2, f3 ]>

julia> G = quo(F, [gen(F,1)])[1]
<fp group of size infinity on the generators [ f1, f2, f3 ]>

julia> simplified_fp_group(G)[1]
<fp group of size infinity on the generators [ f2, f3 ]>
```
"""
function simplified_fp_group(G::FPGroup)
f = GAP.Globals.IsomorphismSimplifiedFpGroup(G.X)
H = FPGroup(GAP.Globals.Image(f))
# TODO: remove the next line once https://github.com/gap-system/gap/pull/4810
# is deployed to Oscar
GAP.Globals.UseIsomorphismRelation(G.X, H.X)
return H, GAPGroupHomomorphism(G,H,f)
end


################################################################################
#
Expand Down
1 change: 1 addition & 0 deletions src/imports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ import AbstractAlgebra:

import AbstractAlgebra.GroupsCore
import AbstractAlgebra.GroupsCore:
hasgens,
isfiniteorder,
istrivial

Expand Down
Loading