From cba84e8a0d2282ea038acf14143bf2bfaf5c4f2f Mon Sep 17 00:00:00 2001
From: Friedrich Rober <37139927+FriedrichRober@users.noreply.github.com>
Date: Wed, 20 Dec 2023 09:26:05 +0100
Subject: [PATCH] Add `LowIndexNormalSubs` operation (#59)
---
.github/workflows/CI.yml | 1 +
.gitignore | 4 +-
dev/tests_doc/README.md | 9 ++
dev/tests_doc/processTests.sh | 17 +++
doc/intro.xml | 108 ++--------------
doc/lins_interface.xml | 120 +++++++++++++++++-
gap/LINS.gd | 18 +++
gap/LINS.gi | 19 ++-
makedoc.g | 3 +
.../Operation/DihedralGroup_20_10_Fp.tst | 14 ++
.../Operation/DihedralGroup_20_10_Pc.tst | 14 ++
.../Operation/DihedralGroup_20_10_Perm.tst | 16 +++
12 files changed, 246 insertions(+), 97 deletions(-)
create mode 100644 dev/tests_doc/README.md
create mode 100755 dev/tests_doc/processTests.sh
create mode 100644 tst/files/quick/Operation/DihedralGroup_20_10_Fp.tst
create mode 100644 tst/files/quick/Operation/DihedralGroup_20_10_Pc.tst
create mode 100644 tst/files/quick/Operation/DihedralGroup_20_10_Perm.tst
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index d93ae12..919f109 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -34,6 +34,7 @@ jobs:
GAPBRANCH: ${{ matrix.gap-branch }}
GAP_PKGS_TO_BUILD: "io profiling grape cohomolo"
- uses: gap-actions/build-pkg@v1
+ - uses: gap-actions/build-pkg-docs@v1
- uses: gap-actions/run-pkg-tests@v2
- uses: gap-actions/process-coverage@v2
- uses: codecov/codecov-action@v3
diff --git a/.gitignore b/.gitignore
index 950d0ab..8bec6f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,4 +29,6 @@
/gh-pages/
-_dev
\ No newline at end of file
+_dev
+
+/tst/files/doc/
diff --git a/dev/tests_doc/README.md b/dev/tests_doc/README.md
new file mode 100644
index 0000000..056c6ef
--- /dev/null
+++ b/dev/tests_doc/README.md
@@ -0,0 +1,9 @@
+# Introduction
+The files in this directory are used to post-process test files
+that are extracted from the documentation examples.
+
+# Main Files
+- `processTests.sh` : processes all doc tests and moves them into `tst/files/doc`.
+
+# Instructions
+In order to post-process the tests and move them into `tst/files/doc` automatically, one needs to execute `processTests.sh` from any place.
diff --git a/dev/tests_doc/processTests.sh b/dev/tests_doc/processTests.sh
new file mode 100755
index 0000000..d15b82a
--- /dev/null
+++ b/dev/tests_doc/processTests.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# go to root of repo
+script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+cd $script_dir/../..
+echo "Working in folder $(pwd)"
+
+# Post-processing for the extracted examples from the documentation.
+# - Move files into test_dir
+test_dir="tst/files/doc"
+mkdir -p $test_dir
+files=($(ls -1 tst/lins*.tst))
+echo "Found ${#files[@]} test file(s)"
+for file in ${files[@]}; do
+ echo "Processing $file"
+ mv $file $test_dir/${file#"tst/"}
+done
diff --git a/doc/intro.xml b/doc/intro.xml
index 6b6633d..a171197 100644
--- a/doc/intro.xml
+++ b/doc/intro.xml
@@ -22,7 +22,7 @@ The current implementation in &GAP; uses a table of groups that was computed by
Examples
In this section we present example sessions which demonstrate
-how to use the search methods provided by &LINS;.
+how to use the main high-level functions provided by &LINS;.
Example : all normal subgroups up to index n
@@ -30,37 +30,10 @@ how to use the search methods provided by &LINS;.
We compute all normal subgroups in D_{50},
the dihedral group of size 50.
n := 50;
-gap> G := DihedralGroup(n);
+gap> G := DihedralGroup(50);
-]]>
-
-The search algorithm automatically translates the group into a finitely presented group
-via a call to IsomorphismFpGroup.
-The isomorphism is stored inside the lins graph.
- gr := LowIndexNormalSubgroupsSearchForAll(G, n);
-
-gap> r := LinsRoot(gr);
-
-gap> H := Grp(r);
-
-gap> Iso := IsomorphismFpGroup(gr);
-[ f1, f2, f3 ] -> [ F1, F2, F3 ]
-gap> Source(Iso) = G;
-true
-gap> Range(Iso) = H;
-true
-]]>
-
-In order to get all nodes from the search graph, we need to use List.
-As expected, the algorithm finds D_{50}, C_{25}, C_5
-and the trivial group.
- L := List(gr);
-[ , , ,
- ]
-gap> IsoTypes := List(L, node -> StructureDescription(Grp(node)));
+gap> L := LowIndexNormalSubs(G, 50);;
+gap> IsoTypes := List(L, StructureDescription);
[ "D50", "C25", "C5", "1" ]
]]>
@@ -72,77 +45,24 @@ gap> IsoTypes := List(L, node -> StructureDescription(Grp(node)));
We compute all normal subgroups of index 5^2 = 25 in C_5^4,
the direct product of 4 copies of the cyclic group of order 5:
p := 5;;
-gap> d := 4;;
-gap> C := CyclicGroup(5);;
-gap> G := DirectProduct(ListWithIdenticalEntries(d, C));
+gap> G := ElementaryAbelianGroup(5^4);
-]]>
-
-Again, the search algorithm automatically translates the group into a finitely presented group
-via a call to IsomorphismFpGroup.
- gr := LowIndexNormalSubgroupsSearchForIndex(G, 5 ^ 2, infinity);
-
-]]>
-
-Now we are not interested in all normal subgroups that the search graph considered,
-but only in those of index 25. Thus we need to use ComputedNormalSubgroups.
-For a prime p, and integers d, s \in \mathbb{N},
-the number of subgroups of order p^s of an elementary abelian p-group of order p^d
-is exactly
-
-\frac
-{\left(p^d - 1\right)\left(p^d - p\right) \cdots \left(p^d - p^{s-1}\right)}
-{\left(p^s - 1\right)\left(p^s - p\right) \cdots \left(p^s - p^{s-1}\right)}\;.
-
-
-( (p^d - 1)(p^d - p) \cdots (p^d - p^{(s-1)}) ) / ( (p^s - 1)(p^s - p) \cdots (p^s - p^{(s-1)}) ) .
-
-Thus we expect to find
-\frac{(5^4-1) \cdot (5^4-5)}{(5^2 - 1) \cdot (5^2 - 5)} = 806
-( (5^4-1) \cdot (5^4-5) ) / ( (5^2 - 1) \cdot (5^2 - 5) ) = 806
-normal subgroups of index 25.
-Furthermore, all subgroups need to be of the isomorphism type C_5^2.
- L := ComputedNormalSubgroups(gr);;
-gap> Length(L);
-806
-gap> IsoTypes := DuplicateFreeList(List(L, node -> StructureDescription(Grp(node))));
-[ "C5 x C5" ]
+gap> L := LowIndexNormalSubs(G, 5 ^ 2 : allSubgroups := false);;
+gap> IsoTypes := Collected(List(L, StructureDescription));
+[ [ "C5 x C5", 806 ] ]
]]>
-
-Example : a normal subgroup of index n
+
-We compute a normal subgroup of index 3 \cdot 5 = 15 in
-C_3 \times C_3 \times C_4 \times C_5,
-a direct product of cyclic groups:
- pList := [3, 3, 4, 5];;
-gap> G := DirectProduct(List(pList, p -> CyclicGroup(p)));
-
-]]>
+
+Main Functions
-Again, the search algorithm automatically translates the group into a finitely presented group
-via a call to IsomorphismFpGroup.
- gr := LowIndexNormalSubgroupsSearchForIndex(G, 15, 1);
-
-]]>
+In this section, we include all the main high-level functions provided to the user.
+For advanced search methods in the lattice of normal subgroups, take a look at Chapter .
-We use ComputedNormalSubgroups in order to get the normal subgroup of index 15.
-As expected, the algorithm finds a group of the isomorphism type C_{12} = C_3 \times C_4.
- L := ComputedNormalSubgroups(gr);
-[ ]
-gap> IsoTypes := List(L, node -> StructureDescription(Grp(node)));
-[ "C12" ]
-]]>
-
-
+<#Include Label="LowIndexNormalSubs">
diff --git a/doc/lins_interface.xml b/doc/lins_interface.xml
index f41a6cd..c937438 100644
--- a/doc/lins_interface.xml
+++ b/doc/lins_interface.xml
@@ -1,7 +1,8 @@
LINS Interface
-This chapter explains the provided search methods
+This chapter is intended for advanced users.
+It explains the provided search methods
and the interface to the search graph structure LinsGraph.
@@ -80,4 +81,121 @@ Returns the index [G : H].
+
+Examples
+
+In this section we present example sessions which demonstrate
+how to use the advanced search methods provided by &LINS;.
+For this we revise the examples from the introduction as well as include new ones.
+
+
+Revised Example : all normal subgroups up to index n
+
+We compute all normal subgroups in D_{50},
+the dihedral group of size 50.
+ G := DihedralGroup(50);
+
+]]>
+
+The search algorithm automatically translates the group into a finitely presented group
+via a call to IsomorphismFpGroup.
+The isomorphism is stored inside the lins graph.
+ gr := LowIndexNormalSubgroupsSearchForAll(G, 50);
+
+gap> r := LinsRoot(gr);
+
+gap> H := Grp(r);
+
+gap> Iso := IsomorphismFpGroup(gr);
+[ f1, f2, f3 ] -> [ F1, F2, F3 ]
+gap> Source(Iso) = G;
+true
+gap> Range(Iso) = H;
+true
+]]>
+
+In order to get all nodes from the search graph, we need to use List.
+As expected, the algorithm finds D_{50}, C_{25}, C_5
+and the trivial group.
+ L := List(gr);
+[ , , ,
+ ]
+gap> IsoTypes := List(L, node -> StructureDescription(Grp(node)));
+[ "D50", "C25", "C5", "1" ]
+]]>
+
+
+
+
+Revised Example : all normal subgroups of index n
+
+We compute all normal subgroups of index 5^2 = 25 in C_5^4,
+the direct product of 4 copies of the cyclic group of order 5:
+ G := ElementaryAbelianGroup(5^4);
+
+]]>
+
+Again, the search algorithm automatically translates the group into a finitely presented group
+via a call to IsomorphismFpGroup.
+ gr := LowIndexNormalSubgroupsSearchForIndex(G, 5 ^ 2, infinity);
+
+]]>
+
+Now we are not interested in all normal subgroups that the search graph considered,
+but only in those of index 25. Thus we need to use ComputedNormalSubgroups.
+For a prime p, and integers d, s \in \mathbb{N},
+the number of subgroups of order p^s of an elementary abelian p-group of order p^d
+is exactly
+
+\frac
+{\left(p^d - 1\right)\left(p^d - p\right) \cdots \left(p^d - p^{s-1}\right)}
+{\left(p^s - 1\right)\left(p^s - p\right) \cdots \left(p^s - p^{s-1}\right)}\;.
+
+
+( (p^d - 1)(p^d - p) \cdots (p^d - p^{(s-1)}) ) / ( (p^s - 1)(p^s - p) \cdots (p^s - p^{(s-1)}) ) .
+
+Thus we expect to find
+\frac{(5^4-1) \cdot (5^4-5)}{(5^2 - 1) \cdot (5^2 - 5)} = 806
+( (5^4-1) \cdot (5^4-5) ) / ( (5^2 - 1) \cdot (5^2 - 5) ) = 806
+normal subgroups of index 25.
+Furthermore, all subgroups need to be of the isomorphism type C_5^2.
+ L := ComputedNormalSubgroups(gr);;
+gap> IsoTypes := Collected(List(L, node -> StructureDescription(Grp(node))));
+[ [ "C5 x C5", 806 ] ]
+]]>
+
+
+
+
+Example : a normal subgroup of index n
+
+We compute a normal subgroup of index 3 \cdot 5 = 15 in
+C_3 \times C_3 \times C_4 \times C_5,
+a direct product of cyclic groups:
+ G := AbelianGroup([3, 3, 4, 5]);
+
+gap> gr := LowIndexNormalSubgroupsSearchForIndex(G, 15, 1);
+
+]]>
+
+We use ComputedNormalSubgroups in order to get the normal subgroup of index 15.
+As expected, the algorithm finds a group of the isomorphism type C_{12} = C_3 \times C_4.
+ L := ComputedNormalSubgroups(gr);
+[ ]
+gap> IsoTypes := List(L, node -> StructureDescription(Grp(node)));
+[ "C12" ]
+]]>
+
+
+
+
+
diff --git a/gap/LINS.gd b/gap/LINS.gd
index 6df2e00..6f510a1 100644
--- a/gap/LINS.gd
+++ b/gap/LINS.gd
@@ -175,6 +175,24 @@ DeclareAttribute( "LinsOptions", IsLinsGraph, "mutable" );
## Main functions
#############################################################################
+## <#GAPDoc Label="LowIndexNormalSubs">
+##
+##
+##
+## Returns a list of all normal subgroups of G with index at most n.
+## If the option allSubgroups is set to false,
+## then onlye the normal subgroups of G with index equal to n are returned.
+##
+## The generic method uses to transform G into an fp-group
+## and then calls some variant of the low-level function .
+##
+## Note that a similar operation exists in the package polycyclic.
+## Due to technical incompabilities, those operations could not be unified.
+##
+##
+## <#/GAPDoc>
+DeclareOperation( "LowIndexNormalSubs", [IsGroup, IsPosInt] );
+
## <#GAPDoc Label="LowIndexNormalSubgroupsSearch">
##
##
diff --git a/gap/LINS.gi b/gap/LINS.gi
index f5e9ad2..6aaf9e2 100644
--- a/gap/LINS.gi
+++ b/gap/LINS.gi
@@ -142,7 +142,7 @@ end);
InstallMethod( ViewObj, "for Lins Graph Node", [IsLinsGraph],
function(gr)
- Print("");
+ Print("");
end);
@@ -464,3 +464,20 @@ end);
InstallGlobalFunction( LowIndexNormalSubgroupsSearchForAll, function(G, n)
return LowIndexNormalSubgroupsSearch(G, n);
end);
+
+InstallMethod( LowIndexNormalSubs, "for groups",
+ [IsGroup, IsPosInt],
+function(G, n)
+ local allSubgroups, gr, iso;
+ allSubgroups := ValueOption("allSubgroups");
+ if allSubgroups = fail then
+ allSubgroups := true;
+ fi;
+ if allSubgroups then
+ gr := LowIndexNormalSubgroupsSearchForAll(G, n);
+ else
+ gr := LowIndexNormalSubgroupsSearchForIndex(G, n, infinity);
+ fi;
+ iso := IsomorphismFpGroup(gr);
+ return List(ComputedNormalSubgroups(gr), rH -> PreImage(iso, Grp(rH)));
+end);
diff --git a/makedoc.g b/makedoc.g
index 4588a7c..fe4d4bb 100644
--- a/makedoc.g
+++ b/makedoc.g
@@ -25,4 +25,7 @@ AutoDoc( rec( scaffold := rec(
"license.xml",
],
),
+ extract_examples := true,
autodoc := true ) );
+
+Exec("dev/tests_doc/processTests.sh");
diff --git a/tst/files/quick/Operation/DihedralGroup_20_10_Fp.tst b/tst/files/quick/Operation/DihedralGroup_20_10_Fp.tst
new file mode 100644
index 0000000..b9887fe
--- /dev/null
+++ b/tst/files/quick/Operation/DihedralGroup_20_10_Fp.tst
@@ -0,0 +1,14 @@
+#############################################################################
+# G = D_20
+# index = 10
+# fp-group
+#############################################################################
+
+gap> G := DihedralGroup(IsFpGroup, 20);
+
+gap> L := LowIndexNormalSubs(G, 10);
+[ , Group([ r ]),
+ Group([ r^-2, s ]), Group([ r^-2, s*r^-1 ]), Group([ r^-2 ]),
+ Group([ r^5 ]) ]
+gap> List(L, H -> Index(G, H));
+[ 1, 2, 2, 2, 4, 10 ]
diff --git a/tst/files/quick/Operation/DihedralGroup_20_10_Pc.tst b/tst/files/quick/Operation/DihedralGroup_20_10_Pc.tst
new file mode 100644
index 0000000..54fa9ad
--- /dev/null
+++ b/tst/files/quick/Operation/DihedralGroup_20_10_Pc.tst
@@ -0,0 +1,14 @@
+#############################################################################
+# G = D_20
+# index = 10
+# pc-group
+#############################################################################
+
+gap> G := DihedralGroup(20);
+
+gap> L := LowIndexNormalSubs(G, 10);
+[ Group([ f1, f2, f3 ]), Group([ f1, f3^4 ]), Group([ f2 ]),
+ Group([ f1*f2*f3^4, f1*f2 ]), Group([ f3^4 ]),
+ Group([ of ..., f2*f3^2 ]) ]
+gap> List(L, H -> Index(G, H));
+[ 1, 2, 2, 2, 4, 10 ]
diff --git a/tst/files/quick/Operation/DihedralGroup_20_10_Perm.tst b/tst/files/quick/Operation/DihedralGroup_20_10_Perm.tst
new file mode 100644
index 0000000..95d0321
--- /dev/null
+++ b/tst/files/quick/Operation/DihedralGroup_20_10_Perm.tst
@@ -0,0 +1,16 @@
+#############################################################################
+# G = D_20
+# index = 10
+# perm-group
+#############################################################################
+
+gap> G := DihedralGroup(IsPermGroup, 20);
+Group([ (1,2,3,4,5,6,7,8,9,10), (2,10)(3,9)(4,8)(5,7) ])
+gap> L := LowIndexNormalSubs(G, 10);
+[ Group([ (2,10)(3,9)(4,8)(5,7), (1,6)(2,7)(3,8)(4,9)(5,10), (1,7,3,9,5)
+ (2,8,4,10,6) ]), Group([ (2,10)(3,9)(4,8)(5,7), (1,7,3,9,5)(2,8,4,10,6)
+ ]), Group([ (1,6)(2,7)(3,8)(4,9)(5,10), (1,7,3,9,5)(2,8,4,10,6) ]),
+ Group([ (1,6)(2,5)(3,4)(7,10)(8,9), (1,7,3,9,5)(2,8,4,10,6) ]),
+ Group([ (1,7,3,9,5)(2,8,4,10,6) ]), Group([ (1,6)(2,7)(3,8)(4,9)(5,10) ]) ]
+gap> List(L, H -> Index(G, H));
+[ 1, 2, 2, 2, 4, 10 ]