diff --git a/doc/manualbib.xml b/doc/manualbib.xml
index 810e115dc8d..15fb7fe24f1 100644
--- a/doc/manualbib.xml
+++ b/doc/manualbib.xml
@@ -2233,6 +2233,34 @@
225
+
+
+ PhilipHall
+
+ A contribution to the theory of groups of prime-power order
+ Proceedings of the London Mathematical Society
+ 1934
+ s2-36
+ 1
+ 29–95
+ https://doi.org/10.1112/plms/s2-36.1.29
+ 10.1112/plms/s2-36.1.29
+
+
+
+
+ PhilipHall
+
+ On a Theorem of Frobenius
+ Proceedings of the London Mathematical Society
+ 1936
+ s2-40
+ 1
+ 468–501
+ https://doi.org/10.1112/plms/s2-40.1.468
+ 10.1112/plms/s2-40.1.468
+
+
PhilipHall
diff --git a/doc/ref/groups.xml b/doc/ref/groups.xml
index f6a84d264e9..6638a8b9ad3 100644
--- a/doc/ref/groups.xml
+++ b/doc/ref/groups.xml
@@ -351,6 +351,7 @@ as they depend on a parameter.
<#Include Label="IsSubsetLocallyFiniteGroup">
<#Include Label="IsPGroup">
<#Include Label="IsPowerfulPGroup">
+<#Include Label="IsRegularPGroup">
<#Include Label="PrimePGroup">
<#Include Label="PClassPGroup">
<#Include Label="RankPGroup">
diff --git a/lib/grp.gd b/lib/grp.gd
index 175fe203c5e..37de1ac8aec 100644
--- a/lib/grp.gd
+++ b/lib/grp.gd
@@ -439,11 +439,8 @@ DeclareOperation( "KnowsHowToDecompose", [ IsGroup, IsList ] );
DeclareProperty( "IsPGroup", IsGroup );
InstallTrueMethod( IsGroup, IsPGroup );
-InstallSubsetMaintenance( IsPGroup,
- IsGroup and IsPGroup, IsGroup );
-
-InstallFactorMaintenance( IsPGroup,
- IsGroup and IsPGroup, IsObject, IsGroup );
+InstallSubsetMaintenance( IsPGroup, IsPGroup, IsGroup );
+InstallFactorMaintenance( IsPGroup, IsPGroup, IsObject, IsGroup );
InstallTrueMethod( IsPGroup, IsGroup and IsTrivial );
InstallTrueMethod( IsPGroup, IsGroup and IsElementaryAbelian );
@@ -458,7 +455,7 @@ InstallTrueMethod( IsPGroup, IsGroup and IsElementaryAbelian );
##
##
## Powerful p-group
-## A finite p-group G is said to be a powerful p-group
+## A finite p-group G is said to be a powerful p-group
## if the commutator subgroup [G,G] is contained in
## G^{p} if the prime p is odd, or if
## [G,G] is contained in G^{4}
@@ -475,12 +472,42 @@ InstallTrueMethod( IsPGroup, IsGroup and IsElementaryAbelian );
DeclareProperty( "IsPowerfulPGroup", IsGroup );
InstallTrueMethod( IsPGroup, IsPowerfulPGroup );
-#Quotients of powerful of powerful p groups are powerful
+#Quotients of powerful p-groups are powerful
InstallFactorMaintenance( IsPowerfulPGroup,
IsPowerfulPGroup, IsGroup, IsGroup );
#abelian p-groups are powerful
InstallTrueMethod( IsPowerfulPGroup, IsFinite and IsPGroup and IsAbelian );
-InstallTrueMethod( IsPGroup, IsPowerfulPGroup );
+
+#############################################################################
+##
+#P IsRegularPGroup( ) . . . . . . . . . . is a group a regular p-group ?
+##
+## <#GAPDoc Label="IsRegularPGroup">
+##
+##
+##
+##
+## Regular p-group
+## A finite p-group G is said to be a regular p-group
+## if for all a,b in G, one has a^p b^p = (ab^p) c^p
+## where c is an element of the derived subgroup of the group generated
+## by a and b (see ).
+## returns true if G is a
+## regular p-group, and false otherwise.
+## Note: This function returns true if G is the trivial
+## group.
+##
+##
+## <#/GAPDoc>
+##
+DeclareProperty( "IsRegularPGroup", IsGroup );
+InstallTrueMethod( IsPGroup, IsRegularPGroup );
+
+InstallSubsetMaintenance( IsRegularPGroup, IsRegularPGroup, IsGroup );
+InstallFactorMaintenance( IsPGroup, IsRegularPGroup, IsObject, IsGroup );
+
+#abelian p-groups are regular
+InstallTrueMethod( IsRegularPGroup, IsFinite and IsPGroup and IsAbelian );
#############################################################################
##
diff --git a/lib/grp.gi b/lib/grp.gi
index 822fc4ab108..e308e311b62 100644
--- a/lib/grp.gi
+++ b/lib/grp.gi
@@ -428,6 +428,93 @@ InstallMethod( IsPowerfulPGroup,
end);
+#############################################################################
+##
+#M IsRegularPGroup( ) . . . . . . . . . . is a group a regular p-group ?
+##
+InstallMethod( IsRegularPGroup,
+ [ IsGroup ],
+function( G )
+local p, hom, reps, a, b, ap_bp, ab_p, H;
+
+ if not IsPGroup(G) then
+ return false;
+ fi;
+
+ p:=PrimePGroup(G);
+ if p = 2 then
+ # see [Hup67, Satz 10.3 a)]
+ return IsAbelian(G);
+ elif p = 3 and DerivedLength(G) > 2 then
+ # see [Hup67, Satz 10.3 b)]
+ return false;
+ elif Size(G) <= p^p then
+ # see [Hal34, Corollary 14.14], [Hall, p. 183], [Hup67, Satz 10.2 b)]
+ return true;
+ elif NilpotencyClassOfGroup(G) < p then
+ # see [Hal34, Corollary 14.13], [Hall, p. 183], [Hup67, Satz 10.2 a)]
+ return true;
+ elif IsCyclic(DerivedSubgroup(G)) then
+ # see [Hup67, Satz 10.2 c)]
+ return true;
+ elif Exponent(G) = p then
+ # see [Hup67, Satz 10.2 d)]
+ return true;
+ elif p = 3 and RankPGroup(G) = 2 then
+ # see [Hup67, Satz 10.3 b)]: at this point we know that the derived
+ # subgroup is not cyclic, hence G is not regular
+ return false;
+ elif Size(G) < p^p * Size(Agemo(G,p)) then
+ # see [Hal36, Theorem 2.3], [Hup67, Satz 10.13]
+ return true;
+ elif Index(DerivedSubgroup(G),Agemo(DerivedSubgroup(G),p)) < p^(p-1) then
+ # see [Hal36, Theorem 2.3], [Hup67, Satz 10.13]
+ return true;
+ fi;
+
+ # Fallback to actually check the defining criterion, i.e.:
+ # for all a,b in G, we must have that a^p*b^p/(a*b)^p in (')^p
+
+ # It suffices to pick 'a' among conjugacy class representatives.
+ # Moreover, if 'a' is central then the criterion automatically holds.
+ # For z,z'\in Z(G), if the criterion holds for (a,b) iff it holds for (az,bz').
+ # We thus choose 'a' among lifts of conjugacy class representatives in G/Z(G).
+ hom := NaturalHomomorphismByNormalSubgroup(G, Center(G));
+ reps := ConjugacyClasses(Image(hom));
+ reps := List(reps, Representative);
+ reps := Filtered(reps, g -> not IsOne(g));
+ reps := List(reps, g -> PreImagesRepresentative(hom, g));
+
+ for b in Image(hom) do
+ b := PreImagesRepresentative(hom, b);
+ for a in reps do
+ # if a and b commute the regularity condition automatically holds
+ if a*b = b*a then continue; fi;
+
+ # regularity is also automatic if a^p * b^p = (a*b)^p
+ ap_bp := a^p * b^p;
+ ab_p := (a*b)^p;
+ if ap_bp = ab_p then continue; fi;
+
+ # if the subgroup generated H by a and b is itself regular, we are also
+ # done. However we don't use recursion, here, as H may be equal to G;
+ # and also we have to be careful to not be use too expensive code here.
+ # But a quick size check is certainly fine.
+ H := Subgroup(G, [a,b]);
+ if Size(H) <= p^p then continue; fi;
+
+ # finally the full check
+ H := DerivedSubgroup(H);
+ if not (ap_bp / ab_p) in Agemo(H, p) then
+ return false;
+ fi;
+ od;
+ od;
+ return true;
+
+end);
+
+
#############################################################################
##
#M PrimePGroup . . . . . . . . . . . . . . . . . . . . . prime of a p-group
diff --git a/tst/testinstall/pgroups.tst b/tst/testinstall/pgroups.tst
index ce83000bfb6..78d7edbe252 100644
--- a/tst/testinstall/pgroups.tst
+++ b/tst/testinstall/pgroups.tst
@@ -168,19 +168,35 @@ true
gap> JenningsSeries(CyclicGroup(4));
[ , Group([ f2 ]),
Group([ of ... ]) ]
+
+#
gap> G:=CyclicGroup(9);;
gap> HasIsPowerfulPGroup(G);
true
gap> IsPowerfulPGroup(G);
true
+gap> HasIsRegularPGroup(G);
+true
+gap> IsRegularPGroup(G);
+true
+
+#
gap> G:=CyclicGroup(10);;
gap> IsPowerfulPGroup(G);
false
+gap> IsRegularPGroup(G);
+false
+
+#
gap> G:=SmallGroup(243,11);;
gap> HasIsPowerfulPGroup(G);
false
gap> IsPowerfulPGroup(G);
true
+gap> HasIsRegularPGroup(G);
+false
+gap> IsRegularPGroup(G);
+true
gap> N:=NormalSubgroups(G)[3];;
gap> H:=FactorGroup(G,N);;
gap> HasIsPowerfulPGroup(H);
@@ -188,17 +204,23 @@ true
gap> IsPowerfulPGroup(H);
true
gap> myList:=AllSmallGroups(5^4);;
-gap> Number(myList,g->IsPowerfulPGroup(g));
+gap> Number(myList,IsPowerfulPGroup);
9
+gap> Number(myList,IsRegularPGroup);
+15
gap> newList:=AllSmallGroups(5^4);;
gap> for g in newList do
> RankPGroup(g);
> Agemo(g,5);
> od;
-gap> Number(newList,g->IsPowerfulPGroup(g));
+gap> Number(newList,IsPowerfulPGroup);
9
gap> myList:=AllSmallGroups(2^4);;
-gap> Number(myList,g->IsPowerfulPGroup(g));
+gap> Number(myList,IsPowerfulPGroup);
+6
+gap> Number(myList,IsPowerfulPGroup);
6
gap> g:=AbelianGroup(ListWithIdenticalEntries(2000,2));;
+
+#
gap> STOP_TEST("pgroups.tst", 1);