Section Backtrack in the reference manual describes the basic functions for a backtrack search. The purpose of this section is to document how the general backtrack algorithm is implemented in GAP and which parts you have to modify if you want to write your own backtrack routines.
points
cellno
firsts points[firsts[j]] is the first point in
points which is in cell j,
lengths lengths could also be
read off the firsts list, but since this need not be increasing, it
would require some searching. Similar for cellno, which could be
replaced by a systematic search of points, keeping track of what cell
is currently being traversed. With the above components, the mth cell
of a partition P is expressed as P.points [ P.firsts[m] ..
P.firsts[m] + P.lengths[m] - 1 ] . The most important
operations, however, to be performed upon P are the splitting of a cell
and the reuniting of the two parts. Following the strategy of J. Leon,
this is done as follows:
(1) The points which make up the cell that is to be split are sorted so
that the ones that remain inside occupy positions [ P.firsts[m] ..
last ] in the list P.points (for a suitable value of last).
(2) The points at positions [ last + 1 .. P.firsts[m] +
P.lengths[m] - 1 ] will form the additional cell. For this new cell
requires additional entries are added to the lists P.firsts (namely,
last+1) and P.lengths (namely, P.firsts[m] +
P.lengths[m] - last - 1).
(3) The entries of the sublist P.cellno [ last+1 .. P.firsts[m]
+ P.lengths[m]-1 ] must be set to the number of the new cell.
(4) The entry P.lengths[m] must be reduced to last -
P.firsts[m] + 1.
Then reuniting the two cells requires only the reversal of steps 2 to 4
above. The list P.points need not be rearranged.
domain
base
partition
where where[ i ] of Pi
rfm
chain
fix Fixcells( Pi )
level chain, this will be changed to chains for the
stabilizers Ga1... ai for i = 1,¼,r during the
backtrack algorithm; if G is a symmetric group, only the number of
moved points is stored for each stabilizer
lev level entry for
Ga1¼ai-1
level2, lev2 false otherwise. This second group H activated if
the R-base is constructed as EmptyRBase( [ G, H ], W,
P0 ) (if G = H, GAP sets level2 = true instead).
nextLevel
As our guiding example, we present code for the function Centralizer
which calculates the centralizer of an element g in the group G. (The
real code is more general and has a few more subtleties.)
P0 := TrivialPartition( W );
\R := EmptyRBase( G, W, P0 );
\R.nextLevel := function( P, rbase )
local fix, p, q, where;
NextRBasePoint( P, rbase );
fix := Fixcells( P );
for p in fix do
q := p ^ g;
where := IsolatePoint( P, q );
if where <> false then
Add( fix, q );
ProcessFixpoint( \R, q );
AddRefinement( \R, "Centralizer", [ P.cellno[ p ], q, where ] );
if P.lengths[ where ] = 1 then
p := FixpointCellNo( P, where );
ProcessFixpoint( \R, p );
AddRefinement( \R, "ProcessFixpoint", [ p, where ] );
fi;
fi;
od;
end;
return PartitionBacktrack( G,
c -> g ^ c = g,
false,
\R,
[ P0, g ],
L, R );
1. W is the set on which G acts and P0 is the first
member of the decreasing sequence of partitions mentioned in
Backtrack searching in the reference manual. We set
P0 = (W), which is constructed as TrivialPartition( W
)), but we could have started with a finer partition, e.g., into unions
of g-cycles of the same length.
2. This statement sets up the R-base in the variable \R.
3--21. These lines define a function \R.nextLevel which is
called whenever an additional member in the sequence P0 ³ P1 ³ ¼ of partitions is needed. If Pi does not yet contain
enough base points in one-point cells, GAP will call \R.nextLevel(
Pi, \R ), and this function will choose a new base point
ai+1, refine Pi to Pi+1 (thereby changing the first
argument) and store all necessary information in \R.
5. This statement selects a new base point ai+1, which is
not yet in a one-point cell of P and still moved by the stabilizer
Ga1¼ai of the earlier base points. If certain points of
W should are preferred as base point (e.g., because they belong to
long cycles of g), a list of points starting with the most wanted ones,
can be given as an optional third argument to NextRBasePoint (actually,
this is done in the real code for Centralizer).
6. Fixcells( P ) returns the list of points in one-point
cells of P (ordered as the cells are ordered in P).
7. For every point p Î fix, if we know the image p ^ g
under c Î CG(e), we also know ( p ^ g ) ^ c = ( p ^ c ) ^
g. We therefore want to isolate these extra points in P.
9. This statement puts point q in a cell of its own, returning
in where the number of the cell of P from which q was taken. If
q was already the only point in its cell, where = false instead.
12. This command does the necessary bookkeeping for the extra
base point q: It prescribes q as next base in the stabilizer chain
for G (needed, e.g., in line 5) and returns false if q was already
fixed the stabilizer of the earlier base points (and true otherwise;
this is not used here). Another call to ProcessFixpoint like this was
implicitly made by the function NextRBasePoint to register the chosen
base point. By contrast, the point q was not chosen this way, so
ProcessFixpoint must be called explicitly for q.
13. This statement registers the function which will be used
during the backtrack search to perform the corresponding refinements on
the ``image partition'' Si (to yield the refined Si+1).
After choosing an image bi+1 for the base point ai+1, GAP
will compute Si Ù({bi+1},W-{bi+1}) and store
this partition in \I.partition, where \I is a black box similar to
\R, but corresponding to the current ``image partition'' (hence it is
an ``R-image'' in analogy to the R-base). Then GAP will call the
function Refinements.Centralizer( \R, \I, P.cellno[ p ], p,
where ), with the then current values of \R and \I, but where
P.cellno[ p ], p, where still have the values they have at
the time of this AddRefinement command. This function call will further
refine \I.partition to yield Si+1 as it is programmed in
the function Refinements.Centralizer, which is described below. (The
global variable Refinements is a record which contains all refinement
functions for all backtracking procedures.)
14--18. If the cell from which q was taken out had only two
points, we now have an additional one-point cell. This condition is
checked in line 13 and if it is true, this extra fixpoint p is taken
(line 15), processed like q before (line 16) and is then (line 17)
passed to another refinement function Refinements.ProcessFixpoint( \R,
\I, p, where ), which is also described below.
22--27. This command starts the backtrack search. Its result will be the centralizer as a subgroup of G. Its arguments are
false if we are looking for a subgroup, true in the case
of a representative search (when the result would be one
representative),
\I.data, which has
in position 1 the first member S0 of the decreasing sequence
of ``image partitions'' mentioned in Backtrack searching in the
reference manual. In the centralizer example, position 2 contains the
element that is to be centralized. In the case of a representative
search, i.e., a conjugacy test g ^ c = h, we
would have h instead of g here, and possibly a S0
different from P0 (e.g., a partition into unions of h=cycles
of same length).
\R.nextLevel, this has to be
executed once the base point ai+1. The analogous refinement step
from Si to Si+1 must be performed for each choice of an
image bi+1 for ai+1, and it will depend on the corresponding
value of SiÙ({bi+1}, W-{bi+1}). But before
we can continue our centralizer example, we must, for the interested
reader, document the record components of the other black box \I, as we
did above for the R-base black box \R. Most of the components change as
GAP walks up and down the levels of the search tree.
data
depth
bimg \R.base
partition
level \R.lev[ i ] at the current level
perm Fixcells( Pi ) to Fixcells( Si
) (this implies mapping (a1,¼,ai) to (b1,¼,bi))
level2, perm2 false
otherwise (and true if \R.level2 = true)
As declared in the above code for Centralizer, the refinement is
performed by the function Refinement.Centralizer( \R, \I,
P.cellno[ p ], p, where ). The functions in the record
Refinement always take two additional arguments before the ones
specified in the AddRefinement call (in line 13 above), namely the
R-base \R and the current value \I of the ``R-image''. In our
example, p is a fixpoint of P = Pi Ù({ai+1}, W-{ai+1}) such that where = P.cellno[ p ^ g ]. The
Refinement functions must return false if the refinement is
unsuccessful (e.g., because it leads to Si+1 having different
cell sizes from Pi+1) and true otherwise. Our particular
function looks like this.
Refinements.Centralizer := function( \R, \I, cellno, p, where )
local S, q;
S := \I.partition;
q := FixpointCellNo( S, cellno ) ^ \I.data[ 2 ];
return IsolatePoint( S, q ) = where and ProcessFixpoint( \I, p, q );
end;
3. The current value of SiÙ({bi+1}, W-{bi+1}) is always found in \I.partition.
4. The image of the only point in cell number cellno =
Pi.cellno[ p ] in S under g = \I.data[ 2 ] is
calculated.
5. The function returns true only if the image q has the same
cell number in S as p had in P (i.e., where) and if q
can be prescribed as an image for p under the coset of the stabilizer
Ga1¼ai+1.c where c Î G is an (already constructed)
element mapping the earlier base points a1,¼,ai+1 to the
already chosen images b1,¼,bi+1. This latter condition is
tested by ProcessFixpoint( \I, p, q ) which, if successful, also
does the necessary bookkeeping in \I. In analogy to the remark about
line 12 in the program above, the chosen image bi+1 for the base
point ai+1 has already been processed implicitly by the function
PartitionBacktrack, and this processing includes the construction of an
element c Î G which maps Fixcells( Pi ) to Fixcells(
Si ) and ai+1 to bi+1. By contrast, the extra
fixpoints p and q in Pi+1 and Si+1 were not chosen
automatically, so they require an explicit call of ProcessFixpoint,
which replaces the element c by some c¢.c (with c¢ Î Ga1¼ai+1) which in addition maps p to q, or returns false if this
is impossible.
You should now be able to guess what Refinements.ProcessFixpoint( \R,
\I, p, where ) does: it simply returns ProcessFixpoint( \I,
p, FixpointCellNo( \I.partition, where ) ).
nextLevel, and the functions in the Refinements record
which you need. Then you can start the backtrack by passing the R-base
and the additional data (for the data component of the ``R-image'') to
PartitionBacktrack.
StratMeetPartition( \R, P, L [, g ] )
MeetPartitionStrat( \R, \I, L¢ [, g¢ ], strat )
Such a StratMeetPartition command would typically appear in the
function call \R.nextLevel( P, \R ) (during the refinement of
Pi to Pi+1). This command replaces P by PÙL (thereby changing the second argument) and retuns a ``meet
strategy'' strat. This is (for us) a black box which serves two
purposes: First, it allows GAP to calculate faster the corresponding
meet SÙL¢, which must then appear in a Refinements
function (during the refinement of Si to Si+1). It is
faster to compute SÙL¢ with the ``meet strategy'' of
PÙL because if the refinement of S is successful
at all, the intersection of a cell from the left hand side of the
Ù sign with a cell from the right hand side must have the same
size in both cases (and strat records these sizes, so that only
non-empty intersections must be calculated for SÙL¢).
Second, if there is a discrepancy between the behaviour prescribed by
strat and the behaviour observed when refining S, the refinement
can immediately be abandoned.
On the other hand, if you only want to meet a partition P with
L for a one-time use, without recording a strategy, you can
simply type StratMeetPartition( P, L ) as in the following
example, which also demonstrates some other partition-related commands.
gap> P := Partition( [[1,2],[3,4,5],[6]] );; Cells( P );
[ [ 1, 2 ], [ 3, 4, 5 ], [ 6 ] ]
gap> Q := OnPartitions( P, (1,3,6) );; Cells( Q );
[ [ 3, 2 ], [ 6, 4, 5 ], [ 1 ] ]
gap> StratMeetPartition( P, Q );
[ ] # the ``meet strategy'' was not recorded, ignore this result
gap> Cells( P );
[ [ 1 ], [ 5, 4 ], [ 6 ], [ 2 ], [ 3 ] ]
You can even say StratMeetPartition( P, D ) where D
is simple a subset of W, it will then be interpreted as the
partition (D,W-D).
GAP makes use of the advantages of a ``meet strategy'' if the
refinement function in Refinements contains a MeetPartitionStrat
command where strat is the ``meet strategy'' calculated by
StratMeetPartition before. Such a command replaces \I.partition by
its meet with L¢, again changing the argument \I. The necessary
reversal of these changes when backtracking from a node (and prescribing
the next possible image for a base point) is automatically done by the
function PartitionBacktrack.
In all cases, an additional argument g means that the meet is to be taken not with L, but instead with L.g-1, where operation on ordered partitions is meant cellwise (and setwise on each cell). (Analogously for the primed arguments.)
gap> P := Partition( [[1,2],[3,4,5],[6]] );;
gap> StratMeetPartition( P, P, (1,6,3) );; Cells( P );
[ [ 1 ], [ 5, 4 ], [ 6 ], [ 2 ], [ 3 ] ] # |$P.(1,3,6) = Q|$
g ^ c <> g). Even if the construction of
c stops before images for all base points have been chosen, because a
refinement was unsuccessful, several multiplications will already have
been performed by (explicit or implicit) calls of ProcessFixpoint, and,
actually, the general backtrack procedure implemented in GAP avoids
this.
For this purpose, GAP does not actually multiply the permutations but
rather stores all the factors of the product in a list. Specifically,
instead of carrying out the multiplication in c® c¢.c mentioned
in the comment to line 5 of the above program --- where c¢ Î Ga1¼ai+1 is a product of factorized inverse transversal
elements, see Stabilizer chain records --- GAP appends the list of
these factorized inverse transversal elements (giving c¢) to the list
of factors already collected for c. Here c¢ is multiplied from the
left and is itself a product of inverses of strong generators of G,
but GAP simply spares itself all the work of inverting permutations
and stores only a ``list of inverses'', whose product is then
(c¢.c)-1 (which is the new value of c-1). The ``list of
inverses'' is extended this way whenever ProcessFixpoint is called to
improve c.
The product has to be multiplied out only when the property is finally
tested for the element c. But it is often possible to delay the
multiplication even further, namely until after the test, so that no
multiplication is required in the case of an unsuccessful test. Then the
test itself must be carried out with the factorized version of the
element c. For this purpose, PartitionBacktrack can be passed its
second argument (the property in question) in a different way, not as a
single GAP function, but as a list like in lines 2--4 of the following
alternative excerpt from the code for Centralizer.
return PartitionBacktrack( G,
[ g, g,
OnPoints,
c -> c!.lftObj = c!.rgtObj ],
false, \R, [ P0, g ], L, R );
The test for c to have the property in question is of the form opr(
left, c ) = right where opr is an operation function as
explained in External sets in the reference manual. In other words,
c passes the test if and only if it maps a ``left object'' to a ``right
object'' under a certain operation. In the centralizer example, we have
opr = OnPoints and left = right = g, but in a conjugacy test, we
would have right = h.
2. Two first two entries (here g and g) are the values of left and right.
3. The third entry (here OnPoints) is the operation opr.
4. The fourth entry is the test to be performed upon the mapped
left object left and preimage of the right object opr( right,
c^-1 ). Here GAP operates with the inverse of c because this is
the product of the permutations stored in the ``list of inverses''. The
preimage of right under c is then calculated by mapping right with
the factors of c-1 one by one, without the need to multiply these
factors. This mapping of right is automatically done by the
ProcessFixpoint function whenever c is extended, the current value of
right is always stored in c!.rgtObj. When the test given by the
fourth entry is finally performed, the element c has two components
c!.lftObj = left and c!.rgtObj = opr( right, c^-1 ),
which must be used to express the desired relation as a function of c.
In our centralizer example, we simply have to test whether they are
equal.
[Top] [Previous] [Up] [Next] [Index]
GAP 4 manual