\Chapter{Methods based on permutation groups}

Most calculations in the {\LOOPS} package are delegated to groups, taking
advantage of the various permutations and permutation groups associated with
quasigroups. This chapter explains in detail how the permutations associated
with a quasigroup are calculated, and it also describes some of the core
methods of {\LOOPS} based on permutations. Additional core methods can be found
in Chapter "Testing properties of quasigroups and loops".

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Parent of a quasigroup}

Let $Q$ be a quasigroup and $S$ a subquasigroup of $Q$. Since the
multiplication in $S$ coincides with the multiplication in $Q$, it is
reasonable not to store the multiplication table of $S$. However, the
quasigroup $S$ then must know that it is a subquasigroup of $Q$. In order to
facilitate this relationship, we introduce the attribute

\>Parent( <Q> ) A

for a quasigroup $Q$.

When $Q$ is <not> created as a subquasigroup of another quasigroup, the
attribute `Parent( <Q> )' is set to $Q$. When $Q$ is created as a subquasigroup
of a quasigroup $H$, we let `Parent( <Q> ) := Parent( <H> )'. Thus, in effect,
`Parent( <Q> )' is the largest quasigroup from which $Q$ has been created.

Let $Q$ be a quasigroup with parent $P$, where $P$ is some $n$-element
quasigroup. Let $x$ be an element of $Q$. Then `x![1]' is the position of $x$
among the elements of $P$, i.e., `x![1] = Position( Elements( P ), x )'. The
position of $x$ among the elements of $Q$ is obtained via

\>Position( <Q>, x ) O

While referring to elements of $Q$ by their positions, we therefore must decide
if the positions are meant among the elements of $Q$, or among the elements of
$P$. Since it requires no calculation to obtain `x![1]', <we always use the
position of an element in its parent quasigroup>. In this way, many attributes
of a quasigroup, including its Cayley table, are permanently tied to its
parent. It is now clear why we have not insisted that Cayley tables of
quasigroups must have entries covering the entire interval $1$, $\dots$, $m$,
for some $m$.

When $S$ is a list of quasigroup elements, not necessarily from the same
quasigroup, the operation

\>PosInParent( <S> )

returns the list of positions of elements of $S$ in the corresponding parent,
i.e., `PosInParent( <S> )[ i ] = S[ i ]![ 1 ] = Position( Parent( S[ i ] ), S[
i ] )'.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Comparing quasigroups with common parent}

\noindent Assume that $A$, $B$ are two quasigroups with common parent $Q$. Let
$G_A$, $G_B$ be the canonical generating sets of $A$ and $B$, respectively,
obtained by the method `GeneratorsSmallest', described above. Then we
define $A \< B$ if and only if $G_A \< G_B$ lexicographically.

Note that if $A$ is a subquasigroup of $B$, we get $A\<B$, but not necessarily
vice versa.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Subquasigroups and subloops}

When $S$ is a subset of a quasigroup $Q$ (loop $L$), the smallest subquasigroup
of $Q$ (subloop of $L$) generated by $S$ is returned via:

\>Subquasigroup( <Q>, <S> ) O
\>Subloop( <L>, <S> ) O

In fact, we allow $S$ to be a list of integers, too, representing the positions
of the respective elements in the parent quasigroup (loop).

If $S$ is empty, `Subloop( <L>, <S> )' returns the one-element subloop of $L$, while `Subquasigroup( <Q>, <S> )' returns the empty set. The empty set is obviously not a subquasigroup of $Q$, but this convention is useful for handling certain situation, for instance when the user calls `Center( <Q> )' for a quasigroup $Q$ with empty center.

