From 632028bbcfb89188f6d7034decf65ff75386a761 Mon Sep 17 00:00:00 2001
From: stertooy <5571903+stertooy@users.noreply.github.com>
Date: Mon, 12 Oct 2026 22:43:30 +0200
Subject: [PATCH 1/2] Use smaller exponents when norming and sifting
Choose residues in (-r/2, r/2] at finite depths.
Assisted-by: Claude Code (Fable 5.1)
---
doc/defins.xml | 4 ++--
gap/basic/pcpelms.gi | 15 +++++++++++++--
gap/basic/pcppara.gi | 8 +++++---
gap/basic/pcppcps.gi | 12 +++++++-----
4 files changed, 27 insertions(+), 12 deletions(-)
diff --git a/doc/defins.xml b/doc/defins.xml
index df937d22..0bd9c9ca 100644
--- a/doc/defins.xml
+++ b/doc/defins.xml
@@ -247,8 +247,8 @@ its relative index. For each pcp-element g there exists an integer
- returns an integer e, either positive or -1, such that the pcp-element
- g raised to the power of e is normed.
+ returns an integer e such that the pcp-element g raised to the
+ power of e is normed.
diff --git a/gap/basic/pcpelms.gi b/gap/basic/pcpelms.gi
index 8f6e4bad..3ee0f56f 100644
--- a/gap/basic/pcpelms.gi
+++ b/gap/basic/pcpelms.gi
@@ -217,6 +217,17 @@ function( g )
return o;
end );
+#############################################################################
+##
+#F SmallestResidue( q, r ) . . . . . . . q mod r in (-r/2, r/2], q if r = 0
+##
+BindGlobal( "SmallestResidue", function( q, r )
+ if r = 0 then return q; fi;
+ q := q mod r;
+ if 2*q > r then q := q - r; fi;
+ return q;
+end );
+
#############################################################################
##
#F NormingExponent( g ) . . . . . . . . .returns f such that g^f is normed
@@ -234,10 +245,10 @@ BindGlobal( "NormingExponent", function( g )
elif r = 0 then
return 1;
elif IsPrime( r ) then
- return l^-1 mod r;
+ return SmallestResidue( l^-1, r );
else
e := Gcdex( r, l ); # = RelativeIndex
- return e.coeff2 mod r; # l * c2 = e mod r
+ return SmallestResidue( e.coeff2, r ); # l * c2 = e mod r
fi;
end );
diff --git a/gap/basic/pcppara.gi b/gap/basic/pcppara.gi
index 33518d8a..5c6c48c7 100644
--- a/gap/basic/pcppara.gi
+++ b/gap/basic/pcppara.gi
@@ -28,10 +28,11 @@ end );
## Apply GcdPcp( g, h ), and apply the same operations on i and j.
##
BindGlobal( "GcdPcpPara", function(g, h, i, j)
- local x, y, a, b, q, r, t, z, w, u;
+ local x, y, a, b, q, r, t, z, w, u, rel;
x := g;
y := h;
+ rel := FactorOrder(g);
a := LeadingExponent(x);
b := LeadingExponent(y);
@@ -52,6 +53,8 @@ BindGlobal( "GcdPcpPara", function(g, h, i, j)
while b <> 0 do
q := QuoInt(a, b);
+ r := a - q * b;
+ q := SmallestResidue(q, rel);
t := x * y ^ -q;
x := y;
@@ -61,7 +64,6 @@ BindGlobal( "GcdPcpPara", function(g, h, i, j)
z := w;
w := u;
- r := a - q * b;
a := b;
b := r;
od;
@@ -183,7 +185,7 @@ function( pcs, gens, ppcs, pgens )
b := LeadingExponent(h);
if a > 0 and b > 0 and a mod b = 0 then
# GcdPcpPara would leave h and hh unchanged.
- q := a/b;
+ q := SmallestResidue(a/b, rels[d]);
g := g * h^-q;
gg := gg * hh^-q;
else
diff --git a/gap/basic/pcppcps.gi b/gap/basic/pcppcps.gi
index 7520fda8..794011ef 100644
--- a/gap/basic/pcppcps.gi
+++ b/gap/basic/pcppcps.gi
@@ -101,13 +101,15 @@ IGSValFun := IGSValFun4;
##
## For two non-trivial PcpElements g and h with the same depth, apply the
## Euclidean algorithm to the leading exponents, and do the same operations
-## on the elements themselves.
+## on the elements themselves. At a depth of finite relative order the
+## quotients are taken modulo that order.
##
BindGlobal( "GcdPcp", function(g, h)
- local x, y, a, b, q, r, t;
+ local x, y, a, b, q, r, t, rel;
x := g;
y := h;
+ rel := FactorOrder(g);
a := LeadingExponent(x);
b := LeadingExponent(y);
@@ -123,12 +125,12 @@ BindGlobal( "GcdPcp", function(g, h)
while b <> 0 do
q := QuoInt(a, b);
+ r := a - q * b;
- t := x * y ^ -q;
+ t := x * y ^ -SmallestResidue(q, rel);
x := y;
y := t;
- r := a - q * b;
a := b;
b := r;
od;
@@ -190,7 +192,7 @@ InstallGlobalFunction(AddToIgs, function(igs, gens)
b := LeadingExponent(h);
if a > 0 and b > 0 and a mod b = 0 then
# GcdPcp would leave h unchanged.
- g := g * h^(-a/b);
+ g := g * h^-SmallestResidue(a/b, rels[d]);
else
pair := GcdPcp(g, h);
h := pair[1];
From 43db182a27b93b7ccc6cf2699c57780de695be8a Mon Sep 17 00:00:00 2001
From: stertooy <5571903+stertooy@users.noreply.github.com>
Date: Sat, 17 Oct 2026 13:50:26 +0200
Subject: [PATCH 2/2] Update docs & changelog
---
CHANGES.md | 3 +++
doc/defins.xml | 3 +++
2 files changed, 6 insertions(+)
diff --git a/CHANGES.md b/CHANGES.md
index cc42fb15..360f48a8 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -40,6 +40,9 @@ This file describes changes in the GAP package 'polycyclic'.
- Allow a finite group fallback for `RepresentativeAction` and `IsConjugate` for
subgroups
- Optimise `NormalIntersection`
+- Avoid needlessly large powers when norming and sifting elements at a depth
+ of finite relative order; this also changes which power `NormedPcpElement`
+ returns
- Make more use of `NC` versions of functions, and avoid the use of generic `PreImage`
and `Image` dispatchers to improve speed and memory usage
- Implement a method for sorting pcp-subgroups, which prevents hangs when creating
diff --git a/doc/defins.xml b/doc/defins.xml
index 0bd9c9ca..7bdca757 100644
--- a/doc/defins.xml
+++ b/doc/defins.xml
@@ -249,6 +249,9 @@ its relative index. For each pcp-element g there exists an integer
returns an integer e such that the pcp-element g raised to the
power of e is normed.
+ For non-trivial g with factor order r, we have
+ -r/2 < e \leq r/2 if r > 0, and
+ e \in \{-1,1\} if r = 0.