Findings and fixes
Each section: the problem, a reproducer, the cause, the fix, why the fix is right, and the evidence. Code
references are to master 81bcf35. For the defects of the second pass we also say plainly whether the defect
can give a wrong result without any message, and whether 2-group generation with default options
(PqDescendants, PqPCover, PqStandardPresentation of a 2-group, no
further options) can reach it.
- First pass: findings 1–9, pull requests #96–#104
- Second pass, wrong results without a message: findings 10–19
- Second pass, crashes and errors: findings 20–22
- Second pass, wrong data: findings 23–24
First pass
1. Automorphisms are sent to pq in the wrong basis
added 17.10.2026 · data: coords.g, gen_codes.g, check_codes.g, ds_children.g · test in the PR: tst/aut_basis.tst
Problem. PqDescendants(G) for a pc group $G$ returns wrong descendants, or crashes
pq, when the pcgs of $G$ is not the pc presentation pq computes for $G$. Groups from
the SmallGroups library are not affected (their pcgs is pq's); copies of them are.
G := PcGroupCode(3850294291458756985713877851, 128);; # a copy of SmallGroup(128, 540)
Length(PqDescendants(G : StepSize := 1)); # 7; SmallGroup(128, 540) has 10
H := PcGroupCode(20922463594250774814923642549699, 128);; # a copy of SmallGroup(128, 773)
PqDescendants(H : StepSize := 1); # pq dies ("iostream dead")
Cause. PQ_PC_PRESENTATION gives pq the presentation of $G$ on
$g_1, \dots, g_n$ = PcgsPCentralSeriesPGroup(G), and pq computes its own pcp for
it: generators $q_1 = g_1, \dots, q_d = g_d$, and beyond them commutators and powers of its own choice, which
need not be $g_{d+1}, \dots, g_n$. PQ_AUT_INPUT writes the image of $g_j$ under each automorphism as
exponents with respect to $g_1, \dots, g_n$; pq reads them as exponents with respect to
$q_1, \dots, q_n$. Where the two bases differ, pq works with maps that are not the automorphisms
of $G$ (often not automorphisms at all): their action on the $p$-multiplicator, and so the orbits on allowable
subgroups, is wrong.
Fix (#96). Before sending automorphisms of the group itself,
PQ_PG_SUPPLY_AUTS asks pq for its current pcp and the images of $g_1,\dots,g_n$ in it
(PQ_PCP_BASIS), and PQ_AUT_INPUT sends, for each automorphism $\alpha$, the images
$\varepsilon(\alpha(\varepsilon^{-1}(q_j)))$ in pq's coordinates, where $\varepsilon$ is the
isomorphism from $G$ to pq's group. If pq's pcp is the pcgs of $G$ the input is
unchanged.
Why it is right. $\varepsilon$ is an isomorphism (pq computes the $p$-quotient of
class $c$ of $G$, which is $G$), so $\varepsilon\alpha\varepsilon^{-1}$ runs over the automorphism group of
pq's group as $\alpha$ runs over that of $G$, and the images of $q_1,\dots,q_d$ determine it. If
the current pcp is the $p$-cover, its first $n$ generators are those of the quotient and the rest span the
multiplicator, so the quotient by them is pq's group.
Evidence. One isomorphic copy, with a pcgs from a randomly conjugated permutation representation, of every group of $p$-class $\ge 3$ of orders 64 and 128 and of every 25th one of order 256 up to id 13 550 (2123 copies, list on Data), compared with the SmallGroups group itself:
| build | copies | wrong descendants | pq crashed |
|---|---|---|---|
| master | 2123 | 121 | 1 |
| with the fix | 2123 | 0 | 0 |
The wrong answers start at order 64 (SmallGroup(64, 128): 6 descendants instead of 12). Also: the copies
of all groups of $p$-class $\ge 3$ of orders 32–128 by three other routes (special pcgs, permutation group,
pq's own presentation of the fp group): 1983 comparisons in each of four runs, 0 differences with the fix,
also under AddressSanitizer. The extra round trip costs nothing measurable (SmallGroups of order 128, every 10th:
28.6 s before, 29.3 s after).
2. Orbit counters in char
added 17.10.2026 · test in the PR: tst/orbit_counters.tst
Problem. With 127 or more automorphisms, the soluble orbit algorithm returns wrong orbits and stabilisers, so wrong descendants (and wrong automorphism groups for them).
Length(PqDescendants(AbelianGroup([2^3, 2^5, 2^7, 2^9, 2^11]) : StepSize := 1));
# 16384; the types [2..2^5] and [2^2..2^10] have 26, and so does this one with the fix
Cause. For each allowable subgroup $k$, soluble_orbits.c keeps $c[k]$, the number of
automorphisms under which $k$ closes its orbit (up to $m + 1$ for $m$ automorphisms), and
find_permutation.c keeps $d[k]$, the index of the automorphism that first reached $k$;
stabiliser.c builds Schreier words from them. The standard presentation code keeps automorphism
indices in a Schreier vector (insoluble_orbits.c) and in word_map
(standard.c). All of these were char, which is signed on x86 and on macOS arm64, and
wraps at 127. For the group above AutPGrp gives 130 soluble automorphisms.
Fix (#99): these arrays are int.
Why it is right. A change of type only; every value stored fits in an int (at most
$m + 1$, or an automorphism index). Independently of the code: appending identity automorphisms does not change
the group they generate, so it must not change the descendants. On master it does, as soon as there are 127
automorphisms (9 descendants become 125 in the example of tst/bugfix.tst); with the fix it does not.
The time for the group above drops from 44 s to 3 s, since the orbits are no longer split.
Who is affected. The soluble automorphisms passed to pq form a pcgs, so there are at most
$\log_2 |\mathrm{Aut}(G)| < d \cdot n$ of them for $|G| = 2^n$ of rank $d$. For $|G| \le 2^{10}$ that is below
100, plus a few insoluble ones: below 127. So step-1 calls on groups of order at most $2^{10}$ are not affected.
3. EpimorphismStandardPresentation is not an isomorphism for pc groups
added 17.10.2026 · test in the PR: tst/sp_epimorphism.tst
Problem. For a pc $p$-group whose generators are not its pcgs, the map returned is not bijective. The standard presentation itself is right.
G := AbelianGroup(IsPcGroup, [4, 2]);; e := EpimorphismStandardPresentation(G);;
# the images of MappingGeneratorsImages(e)[1] do not define a bijection onto the SP group
Not bijective before the fix for AbelianGroup(IsPcGroup,[4,2]), [2,4],
Group(G.1,G.2) in SmallGroup(64,100), C4 x Q8 as a direct product, and a
Sylow 2-subgroup of $S_8$; bijective after.
Cause. pq is given the presentation on datarec.pcgs, and the last $k$
entries of its map are the images of those elements, but anusp.gi paired them with
GeneratorsOfGroup(G), and built the map with GroupHomomorphismByImagesNC.
Fix (#100): pair them with datarec.pcgs, as
PQ_GROUP_FROM_PCP already does.
4. A group that breaks the exponent law asked for crashes pq
added 17.10.2026 · test in the PR: tst/descendants_law.tst
PqDescendants(SmallGroup(16, 5) : Exponent := 4); # C8 x C2: pq dies; with the fix: [ ]
Cause. pq first computes the $p$-quotient of $G$ of its class under the law; if $G$ breaks
the law, that quotient is smaller than $G$ (here 3 generators instead of 4), but the automorphisms are still
sent with $|{\rm pcgs}(G)|$ exponents each, and pq takes the surplus as answers to its next
questions.
Fix (#97). Every descendant of $G$ has $G$ as a quotient, so it breaks the law
too: the answer is the empty list, returned before pq is called, as for a group that is not
capable. $G$ satisfies $x^e = 1$ iff $\exp(G) \mid e$, and is metabelian iff its derived length is at most 2.
5. PqStandardPresentation hangs with Relators of another rank
added 17.10.2026 · test in the PR: tst/sp_relators.tst
PqStandardPresentation(FreeGroup("a", "b") : Prime := 2, Relators := ["a^8", "b*a^-2"]);
# hangs; with the fix: the standard presentation of C8
Cause. The rank of the class-1 quotient, for the automorphisms $GL(\mathrm{rank}, p)$, is taken from the
group, while pq works with the relators of the option. Here it sends $2 \times 2$ matrices for a
group of rank 1; pq takes the extra entries as further answers and waits for input, while GAP waits
for pq.
Fix (#98): take the rank from the group the relators define
(PqGAPRelators).
6. An interactive PqDescendants returns groups of earlier commands
added 17.10.2026 · test in the PR: tst/stale_descendants.tst
procId := PqStart(FreeGroup(2) : Prime := 3);; Pq(procId : ClassBound := 1);;
PqSetPQuotientToGroup(procId); PqComputePCover(procId); PqPGSupplyAutomorphisms(procId);
PqPGConstructDescendants(procId : StepSize := 1); # 3 groups of order 27
List(PqDescendants(procId : StepSize := 2), Size); # [ 27, 27, 27, 81, 81, 81 ]; fixed: [ 81, 81, 81 ]
Cause. pq appends to GAP_library for the life of the process; the file is
emptied only by PqStart. Fix (#101): the groups of this call are
the last ndescendants entries; keep those.
7. SavePqList writes files PqList cannot read
added 17.10.2026 · test in the PR: tst/savepqlist.tst
For a group with an automorphism group, it wrote a 4-argument call of the 6-argument
ANUPQSetAutomorphismGroup. Fix (#102): write the automorphism data
PqDescendants attached, in the call pq itself writes; the groups read back have the same
data and the same descendants.
8. The option RelativeOrders
added 17.10.2026 · test in the PR: tst/relative_orders.tst
PQ_MANUAL_AUT_INPUT looped for i in RelativeOrders and sent
RelativeOrders[i]: [3, 2] gave an error, [2, 3, 3] sent 3, 3, 3.
Fix (#103): loop over the positions.
9. IsIsomorphicPGroup on two trivial groups
added 17.10.2026 · test in the PR: tst/isomorphic_trivial.tst
Raised <G> must be a p-group (the smallest root of 1 is not a prime). Fix
(#104): return true.
Second pass: wrong results without a message
All ten need something that 2-group generation with default options does not do; the last line of each section says what.
10. PcgsAutomorphisms with a sequence that is not PAG
added 18.10.2026 · data: pcgs_automorphisms.g · test in the PR: tst/pcgs_automorphisms.tst
Problem. With the option PcgsAutomorphisms, pq takes the automorphisms it is
given for a PAG generating sequence (each generated subgroup normal of prime index in the next), and nothing
checks that they are. If they are not, the soluble orbit algorithm returns wrong orbits and stabilisers.
G := ElementaryAbelianGroup(4);;
Length(PqDescendants(G : ClassBound := 3)); # 136
Length(PqDescendants(G : ClassBound := 3, PcgsAutomorphisms)); # 161: 25 isomorphic duplicates; fixed: 136
For ElementaryAbelianGroup(8), whose automorphism group $GL(3,2)$ is not soluble, pq
reports 116 descendants of order $2^5$ (there are 15 of rank 3 and 2-class 2) and then dies; the manual promises
an error message.
Cause. trace_action (src/soluble_orbits.c:96–131) follows only the image of the
last element of an orbit, which is right only if $\langle g_1, \dots, g_{i-1} \rangle$ is normal of prime index in
$\langle g_1, \dots, g_i \rangle$. PQ_AUT_INPUT sends the agAutos and then the
glAutos of AutPGrp; the glAutos only generate the quotient and are not a pcgs (for
$C_2^2$: two involutions generating $S_3$). The manual says the default is IsSolvable(Aut(G)); in the
code it is false. Commit 71e4ac2 (#92) fixed the out-of-bounds read on this path, not the orbits.
Fix (#110). If PcgsAutomorphisms is set when the automorphisms are
sent and the image of the glAutos in $GL(d,p)$ is soluble, they are replaced by preimages of a pcgs of
that image, with their relative orders; otherwise the call stops with an error, as do matrices given by hand that
fail the PAG test on the Frattini quotient. The manual now gives the real default. A pcgs of a soluble group is a
PAG sequence and generates the same group, so the orbits are those of the same group.
Without a message? Yes. Default 2-group generation: not affected; the default is
false, and our runs never set the option. All non-cyclic 2-groups of order at most 32 with soluble
automorphism group gave the same step-1 descendants with and without it; the error shows from the second step on
(ClassBound at least class + 2) and with automorphisms given by hand.
11. An exponent law with automorphisms supplied by hand
added 18.10.2026 · data: exponent_reduced_list.g · test in the PR: tst/exponent_reduced_list.tst
Problem. After PqSupplyAutomorphisms, PqNextClass imposes an exponent law by
testing a reduced list of words and closing what it finds under the automorphisms. That is right only if the
automorphisms induce all of $GL(d,p)$ on a relatively free group, as a comment in
src/extra_relations.c:39–42 says; the manual does not. Otherwise part of the law is not imposed, and
the group is too large.
id := PqStart(FreeGroup(4) : Prime := 2, Exponent := 2);;
PqPcPresentation(id : ClassBound := 1);;
PqSupplyAutomorphisms(id, [ IdentityMat(4) ]);;
PqNextClass(id); # order 2^13; the group of exponent 2 has order 2^4, as without automorphisms
Genuine automorphisms that do not induce $GL(d,p)$ give the same: $\langle a, b \mid a^2 \rangle$ with exponent 4 and $b \mapsto ab$ gives $2^{13}$ instead of $2^6$; $p = 3$, exponent 3, $d = 2$ and $\mathrm{diag}(2,1)$ give $3^8$ instead of $3^3$.
Cause. With automorphisms present, next_class.c:81–86 uses REDUCED_LIST: test
words of weight at most $2c/3$, and of the generators of weight 1 only the first
(extra_relations.c:216, 239–241).
Fix (#112, to be merged after #106). The reduced list is kept only when there are no relations, no metabelian law, and the matrices generate $GL(d,p)$ (checked by enumeration while $|GL(d,p)| \le 2^{24}$); otherwise all words of all weights are tested, and the result closed under the automorphisms as before. Closing under genuine automorphisms only adds consequences of the law, so the result is the group of the law. 19 cases with automorphisms now give the order computed without them; the example B4-4-a-i still gives $2^{422}$ in the same time.
Without a message? Yes; the order is always too large. Default 2-group generation: not affected;
PqDescendants, PqPCover and PqStandardPresentation test all words and do not
use these automorphisms.
12. The exponent-law queue is not enlarged at class 2
added 18.10.2026 · data: exponent_queue.g · test in the PR: tst/exponent_queue.tst
Problem. next_class allocates the queue of redundancies found by the exponent law before the
tails, with room for 4 entries, and enlarges it only for classes other than 2
(src/next_class.c:37, 44–50). At class 2, extra_relations and
close_relations write past its end. Through GAP the outcome depends on the heap: right, wrong, or a
crash.
procId := PqStart(FreeGroup(7) : Prime := 2, Exponent := 2);;
PqPcPresentation(procId : ClassBound := 1);;
PqSupplyAutomorphisms(procId, auts);; # auts: a 7-cycle and a transvection, generating GL(7,2)
PqNextClass(procId); # right: 2^7, class 1
On master, 10 runs for each $d$: $d = 7$ gave $2^{11}$ six times and crashed once; $d = 8$, 9, 10 gave a wrong
order ($2^{26}$, $2^{34}$, $2^{43}$) 6, 7 and 7 times. Under ASan: heap-buffer-overflow in
close_relations.c:128 every time.
Fix (#106): enlarge the queue at every class, class 2 included. The new test failed in 14 of 15 runs on master and passes with the fix; ASan reports nothing.
Without a message? Sometimes; otherwise pq crashes. Default 2-group generation: not
affected; the branch needs automorphisms of the $p$-quotient, which only PqSupplyAutomorphisms and
PqExtendAutomorphisms set.
13. glOrder is wrong when $p^d > 10000$
added 18.10.2026 · data: gl_order.g · test in the PR: tst/gl_order.tst
Problem. PqStabiliserOfAllowableSubgroup gives AutPGrp the order of the automorphism group
$A$ modulo its soluble part $S$. For $p^d > 10000$ ($d$ the rank), lib/anustab.gi:121–123 takes it
as ANUPQsize / p^ANUPQagsize, as if every soluble relative order were $p$, and sets no
glOper. The relative orders are not all $p$ (for $p = 101$: 5, 5, 2, 2), so glOrder is
wrong and often not an integer. AutPGrp stops enumerating an orbit at glOrder$/q$ and takes
glOrder for its length, so the stabiliser comes out too small, the descendants get too small
automorphism groups, and isomorphic duplicates follow at the next step.
SetInfoLevel(InfoANUPQ, 2);
PqDescendants(ElementaryAbelianGroup(101^2) : OrderBound := 4, ClassBound := 2);
# #I Order of GL subgroup is 1020000/1030301 right: |GL(2,101)|/100 = 1030200
Evidence. The 6 descendants of order $101^4$ are right (pq computes those orbits itself);
the run to class 3 did not finish. On the same code with the threshold lowered to 0, so that $p = 5$ takes this
branch, $C_5^2$ to order $5^4$ gives 18 groups of order 625 instead of 9 (isomorphic duplicates), and $C_3^2$ to
$3^6$ gives 139 groups instead of 132; with the fix 9 and 132, as in SmallGroups.
Fix (#117): glOrder is $|P|/|S|$ for the matrix groups of all and of the
soluble automorphisms on the Frattini quotient, the index the other branch computes since PR #85.
Without a message? Yes. Default 2-group generation: not affected; for $p = 2$ the branch needs rank at least 14, our groups have rank at most 11.
14. PqEpimorphism for a pc group that is not a $p$-group
added 18.10.2026 · data: epimorphism_non_pgroup.g · test in the PR: tst/epimorphism_non_pgroup.tst
Problem. For a pc group $G$ that is not a $p$-group, PqEpimorphism,
EpimorphismPqStandardPresentation and EpimorphismStandardPresentation pair the images of
pq's generators with GeneratorsOfGroup(G), while pq was given a presentation
on Pcgs(G). If the two differ, the map is not a homomorphism, or not surjective, or the call fails.
P := Image(IsomorphismPcGroup(DirectProduct(SymmetricGroup(3), CyclicGroup(4))));;
B := Group(Reversed(GeneratorsOfGroup(P)));; # the same group, generators reversed
PqEpimorphism(B : Prime := 2, ClassBound := 3); # [f4,f3,f2,f1] -> [f1,f3,f2,1]: not a homomorphism
C := Group(P.1*P.3, P.2*P.4);; # a subgroup of order 12
PqEpimorphism(C : Prime := 2, ClassBound := 3); # Error, <gens> and <imgs> must be lists of same length
Cause. lib/anupqi.gi:270–273 gives pq FpGroupPcGroup(G), whose
generators correspond to Pcgs(G), and sets no datarec.pcgs; PQ_GROUP_FROM_PCP
and anusp.gi:222 then fall back to GeneratorsOfGroup. Finding 3 fixed the branch of
$p$-groups only.
Fix (#111, to be merged after #100): keep the preimages
of the fp generators and pair the images with them. The earlier note that a check on a subgroup of
$S_4 \times C_3$ did not finish was not a hang: the standard presentation is returned as an fp group that lists
only the non-trivial pc relations, which is infinite as an fp group, so Size runs for ever.
Without a message? Yes, in the first case. Default 2-group generation: not affected; $p$-groups and fp groups take other branches.
15. Step size above the nuclear rank, and two more bookkeeping errors
added 18.10.2026 · data: step_size.g · test in the PR: tst/step_size_dialogue.tsttst/group_name.tsttst/sp_tmpdir.tst
Problem. Three errors in the bookkeeping of the GAP interface; the first can give wrong descendants.
(a) Step size above the nuclear rank. Under GAP, pq asks again for the step size when it
exceeds the nuclear rank (src/defaults_pga.c:283), but GAP has already sent its next answers, which
are then read as step sizes until one is accepted. GAP's own guard (lib/anupqi.gi:2859) works only if
the nuclear rank is already known.
G := SmallGroup(4, 2);; # nuclear rank 3
PqDescendants(G : StepSize := 4, PcgsAutomorphisms); # 3 groups of order 8; right: [ ]
PqDescendants(G : StepSize := 4); # [ ]: right, but only by chance
(b) ANUPQDirectoryTemporary changes the temporary directory but not
ANUPQData.SPimages, so the next PqStandardPresentation reads an empty file and fails
(lib/anupq.gi:37). (c) A group name containing _ (option GroupName, or
Name(G)) makes PqDescendants fail: the class is read from the file name
my_G_class2 by splitting at _ (lib/anupqi.gi:2975); with a space, the
interactive PqPGSetDescendantToPcp kills pq.
Fix (#119, #120, #121): under GAP pq accepts the step size and returns no
descendants, as it does with input from a file; ANUPQDirectoryTemporary updates
SPimages; the class is read from the digits at the end of the file name, and white space in
GroupName becomes _. With it the three reproducers give [ ], the group, and
6 descendants.
Without a message? (a) yes, with PcgsAutomorphisms (or
RankInitialSegmentSubgroups); (b) and (c) always end in an error. Default 2-group
generation: not affected; with default options (a) returns the right empty list. Our
SmallGroups check ran all step sizes in range, some above the nuclear rank,
without PcgsAutomorphisms; its result stands.
16. Primes $p \ge 2^{15}$
added 18.10.2026 · data: prime_bound.g · test in the PR: tst/prime_bound.tst
Problem. Exponents are packed into 15 bits of an int
(include/storage_fixed.h), and nothing bounds $p$, so for $p \ge 2^{15}$ an exponent up to $p - 1$
runs into the sign bit.
F := FreeGroup("a", "b", "c");; a := F.1;; b := F.2;; c := F.3;;
G := F / [a^2*b^3*c^5, Comm(a,b)^7*Comm(b,c)^11*c^2];;
PqEpimorphism(G : Prime := 32771, ClassBound := 1);
# a, b, c -> [1], [31914], [7068]; right: [1], [10923], [0]. The second relation fails in the image.
With $p = 65537$ GAP fails on an empty image, and PqPCover(ElementaryAbelianGroup(32771^2)) returned
a group of the wrong order. The standalone pq checks only that $p$ is odd: $p = 9$ with the relation
$a^3$ loops in invert_modp (a division by zero; endless on arm64).
Fix (#116): $p \le 32749$, the largest prime below $2^{15}$, checked by GAP for the
option Prime and for a prime taken from a $p$-group, and by pq together with a
primality test. Not reproduced, and not in the fix: an overflow of the unreduced sum in
src/update.c:126 for $p$ around $10^3$; 137 random presentations with a 64-bit probe reached at most
$1.06 \cdot 10^9 < 2^{31}$.
Without a message? Yes. Default 2-group generation: not affected; $p = 2$.
17. More than 65535 pc generators in one class
added 18.10.2026 · data: pc_generator_limit.g · test in the PR: tst/pc_generator_limit.tst
Problem. Generator numbers are packed into 16 bits (MAXPC = 65535), and the limit is checked
only before a class, in the basic menu (src/options.c:101). Within a class,
create_tail, extend_tail and update_generators number new generators
without a check, and a number above 65535 aliases: generator $65536 + k$ is stored as generator $k$ squared.
procId := PqStart(FreeGroup(361) : Prime := 2);;
PqPcPresentation(procId : ClassBound := 1);;
PqNextClass(procId);
PqFactoredOrder(procId); # [ 2, 65702 ], from a corrupted presentation; fixed: an error
Rank 360 ($2^{65340}$) is computed correctly; rank 361 needs 65702 generators.
Fix (#114): a check before each new generator; over the limit pq
stops with The number of pc generators would exceed the limit 65535, which GAP turns into an error.
Without a message? Yes. Default 2-group generation: not affected; the $p$-covers of 2-groups of order at most $2^{11}$ have tens to hundreds of generators.
18. Too little workspace reserved for a commutator relation with an exponent
added 18.10.2026 · data: commutator_workspace.g · test in the PR: tst/commutator_workspace.tst
Problem. For a defining relation with a commutator raised to a power, collect_relations
reserves $7 \cdot \mathrm{lastg} + 7$ words above the used workspace, but the collection of the commutator and
power use $11 \cdot \mathrm{lastg} + 9$. Near the limit of the workspace, power
overwrites the collected left side and data below it. ASan sees nothing: the writes stay inside the
workspace.
F := FreeGroup("a", "b");; G := F / [Comm(F.2, F.1)^3];;
Pq(G : Prime := 2, ClassBound := 3, Relators := ["[b,a]^3"], PqWorkspace := 210); # order 2^7; right: 2^6
Pq(G : Prime := 2, ClassBound := 3, PqWorkspace := 210); # 2^6
Fix (#109): reserve $11 \cdot \mathrm{lastg} + 9$; near the limit
pq now stops with Ran out of space.
Without a message? Yes. Default 2-group generation: not affected; it needs a commutator power in
the strings of the option Relators (pc groups send commutators with exponent 1), and a workspace
(default $10^7$ words) within about $4 \cdot \mathrm{lastg}$ words of full.
19. StandardPresentation with a pQuotient in another pc presentation
added 18.10.2026 · data: sp_pquotient.g · test in the PR: tst/sp_pquotient_basis.tst
Problem. With the option pQuotient := Q, the automorphisms of $Q$ are sent to
pq as exponents with respect to PcgsPCentralSeriesPGroup(Q), but pq never
sees $Q$: it computes its own pcp of the quotient of class $c - 1$ and reads the exponents in that basis. Where the
two differ, pq works with maps that are not automorphisms of its group, and the standard presentation
comes out different, or pq crashes. This is the mechanism of finding 1; its fix (#96) covers only the
path of PqDescendants.
Key := F -> List(RelatorsOfFpGroup(F), LetterRepAssocWord);;
G := SmallGroup(64, 6);; # 2-class 3
Q := PcGroupCode(33300, 16);; # a copy of its quotient of class 2, IdGroup [ 16, 3 ]
Key(StandardPresentation(G : pQuotient := Q, ClassBound := 3)) = Key(StandardPresentation(G)); # false; fixed: true
H := SmallGroup(64, 112);;
StandardPresentation(H : pQuotient := PcGroupCode(2768831001, 32), ClassBound := 3); # pq dies; fixed: the SP of H
Evidence. All 177 groups of order 64 of 2-class at least 3, with two random copies of the quotient of
class $c - 1$ each (354 copies): 59 wrong standard presentations and 109 crashes of pq, the same on
master and on correctness; 0 and 0 with the fix. Every 25th group of order 128 and every 500th of
order 256: 0 and 0 with the fix (the master runs there did not finish under the load of the machine; as far as
they got, order 128 gave 3 wrong presentations and 5 crashes). With $Q$ the quotient computed by pq
itself, or the SmallGroups group of the same id, no build differs.
Cause. PQ_SP_STANDARD_PRESENTATION (lib/anupqi.gi:2418) calls
PQ_AUT_INPUT(datarec, datarec.pQuotient), and $Q$ itself is not passed to pq.
Fix (#118). Before the automorphisms are sent, GAP asks pq for its current
pcp, as in finding 1, and takes the quotient of the $p$-cover by its last generators, which is pq's
group in its own pcp; the automorphisms are computed for that group. If its order is not $|Q|$, the call stops with
an error. Even with the right basis, a pQuotient of class at least 2 does not give the canonical
standard presentation: by design pq takes its pcp of the quotient as standard and standardises only
from class $c + 1$ on. For an isomorphism test only class 1, the default, is suitable.
Without a message? Yes, for 59 of the 354 copies above; 109 crashed. Default 2-group
generation: not affected; PqDescendants and PqPCover do not take this path, and
PqStandardPresentation without pQuotient starts from class 1 with $GL(d,p)$, which does
not depend on the basis. Inside the package pQuotient is passed only a quotient of class 1 or
pq's own output.
Second pass: crashes and errors
20. Saved group files with GMP
added 18.10.2026 · data: gmp_separator.g · test in the PR: tst/gmp_separator.tst
Problem. With GMP, save_pga (src/write.c:40) writes the order of the
automorphism group as decimal text and, without a separator, the binary record whose first field is
int p. On reading, mpz_inp_str takes digits as long as they come; if the low byte of $p$
is an ASCII digit ($p \bmod 256 \in [48, 57]$: 53, 307, 311, 313, 563, …), it goes into the number, and the record
is read shifted by one byte.
PqDescendants(CyclicGroup(53) : StepSize := 1); # pq dies; fixed: one group, IdGroup [ 2809, 1 ]
Fix (#105): a newline after the number, checked on reading.
Without a message? No; every case we ran crashed. Default 2-group generation: not affected; for $p = 2$ the first byte is 2, not a digit, and the output for 2-groups is the same with the fix.
21. Schreier words are written without a space check
added 18.10.2026 · data: stabiliser_space.g
Problem. stabiliser_generators (src/stabiliser.c:154–185) writes Schreier
words into the workspace and never checks for space. With genuine automorphisms the words are short (at most 25
words measured, against about $10^7$ free); with maps that are not automorphisms (finding 1) a word never closes,
overwrites pq's tables at the top of the workspace, and then runs past the allocation.
G := PcGroupCode(723005982809892258877217511128, 256);; # a copy of SmallGroup(256, 1325)
PqDescendants(G : StepSize := 1); # master: pq dies (ASan: heap-buffer-overflow, stabiliser.c:173); with #96: 7 groups
Fix (#108): a check before each letter; when space runs out, Ran out of space
during computation, as elsewhere in pq. Shrinking PqWorkspace step by step, we found no
size at which the earlier checks pass and only this loop overflows. There is no new test: after #96 this input no
longer reaches the loop.
Without a message? On master, as a consequence of finding 1, memory is overwritten before the crash; we did not see a wrong answer from it. Default 2-group generation: not affected once #96 is in.
22. Overflow of the number of allowable subgroups
added 18.10.2026 · data: degree_overflow.g · test in the PR: tst/degree_overflow.tst
Problem. For standard presentations pq builds a permutation representation on allowable
subgroups, of degree a Gaussian binomial coefficient, which for a large Frattini rank exceeds $2^{31}$.
compute_degree detects this (src/degree.c:51–60) and prints a message, but under GAP
(pq -G counts as interactive) it returns instead of exiting, and pq goes on with a
partial degree until it crashes. GAP sees the message first and raises an error, so through GAP the result is a
clean error; the standalone pq fed from a file crashed (SEGV).
G := PcGroupCode(5720184869874057666191942025326282294078349057, 2^9);; # rank 6, class 2, |G'| = 2^3
StandardPresentation(G);
# Error, pq program terminated, with error condition:
# Evaluation in compute_degree may cause integer overflow
Fix (#113): exit in every mode. The limit itself stays (issue #32): a permutation representation of degree above $2^{31}$ is not practical.
Without a message? No. Default 2-group generation: PqDescendants is not affected.
Standard presentations of some 2-groups of order $2^9$ to $2^{11}$ stop with this error, before and after the
fix: for $p = 2$ the degree at the commutator stage is $\binom{q}{s}_2$ with $q = \binom{d}{2}$, which overflows
from rank 6 with $|G'| \ge 2^3$, from rank 7 with $s \ge 2$, and always from rank 9. An error, never a wrong
result.
Second pass: wrong data
23. The relative orders written for GAP repeat the last one
added 18.10.2026 · data: relative_orders_gap.g · test in the PR: tst/relative_orders_gap.tst
Problem. GAP_auts loops to i <= nmr_soluble and then writes the last entry
again (src/GAP_present.c:336–340), so relativeOrders in
ANUPQAutomorphisms has one entry too many. GAP_link_via_file.c:78 has the right
bound.
des := PqDescendants(Pq(FreeGroup(2) : Prime := 3, ClassBound := 1) : StepSize := 1);;
ANUPQAutomorphisms(des[3]).relativeOrders; # [ 3, 2, 2 ] for 2 automorphisms; fixed: [ 3, 2 ]
Fix (#107): < for <=. Not changed: in
PqSupplementInnerAutomorphisms, agAutos are the central automorphisms while the relative
orders also cover the soluble part of the stabiliser, so agOrder and agAutos still differ
in length; changing that changes the documented interface.
Without a message? Wrong data, no wrong group: ANUPQAutomorphisms is read only by
PqSupplementInnerAutomorphisms and SavePqList; PqDescendants takes the
automorphisms from AutPGrp. Default 2-group generation: not affected.
24. The relative orders of a soluble stabiliser are not recomputed
added 18.10.2026 · data: stabiliser_relative.g · test in the PR: tst/stabiliser_relative.tst
Problem. In the soluble branch of stabiliser_of_rep (src/stabiliser.c:74–88),
pq builds the generators of the stabiliser but keeps nmr_soluble and the relative orders
of the previous stage, so ANUPQAutomorphisms(G).relativeOrders can list more soluble generators than
there are automorphisms.
for G in PqDescendants(Pq(FreeGroup(3) : Prime := 2, ClassBound := 1) : StepSize := 1) do
a := ANUPQAutomorphisms(G);
Print(Length(a.centralAutos) + Length(a.otherAutos), " ", Length(a.relativeOrders) - 1, "\n");
od; # 7 7, 4 5, 5 5, 3 5; fixed: 7 7, 4 4, 5 5, 3 3 (- 1: the repeated entry of finding 23)
Fix (#115): recompute both from the Schreier words. Generator $t$ of the
stabiliser is automorphism $\alpha_t$ times a word in automorphisms of smaller index, with $\alpha_t$ increasing,
so the soluble ones are those with $\alpha_t$ at most the old nmr_soluble, with the old relative order
of $\alpha_t$.
Why the descendants are right anyway. The stale values reach only the output for GAP and
pq's own group files. The one place where they could change orbits, the call to GAP for an insoluble
stabiliser, never receives them (a probe build: 513 calls, no difference). Whole descendant trees of 2-groups to
$2^7$ and 3-groups to $3^6$ against SmallGroups: 0 differences, on master and with the fix.
Without a message? Wrong data only. Default 2-group generation: not affected.