The following two operations test if a quasigroup (loop) $S$ is a subquasigroup
(subloop) of a quasigroup $Q$. They return `true' if and only if $Q$ and $S$
have the same parent, and if $S$ is a subset of $Q$.

\>IsSubquasigroup( <Q>, <S> ) O
\>IsSubloop( <Q>, <S> ) O

The operation

\>AllSubloops( <L> ) O

returns a list of all subloops of the loop $L$.

Let $S$ be a subloop of $L$. The list of all right cosets\index{coset} of $S$
in $L$ is obtained via

\>RightCosets( <L>, <S> ) F

The coset $S$ is listed first, and the elements of each coset are ordered in
the same way as elements of $S$, i.e., if $S = [s_1,\dots,s_m]$ then
$Sx=[s_1x$, $\dots$, $s_mx]$.

When $S$ is a subloop of $L$, the right transversal\index{transversal} of $S$
with respect to $L$ is a subset of $L$ containing one element from each right
coset of $S$ in $L$. It is obtained by

\>RightTransversal( <L>, <S> ) O

and it returns the first element from each right coset obtained by
`RightCosets( <L>, <S> )'.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Translations and sections}

When $x$ is an element of a quasigroup $Q$, the left translation $L_x$ is a
permutation of $Q$. In {\LOOPS}, all permutations associated with quasigroups
and their elements are permutations in the sense of \GAP, i.e., bijections of
some interval $1$, $\dots$, $n$. Moreover, following our convention, the
numerical entries of the permutation point to the positions among elements of
`Parent( <Q> )', not $Q$.

The left and right translations by $x$ in $Q$ are obtained by

\>LeftTranslation( <Q>, <x> ) O
\>RightTranslation( <Q>, <x> ) O

The following two attributes calculate the left and right section of a
quasigroup $Q$:

\>LeftSection( <Q> ) A
\>RightSection( <Q> ) A

Here is an example illustrating the main features of the subquasigroup
construction and the relationship between a quasigroup and its parent.

Note how the Cayley table of the subquasigroup is created only upon explicit
demand. Also note that changing the names of elements of a subquasigroup
(subloop) automatically changes the names of the elements of the parent
subquasigroup (subloop). This is because the elements are shared.

\beginexample
gap> M := MoufangLoop( 12, 1 );; S := Subloop( M, [ M.5 ] );
<loop of order 3>
gap> [ Parent( S ) = M, Elements( S ), PosInParent( S ) ];
[ true, [ l1, l3, l5], [ 1, 3, 5 ] ]
gap> HasCayleyTable( S );
false
gap> SetLoopElmName( S, "s" );; Elements( S ); Elements( M );
[ s1, s3, s5 ]
[ s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12 ]
gap> CayleyTable( S );
[ [ 1, 3, 5 ], [ 3, 5, 1 ], [ 5, 1, 3 ] ]
gap> LeftSection( S );
[ (), (1,3,5), (1,5,3) ]
gap> [ HasCayleyTable( S ), Parent( S ) = M ];
[ true, true ]
gap> L := LoopByCayleyTable( CayleyTable( S ) );; Elements( L );
[ l1, l2, l3 ]
gap> [ Parent( L ) = L, IsSubloop( M, S ), IsSubloop( M, L ) ];
[ true, true, false ]
gap> LeftSection( L );
[ (), (1,2,3), (1,3,2) ]
\endexample

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Multiplication groups}

The left multiplication group, right multiplication group and the
multiplication group of a quasigroup $Q$ are calculated as follows:

\>LeftMultiplicationGroup( <Q> ) A
\>RightMultiplicationGroup( <Q> ) A
\>MultiplicationGroup( <Q> ) A

Let $S$ be a subloop of a loop $L$. Then the <relative left multiplication
group>\index{multiplication group!relative left} of $L$ with respect to $S$ is
the group $\langle L(x)|x\in S\rangle$, where $L(x)$ is the left translation by
$x$ in $Q$ restricted to $S$. The <relative right multiplication
group>\index{multiplication group!relative right} and <relative multiplication
group> \index{multiplication group!relative} are defined analogously.

\>RelativeLeftMultiplicationGroup( <L>, <S> ) O
\>RelativeRightMultiplicationGroup( <L>, <S> ) O
\>RelativeMultiplicationGroup( <L>, <S> ) O

See Section "Realizing groups as multiplication groups of loops" for more on
that topic.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Inner mapping groups}

The <inner mapping group>\index{inner mapping group} of a loop $L$ is the
stabilizer of the unit element in ${\rm{Mlt}}(L)$. The elements of this
stabilizer are called <inner maps> of $L$.

The <left inner mapping group>\index{inner mapping group!left} of a loop $L$ is
the stabilizer of the unit element in ${\rm{LMlt}}(L)$. The <right inner
mapping group>\index{inner mapping group!right} is defined dually.

Equivalently, the left inner mapping group is generated by all <left inner
mappings>\index{inner mapping!left} $L(x,y) = L_{yx}^{-1}L_yL_x$, and the right
inner mapping group is generated by all <right inner mappings>\index{inner
mapping!right} $R(x,y) = R_{xy}^{-1}R_yR_x$.

In analogy with group theory, we define the <conjugation>\index{conjugation},
or the <middle inner mapping>\index{inner mapping!middle} by $x$ as $T(x) =
L_x^{-1}R_x$. The <middle inner mapping grroup>\index{inner mapping
group!middle} is then the subgroup of ${\rm{Mlt}}(L)$ generated by all
conjugations.

The corresponding commands in {\LOOPS} are

\>LeftInnerMapping( <L>, <x>, <y> ) O
\>MiddleInnerMapping( <L>, <x> ) O
\>RightInnerMapping( <L>, <x>, <y> ) O
\>LeftInnerMappingGroup( <L> ) A
\>MiddleInnerMappingGroup( <L> ) A
\>RightInnerMappingGroup( <L> ) A
\>InnerMappingGroup( <L> ) A

Here is an example for multiplication groups and inner mapping groups:

\beginexample
gap> M := MoufangLoop( 12, 1 );
<Moufang loop 12/1>
gap> LeftSection( M )[ 2 ];
(1,2)(3,4)(5,6)(7,8)(9,12)(10,11)
gap> Mlt := MultiplicationGroup( M ); Inn := InnerMappingGroup( M );
<permutation group of size 2592 with 23 generators>
Group([ (4,6)(7,11), (7,11)(8,10), (2,6,4)(7,9,11), (3,5)(9,11), (8,12,10) ])
gap> Size( Inn );
216
\endexample

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Nuclei, commutant, center, and associator subloop}

Let $Q$ be a quasigroup. The <left nucleus>\index{nucleus!left} $N_\lambda(Q)$
of $Q$ is the set $\{x\in Q| x(yz)=(xy)z$ for every $y$, $z\in Q\}$. One
defines similarly the <middle nucleus>\index{nucleus!middle} $N_\mu(Q)$ and the
<right nucleus>\index{nucleus!right} $N_\rho(Q)$. Then the
<nucleus>\index{nucleus} $N(Q)$ of $Q$ is the intersection of the three nuclei.

The nuclei are calculated in {\LOOPS} as follows:

\>LeftNucleus( <Q> ) A
\>MiddleNucleus( <Q> ) A
\>RightNucleus( <Q> ) A
\>Nuc( <Q> ) A

Unfortunately, the word `Nucleus' is reserved in the core of {\GAP} for a
global function with two variables. That is the reason why we have used the
abbreviation `Nuc', which is also common in the literature. However, we support
these synonyms of `Nuc':

