<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
<!-- %% -->
<!-- %W  stbchain.tex              GAP documentation            Heiko Theißen -->
<!-- %% -->
<!-- %H  @(#)<M>Id: stbchain.tex,v 4.18 2002/10/14 12:38:11 gap Exp </M> -->
<!-- %% -->
<!-- %Y  Copyright 1997,  Lehrstuhl D für Mathematik,  RWTH Aachen,   Germany -->
<!-- %% -->
<Chapter Label="More about Stabilizer Chains">
<Heading>More about Stabilizer Chains</Heading>

This  chapter contains some rather  technical complements to the material
handled in the chapters&nbsp;<Ref Chap="Permutations"/>
and <Ref Chap="Permutation Groups"/>.


<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
<Section Label="Generalized Conjugation Technique">
<Heading>Generalized Conjugation Technique</Heading>

The command <C>ConjugateGroup( <A>G</A>, <A>p</A> )</C>
(see&nbsp;<Ref Oper="ConjugateGroup"/>)
for a  permutation  group  <A>G</A>  with  stabilizer  chain
equips its result also with a stabilizer chain, namely with the chain  of
<A>G</A> conjugate by <A>p</A>. Conjugating a stabilizer chain by a permutation <A>p</A>
means replacing all the points which appear in the <C>orbit</C> components  by
their images under <A>p</A> and replacing every permutation <A>g</A> which  appears
in a <C>labels</C> or <C>transversal</C> component  by  its  conjugate  <M>g^p</M>.  The
conjugate <M>g^p</M> acts on the mapped points  exactly  as  <A>g</A>  did  on  the
original points, i.e., <M>(pnt.p). g^p = (pnt.g).p</M>. Since the  entries  in
the <C>translabels</C> components are integers pointing to  positions  of  the
<C>labels</C> list, the <C>translabels</C> lists just have to be  permuted  by  <A>p</A>
for the conjugated stabilizer.
Then <C>generators</C> is reconstructed as
<C>labels{ genlabels }</C> and
<C>transversal{ orbit }</C> as
<C>labels{ translabels{ orbit } }</C>.
<P/>
<Index>generalized conjugation technique</Index>
This conjugation technique can be generalized. Instead of mapping  points
and  permutations  under  the  same  permutation  <A>p</A>,  it  is  sometimes
desirable (e.g., in the context of permutation  group  homomorphisms)  to
map the points with an arbitrary mapping <M>map</M> and the permutations  with
a homomorphism <M>hom</M> such that the compatibility of the actions is  still
valid:   <M>map(pnt).hom(g) = map(pnt.g)</M>.   (Of   course    the   ordinary
conjugation is a special case  of  this,  with   <M>map(pnt) = pnt.p</M>   and
<M>hom(g) = g^p</M>.)
<P/>
In  the  generalized  case,  the  <Q>conjugated</Q>  chain  need  not  be  a
stabilizer chain for the image of <M>hom</M>, since the  <Q>preimage</Q>  of  the
stabilizer of <M>map(b)</M> (where <M>b</M> is a base point) need not fix <M>b</M>,  but
only fixes the preimage <M>map^{{-1}}( map(b) )</M> setwise. Therefore the  method
can be applied only to one level and the next stabilizer must be computed
explicitly.
But if <M>map</M> is injective, we have <M>map(b).hom(g) = map(b)</M> if and
only if <M>b.g = b</M>, and if this holds, then <M>g = w(g_1, \ldots, g_n)</M>
is a  word  in  the
generators <M>g_1, \ldots, g_n</M> of the stabilizer of&nbsp;<M>b</M> and
<!-- % replaced \buildrel *\over= by {$*$}{$=$} ... easiest compromise for HTML -->
<M>hom(g) =^* w( hom(g_1), \ldots, hom(g_n) )</M> is in the
<Q>conjugated</Q> stabilizer.
If, more generally, <M>hom</M> is a right inverse to a homomorphism
<M>\varphi</M> (i.e., <M>\varphi(hom(g)) = g</M> for all <M>g</M>),
equality <M>*</M> holds modulo the kernel of <M>\varphi</M>;
in this case the <Q>conjugated</Q> chain can be made into a real stabilizer
chain by extending each level with the generators of the kernel and
appending a proper stabilizer chain of the kernel at the end.
These special cases will occur in the algorithms for permutation group
homomorphisms (see&nbsp;<Ref Chap="Group Homomorphisms"/>).
<P/>
To <Q>conjugate</Q> the points (i.e., <C>orbit</C>) and permutations (i.e.,
<C>labels</C>) of the Schreier tree, a loop is set up over the <C>orbit</C> list
constructed during the orbit algorithm, and  for  each  vertex  <M>b</M>  with
unique edge <M>a(l)b</M> ending at <M>b</M>, the label <M>l</M> is mapped with <M>hom</M> and
<M>b</M> with <M>map</M>. We assume  that  the  <C>orbit</C>  list  was  built  w.r.t.&nbsp;a
certain ordering <M>&lt;</M> of the labels,
where <M>l' &lt; l</M> means that every  point  in
the orbit was mapped with <M>l'</M> before it was mapped with <M>l</M>. This  shape
of the <C>orbit</C> list is guaranteed if the Schreier tree is  extended  only
by <Ref Func="AddGeneratorsExtendSchreierTree"/>,
and it is then also guaranteed  for
the <Q>conjugated</Q> Schreier tree. (The ordering of the labels  cannot  be
read from the Schreier tree, however.)
<P/>
In the generalized case, it can happen that  the  edge  <M>a(l)b</M>  bears  a
label <M>l</M> whose image is <Q>old</Q>, i.e., equal to the image of an  earlier
label <M>l' &lt; l</M>. Because of the compatibility of the actions we  then  have
<M>map(b) = map(a).hom(l)^{{-1}} = map(a).hom(l')^{{-1}} = map(a{{l'}}^{{-1}})</M>,
so <M>map(b)</M> is already equal to the image  of  the  vertex  <M>a{{l'}}^{{-1}}</M>.
This vertex must have been  encountered  before  <M>b = al^{{-1}}</M>  because
<M>l' &lt; l</M>. We conclude that the image of a label can be <Q>old</Q> only if the
vertex at the end of the corresponding edge has an  <Q>old</Q>  image,  too,
but then it need not be <Q>conjugated</Q> at all. A similar  remark  applies
to labels which map under <M>hom</M> to the identity.

</Section>


<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
<Section Label="The General Backtrack Algorithm with Ordered Partitions">
<Heading>The General Backtrack Algorithm with Ordered Partitions</Heading>

Section <Ref Sect="Backtrack"/> 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.

<Subsection Label="Internal representation of ordered partitions">
<Heading>Internal representation of ordered partitions</Heading>

<Index Subkey="internal representation">ordered partitions</Index>
&GAP; represents  an  ordered  partition  as  a  record  with   the   following
components.
<List>
<Mark><C>points</C></Mark>
<Item>
        a list  of all points contained in  the  partition, such that the
        points of each cell from lie consecutively,
</Item>
<Mark><C>cellno</C></Mark>
<Item>
        a list whose <A>i</A>th entry is the number of the cell which contains
        the point <A>i</A>,
</Item>
<Mark><C>firsts</C></Mark>
<Item>
        a list  such that  <C>points[firsts[<A>j</A>]]</C>  is  the first point  in
        <C>points</C> which is in cell <A>j</A>,
</Item>
<Mark><C>lengths</C></Mark>
<Item>
        a list of the  cell lengths.
</Item>
</List>
Some of the information is  redundant, e.g., the  <C>lengths</C> could also be
read off the <C>firsts</C> list,  but since this   need not be increasing,  it
would    require some searching. Similar  for    <C>cellno</C>, which could be
replaced by a systematic search  of <C>points</C>, keeping  track of what cell
is currently being  traversed.
With the above components, the <A>m</A>th cell of a partition <A>P</A>
is expressed as <C><A>P</A>.points{ [ <A>P</A>.firsts[<A>m</A>] ..
<A>P</A>.firsts[<A>m</A>] + <A>P</A>.lengths[<A>m</A>] - 1 ] }</C>.
The   most   important
operations, however, to be performed upon <A>P</A> are the splitting of a cell
and the reuniting  of the two parts. Following  the strategy  of J.&nbsp;Leon,
<!-- explicit citation, please -->
this is done as follows:
<P/>
<List>
<Mark>(1)</Mark>
<Item>
The points which make up the cell that is to be split are sorted so  that
the ones that remain inside occupy positions <C>[ <A>P</A>.firsts[<A>m</A>] .. <A>last</A>
]</C> in the list <C><A>P</A>.points</C> (for a suitable value of <A>last</A>).
</Item>
<Mark>(2)</Mark>
<Item>
The  points  at  positions  <C>[ <A>last</A> + 1 .. <A>P</A>.firsts[<A>m</A>] +
<A>P</A>.lengths[<A>m</A>] - 1 ]</C> will form the additional cell.
For this new  cell
requires additional entries are added to the lists <C><A>P</A>.firsts</C>  (namely,
<C><A>last</A>+1</C>) and <C><A>P</A>.lengths</C> (namely,
<C><A>P</A>.firsts[<A>m</A>] + <A>P</A>.lengths[<A>m</A>] - <A>last</A> - 1</C>).
</Item>
<Mark>(3)</Mark>
<Item>
The entries of the sublist
<C><A>P</A>.cellno{ [ <A>last</A>+1 .. <A>P</A>.firsts[<A>m</A>] +
P.lengths[<A>m</A>]-1 ] }</C> must be set to the number of the new cell.
</Item>
<Mark>(4)</Mark>
<Item>
  The entry <C><A>P</A>.lengths[<A>m</A>]</C> must be reduced to
  <C><A>last</A> - <A>P</A>.firsts[<A>m</A>] + 1</C>.
</Item>
</List>
<P/>
Then reuniting the  two cells requires  only the reversal of steps&nbsp;2 to&nbsp;4
above. The list <C><A>P</A>.points</C> need not be rearranged.

</Subsection>

<Subsection Label="Functions for setting up an R-base">
<Heading>Functions for setting up an R-base</Heading>

This subsection explains
some  &GAP;  functions   which   are   local   to   the   library   file
<F>lib/stbcbckt.gi</F> which contains the code for backtracking in permutation
groups. They are mentioned here because you might find them helpful  when
you want  to  implement  you  own  backtracking  function  based  on  the
partition concept. An important argument to most of the functions is  the
R-base <M>R</M>, which you should regard as a black box. We will tell you how
to set it up, how to maintain it and where to pass it as argument, but it
is not necessary for you to know its internal representation. However, if
you insist to learn the whole story: Here are the record components  from
which an R-base is made up:
<P/>
<List>
<Mark><C>domain</C></Mark>
<Item>
    the set <M>\Omega</M> on which the group <M>G</M> operates
</Item>
<Mark><C>base</C></Mark>
<Item>
    the sequence <M>(a_1, \ldots, a_r)</M> of base points
</Item>
<Mark><C>partition</C></Mark>
<Item>
    an  ordered  partition, initially  <M>\Pi_0</M>, this  will be  refined to
    <M>\Pi_1, \ldots, \Pi_r</M> during the backtrack algorithm
</Item>
<Mark><C>where</C></Mark>
<Item>
    a list such that <M>a_i</M> lies in cell number <C>where</C><M>[i]</M> of <M>\Pi_i</M>
</Item>
<Mark><C>rfm</C></Mark>
<Item>
    a    list whose <M>i</M>th entry  is   a  list of   refinements which take
    <M>\Sigma_i</M>  to <M>\Sigma_{{i+1}}</M>;  the    structure of a  refinement  is
    described below
</Item>
<Mark><C>chain</C></Mark>
<Item>
    a (copy of a) stabilizer  chain for <M>G</M> (not  if  <M>G</M> is a  symmetric
    group)
</Item>
<Mark><C>fix</C></Mark>
<Item>
    only if  <M>G</M> is a  symmetric group:  a list whose  <M>i</M> entry contains
    <C>Fixcells( </C><M>\Pi_i</M><C> )</C>
</Item>
<Mark><C>level</C></Mark>
<Item>
    initially equal to <C>chain</C>,  this will be changed  to chains  for the
    stabilizers <M>G_{{a_1 \ldots a_i}}</M> for <M>i = 1, \ldots, r</M>
    during the
    backtrack algorithm; if <M>G</M> is a  symmetric group, only the number of
    moved points is stored for each stabilizer
</Item>
<Mark><C>lev</C></Mark>
<Item>
    a  list   whose  <M>i</M>th  entry   remembers   the  <C>level</C> entry    for
    <M>G_{{a_1 \ldots a_{{i-1}}}}</M>
</Item>
<Mark><C>level2</C>, <C>lev2</C></Mark>
<Item>
    a similar  construction   for a second  group  (used  in intersection
    calculations), <K>false</K> otherwise.  This second group <M>H</M> activated if
    the R-base  is constructed as  <C>EmptyRBase( </C><M>[ G, H ], \Omega,
    \Pi_0</M><C> )</C> (if <M>G = H</M>, &GAP; sets <C>level2 = </C><K>true</K> instead).
</Item>
<Mark><C>nextLevel</C></Mark>
<Item>
    this is described below
</Item>
</List>
<P/>
As  our guiding example, we  present  code for the function
<Ref Oper="Centralizer" Label="for a magma and a submagma"/>
which calculates the centralizer of an element <M>g</M> in the group <M>G</M>.
(The real code is more general and has a few more subtleties.)
<P/>
<Listing><![CDATA[
Pi_0 := TrivialPartition( omega );
R := EmptyRBase( G, omega, Pi_0 );
R.nextLevel := function( Pi, rbase )
local  fix, p, q, where;
NextRBasePoint( Pi, rbase );
fix := Fixcells( Pi );
for p  in fix  do
  q := p ^ g;
  where := IsolatePoint( Pi, q );
  if where <> false  then
    Add( fix, q );
    ProcessFixpoint( R, q );
    AddRefinement( R, "Centralizer", [ Pi.cellno[ p ], q, where ] );
    if Pi.lengths[ where ] = 1  then
      p := FixpointCellNo( Pi, where );
      ProcessFixpoint( R, p );
      AddRefinement( R, "ProcessFixpoint", [ p, where ] );
    fi;
  fi;
od;
end;

return PartitionBacktrack(
  G,
  c -> g ^ c = g,
  false,
  R,
  [ Pi_0, g ],
  L, R );
]]></Listing>
<P/>
The list numbers below refer to the line numbers of the code above.
<P/>
<List>
  <Mark>1.</Mark>
  <Item>
    <C>omega</C> is the set on which <C>G</C> acts and <C>Pi_0</C>
    is the first member  of the decreasing sequence of partitions mentioned
    in <Ref Sect="Backtrack"/>.
    We  set  <C>Pi_0 = omega</C>,  which  is  constructed  as
    <C>TrivialPartition( omega )</C>, but we could have started with  a  finer
    partition, e.g., into unions of <C>g</C>-cycles of the same length.
  </Item>
  <Mark>2.</Mark>
  <Item>
    This statement sets up the R-base in the variable <C>R</C>.
  </Item>
  <Mark>3.-21.</Mark>
  <Item>
    These lines define a function <C>R.nextLevel</C> which  is  called
    whenever an additional member in the sequence
    <C>Pi_0 </C><M>\geq \Pi_1 \geq \ldots</M>
    of partitions is needed. If <M>\Pi_i</M> does  not  yet  contain  enough
    base points in one-point cells, &GAP;  will  call
    <C>R.nextLevel( </C><M>\Pi_i,</M><C> R )</C>,
    and this function will choose a new base point <M>a_{{i+1}}</M>, refine
    <M>\Pi_i</M> to <M>\Pi_{{i+1}}</M>
    (thereby <E>changing</E> the first argument) and  store
    all necessary information in&nbsp;<C>R</C>.
  </Item>
  <Mark>5.</Mark>
  <Item>
    This statement selects a new base point <M>a_{{i+1}}</M>,
    which is not yet in  a one-point cell of <M>\Pi</M> and still moved
    by the stabilizer <M>G_{{a_1 \ldots a_i}}</M> of the earlier base points.
    If certain points  of  <C>omega</C>  should be preferred as base point
    (e.g., because they belong to long cycles  of <C>g</C>),
    a list of points starting with the most wanted ones, can  be  given
    as an optional third argument to <C>NextRBasePoint</C>
    (actually, this is done in the real code for
    <Ref Oper="Centralizer" Label="for a magma and a submagma"/>).
  </Item>
  <Mark>6.</Mark>
  <Item>
    <C>Fixcells( </C><M>\Pi</M><C> )</C> returns the list of  points  in
    one-point  cells  of <M>\Pi</M>
    (ordered as the cells are ordered in <M>\Pi</M>).
  </Item>
  <Mark>7.</Mark>
  <Item>
    For every point <M>p \in fix</M>, if we know the image
    <M>p</M><C>^</C><M>g</M> under  <M>c \in C_G(e)</M>,
    we also know <M>( p</M><C>^</C><M>g )</M><C>^</C><M>c
    = ( p</M><C>^</C><M>c )</M><C>^</C><M>g</M>.
    We therefore want to isolate these extra points in <M>\Pi</M>.
  </Item>
  <Mark>9.</Mark>
  <Item>
    This statement puts point <M>q</M> in a cell of its own,
    returning in  <C>where</C> the number of the cell of <M>\Pi</M>
    from which <M>q</M>  was  taken.
    If  <M>q</M>  was already the only point in its cell,
    <C>where = </C><K>false</K> instead.
  </Item>
  <Mark>12.</Mark>
  <Item>
    This command does the necessary bookkeeping for the extra base point
    <M>q</M>:
    It prescribes <M>q</M> as next base in the stabilizer chain for  <M>G</M>
    (needed, e.g., in line&nbsp;5)
    and  returns  <K>false</K>  if  <M>q</M>  was  already  fixed  the
    stabilizer of the earlier base points
    (and <K>true</K> otherwise; this is  not used here).
    Another call to <C>ProcessFixpoint</C> like  this  was  implicitly
    made by the function <C>NextRBasePoint</C> to register the chosen
    base  point.
    By contrast, the point <M>q</M> was not chosen this way,
    so  <C>ProcessFixpoint</C> must be called explicitly for&nbsp;<M>q</M>.
  </Item>
  <Mark>13.</Mark>
  <Item>
    This statement registers the function  which  will  be  used  during  the
    backtrack search to perform the corresponding refinements on the  <Q>image
    partition</Q> <M>\Sigma_i</M>
    (to  yield  the  refined  <M>\Sigma_{{i+1}}</M>).
    After choosing an image <M>b_{{i+1}}</M> for the base point
    <M>a_{{i+1}}</M>, &GAP; will compute
    <M>\Sigma_i \wedge (\{ b_{{i+1}} \}, \Omega \setminus \{ b_{{i+1}} \})</M>
    and store this partition in <M>I</M><C>.partition</C>,
    where <M>I</M> is a black box similar to <M>R</M>,
    but corresponding to the current <Q>image  partition</Q>
    (hence  it  is  an <Q>R-image</Q> in analogy to the R-base).
    Then &GAP; will call the function
    <C>Refinements.Centralizer( R, I, Pi.cellno[ p ], p, where )</C>,
    with  the  then  current  values  of  <M>R</M>  and  <M>I</M>,
    but   where <M>\Pi</M><C>.cellno</C><M>[ p ]</M>, <M>p</M>,
    <C>where</C> still have the values  they  have  at the time of this
    <C>AddRefinement</C> command.
    This function call will further refine <M>I</M><C>.partition</C>
    to yield <M>\Sigma_{{i+1}}</M> as it  is  programmed  in the function
    <C>Refinements.Centralizer</C>, which is  described  below.
    (The global variable <C>Refinements</C> is a record which contains  all
    refinement functions for all backtracking procedures.)
  </Item>
  <Mark>14.-19.</Mark>
  <Item>
    If the cell from which <M>q</M> was taken out had only two points,
    we now have an additional one-point cell.
    This condition is checked in line&nbsp;13 and if it is true,
    this extra fixpoint <M>p</M> is taken  (line&nbsp;15),
    processed  like <M>q</M> before (line&nbsp;16) and is then (line&nbsp;17)
    passed to  another  refinement function
    <C>Refinements.ProcessFixpoint( R, I, p, where )</C>,
    which is also described below.
  </Item>
  <Mark>23.-29.</Mark>
  <Item>
    This command  starts  the  backtrack  search.  Its  result  will  be  the
    centralizer as a subgroup of <M>G</M>. Its arguments are
  </Item>
  <Mark>24.</Mark>
  <Item>
    the group we want to run through,
  </Item>
  <Mark>25.</Mark>
  <Item>
    the property we want to test, as a &GAP; function,
  </Item>
  <Mark>26.</Mark>
  <Item>
    <K>false</K> if we are looking for a subgroup, <K>true</K> in the case
    of   a  representative  search    (when  the result   would    be one
    representative),
  </Item>
  <Mark>27.</Mark>
  <Item>
    the R-base,
  </Item>
  <Mark>28.</Mark>
  <Item>
    a list  of data, to be stored  in <M>I</M><C>.data</C>, which has
    in position&nbsp;1 the first member <M>\Sigma_0</M>  of the decreasing
    sequence of <Q>image partitions</Q> mentioned in
    <Ref Sect="Backtrack"/>.
    In the centralizer example, position&nbsp;2 contains the
    element that is  to be centralized. In the  case of  a representative
    search,  i.e.,  a conjugacy test  <M>g</M><C>^</C><M>c</M><C> ?= </C><M>h</M>, we
    would  have <M>h</M>   instead of  <M>g</M>   here, and   possibly a <M>\Sigma_0</M>
    different from <M>\Pi_0</M> (e.g., a  partition into unions of  <M>h</M>-cycles
    of same length).
  </Item>
  <Mark>29.</Mark>
  <Item>
    two subgroups <M>L \leq C_G(g)</M> and <M>R \leq C_G(h)</M> known  in
    advance (we have <M>L = R</M> in the centralizer case).
  </Item>
</List>

</Subsection>


<Subsection Label="Refinement functions for the backtrack search">
<Heading>Refinement functions for the backtrack search</Heading>

The last
subsection showed   how the refinement   process leading from  <M>\Pi_i</M> to
<M>\Pi_{{i+1}}</M>  is coded in the  function  <M>R</M><C>.nextLevel</C>, this  has to be
executed once the  base point  <M>a_{{i+1}}</M>.  The analogous refinement  step
from <M>\Sigma_i</M> to <M>\Sigma_{{i+1}}</M> must be performed for each choice of an
image <M>b_{{i+1}}</M> for  <M>a_{{i+1}}</M>, and it will  depend  on the corresponding
value of <M>\Sigma_i \wedge (\{b_{{i+1}}\}, \Omega \setminus \{b_{{i+1}}\})</M>.
But  before
we  can continue  our centralizer example,  we  must,  for the interested
reader, document the record components of the other black box <M>I</M>, as we
did above for the R-base black box <M>R</M>. Most of the components change as
&GAP; walks up and down the levels of the search tree.
<List>
  <Mark><C>data</C></Mark>
  <Item>
    this will be mentioned below
  </Item>
  <Mark><C>depth</C></Mark>
  <Item>
    the level <M>i</M> in the search tree of the current node <M>\Sigma_i</M>
  </Item>
  <Mark><C>bimg</C></Mark>
  <Item>
    a list of images of the points in <M>R</M><C>.base</C>
  </Item>
  <Mark><C>partition</C></Mark>
  <Item>
    the partition <M>\Sigma_i</M> of the current node
  </Item>
  <Mark><C>level</C></Mark>
  <Item>
    the stabilizer chain <M>R</M><C>.lev</C><M>[i]</M> at the current level
  </Item>
  <Mark><C>perm</C></Mark>
  <Item>
    a permutation mapping <C>Fixcells</C><M>( \Pi_i )</M> to
    <C>Fixcells</C><M>( \Sigma_i )</M>;
    this implies mapping <M>(a_1, \ldots, a_i)</M> to
    <M>(b_1, \ldots, b_i)</M>
  </Item>
  <Mark><C>level2</C>, <C>perm2</C></Mark>
  <Item>
    a  similar construction for    the second stabilizer chain,   <K>false</K>
    otherwise (and <K>true</K> if <M>R</M><C>.level2 = </C><K>true</K>)
  </Item>
</List>
<P/>
As declared in the above code for <Ref Oper="Centralizer" Label="for a magma and a submagma"/>,
the refinement is performed by the function
<C>Refinement.Centralizer</C><M>( R, I, \Pi</M><C>.cellno</C><M>[p], p, where )</M>.
The  functions in the  record
<C>Refinement</C> always   take two  additional   arguments  before  the  ones
specified  in  the <C>AddRefinement</C>  call (in  line&nbsp;13 above),  namely the
R-base  <M>R</M> and  the  current  value  <M>I</M>  of the <Q>R-image</Q>.
In our example, <M>p</M> is a fixpoint of
<M>\Pi = \Pi_i \wedge (\{ a_{{i+1}} \}, \Omega \setminus \{ a_{{i+1}} \})</M>
such that <M>where = \Pi</M><C>.cellno</C><M>[ p^g ]</M>.
The <C>Refinement</C> functions must return <K>false</K> if the refinement is
unsuccessful (e.g., because it  leads to <M>\Sigma_{{i+1}}</M> having  different
cell   sizes from  <M>\Pi_{{i+1}}</M>)  and  <K>true</K>  otherwise.
Our particular function looks like this.
<P/>
<Listing><![CDATA[
Refinements.Centralizer := function( R, I, cellno, p, where )
local  Sigma, q;
Sigma := I.partition;
q := FixpointCellNo( Sigma, cellno ) ^ I.data[ 2 ];
return IsolatePoint( Sigma, q ) = where and ProcessFixpoint( I, p, q );
end;
]]></Listing>
<P/>
The list numbers below refer to the line numbers of the code immediately
above.
<P/>
<List>
  <Mark>3.</Mark>
  <Item>
    The current value of
    <M>\Sigma_i \wedge (\{ b_{{i+1}} \}, \Omega \setminus \{ b_{{i+1}} \})</M>
    is always found in <M>I</M><C>.partition</C>.
  </Item>
  <Mark>4.</Mark>
  <Item>
    The image of the only point in cell number
    <M>cellno = \Pi_i</M><C>.cellno</C><M>[ p ]</M> in <M>\Sigma</M>
    under <M>g = I</M><C>.data</C><M>[ 2 ]</M> is calculated.
  </Item>
  <Mark>5.</Mark>
  <Item>
    The function returns <K>true</K> only if the  image  <M>q</M>  has  the
    same  cell number in <M>\Sigma</M> as <M>p</M> had in <M>\Pi</M>
    (i.e., <M>where</M>) and if <M>q</M> can  be prescribed as an  image
    for  <M>p</M>  under  the  coset  of  the  stabilizer
    <M>G_{{a_1 \ldots a_{{i+1}}}}.c</M> where <M>c \in G</M>  is  an
    (already  constructed) element mapping the  earlier  base  points
    <M>a_1, \ldots, a_{{i+1}}</M>  to  the already chosen images
    <M>b_1, \ldots, b_{{i+1}}</M>.
    This  latter  condition  is tested by
    <C>ProcessFixpoint</C><M>( I, p, q )</M> which, if successful,
    also does the necessary bookkeeping in <M>I</M>.
    In analogy to  the  remark  about line&nbsp;12 in the program above,
    the chosen image  <M>b_{{i+1}}</M>  for  the  base point <M>a_{{i+1}}</M>
    has already been processed  implicitly  by  the  function
    <C>PartitionBacktrack</C>,
    and this processing includes the construction of an element
    <M>c \in  G</M>  which  maps  <C>Fixcells</C><M>( \Pi_i )</M>  to
    <C>Fixcells</C><M>( \Sigma_i )</M>  and  <M>a_{{i+1}}</M>  to
    <M>b_{{i+1}}</M>.
    By  contrast,  the  extra fixpoints <M>p</M> and <M>q</M> in
    <M>\Pi_{{i+1}}</M> and <M>\Sigma_{{i+1}}</M> were  not  chosen automatically,
    so they require an  explicit  call  of  <C>ProcessFixpoint</C>,
    which replaces the element <M>c</M> by some <M>c'.c</M>
    (with  <M>c' \in  G_{{a_1 \ldots a_{{i+1}}}}</M>) which in addition maps
    <M>p</M> to <M>q</M>, or returns <K>false</K> if  this is impossible.
  </Item>
</List>
<P/>
You should now be able to  guess what
<C>Refinements.ProcessFixpoint</C><M>( R, I, p, where )</M> does:
it  simply returns
<C>ProcessFixpoint</C><M>( I, p, </M><C>FixpointCellNo</C><M>(
 I</M><C>.partition</C><M>, where ) )</M>.
<P/>
<E>Summary.</E>
<P/>
When you write  your  own backtrack functions using
the  partition technique,  you  have  to  supply  an R-base, including  a
component <C>nextLevel</C>, and   the  functions in the   <C>Refinements</C> record
which  you need. Then  you can start  the backtrack by passing the R-base
and the additional data (for the  <C>data</C> component of the <Q>R-image</Q>) to
<C>PartitionBacktrack</C>.

</Subsection>

<Subsection Label="Functions for meeting ordered partitions">
<Heading>Functions for meeting ordered partitions</Heading>

A   kind  of
refinement that   occurs  in particular  in   the  normalizer calculation
involves computing  the  meet of <M>\Pi</M>  (cf.&nbsp;lines&nbsp;6ff.&nbsp;above) with an
arbitrary other partition  <M>\Lambda</M>, not just  with one point. To do this
efficiently, &GAP; uses the following two functions.
<P/>
<C>StratMeetPartition( </C><M>R</M>, <M>\Pi</M>, <M>\Lambda</M> <C>[</C>, <M>g</M> <C>] )</C>
<P/>
<C>MeetPartitionStrat( </C><M>R</M>, <M>I</M><C>{, </C><M>\Lambda'</M><C>}[, {</C><M>g'</M><C>}]</C>, <M>strat</M> <C>)</C>
<P/>
<Index>meet strategy</Index>
Such a  <C>StratMeetPartition</C>   command would   typically appear in    the
function call <M>R</M><C>.nextLevel</C><M>(  \Pi, R )</M>  (during the refinement of
<M>\Pi_i</M>  to  <M>\Pi_{{i+1}}</M>).
This command replaces <M>\Pi</M> by <M>\Pi \wedge \Lambda</M>
(thereby  <E>changing</E> the  second  argument) and returns a <Q>meet
strategy</Q>  <M>strat</M>. This  is  (for  us) a  black   box which  serves two
purposes:  First, it allows &GAP; to  calculate faster the corresponding
meet <M>\Sigma \wedge \Lambda'</M>,  which must then  appear in a <C>Refinements</C>
function  (during the refinement of  <M>\Sigma_i</M> to <M>\Sigma_{{i+1}}</M>). It is
faster  to compute <M>\Sigma \wedge \Lambda'</M> with  the <Q>meet strategy</Q> of
<M>\Pi \wedge \Lambda</M> because  if the refinement  of <M>\Sigma</M> is successful
at  all, the  intersection  of a  cell from   the left hand  side of  the
<M>\wedge</M> sign  with a cell  from the right hand side  must  have the same
size   in both cases  (and  <M>strat</M>  records these   sizes, so  that only
non-empty intersections must  be calculated for <M>\Sigma \wedge \Lambda'</M>).
Second, if  there  is a discrepancy  between  the behaviour prescribed by
<M>strat</M> and the behaviour observed when refining <M>\Sigma</M>, the refinement
can immediately be abandoned.
<P/>
On  the  other hand, if you  only  want to meet   a  partition <M>\Pi</M> with
<M>\Lambda</M>  for  a one-time  use, without recording   a strategy,  you can
simply type <C>StratMeetPartition</C><M>( \Pi, \Lambda )</M>  as in the following
example, which also demonstrates some other partition-related commands.
<P/>
<!-- % <Example><![CDATA[ -->
<!-- % 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 ] ] -->
<!-- % ]]></Example> -->
<!--  -->
<!-- % The preceding (original) example doesn't work because there is no -->
<!-- % function 'OnPartitions'. The following example works, but I don't -->
<!-- % know if it makes sense to have it here at all. (Note, in particular, -->
<!-- % that the function 'Partition' is undocumented.)  VF  14.10.02 -->

<Example><![CDATA[
gap> P := Partition( [[1,2],[3,4,5],[6]] );;  Cells( P );
[ [ 1, 2 ], [ 3, 4, 5 ], [ 6 ] ]
gap> Q := Partition( OnTuplesTuples( last, (1,3,6) ) );;  Cells( Q );
[ [ 3, 2 ], [ 6, 4, 5 ], [ 1 ] ]
gap> StratMeetPartition( P, Q );
[  ]
gap> # The ``meet strategy'' was not recorded, ignore this result.
gap> Cells( P );
[ [ 1 ], [ 5, 4 ], [ 6 ], [ 2 ], [ 3 ] ]
]]></Example>
<P/>
You can even say  <C>StratMeetPartition</C><M>( \Pi, \Delta )</M>  where <M>\Delta</M>
is simply  a subset  of  <M>\Omega</M>, it   will then  be interpreted as  the
partition <M>(\Delta, \Omega \setminus \Delta)</M>.
<P/>
&GAP; makes use   of  the advantages  of   a <Q>meet  strategy</Q>  if  the
refinement   function  in <C>Refinements</C>  contains  a <C>MeetPartitionStrat</C>
command where   <M>strat</M>  is   the    <Q>meet  strategy</Q>  calculated    by
<C>StratMeetPartition</C> before.  Such a command replaces <M>I</M><C>.partition</C> by
its meet with <M>\Lambda'</M>, again changing the argument <M>I</M>. 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 <C>PartitionBacktrack</C>.
<P/>
In  all cases, an additional  argument <M>g</M> means that the   meet is to be
taken  not with <M>\Lambda</M>, but instead with <M>\Lambda.{{g^{{-1}}}}</M>,
where
operation  on ordered partitions is  meant cellwise  (and setwise on each
cell). (Analogously for the primed arguments.)
<P/>
<Example><![CDATA[
gap> P := Partition( [[1,2],[3,4,5],[6]] );;
gap> StratMeetPartition( P, P, (1,6,3) );;  Cells( P );
[ [ 1 ], [ 5, 4 ], [ 6 ], [ 2 ], [ 3 ] ]
]]></Example>
Note that <M>P.(1,3,6) = Q</M>.

</Subsection>
</Section>


<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
<Section Label="Stabilizer Chains for Automorphisms Acting on Enumerators">
<Heading>Stabilizer Chains for Automorphisms Acting on Enumerators</Heading>

This section describes a way of representing the automorphism group of a
group as permutation group, following <Cite Key="Sims97"/>. The code however is
not yet included in the &GAP; library.
<P/>
In this section  we present an example in  which objects we  already know
(namely,  automorphisms  of   solvable  groups)   are  equipped  with the
permutation-like operations <C>^</C> and <C>/</C>  for action on positive integers.
To achieve this, we must  define a new  type of objects which behave like
permutations   but are  represented     as automorphisms  acting  on   an
enumerator.  Our  goal is to  generalize  the Schreier-Sims algorithm for
construction of a stabilizer chain to groups of such new automorphisms.


<Subsection Label="An operation domain for automorphisms">
<Heading>An operation domain for automorphisms</Heading>

The idea we describe here is due  to C.&nbsp;Sims.
We consider  a group <M>A</M>  of  automorphisms of a
group <M>G</M>, given by generators,  and we would like  to know its order. Of
course we  could   follow the strategy  of  the   Schreier-Sims algorithm
(described  in <Ref Sect="Stabilizer Chains"/>) for <M>A</M>
acting   on   <M>G</M>. This would    involve   a  call  of
<C>StabChainStrong( EmptyStabChain( [], One( <A>A</A> ) ), GroupGenerators( <A>A</A> ) )</C>  where
<C>StabChainStrong</C>  is a function as the  one described in the pseudo-code
below:
<P/>
<Listing><![CDATA[
StabChainStrong := function( S, newgens )
  Extend the Schreier tree of S with newgens.
  for sch  in  Schreier generators  do
    if not sch in S.stabilizer  then
      StabChainStrong( S.stabilizer, [ sch ] );
    fi;
  od;
end;
]]></Listing>
<P/>
The membership test <M>sch \notin S</M><C>.stabilizer</C> can be performed because
the  stabilizer chain  of <M>S</M><C>.stabilizer</C>  is   already correct at  that
moment. We  even know a base  in advance, namely  any  generating set for
<M>G</M>.
Fix such a generating set <M>(g_1, \ldots, g_d)</M> and observe that this
base  is  generally very   short compared  to   the degree <M>|G|</M>  of  the
operation. The problem with the Schreier-Sims algorithm, however, is then
that the length of the first  basic orbit <M>g_1.A</M>  would already have the
magnitude of <M>|G|</M>,  and the basic orbits at  deeper levels would  not be
much shorter. For the advantage of a short base  we pay the high price of
long basic  orbits, since the  product of  the  (few) basic orbit lengths
must  equal <M>|A|</M>.  Such  long  orbits  make the Schreier-Sims  algorithm
infeasible,   so we have to   look for a  longer base  with shorter basic
orbits.
<P/>
Assume that   <M>G</M> is solvable  and  choose  a  characteristic series with
elementary abelian factors. For the sake of  simplicity we assume that <M>N
&lt; G</M> is an   elementary abelian characteristic subgroup  with elementary
abelian factor group <M>G/N</M>. Since <M>N</M> is characteristic, <M>A</M> also acts as
a group of automorphisms  on the factor  group <M>G/N</M>,  but of course  not
necessarily  faithfully. To retain  a faithful action,  we let <M>A</M> act on
the   disjoint   union   <M>G/N</M>   with   <M>G</M>,   and   choose    as    base
<M>(g_1 N, \ldots, g_d N, g_1, \ldots, g_d)</M>.
Now the first <M>d</M> basic  orbits  lie
inside <M>G/N</M> and can have length at most <M>[G:N]</M>. Since the  base
points <M>g_1 N, \ldots, g_d N</M>  form  a  generating  set  for  <M>G/N</M>,  their
iterated stabilizer <M>A^{(d+1)}</M> acts trivially on the factor group <M>G/N</M>,
i.e., it leaves the cosets <M>g_i N</M> invariant. Accordingly,  the  next  <M>d</M>
basic orbits lie inside <M>g_i N</M> (for <M>i = 1, \ldots, d</M>) and can  have  length
at most&nbsp;<M>|N|</M>.
<P/>
Generalizing this method to a characteristic series
<M>G = N_0 &gt; N_1 &gt; \ldots &gt; N_l = \{ 1 \}</M> of length
<M>l &gt; 2</M>, we  can always find  a base of length <M>l.d</M>
such that each  basic orbit is  contained in a  coset of a characteristic
factor, i.e. in a set of the form <M>g_i N_{{j-1}} / N_j</M>
(where <M>g_i</M> is one of
the generators  of <M>G</M> and <M>1 \leq j \leq l</M>).
In particular, the  length of
the basic  orbits   is  bounded   by  the  size  of    the  corresponding
characteristic factors. To implement a Schreier-Sims algorithm for such a
base, we  must  be   able  to  let   automorphisms  act  on   cosets   of
characteristic  factors <M>g_i N_{{j-1}} / N_j</M>,
for  varying  <M>i</M>  and <M>j</M>.  We
would    like to    translate each such     action  into  an  action   on
<M>\{ 1, \ldots, [ N_{{j-1}}:N_j] \}</M>,
because then we need not enumerate the operation domain,
which is the disjoint union of
<M>G / N_1</M>, <M>G / N_2 \ldots G / N_l</M>,
as a whole. Enumerating it  as a whole would result  in basic orbits like
<C>orbit</C><M> \subseteq \{ 1001, \ldots, 1100 \}</M>
with a  <C>transversal</C> list whose
first 1000 entries would be unbound, but  still require 4&nbsp;bytes of memory
each (see&nbsp;<Ref Sect="Stabilizer Chain Records"/>).
<P/>
Identifying each coset <M>g_i N_{{j-1}} / N_j</M> into
<M>\{ 1, \ldots, [N_{{j-1}}:N_j] \}</M> of course means
that we have  to change the action  of
the automorphisms on     every  level of   the  stabilizer   chain.  Such
flexibility is not   possible with permutations  because their  effect on
positive  integers  is <Q>hardwired</Q>  into them,  but  we can install new
operations for automorphisms.

</Subsection>


<Subsection Label="Enumerators for cosets of characteristic factors">
<Heading>Enumerators for cosets of characteristic factors</Heading>

So far  we
have  not used the  fact that  the characteristic  factors are elementary
abelian, but we will do so from  here on.
Our first task is to implement an enumerator
(see <Ref Attr="AsList"/> and
<Ref Sect="Enumerators"/>)
for a coset of a characteristic factor in a solvable  group  <M>G</M>.
We assume that such a coset <M>g N/M</M> is given by
<P/>
<List>
  <Mark>(1)</Mark>
  <Item>
    a pcgs for  the group  <M>G</M> (see  <Ref Attr="Pcgs"/>),
    let <M>n = </M><C>Length( </C><M>pcgs</M><C> )</C>;
  </Item>
  <Mark>(2)</Mark>
  <Item>
    a range <M>range = [ start .. stop ]</M> indicating that
    <M>N = \langle pcgs\{ [ start .. n ] \} \rangle</M> and
    <M>M = \langle pcgs\{ [ stop + 1 .. n ] \} \rangle</M>,
    i.e., the cosets of <M>pcgs\{ range \}</M> form a base
    for the vector space <M>N/M</M>;
  </Item>
  <Mark>(3)</Mark>
  <Item>
    the representative <M>g</M>.
  </Item>
</List>
<P/>
We   first  define a  new representation  for   such enumerators and then
construct them by simply putting these three pieces of data into a record
object. The  enumerator  should  behave as  a   list of  group   elements
(representing cosets modulo <M>M</M>),   consequently, its family will  be the
family of the <M>pcgs</M> itself.
<P/>
<Log><![CDATA[
DeclareRepresentation( "IsCosetSolvableFactorEnumeratorRep", IsEnumerator,
    [ "pcgs", "range", "representative" ] );

EnumeratorCosetSolvableFactor := function( pcgs, range, g )
    return Objectify( NewType( FamilyObj( pcgs ),
                   IsCosetSolvableFactorEnumeratorRep ),
                   rec( pcgs := pcgs,
                       range := range,
              representative := g ) );
end;
]]></Log>
<P/>
The definition of the operations <Ref Attr="Length"/>,
<Ref Oper="\[\]"/> and <Ref Oper="Position"/>
is now
straightforward. The  code has sometimes  been  abbreviated and is  meant
<Q>cum grano salis</Q>,  e.g.,  the declaration of  the local  variables has
been left out.
<P/>
<Log><![CDATA[
InstallMethod( Length, [ IsCosetSolvableFactorEnumeratorRep ],
    enum -> Product( RelativeOrdersPcgs( enum!.pcgs ){ enum!.range } ) );

InstallMethod( \[\], [ IsCosetSolvableFactorEnumeratorRep,
        IsPosRat and IsInt ],
    function( enum, pos )
    elm := ();
    pos := pos - 1;
    for i  in Reversed( enum!.range )  do
        p := RelativeOrderOfPcElement( enum!.pcgs, i );
        elm := enum!.pcgs[ i ] ^ ( pos mod p ) * elm;
        pos := QuoInt( pos, p );
    od;
    return enum!.representative * elm;
end );

InstallMethod( Position, [ IsCosetSolvableFactorEnumeratorRep,
        IsObject, IsZeroCyc ],
    function( enum, elm, zero )
    exp := ExponentsOfPcElement( enum!.pcgs,
                   LeftQuotient( enum!.representative, elm ) );
    pos := 0;
    for i  in enum!.range  do
        pos := pos * RelativeOrderOfPcElement( pcgs, i ) + exp[ i ];
    od;
    return pos + 1;
end );
]]></Log>

</Subsection>


<Subsection Label="Making automorphisms act on such enumerators">
<Heading>Making automorphisms act on such enumerators</Heading>

Our next task is to make automorphisms of the solvable group
<M>pcgs</M><C>!.group</C> act on
<M>[ 1 .. </M><C>Length</C><M>( enum ) ]</M> for such an enumerator
<M>enum</M>. We achieve this
by  introducing a new  representation of automorphisms on enumerators and
by putting the enumerator together  with the automorphism into an  object
which behaves like  a permutation. Turning  an ordinary automorphism into
such  a special  automorphism requires  then   the construction of  a new
object which has the new type. We provide an operation
<C>PermOnEnumerator( <A>model</A>, <A>aut</A> )</C> which constructs such a new
object having the same type as <A>model</A>,
but representing the  automorphism  <A>aut</A>. So <A>aut</A>  can be
either an ordinary automorphism or one which already has an enumerator in
its type, but perhaps  different from the one  we want (i.e. from the one
in <A>model</A>).
<P/>
<Log><![CDATA[
DeclareCategory( "IsPermOnEnumerator",
    IsMultiplicativeElementWithInverse and IsPerm );

DeclareRepresentation( "IsPermOnEnumeratorDefaultRep",
    IsPermOnEnumerator and IsAttributeStoringRep,
    [ "perm" ] );

DeclareOperation( "PermOnEnumerator",
    [ IsEnumerator, IsObject ] );

InstallMethod( PermOnEnumerator,
    [ IsEnumerator, IsObject ],
    function( enum, a )
    SetFilterObj( a, IsMultiplicativeElementWithInverse );
    a := Objectify( NewKind( PermutationsOnEnumeratorsFamily,
                 IsPermOnEnumeratorDefaultRep ),
                 rec( perm := a ) );
    SetEnumerator( a, enum );
    return a;
end );

InstallMethod( PermOnEnumerator,
    [ IsEnumerator, IsPermOnEnumeratorDefaultRep ],
    function( enum, a )
    a := Objectify( TypeObj( a ), rec( perm := a!.perm ) );
    SetEnumerator( a, enum );
    return a;
end );
]]></Log>
<P/>
Next we  have to install new  methods for the  operations which calculate
the  product of two automorphisms, because   this product must again have
the    right type. We    also have to write  a    function which uses the
enumerators to apply such an automorphism to positive integers.
<P/>
<Log><![CDATA[
InstallMethod( \*, IsIdenticalObj,
    [ IsPermOnEnumeratorDefaultRep, IsPermOnEnumeratorDefaultRep ],
    function( a, b )
    perm := a!.perm * b!.perm;
    SetIsBijective( perm, true );
    return PermOnEnumerator( Enumerator( a ), perm );
end );

InstallMethod( \^,
    [ IsPosRat and IsInt, IsPermOnEnumeratorDefaultRep ],
    function( p, a )
    return PositionCanonical( Enumerator( a ),
                   Enumerator( a )[ p ] ^ a!.perm );
end );
]]></Log>
<P/>
How the corresponding methods for <C><A>p</A> / <A>aut</A></C>
and <C><A>aut</A> ^ <A>n</A></C> look like is obvious.
<P/>
Now we  can  formulate  the recursive procedure   <C>StabChainStrong</C> which
extends  the stabilizer chain by adding  in new  generators <M>newgens</M>. We
content  ourselves again   with pseudo-code, emphasizing  only  the lines
which set the <C>EnumeratorDomainPermutation</C>. We assume that initially <M>S</M>
is a stabilizer chain for the trivial subgroup with a level for each pair
<M>(range,g)</M> characterizing an enumerator  (as  described above). We  also
assume that  the <C>identity</C>  element at each  level already  has the type
corresponding to that level.
<P/>
<Listing><![CDATA[
StabChainStrong := function( S, newgens )
  for i  in [ 1 .. Length( newgens ) ]  do
    newgens[ i ] := AutomorphismOnEnumerator( S.identity, newgens[ i ] );
  od;
  Extend the Schreier tree of S with newgens.
  for sch  in  Schreier generators  do
    if not sch in S.stabilizer  then
      StabChainStrong( S.stabilizer, [ sch ] );
    fi;
  od;
end;
]]></Listing>

</Subsection>
</Section>
</Chapter>


<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
<!-- %% -->
<!-- %E -->

