Welcome To Our Shell

Mister Spy & Souheyl Bypass Shell

Current Path : /usr/share/gap/doc/ref/

Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64
Upload File :
Current File : //usr/share/gap/doc/ref/chap81.txt

  
  81 An Example – Residue Class Rings
  
  In  this  chapter,  we  give  an example how GAP can be extended by new data
  structures  and  new  functionality.  In order to focus on the issues of the
  implementation, the mathematics in the example chosen is trivial. Namely, we
  will discuss computations with elements of residue class rings ℤ / nℤ.
  
  The  first  attempt is straightforward (see Section 81.1), it deals with the
  implementation  of  the  necessary arithmetic operations. Section 81.2 deals
  with  the  question  why it might be useful to use an approach that involves
  creating  a  new  data structure and integrating the algorithms dealing with
  these  new  GAP  objects into the system. Section 81.3 shows how this can be
  done in our example, and Section 81.4, the question of further compatibility
  of   the   new  objects  with  known  GAP  objects  is  discussed.  Finally,
  Section 81.5  gives  some  hints how to improve the implementation presented
  before.
  
  
  81.1 A First Attempt to Implement Elements of Residue Class Rings
  
  Suppose  we  want to do computations with elements of a ring ℤ / nℤ, where n
  is a positive integer.
  
  First  we  have to decide how to represent the element k + nℤ in GAP. If the
  modulus n is fixed then we can use the integer k. More precisely, we can use
  any  integer k' such that k - k' is a multiple of n. If different moduli are
  likely  to  occur then using a list of the form [ k, n ], or a record of the
  form  rec(  residue  :=  k,  modulus  :=  n  )  is  more appropriate. In the
  following,  let  us  assume  the  list  representation  [  k, n ] is chosen.
  Moreover,  we  decide that the residue k in all such lists satisfies 0 ≤ k <
  n,  i.e., the result of adding two residue classes represented by [ k_1, n ]
  and  [  k_2, n ] (of course with same modulus n) will be [ k, n ] with k_1 +
  k_2 congruent to k modulo n and 0 ≤ k < n.
  
  Now  we  can  implement  the arithmetic operations for residue classes. Note
  that  the result of the mod operator is normalized as required. The division
  by a noninvertible residue class results in fail.
  
    Example  
    gap> resclass_sum := function( c1, c2 )
    >    if c1[2] <> c2[2] then Error( "different moduli" ); fi;
    >    return [ ( c1[1] + c2[1] ) mod c1[2], c1[2] ];
    > end;;
    gap> 
    gap> resclass_diff := function( c1, c2 )
    >    if c1[2] <> c2[2] then Error( "different moduli" ); fi;
    >    return [ ( c1[1] - c2[1] ) mod c1[2], c1[2] ];
    > end;;
    gap> 
    gap> resclass_prod := function( c1, c2 )
    >    if c1[2] <> c2[2] then Error( "different moduli" ); fi;
    >    return [ ( c1[1] * c2[1] ) mod c1[2], c1[2] ];
    > end;;
    gap> 
    gap> resclass_quo := function( c1, c2 )
    >    local quo;
    >    if c1[2] <> c2[2] then Error( "different moduli" ); fi;
    >    quo:= QuotientMod( c1[1], c2[1], c1[2] );
    >    if quo <> fail then
    >      quo:= [ quo, c1[2] ];
    >    fi;
    >    return quo;
    > end;;
  
  
  With these functions, we can in principle compute with residue classes.
  
    Example  
    gap> list:= List( [ 0 .. 3 ], k -> [ k, 4 ] );
    [ [ 0, 4 ], [ 1, 4 ], [ 2, 4 ], [ 3, 4 ] ]
    gap> resclass_sum( list[2], list[4] );
    [ 0, 4 ]
    gap> resclass_diff( list[1], list[2] );
    [ 3, 4 ]
    gap> resclass_prod( list[2], list[4] );
    [ 3, 4 ]
    gap> resclass_prod( list[3], list[4] );
    [ 2, 4 ]
    gap> List( list, x -> resclass_quo( list[2], x ) );
    [ fail, [ 1, 4 ], fail, [ 3, 4 ] ]
  
  
  
  81.2 Why Proceed in a Different Way?
  
  It  depends  on  the  computations  we  intended  to do with residue classes
  whether  or  not  the  implementation  described  in the previous section is
  satisfactory for us.
  
  Probably  we  are mainly interested in more complex data structures than the
  residue  classes themselves, for example in matrix algebras or matrix groups
  over  a  ring  such as ℤ / 4ℤ. For this, we need functions to add, multiply,
  invert  etc. matrices  of residue classes. Of course this is not a difficult
  task, but it requires to write additional GAP code.
  
  And  when  we  have  implemented  the  arithmetic operations for matrices of
  residue  classes,  we  might  be  interested  in  domain  operations such as
  computing  the  order of a matrix group over ℤ / 4ℤ, a Sylow 2 subgroup, and
  so on. The problem is that a residue class represented as a pair [ k, n ] is
  not  regarded  as  a  group  element by GAP. We have not yet discussed how a
  matrix of residue classes shall be represented, but if we choose the obvious
  representation  of  a list of lists of our residue classes then also this is
  not  a  valid group element in GAP. Hence we cannot apply the function Group
  (39.2-1)  to  create  a  group  of residue classes or a group of matrices of
  residue  classes.  This  is  because  GAP assumes that group elements can be
  multiplied  via  the  infix  operator  * (equivalently, via the operation \*
  (31.12-1)).  Note that in fact the multiplication of two lists [ k_1, n ], [
  k_2,  n  ] is defined, but we have [ k_1, n ] * [ k_2, n ] = k_1 * k_2 + n *
  n,  the  standard scalar product of two row vectors of same length. That is,
  the  multiplication with * is not compatible with the function resclass_prod
  introduced  in the previous section. Similarly, ring elements are assumed to
  be  added  via  the infix operator +; the addition of residue classes is not
  compatible with the available addition of row vectors.
  
  What we have done in the previous section can be described as implementation
  of  a  standalone  arithmetic  for  residue  classes.  In  order  to use the
  machinery  of  the  GAP  library  for  creating higher level objects such as
  matrices,  polynomials,  or  domains  over  residue  class rings, we have to
  integrate  this implementation into the GAP library. The key step will be to
  create  a  new  kind  of  GAP  objects.  This  will be done in the following
  sections;  there  we assume that residue classes and residue class rings are
  not   yet   available  in  GAP;  in  fact  they  are  available,  and  their
  implementation is very close to what is described here.
  
  
  81.3 A Second Attempt to Implement Elements of Residue Class Rings
  
  Faced  with  the  problem to implement elements of the rings ℤ / nℤ, we must
  define  the  types  of  these elements as far as is necessary to distinguish
  them from other GAP objects.
  
  As  is  described  in  Chapter 13,  the  type of an object comprises several
  aspects of information about this object; the family determines the relation
  of the object to other objects, the categories determine what operations the
  object  admits,  the  representation  determines  how  an object is actually
  represented, and the attributes describe knowledge about the object.
  
  First  of  all,  we  must  decide  about the family of each residue class. A
  natural  way  to  do  this is to put the elements of each ring ℤ / nℤ into a
  family  of their own. This means that for example elements of ℤ / 3ℤ and ℤ /
  9ℤ  lie  in different families. So the only interesting relation between the
  families  of  two  residue classes is equality; binary arithmetic operations
  with  two  residue  classes  will  be  admissible only if their families are
  equal.  Note that in the naive approach in Section 81.1, we had to take care
  of  different moduli by a check in each function; these checks may disappear
  in the new approach because of our choice of families.
  
  Note  that  we  do  not  need  to tell GAP anything about the above decision
  concerning  the families of the objects that we are going to implement, that
  is,  the  declaration  part  (see 79.19)  of  the  little GAP package we are
  writing  contains  nothing  about  the  distribution of the new objects into
  families.  (The  actual  construction  of  a  family happens in the function
  MyZmodnZ shown below.)
  
  Second,  we  want to describe methods to add or multiply two elements in ℤ /
  nℤ,  and  these  methods  shall  be not applicable to other GAP objects. The
  natural  way to do this is to create a new category in which all elements of
  all rings ℤ / nℤ lie. This is done as follows.
  
    Example  
    gap> DeclareCategory( "IsMyZmodnZObj", IsScalar );
    gap> cat:= CategoryCollections( IsMyZmodnZObj );;
    gap> cat:= CategoryCollections( cat );;
    gap> cat:= CategoryCollections( cat );;
  
  
  So  all elements in the rings ℤ / nℤ will lie in the category IsMyZmodnZObj,
  which is a subcategory of IsScalar (31.14-20). The latter means that one can
  add,  subtract,  multiply  and divide two such elements that lie in the same
  family,  with  the obvious restriction that the second operand of a division
  must  be  invertible.  (The name IsMyZmodnZObj is chosen because IsZmodnZObj
  (14.5-4) is already defined in GAP, for an implementation of residue classes
  that is very similar to the one developed in this manual chapter. Using this
  different name, one can simply enter the GAP code of this chapter into a GAP
  session,  either  interactively  or  by  reading  a file with this code, and
  experiment after each step whether the expected behaviour has been achieved,
  and what is still missing.)
  
  The  next lines of GAP code above create the categories CategoryCollections(
  IsMyZmodnZObj  )  and  two  higher levels of collections categories of this,
  which  will  be  needed  later;  it  is important to create these categories
  before collections of the objects in IsMyZmodnZObj actually arise.
  
  Note   that  the  only  difference  between  DeclareCategory  (79.18-1)  and
  NewCategory  (79.1-1)  is  that  in  a  call to DeclareCategory (79.18-1), a
  variable corresponding to the first argument is set to the new category, and
  this    variable    is   read-only   (see 79.18).   The   same   holds   for
  DeclareRepresentation (79.18-8) and NewRepresentation (79.2-1) etc.
  
  There  is  no  analogue of categories in the implementation in Section 81.1,
  since  there  it was not necessary to distinguish residue classes from other
  GAP objects. Note that the functions there assumed that their arguments were
  residue  classes,  and  the user was responsible not to call them with other
  arguments.  Thus  an  important  aspect of types is to describe arguments of
  functions explicitly.
  
  Third,  we  must  decide  about  the  representation of our objects. This is
  something we know already from Section 81.1, where we chose a list of length
  two.  Here  we  may choose between two essentially different representations
  for  the  new  GAP  objects,  namely  as  component  object (record-like) or
  positional  object  (list-like).  We  decide  to  store  the modulus of each
  residue  class in its family, and to encode the element k + nℤ by the unique
  residue  in  the range [ 0 .. n-1 ] that is congruent to k modulo n, and the
  object  itself  is chosen to be a positional object with this residue at the
  first and only position (see 79.11).
  
    Example  
    gap> DeclareRepresentation("IsMyModulusRep", IsPositionalObjectRep, [1]);
  
  
  The  fourth  ingredients  of  a  type,  attributes,  are  usually  of  minor
  importance  for  element objects. In particular, we do not need to introduce
  special attributes for residue classes.
  
  Having defined what the new objects shall look like, we now declare a global
  function  (see 79.19),  to  create  an  element  when family and residue are
  given.
  
    Example  
    gap> DeclareGlobalFunction( "MyZmodnZObj" );
  
  
  Now we have declared what we need, and we can start to implement the missing
  methods   resp.   functions;   so  the  following  command  belongs  to  the
  implementation part of our package (see 79.19).
  
  The  probably  most  interesting  function is the one to construct a residue
  class.
  
    Example  
    gap> InstallGlobalFunction( MyZmodnZObj, function( Fam, residue )
    >    return Objectify( NewType( Fam, IsMyZmodnZObj and IsMyModulusRep ),
    >                      [ residue mod Fam!.modulus ] );
    > end );
  
  
  Note  that  we  normalize  residue explicitly using mod; we assumed that the
  modulus  is  stored  in Fam, so we must take care of this below. If Fam is a
  family  of  residue  classes, and residue is an integer, MyZmodnZObj returns
  the  corresponding  object  in  the  family  Fam, which lies in the category
  IsMyZmodnZObj and in the representation IsMyModulusRep.
  
  MyZmodnZObj needs an appropriate family as first argument, so let us see how
  to  get  our  hands  on  this.  Of course we could write a handy function to
  create  such  a family for given modulus, but we choose another way. In fact
  we  do not really want to call MyZmodnZObj explicitly when we want to create
  residue  classes. For example, if we want to enter a matrix of residues then
  usually  we  start  with  a matrix of corresponding integers, and it is more
  elegant to do the conversion via multiplying the matrix with the identity of
  the  required  ring ℤ / nℤ; this is also done for the conversion of integral
  matrices  to  finite  field  matrices.  (Note that we will have to install a
  method  for  this.)  So  it is often sufficient to access this identity, for
  example  via  One(  MyZmodnZ(  n  )  ),  where  MyZmodnZ  returns  a  domain
  representing the ring ℤ / nℤ when called with the argument n. We decide that
  constructing  this  ring is a natural place where the creation of the family
  can  be  hidden,  and  implement  the  function.  (Note that the declaration
  belongs  to  the  declaration  part,  and  the  installation  belongs to the
  implementation part, see 79.19).
  
    Example  
    gap> DeclareGlobalFunction( "MyZmodnZ" );
    gap> 
    gap> InstallGlobalFunction( MyZmodnZ, function( n )
    >    local F, R;
    > 
    >    if not IsPosInt( n ) then
    >      Error( "<n> must be a positive integer" );
    >    fi;
    > 
    >    # Construct the family of element objects of our ring.
    >    F:= NewFamily( Concatenation( "MyZmod", String( n ), "Z" ),
    >                   IsMyZmodnZObj );
    > 
    >    # Install the data.
    >    F!.modulus:= n;
    > 
    >    # Make the domain.
    >    R:= RingWithOneByGenerators( [ MyZmodnZObj( F, 1 ) ] );
    >    SetIsWholeFamily( R, true );
    >    SetName( R, Concatenation( "(Integers mod ", String(n), ")" ) );
    > 
    >    # Return the ring.
    >    return R;
    > end );
  
  
  Note that the modulus n is stored in the component modulus of the family, as
  is  assumed  by  MyZmodnZ.  Thus it is not necessary to store the modulus in
  each  element. When storing n with the !. operator as value of the component
  modulus,  we  used  that  all  families are in fact represented as component
  objects (see 79.10).
  
  We  see that we can use RingWithOneByGenerators (56.3-3) to construct a ring
  with  one  if  we  have  the  appropriate  generators.  The construction via
  RingWithOneByGenerators (56.3-3) makes sure that IsRingWithOne (56.3-1) (and
  IsRing (56.1-1)) is true for each output of MyZmodnZ. So the main problem is
  to  create  the  identity element of the ring, which in our case suffices to
  generate  the ring. In order to create this element via MyZmodnZObj, we have
  to construct its family first, at each call of MyZmodnZ.
  
  Also  note that we may enter known information about the ring. Here we store
  that  it  contains  the whole family of elements; this is useful for example
  when we want to check the membership of an element in the ring, which can be
  decided from the type of the element if the ring contains its whole elements
  family.  Giving  a  name  to  the  ring  causes  that it will be printed via
  printing the name. (By the way: This name (Integers mod n) looks like a call
  to  \mod (31.12-1) with the arguments Integers (14) and n; a construction of
  the  ring  via  this call seems to be more natural than by calling MyZmodnZ;
  later  we  shall  install  a  \mod  (31.12-1)  method in order to admit this
  construction.)
  
  Now we can read the above code into GAP, and the following works already.
  
    Example  
    gap> R:= MyZmodnZ( 4 );
    (Integers mod 4)
    gap> IsRing( R );
    true
    gap> gens:= GeneratorsOfRingWithOne( R );
    [ <object> ]
  
  
  But  of course this means just to ask for the information we have explicitly
  stored in the ring. Already the questions whether the ring is finite and how
  many  elements  it  has,  cannot  be  answered  by  GAP. Clearly we know the
  answers,  and  we  could store them in the ring, by setting the value of the
  property  IsFinite  (30.4-2)  to  true  and  the value of the attribute Size
  (30.4-6)  to  n (the argument of the call to MyZmodnZ). If we do not want to
  do so then GAP could only try to find out the number of elements of the ring
  via forming the closure of the generators under addition and multiplication,
  but  up to now, GAP does not know how to add or multiply two elements of our
  ring.
  
  So  we  must install some methods for arithmetic and other operations if the
  elements are to behave as we want.
  
  We  start with a method for showing elements nicely on the screen. There are
  different  operations  for  this  purpose.  One of them is PrintObj (6.3-5),
  which  is  called  for  each  argument in an explicit call to Print (6.3-4).
  Another  one is ViewObj (6.3-5), which is called in the read-eval-print loop
  for  each  object.  ViewObj  (6.3-5)  shall produce short and human readable
  information  about  the  object  in question, whereas PrintObj (6.3-5) shall
  produce  information  that  may be longer and is (if reasonable) readable by
  GAP.  We cannot satisfy the latter requirement for a PrintObj (6.3-5) method
  because  there  is  no  way  to  make a family GAP readable. So we decide to
  display  the  expression  (  k  mod  n  ) for an object that is given by the
  residue  k  and  the  modulus  n,  which  would be fine as a ViewObj (6.3-5)
  method.  Since  the default for ViewObj (6.3-5) is to call PrintObj (6.3-5),
  and  since no other ViewObj (6.3-5) method is applicable to our elements, we
  need only a PrintObj (6.3-5) method.
  
    Example  
    gap> InstallMethod( PrintObj,
    >    "for element in Z/nZ (ModulusRep)",
    >    [ IsMyZmodnZObj and IsMyModulusRep ],
    >    function( x )
    >    Print( "( ", x![1], " mod ", FamilyObj(x)!.modulus, " )" );
    >    end );
  
  
  So  we  installed  a  method  for  the  operation  PrintObj  (6.3-5)  (first
  argument),  and we gave it a suitable information message (second argument),
  see 7.2-1  and 7.3  for  applications  of this information string. The third
  argument tells GAP that the method is applicable for objects that lie in the
  category  IsMyZmodnZObj  and  in  the representation IsMyModulusRep. and the
  fourth  argument  is  the  method  itself.  More details about InstallMethod
  (78.2-1) can be found in 78.2.
  
  Note  that  the  requirement  IsMyModulusRep for the argument x allows us to
  access the residue as x![1]. Since the family of x has the component modulus
  bound  if  it  is  constructed by MyZmodnZ, we may access this component. We
  check whether the method installation has some effect.
  
    Example  
    gap> gens;
    [ ( 1 mod 4 ) ]
  
  
  Next  we  install  methods  for  the comparison operations. Note that we can
  assume that the residues in the representation chosen are normalized.
  
    Example  
    gap> InstallMethod( \=,
    >    "for two elements in Z/nZ (ModulusRep)",
    >    IsIdenticalObj,
    >    [IsMyZmodnZObj and IsMyModulusRep, IsMyZmodnZObj and IsMyModulusRep],
    >    function( x, y ) return x![1] = y![1]; end );
    gap> 
    gap> InstallMethod( \<,
    >    "for two elements in Z/nZ (ModulusRep)",
    >    IsIdenticalObj,
    >    [IsMyZmodnZObj and IsMyModulusRep, IsMyZmodnZObj and IsMyModulusRep],
    >    function( x, y ) return x![1] < y![1]; end );
  
  
  The  third  argument  used  in  these  installations  specifies the required
  relation  between the families of the arguments (see 13.1). This argument of
  a  method  installation,  if present, is a function that shall be applied to
  the  families  of  the  arguments.  IsIdenticalObj  (12.5-1)  means that the
  methods  are  applicable  only if both arguments lie in the same family. (In
  installations  for unary methods, obviously no relation is required, so this
  argument is left out there.)
  
  Up  to  now,  we  see  no  advantage  of  the  new  approach over the one in
  Section 81.1.  For  a  residue  class represented as [ k, n ], the way it is
  printed  on  the  screen is sufficient, and equality and comparison of lists
  are  good  enough  to  define  equality and comparison of residue classes if
  needed.  But  this  is  not the case in other situations. For example, if we
  would  have  decided that the residue k need not be normalized then we would
  have  needed  functions  in  Section 81.1  that  compute whether two residue
  classes  are  equal,  and which of two residue classes is regarded as larger
  than  another. Note that we are free to define what larger means for objects
  that are newly introduced.
  
  Next  we  install  methods  for  the  arithmetic  operations,  first for the
  additive structure.
  
    Example  
    gap> InstallMethod( \+,
    >    "for two elements in Z/nZ (ModulusRep)",
    >    IsIdenticalObj,
    >    [IsMyZmodnZObj and IsMyModulusRep, IsMyZmodnZObj and IsMyModulusRep],
    >    function( x, y )
    >    return MyZmodnZObj( FamilyObj( x ), x![1] + y![1] );
    >    end );
    gap> 
    gap> InstallMethod( ZeroOp,
    >    "for element in Z/nZ (ModulusRep)",
    >    [ IsMyZmodnZObj ],
    >    x -> MyZmodnZObj( FamilyObj( x ), 0 ) );
    gap> 
    gap> InstallMethod( AdditiveInverseOp,
    >    "for element in Z/nZ (ModulusRep)",
    >    [ IsMyZmodnZObj and IsMyModulusRep ],
    >    x -> MyZmodnZObj( FamilyObj( x ), AdditiveInverse( x![1] ) ) );
  
  
  Here  the  new  approach  starts to pay off. The method for the operation \+
  (31.12-1)  allows  us  to  use the infix operator + for residue classes. The
  method  for  ZeroOp  (31.10-3)  is  used  when we call this operation or the
  attribute  Zero  (31.10-3)  explicitly, and ZeroOp (31.10-3) it is also used
  when we ask for 0 * rescl, where rescl is a residue class.
  
  (Note that Zero (31.10-3) and ZeroOp (31.10-3) are distinguished because 0 *
  obj  is  guaranteed to return a mutable result whenever a mutable version of
  this  result  exists  in  GAP  –for example if obj is a matrix– whereas Zero
  (31.10-3)  is  an attribute and therefore returns immutable results; for our
  example  there  is  no  difference  since  the  residue  classes  are always
  immutable,  nevertheless we have to install the method for ZeroOp (31.10-3).
  The  same  holds  for  AdditiveInverse (31.10-9), One (31.10-2), and Inverse
  (31.10-8).)
  
  Similarly,  AdditiveInverseOp (31.10-9) can be either called directly or via
  the  unary - operator; so we can compute the additive inverse of the residue
  class rescl as -rescl.
  
  It  is  not  necessary  to  install  methods  for subtraction, since this is
  handled  via  addition  of the additive inverse of the second argument if no
  other method is installed.
  
  Let us try what we can do with the methods that are available now.
  
    Example  
    gap> x:= gens[1];  y:= x + x;
    ( 1 mod 4 )
    ( 2 mod 4 )
    gap> 0 * x;  -x;
    ( 0 mod 4 )
    ( 3 mod 4 )
    gap> y = -y;  x = y;  x < y;  -x < y;
    true
    false
    true
    false
  
  
  We  might  want  to admit the addition of integers and elements in rings ℤ /
  nℤ,  where an integer is implicitly identified with its residue modulo n. To
  achieve  this,  we  install  methods  to  add  an  integer  to  an object in
  IsMyZmodnZObj from the left and from the right.
  
    Example  
    gap> InstallMethod( \+,
    >    "for element in Z/nZ (ModulusRep) and integer",
    >    [ IsMyZmodnZObj and IsMyModulusRep, IsInt ],
    >    function( x, y )
    >    return MyZmodnZObj( FamilyObj( x ), x![1] + y );
    >    end );
    gap> 
    gap> InstallMethod( \+,
    >    "for integer and element in Z/nZ (ModulusRep)",
    >    [ IsInt, IsMyZmodnZObj and IsMyModulusRep ],
    >    function( x, y )
    >    return MyZmodnZObj( FamilyObj( y ), x + y![1] );
    >    end );
  
  
  Now we can do also the following.
  
    Example  
    gap> 2 + x;  7 - x;  y - 2;
    ( 3 mod 4 )
    ( 2 mod 4 )
    ( 0 mod 4 )
  
  
  Similarly  we install the methods dealing with the multiplicative structure.
  We  need methods to multiply two of our objects, and to compute identity and
  inverse.  The  operation  OneOp (31.10-2) is called when we ask for rescl^0,
  and  InverseOp  (31.10-8)  is called when we ask for rescl^-1. Note that the
  method  for  InverseOp  (31.10-8)  returns  fail  if  the  argument  is  not
  invertible.
  
    Example  
    gap> InstallMethod( \*,
    >    "for two elements in Z/nZ (ModulusRep)",
    >    IsIdenticalObj,
    >    [IsMyZmodnZObj and IsMyModulusRep, IsMyZmodnZObj and IsMyModulusRep],
    >    function( x, y )
    >    return MyZmodnZObj( FamilyObj( x ), x![1] * y![1] );
    >    end );
    gap> 
    gap> InstallMethod( OneOp,
    >    "for element in Z/nZ (ModulusRep)",
    >    [ IsMyZmodnZObj ],
    >    elm -> MyZmodnZObj( FamilyObj( elm ), 1 ) );
    gap> 
    gap> InstallMethod( InverseOp,
    >    "for element in Z/nZ (ModulusRep)",
    >    [ IsMyZmodnZObj and IsMyModulusRep ],
    >    function( elm )
    >    local residue;
    >    residue:= QuotientMod( 1, elm![1], FamilyObj( elm )!.modulus );
    >    if residue <> fail then
    >      residue:= MyZmodnZObj( FamilyObj( elm ), residue );
    >    fi;
    >    return residue;
    >    end );
  
  
  To  be  able to multiply our objects with integers, we need not (but we may,
  and  we should if we are going for efficiency) install special methods. This
  is  because  in general, GAP interprets the multiplication of an integer and
  an additive object as abbreviation of successive additions, and there is one
  generic  method  for  such a multiplication that uses only additions and –in
  the  case  of  a negative integer– taking the additive inverse. Analogously,
  there  is  a  generic  method  for  powering  by  integers  that  uses  only
  multiplications and taking the multiplicative inverse.
  
  Note  that  we  could also interpret the multiplication with an integer as a
  shorthand  for  the  multiplication with the corresponding residue class. We
  are  lucky  that  this  interpretation  is  compatible  with the one that is
  already available. If this would not be the case then of course we would get
  into  trouble  by  installing  a  concurrent  multiplication  that  computes
  something  different  from the multiplication that is already defined, since
  GAP  does  not  guarantee which of the applicable methods is actually chosen
  (see 78.3).
  
  Now  we  have  implemented  methods  for  the  arithmetic operations for our
  elements, and the following calculations work.
  
    Example  
    gap> y:= 2 * x;  z:= (-5) * x;
    ( 2 mod 4 )
    ( 3 mod 4 )
    gap> y * z;  y * y;
    ( 2 mod 4 )
    ( 0 mod 4 )
    gap> y^-1;  y^0;
    fail
    ( 1 mod 4 )
    gap> z^-1;
    ( 3 mod 4 )
  
  
  There  are  some  other  operations  in  GAP  that we may want to accept our
  elements  as  arguments.  An  example  is  the  operation  Int (14.2-3) that
  returns,  e.g.,  the  integral  part  of  a  rational  number or the integer
  corresponding to an element in a finite prime field. For our objects, we may
  define that Int (14.2-3) returns the normalized residue.
  
  Note  that  we  define  this  behaviour for elements but we implement it for
  objects  in  the  representation  IsMyModulusRep. This means that if someone
  implements  another  representation of residue classes then this person must
  be  careful  to  implement  Int  (14.2-3)  methods  for  objects in this new
  representation compatibly with our definition, i.e., such that the result is
  independent of the representation.
  
    Example  
    gap> InstallMethod( Int,
    >    "for element in Z/nZ (ModulusRep)",
    >    [ IsMyZmodnZObj and IsMyModulusRep ],
    >    z -> z![1] );
  
  
  Another  example of an operation for which we might want to install a method
  is  \mod  (31.12-1).  We make the ring print itself as Integers (14) mod the
  modulus,  and  then it is reasonable to allow a construction this way, which
  makes the PrintObj (6.3-5) output of the ring GAP readable.
  
    Example  
    gap> InstallMethod( PrintObj,
    >    "for full collection Z/nZ",
    >    [ CategoryCollections( IsMyZmodnZObj ) and IsWholeFamily ],
    >    function( R )
    >    Print( "(Integers mod ",
    >           ElementsFamily( FamilyObj(R) )!.modulus, ")" );
    >    end );
    gap> 
    gap> InstallMethod( \mod,
    >    "for `Integers', and a positive integer",
    >    [ IsIntegers, IsPosRat and IsInt ],
    >    function( Integers, n ) return MyZmodnZ( n ); end );
  
  
  Let us try this.
  
    Example  
    gap> Int( y );
    2
    gap> Integers mod 1789;
    (Integers mod 1789)
  
  
  Probably  it  is  not  necessary  to  emphasize  that  with  the approach of
  Section 81.1,  installing  methods  for  existing  operations is usually not
  possible  or  at least not recommended. For example, installing the function
  resclass_sum defined in Section 81.1 as a \+ (31.12-1) method for adding two
  lists  of length two (with integer entries) would not be compatible with the
  general definition of the addition of two lists of same length. Installing a
  method for the operation Int (14.2-3) that takes a list [ k, n ] and returns
  k  would in principle be possible, since there is no Int (14.2-3) method for
  lists  yet,  but  it is not sensible to do so because one can think of other
  interpretations of such a list where different Int (14.2-3) methods could be
  installed with the same right.
  
  As mentioned in Section 81.2, one advantage of the new approach is that with
  the implementation we have up to now, automatically also matrices of residue
  classes can be treated.
  
    Example  
    gap> r:= Integers mod 16;
    (Integers mod 16)
    gap> x:= One( r );
    ( 1 mod 16 )
    gap> mat:= IdentityMat( 2 ) * x;
    [ [ ( 1 mod 16 ), ( 0 mod 16 ) ], [ ( 0 mod 16 ), ( 1 mod 16 ) ] ]
    gap> mat[1][2]:= x;;
    gap> mat;
    [ [ ( 1 mod 16 ), ( 1 mod 16 ) ], [ ( 0 mod 16 ), ( 1 mod 16 ) ] ]
    gap> Order( mat );
    16
    gap> mat + mat;
    [ [ ( 2 mod 16 ), ( 2 mod 16 ) ], [ ( 0 mod 16 ), ( 2 mod 16 ) ] ]
    gap> last^4;
    [ [ ( 0 mod 16 ), ( 0 mod 16 ) ], [ ( 0 mod 16 ), ( 0 mod 16 ) ] ]
  
  
  Such  matrices,  if  they  are  invertible, are valid as group elements. One
  technical  problem  is that the default algorithm for inverting matrices may
  give  up  since  Gaussian  elimination  need  not  be  successful over rings
  containing  zero  divisors.  Therefore  we  install a simpleminded inversion
  method that inverts an integer matrix.
  
    Example  
    gap> InstallMethod( InverseOp,
    >    "for an ordinary matrix over a ring Z/nZ",
    >    [ IsMatrix and IsOrdinaryMatrix
    >      and CategoryCollections( CategoryCollections( IsMyZmodnZObj ) ) ],
    >    function( mat )
    >    local one, modulus;
    > 
    >    one:= One( mat[1][1] );
    >    modulus:= FamilyObj( one )!.modulus;
    >    mat:= InverseOp( List( mat, row -> List( row, Int ) ) );
    >    if mat <> fail then
    >      mat:= ( mat mod modulus ) * one;
    >    fi;
    >    if not IsMatrix( mat ) then
    >      mat:= fail;
    >    fi;
    >    return mat;
    >    end );
  
  
  Additionally  we  install  a  method  for finding a domain that contains the
  matrix entries; this is used by some GAP library functions.
  
    Example  
    gap> InstallMethod( DefaultFieldOfMatrixGroup,
    >     "for a matrix group over a ring Z/nZ",
    >     [ IsMatrixGroup and CategoryCollections( CategoryCollections(
    >           CategoryCollections( IsMyZmodnZObj ) ) ) ],
    >     G -> RingWithOneByGenerators([ One( Representative( G )[1][1] ) ]));
  
  
  Now we can deal with matrix groups over residue class rings.
  
    Example  
    gap> mat2:= IdentityMat( 2 ) * x;;
    gap> mat2[2][1]:= x;;
    gap> g:= Group( mat, mat2 );;
    gap> Size( g );
    3072
    gap> Factors( last );
    [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3 ]
    gap> syl3:= SylowSubgroup( g, 3 );;
    gap> gens:= GeneratorsOfGroup( syl3 );
    [ [ [ ( 1 mod 16 ), ( 7 mod 16 ) ], [ ( 11 mod 16 ), ( 14 mod 16 ) ] 
         ] ]
    gap> Order( gens[1] );
    3
  
  
  It should be noted that this way more involved methods for matrix groups may
  not  be  available.  For example, many questions about a finite matrix group
  can  be  delegated  to  an isomorphic permutation group via a so-called nice
  monomorphism;     this     can     be     controlled     by    the    filter
  IsHandledByNiceMonomorphism (40.5-1).
  
  By  the  way, also groups of (invertible) residue classes can be formed, but
  this may be of minor interest.
  
    Example  
    gap> g:= Group( x );;  Size( g );
    #I  default `IsGeneratorsOfMagmaWithInverses' method returns `true' for 
    [ ( 1 mod 16 ) ]
    1
    gap> g:= Group( 3*x );;  Size( g );
    #I  default `IsGeneratorsOfMagmaWithInverses' method returns `true' for 
    [ ( 3 mod 16 ) ]
    4
  
  
  (The  messages  above  tell  that  GAP  does  not know a method for deciding
  whether  the  given  elements  are  valid  group  elements.  We could add an
  appropriate IsGeneratorsOfMagmaWithInverses method if we would want.)
  
  Having  done  enough  for the elements, we may install some more methods for
  the  rings  if we want to use them as arguments. These rings are finite, and
  there  are  many  generic methods that will work if they are able to compute
  the list of elements of the ring, so we install a method for this.
  
    Example  
    gap> InstallMethod( Enumerator,
    >    "for full collection Z/nZ",
    >    [ CategoryCollections( IsMyZmodnZObj ) and IsWholeFamily ],
    >    function( R )
    >    local F;
    >    F:= ElementsFamily( FamilyObj(R) );
    >    return List( [ 0 .. Size( R ) - 1 ], x -> MyZmodnZObj( F, x ) );
    >    end );
  
  
  Note  that  this  method is applicable only to full rings ℤ / nℤ, for proper
  subrings  it  would  return  a wrong result. Furthermore, it is not required
  that  the  argument is a ring; in fact this method is applicable also to the
  additive  group formed by all elements in the family, provided that it knows
  to contain the whole family.
  
  Analogously,  we  install methods to compute the size, a random element, and
  the units of full rings ℤ / nℤ.
  
    Example  
    gap> InstallMethod( Random,
    >    "for full collection Z/nZ",
    >    [ CategoryCollections( IsMyZmodnZObj ) and IsWholeFamily ],
    >    R -> MyZmodnZObj( ElementsFamily( FamilyObj(R) ),
    >                    Random( [ 0 .. Size( R ) - 1 ] ) ) );
    gap> 
    gap> InstallMethod( Size,
    >    "for full ring Z/nZ",
    >    [ CategoryCollections( IsMyZmodnZObj ) and IsWholeFamily ],
    >    R -> ElementsFamily( FamilyObj(R) )!.modulus );
    gap> 
    gap> InstallMethod( Units,
    >    "for full ring Z/nZ",
    >    [     CategoryCollections( IsMyZmodnZObj )
    >      and IsWholeFamily and IsRing ],
    >    function( R )
    >    local F;
    >    F:= ElementsFamily( FamilyObj( R ) );
    >    return List( PrimeResidues( Size(R) ), x -> MyZmodnZObj( F, x ) );
    >    end );
  
  
  The  Units  (56.5-2) method has the disadvantage that the result is returned
  as  a list (in fact this list is also strictly sorted). We could improve the
  implementation  by returning the units as a group; if we do not want to take
  the   full  list  of  elements  as  generators,  we  can  use  the  function
  GeneratorsPrimeResidues (15.2-4).
  
    Example  
    gap> InstallMethod( Units,
    >    "for full ring Z/nZ",
    >    [     CategoryCollections( IsMyZmodnZObj )
    >      and IsWholeFamily and IsRing ],
    >    function( R )
    >    local G, gens;
    > 
    >    gens:= GeneratorsPrimeResidues( Size( R ) ).generators;
    >    if not IsEmpty( gens ) and gens[ 1 ] = 1 then
    >      gens:= gens{ [ 2 .. Length( gens ) ] };
    >    fi;
    >    gens:= Flat( gens ) * One( R );
    >    return GroupByGenerators( gens, One( R ) );
    >    end );
  
  
  Each  ring ℤ / nℤ is finite, and we could install a method that returns true
  when IsFinite (30.4-2) is called with ℤ / nℤ as argument. But we can do this
  more elegantly via installing a logical implication.
  
    Example  
    gap> InstallTrueMethod( IsFinite,
    >    CategoryCollections( IsMyZmodnZObj ) and IsDomain );
  
  
  In  effect,  every  domain  that  consists of elements in IsMyZmodnZObj will
  automatically  store  that  it  is  finite, even if IsFinite (30.4-2) is not
  called for it.
  
  
  81.4 Compatibility of Residue Class Rings with Prime Fields
  
  The  above  implementation of residue classes and residue class rings has at
  least  two  disadvantages. First, if p is a prime then the ring ℤ / pℤ is in
  fact a field, but the return values of MyZmodnZ are never regarded as fields
  because  they are not in the category IsMagmaWithInversesIfNonzero (35.1-3).
  Second,  and  this  makes  the example really interesting, there are already
  elements  of  finite  prime  fields  implemented  in GAP, and we may want to
  identify them with elements in ℤ / pℤ.
  
  To  be  more  precise,  elements of finite fields in GAP lie in the category
  IsFFE  (59.1-1),  and  there  is already a representation, IsInternalRep, of
  these  elements  via discrete logarithms. The aim of this section is to make
  IsMyModulusRep  an  alternative  representation  of elements in finite prime
  fields.
  
  Note  that  this is only one step towards the desired compatibility. Namely,
  after  having a second representation of elements in finite prime fields, we
  may  wish  that  the  function  GF  (59.3-2) (which is the usual function to
  create finite fields in GAP) is able to return MyZmodnZ( p ) when GF( p ) is
  called  for  a  prime  p.  Moreover,  then we have to decide about a default
  representation  of  elements  in  GF(  p  )  for  primes  p  for  which both
  representations are available. Of course we can force the new representation
  by  explicitly  calling MyZmodnZ and MyZmodnZObj whenever we want, but it is
  not a priori clear in which situation which representation is preferable.
  
  The same questions will occur when we want to implement a new representation
  for  non-prime  fields. The steps of this implementation will be the same as
  described  in  this  chapter, and we will have to achieve compatibility with
  both  the internal representation of elements in small finite fields and the
  representation IsMyModulusRep of elements in arbitrary prime fields.
  
  But  let  us  now turn back to the task of this section. We first adjust the
  setup  of  the declaration part of the previous section, and then repeat the
  installations with suitable modifications.
  
  (We  should  start  a  new GAP session for that, otherwise GAP will complain
  that the objects to be declared are already bound; additionally, the methods
  installed above may be not compatible with the ones we want.)
  
    Example  
    gap> DeclareCategory( "IsMyZmodnZObj", IsScalar );
    gap> 
    gap> DeclareCategory( "IsMyZmodnZObjNonprime", IsMyZmodnZObj );
    gap> 
    gap> DeclareSynonym( "IsMyZmodpZObj", IsMyZmodnZObj and IsFFE );
    gap> 
    gap> DeclareRepresentation( "IsMyModulusRep", IsPositionalObjectRep, [ 1 ] );
    gap> 
    gap> DeclareGlobalFunction( "MyZmodnZObj" );
    gap> 
    gap> DeclareGlobalFunction( "MyZmodnZ" );
  
  
  As  in the previous section, all (newly introduced) elements of rings ℤ / nℤ
  lie  in  the category IsMyZmodnZObj. But now we introduce two subcategories,
  namely IsMyZmodnZObjNonprime for all elements in rings ℤ / nℤ where n is not
  a  prime, and IsMyZmodpZObj for elements in finite prime fields. All objects
  in  the latter are automatically known to lie in the category IsFFE (59.1-1)
  of finite field elements.
  
  It  would be reasonable if also those internally represented elements in the
  category  IsFFE (59.1-1) that do in fact lie in a prime field would also lie
  in  the category IsMyZmodnZObj (and thus in fact in IsMyZmodpZObj). But this
  cannot  be  achieved because internally represented finite field elements do
  in general not store whether they lie in a prime field.
  
  As  for  the implementation part, again let us start with the definitions of
  MyZmodnZObj and MyZmodnZ.
  
    Example  
    gap> InstallGlobalFunction( MyZmodnZObj, function( Fam, residue )
    >    if IsFFEFamily( Fam ) then
    >      return Objectify( NewType( Fam, IsMyZmodpZObj
    >                                  and IsMyModulusRep ),
    >                    [ residue mod Characteristic( Fam ) ] );
    >    else
    >      return Objectify( NewType( Fam, IsMyZmodnZObjNonprime
    >                                  and IsMyModulusRep ),
    >                    [ residue mod Fam!.modulus ] );
    >    fi;
    > end );
    
    gap> InstallGlobalFunction( MyZmodnZ, function( n )
    >    local F, R;
    > 
    >    if not ( IsInt( n ) and IsPosRat( n ) ) then
    >      Error( "<n> must be a positive integer" );
    >    elif IsPrimeInt( n ) then
    >      # Construct the family of element objects of our field.
    >      F:= FFEFamily( n );
    >      # Make the domain.
    >      R:= FieldOverItselfByGenerators( [ MyZmodnZObj( F, 1 ) ] );
    >      SetIsPrimeField( R, true );
    >    else
    >      # Construct the family of element objects of our ring.
    >      F:= NewFamily( Concatenation( "MyZmod", String( n ), "Z" ),
    >                     IsMyZmodnZObjNonprime );
    >      # Install the data.
    >      F!.modulus:= n;
    >      # Make the domain.
    >      R:= RingWithOneByGenerators( [ MyZmodnZObj( F, 1 ) ] );
    >      SetIsWholeFamily( R, true );
    >      SetName( R, Concatenation( "(Integers mod ",String(n),")" ) );
    >    fi;
    > 
    >    # Return the ring resp. field.
    >    return R;
    > end );
  
  
  Note  that  the  result of MyZmodnZ with a prime as argument is a field that
  does  not  contain  the whole family of its elements, since all finite field
  elements of a fixed characteristic lie in the same family. Further note that
  we  cannot  expect  a  family  of  finite field elements to have a component
  modulus,  so  we  use Characteristic (31.10-1) to get the modulus. Requiring
  that  Fam!.modulus  works  also  if Fam is a family of finite field elements
  would  violate the rule that an extension of GAP should not force changes in
  existing  code,  in  this  case  code  dealing with families of finite field
  elements.
  
    Example  
    gap> InstallMethod( PrintObj,
    >    "for element in Z/nZ (ModulusRep)",
    >    [ IsMyZmodnZObjNonprime and IsMyModulusRep ],
    >    function( x )
    >    Print( "( ", x![1], " mod ", FamilyObj(x)!.modulus, " )" );
    >    end );
    gap> 
    gap> InstallMethod( PrintObj,
    >    "for element in Z/pZ (ModulusRep)",
    >    [ IsMyZmodpZObj and IsMyModulusRep ],
    >    function( x )
    >    Print( "( ", x![1], " mod ", Characteristic(x), " )" );
    >    end );
    gap> 
    gap> InstallMethod( \=,
    >    "for two elements in Z/nZ (ModulusRep)",
    >    IsIdenticalObj,
    >    [ IsMyZmodnZObj and IsMyModulusRep,
    >      IsMyZmodnZObj and IsMyModulusRep ],
    >    function( x, y ) return x![1] = y![1]; end );
  
  
  The  above  method to check equality is independent of whether the arguments
  have  a  prime  or  nonprime  modulus,  so  we installed it for arguments in
  IsMyZmodnZObj.   Now   we   install  also  methods  to  compare  objects  in
  IsMyZmodpZObj with the old finite field elements.
  
    Example  
    gap> InstallMethod( \=,
    >    "for element in Z/pZ (ModulusRep) and internal FFE",
    >    IsIdenticalObj,
    >    [ IsMyZmodpZObj and IsMyModulusRep, IsFFE and IsInternalRep ],
    >    function( x, y )
    >    return DegreeFFE( y ) = 1 and x![1] = IntFFE( y );
    >    end );
    gap> 
    gap> InstallMethod( \=,
    >    "for internal FFE and element in Z/pZ (ModulusRep)",
    >    IsIdenticalObj,
    >    [ IsFFE and IsInternalRep, IsMyZmodpZObj and IsMyModulusRep ],
    >    function( x, y )
    >    return DegreeFFE( x ) = 1 and IntFFE( x ) = y![1];
    >    end );
  
  
  The  situation with the operation < is more difficult. Of course we are free
  to  define  the  comparison of objects in IsMyZmodnZObjNonprime, but for the
  finite field elements, the comparison must be compatible with the predefined
  comparison  of  the  old  finite  field  elements.  The  definition of the <
  comparison  of  internally represented finite field elements can be found in
  Chapter 59.  In  situations  where  the  documentation  does not provide the
  required  information,  one  has to look it up in the GAP code; for example,
  the  comparison in our case can be found in the appropriate source code file
  of the GAP kernel.
  
    Example  
    gap> InstallMethod( \<,
    >    "for two elements in Z/nZ (ModulusRep, nonprime)",
    >    IsIdenticalObj,
    >    [ IsMyZmodnZObjNonprime and IsMyModulusRep,
    >      IsMyZmodnZObjNonprime and IsMyModulusRep ],
    >    function( x, y ) return x![1] < y![1]; end );
    gap> 
    gap> InstallMethod( \<,
    >    "for two elements in Z/pZ (ModulusRep)",
    >    IsIdenticalObj,
    >    [ IsMyZmodpZObj and IsMyModulusRep,
    >      IsMyZmodpZObj and IsMyModulusRep ],
    >    function( x, y )
    >    local p, r;      # characteristic and primitive root
    >    if x![1] = 0 then
    >      return y![1] <> 0;
    >    elif y![1] = 0 then
    >      return false;
    >    else
    >      p:= Characteristic( x );
    >      r:= PrimitiveRootMod( p );
    >      return LogMod( x![1], r, p ) < LogMod( y![1], r, p );
    >    fi;
    >    end );
    gap> 
    gap> InstallMethod( \<,
    >    "for element in Z/pZ (ModulusRep) and internal FFE",
    >    IsIdenticalObj,
    >    [ IsMyZmodpZObj and IsMyModulusRep, IsFFE and IsInternalRep ],
    >    function( x, y )
    >    return x![1] * One( y ) < y;
    >    end );
    gap> 
    gap> InstallMethod( \<,
    >    "for internal FFE and element in Z/pZ (ModulusRep)",
    >    IsIdenticalObj,
    >    [ IsFFE and IsInternalRep, IsMyZmodpZObj and IsMyModulusRep ],
    >    function( x, y )
    >    return x < y![1] * One( x );
    >    end );
  
  
  Now  we install the same methods for the arithmetic operations \+ (31.12-1),
  ZeroOp  (31.10-3),  AdditiveInverseOp (31.10-9), \-, \* (31.12-1), and OneOp
  (31.10-2)  as  in the previous section, without listing them below. Also the
  same  Int  (14.2-3)  method  is installed for objects in IsMyZmodnZObj. Note
  that  it  is compatible with the definition of Int (14.2-3) for finite field
  elements. And of course the same method for \mod (31.12-1) is installed.
  
  We have to be careful, however, with the methods for InverseOp (31.10-8), \/
  (31.12-1),  and  \^  (31.12-1).  These  methods  and the missing methods for
  arithmetic  operations  with one argument in IsMyModulusRep and the other in
  IsInternalRep are given below.
  
    Example  
    gap> InstallMethod( \+,
    >    "for element in Z/pZ (ModulusRep) and internal FFE",
    >    IsIdenticalObj,
    >    [ IsMyZmodpZObj and IsMyModulusRep, IsFFE and IsInternalRep ],
    >    function( x, y ) return x![1] + y; end );
    gap> 
    gap> InstallMethod( \+,
    >    "for internal FFE and element in Z/pZ (ModulusRep)",
    >    IsIdenticalObj,
    >    [ IsFFE and IsInternalRep, IsMyZmodpZObj and IsMyModulusRep ],
    >    function( x, y ) return x + y![1]; end );
    gap> 
    gap> InstallMethod( \*,
    >    "for element in Z/pZ (ModulusRep) and internal FFE",
    >    IsIdenticalObj,
    >    [ IsMyZmodpZObj and IsMyModulusRep, IsFFE and IsInternalRep ],
    >    function( x, y ) return x![1] * y; end );
    gap> 
    gap> InstallMethod( \*,
    >    "for internal FFE and element in Z/pZ (ModulusRep)",
    >    IsIdenticalObj,
    >    [ IsFFE and IsInternalRep, IsMyZmodpZObj and IsMyModulusRep ],
    >    function( x, y ) return x * y![1]; end );
    gap> 
    gap> InstallMethod( InverseOp,
    >    "for element in Z/nZ (ModulusRep, nonprime)",
    >    [ IsMyZmodnZObjNonprime and IsMyModulusRep ],
    >    function( x )
    >    local residue;
    >    residue:= QuotientMod( 1, x![1], FamilyObj(x)!.modulus );
    >    if residue <> fail then
    >      residue:= MyZmodnZObj( FamilyObj(x), residue );
    >    fi;
    >    return residue;
    >    end );
    gap> 
    gap> InstallMethod( InverseOp,
    >    "for element in Z/pZ (ModulusRep)",
    >    [ IsMyZmodpZObj and IsMyModulusRep ],
    >    function( x )
    >    local residue;
    >    residue:= QuotientMod( 1, x![1], Characteristic( FamilyObj(x) ) );
    >    if residue <> fail then
    >      residue:= MyZmodnZObj( FamilyObj(x), residue );
    >    fi;
    >    return residue;
    >    end );
  
  
  The  operation  DegreeFFE  (59.2-1) is defined for finite field elements, we
  need  a  method  for objects in IsMyZmodpZObj. Note that we need not require
  IsMyModulusRep since no access to representation dependent data occurs.
  
    Example  
    gap> InstallMethod( DegreeFFE,
    >    "for element in Z/pZ",
    >    [ IsMyZmodpZObj ],
    >    z -> 1 );
  
  
  The  methods  for  Enumerator  (30.3-2), Random (30.7-1), Size (30.4-6), and
  Units  (56.5-2),  that  we  had  installed  in  the previous section had all
  assumed  that  their  argument contains the whole family of its elements. So
  these  methods  make  sense  only for the nonprime case. For the prime case,
  there are already methods for these operations with argument a field.
  
    Example  
    gap> InstallMethod( Enumerator,
    >    "for full ring Z/nZ",
    >    [ CategoryCollections( IsMyZmodnZObjNonprime ) and IsWholeFamily ],
    >    function( R )
    >    local F;
    >    F:= ElementsFamily( FamilyObj( R ) );
    >    return List( [ 0 .. Size( R ) - 1 ], x -> MyZmodnZObj( F, x ) );
    >    end );
    gap> 
    gap> InstallMethod( Random,
    >    "for full ring Z/nZ",
    >    [ CategoryCollections( IsMyZmodnZObjNonprime ) and IsWholeFamily ],
    >    R -> MyZmodnZObj( ElementsFamily( FamilyObj( R ) ),
    >                    Random( [ 0 .. Size( R ) - 1 ] ) ) );
    gap> 
    gap> InstallMethod( Size,
    >    "for full ring Z/nZ",
    >    [ CategoryCollections( IsMyZmodnZObjNonprime ) and IsWholeFamily ],
    >    R -> ElementsFamily( FamilyObj( R ) )!.modulus );
    gap> 
    gap> InstallMethod( Units,
    >    "for full ring Z/nZ",
    >    [     CategoryCollections( IsMyZmodnZObjNonprime )
    >      and IsWholeFamily and IsRing ],
    >    function( R )
    >    local G, gens;
    > 
    >    gens:= GeneratorsPrimeResidues( Size( R ) ).generators;
    >    if not IsEmpty( gens ) and gens[ 1 ] = 1 then
    >      gens:= gens{ [ 2 .. Length( gens ) ] };
    >    fi;
    >    gens:= Flat( gens ) * One( R );
    >    return GroupByGenerators( gens, One( R ) );
    >    end );
    gap> 
    gap> InstallTrueMethod( IsFinite,
    >    CategoryCollections( IsMyZmodnZObjNonprime ) and IsDomain );
  
  
  
  81.5 Further Improvements in Implementing Residue Class Rings
  
  There are of course many possibilities to improve the implementation.
  
  With  the  setup as described above, subsequent calls MyZmodnZ( n ) with the
  same  n  yield  incompatible  rings  in  the sense that elements of one ring
  cannot  be  added to elements of an other one. The solution for this problem
  is  to  keep  a  global  list  of all results of MyZmodnZ in the current GAP
  session,  and  to return the stored values whenever possible. Note that this
  approach  would  admit  PrintObj  (6.3-5)  methods that produce GAP readable
  output.
  
  One  can  improve  the Units (56.5-2) method for the full ring in such a way
  that  a  group  is  returned  and  not only a list of its elements; then the
  result  of  Units  (56.5-2)  can  be used, e. g., as input for the operation
  SylowSubgroup (39.13-1).
  
  To  make  computations  more  efficient,  one can install methods for \-, \/
  (31.12-1), and \^ (31.12-1); one reason for doing so may be that this avoids
  the  unnecessary  construction of the additive or multiplicative inverse, or
  of intermediate powers.
  
    Example  
    InstallMethod( \-, "two elements in Z/nZ (ModulusRep)", ... );
    InstallMethod( \-, "Z/nZ-obj. (ModulusRep) and integer", ... );
    InstallMethod( \-, "integer and Z/nZ-obj. (ModulusRep)", ... );
    InstallMethod( \-, "Z/pZ-obj. (ModulusRep) and internal FFE", ... );
    InstallMethod( \-, "internal FFE and Z/pZ-obj. (ModulusRep)", ... );
    InstallMethod( \*, "Z/nZ-obj. (ModulusRep) and integer", ... );
    InstallMethod( \*, "integer and Z/nZ-obj. (ModulusRep)", ... );
    InstallMethod( \/, "two Z/nZ-objs. (ModulusRep, nonprime)", ... );
    InstallMethod( \/, "two Z/pZ-objs. (ModulusRep)", ... );
    InstallMethod( \/, "Z/nZ-obj. (ModulusRep) and integer", ... );
    InstallMethod( \/, "integer and Z/nZ-obj. (ModulusRep)", ... );
    InstallMethod( \/, "Z/pZ-obj. (ModulusRep) and internal FFE", ... );
    InstallMethod( \/, "internal FFE and Z/pZ-obj. (ModulusRep)", ... );
    InstallMethod( \^, "Z/nZ-obj. (ModulusRep, nonprime) & int.", ... );
    InstallMethod( \^, "Z/pZ-obj. (ModulusRep), and integer", ... );
  
  
  The  call  to  NewType (79.8-1) in MyZmodnZObj can be avoided by storing the
  required  type,  e.g.,  in the family. But note that it is not admissible to
  take the type of an existing object as first argument of Objectify (79.9-1).
  For  example,  suppose  two objects in IsMyZmodnZObj shall be added. Then we
  must  not  use  the  type  of  one  of  the arguments in a call of Objectify
  (79.9-1),  because  the  argument may have knowledge that is not correct for
  the  result  of  the addition. One may think of the property IsOne (31.10-5)
  that may hold for both arguments but certainly not for their sum.
  
  For  comparing two objects in IsMyZmodpZObj via <, we had to install a quite
  expensive  method because of the compatibility with the comparison of finite
  field  elements  that  did already exist. In fact GAP supports finite fields
  with  elements  represented via discrete logarithms only up to a given size.
  So in principle we have the freedom to define a cheaper comparison via < for
  objects in IsMyZmodpZObj if the modulus is large enough. This is possible by
  introducing  two categories IsMyZmodpZObjSmall and IsMyZmodpZObjLarge, which
  are  subcategories  of  IsMyZmodpZObj, and to install different \< (31.11-1)
  methods for pairs of objects in these categories.
  

bypass 1.0, Devloped By El Moujahidin (the source has been moved and devloped)
Email: contact@elmoujehidin.net bypass 1.0, Devloped By El Moujahidin (the source has been moved and devloped) Email: contact@elmoujehidin.net