\>NucleusOfLoop( <Q> ) A
\>NucleusOfQuasigroup( <Q> ) A

Since all nuclei are subquasigroups of $Q$, they are returned as subquasigroups
(resp. subloops). When $Q$ is a loop then all nuclei are in fact groups, and
they are returned as associative loops.

The <commutant>\index{commutant} $C(Q)$ of $Q$ is the set $\{x\in Q\,|\,xy=yx$
for every $y\in Q\}$. It is obtained via

\>Commutant( <Q> ) A

The center $Z(Q)$ is defined as the intersection of $C(Q)$ and $N(Q)$, and it
is obtained via

\>Center( <Q> ) A

It is a subgroup of $<Q>$ and is therefore returned as an associative loop.

Finally, the <associator
subloop>\index{associator!subloop}\index{subloop!associator} of a loop $L$ is
the smallest normal subloop $A(L)$ of $L$ containing all associators of $L$.
Equivalently, $A(L)$ is the smallest normal subloop $K$ such that $L/K$ is
associative. We use another equivalent reformulation for the purposes of
computation: $A(L)$ is the smallest normal subloop of $L$ containing
$\{x\setminus\alpha(x)\,|\,x\in L,\,\alpha$ is a left inner mapping$\}$.

\>AssociatorSubloop( <L> )  A

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Normal subloops}

