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

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:

buildcopieswrong descendantspq crashed
master21231211
with the fix212300

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.tst
tst/group_name.tst
tst/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.