A subloop $S$ of a loop $L$ is
<normal>\index{normal!subloop}\index{subloop!normal} if it is invariant under
all inner mappings of $L$. Normality is tested via:

\>IsNormal( <L>, <S> ) O

When $S$ is a subset of a loop $L$ or a subloop of $L$, the <normal
closure>\index{normal!closure} of $S$ in $L$ is the smallest normal subloop of
$L$ containing $S$. It is obtained by

\>NormalClosure( <L>, <S> ) O

A loop $L$ is <simple>\index{simple loop}\index{loop!simple} if all normal
subloops of $L$ are trivial. The corresponding test in {\LOOPS} is:

\>IsSimple( <L> ) O

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Factor loops}

When $N$ is a normal subloop of a loop $L$, the factor loop $L/N$ can be
obtained directly via the command `L/N', or by

\>FactorLoop( <L>, <N> ) O

The natural projection from $L$ to $L/N$ is returned by

\>NaturalHomomorphismByNormalSubloop( <L>, <N> ) O

Here is an illustrating example:

\beginexample
gap> M := MoufangLoop( 12, 1 );; S := Subloop( M, [ M.3 ] );
<loop of order 3>
gap> IsNormal( M, S );
true
gap> F := FactorLoop( M, S );
<loop of order 4>
gap> NaturalHomomorphismByNormalSubloop( M, S );
MappingByFunction( <loop of order 12>, <loop of order 4>,
    function( x ) ... end )
\endexample

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Nilpotency and central series}

The definition of nilpotency and nilpotence class is the same as in group
theory. The corresponding commands are:

\>NilpotencyClassOfLoop( <L> ) A
\>IsNilpotent( <L> ) P

When $L$ is not nilpotent, `NilpotencyClassOfLoop( <L> )' returns `fail'.

A loop $L$ is said to be <strongly nilpotent>\index{strongly nilpotent
loop}\index{nilpotent loop!strongly}\index{loop!strongly nilpotent} if its
multiplication group is nilpotent. This property is obtained by

\>IsStronglyNilpotent( <L> ) P

Let $L$ be a loop. Define <iterated centers>\index{center!iterated} $Z_i(L)$ as
follows: $Z_0(L)=Z(L)$, $Z_{i+1}(L) = \pi_i^{-1}( Z_i(L) )$, where $\pi_i$ is
the canonical projection $L\to L/Z_i(L)$. The longest series $Z_i(L)$,
$Z_{i-1}(L)$, $\dots$, $Z_0(L)$ with $Z_i(L)>Z_{i-1}(L)>\cdots >Z_0(L)$ is
called the <upper central series>\index{central series!upper} of $L$, and is
returned via

\>UpperCentralSeries( <L> ) A

The <lower central series>\index{central series!lower}, defined in the usual
way, is obtained by

\>LowerCentralSeries( <L> ) A

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Solvability}

The definition of solvability, derived subloop, derived length, Frattini
subloop and Frattini factor size is the same as for groups. Frattini subloop is
calculated only for strongly nilpotent loops.

\>IsSolvable( <L> ) P
\>DerivedSubloop( <L> ) A
\>DerivedLength( <L> ) A
\>FrattiniSubloop( <L> ) A
\>FrattinifactorSize( <L> ) A

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Isomorphisms and automorphisms}

All isomorphisms between two loops can be found with {\LOOPS}. The operation

\>IsomorphismLoops( <L>, <M> ) O

returns a single isomorphism between loops $L$, $M$ if the loops are
isomorphic, and `fail' otherwise.

If an isomorphism exists, it is returned as a permutation $p$ of $1$, $\dots$,
$|L|$, where $i^p=j$ means that the $i$th element of $L$ is mapped onto the
$j$th element of $M$. This is true even if $L$ or $M$ are not their own
parents.

Since one frequently needs to filter a list of loops up to isomorphism, we
support

\>LoopsUpToIsomorphism( <ls> ) O

Given a list $ls$ of loops, the operation returns a sublist of $ls$ containing
one loop from each isomorphism class of loops present in $ls$.

The attribute

\>AutomorphismGroup( <L> ) A

returns the automorphism group of the loop $L$, with the same convention on
permutations as in the case of `IsomorphismLoops'.

Since two isomorphisms \"differ\" by an automorphism, all isomorphisms can be
obtained by the above two functions.

While dealing with Cayley tables, it is often useful to rename or reorder the
elements without changing the isomorphism type. When $Q$ is a quasigroup of
size $n$ and $p$ is a permutation of $\{1,\dots,n\}$,

\>IsomorphicCopyByPerm( Q, p )

returns quasigroup $(Q,\circ)$ such that $p(xy) = p(x)\circ p(y)$, i.e.,
$x\circ y = p( p^{-1}(x)p^{-1}(y))$. When $Q$ is a declared loop, a loop is
returned. Consequently, when $Q$ is a declared loop and $p(1) = k\ne 1$, then
$p$ is first replaced by $p\circ (1,k)$, to make sure that the Cayley table is
normalized.

When $S$ is a normal subloop of $L$,

\>IsomorphicCopyByNormalSubloop( L, S )

returns an isomorphic copy of $L$ in which the elements are ordered according
to the right cosets of $S$. In particular, the Cayley table of $S$ will appear
in the top left corner of the Cayley table of the resulting loop.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{How are isomorphisms computed}

In order to speed up the search for isomorphisms and automorphisms, we first
calculate some loop invariants preserved under isomorphisms, and use these
invariants to partition the loop into blocks of elements preserved under
isomorphisms. These invariants for a loop $L$ can be obtained via

\>Discriminator( <L> ) O

Since the details are technical, we will not present them here. See \cite{Vo}
or the file `iso.gi' for more.

If two loops have different discriminators, they are not isomorphic. If they
have identical discriminators, they may or may not be isomorphic. The operation

\>AreEqualDiscriminators( <D1>, <D2> ) O

returns `true' if the discriminators $D1$, $D2$ are equal.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\Section{Isotopisms}

The package contains a slow method for testing if two loops are isotopic:

\>IsotopismLoops( <K>, <L> ) O

It returns `fail' if $K$, $L$ are not isotopic, else it returns an isotopism as
a triple of bijections on $\{1,\dots,|K|\}$.

The method works as follows: It is well known that if a loop $K$ is isotopic to
a loop $L$ then there exist a principal loop isotope $P$ of $K$ such that $P$
is isomorphic to $L$. The algorithm first finds all principal isotopes of $K$,
then filters them up to isomorphism, and then checks if any of them is
isomorphic to $L$. This is rather slow already for small orders, say $30$.

The function

\>LoopsUpToIsotopism( <ls> ) O

filters the list $ls$ in a way similar to `LoopsUpToIsomorphism', but using
isotopism as the underlying equivalence relation.
