##############################################################################
##
#A  coho4.g                     March 2000                      Derek Holt
##
##  Converted from the GAP 3.4.4 verion, Derek Holt 2000/03/10.
##  Changes are minimal - SplitExtension renamed SplitExtensionCHR
##
#A  @(#)$Id: coho.g,v 1.1.1.1 1996/12/11 12:41:54 werner Exp $
##
#H  $Log: coho.g,v $
#H  Revision 1.1.1.1  1996/12/11 12:41:54  werner
#H  Preparing 3.4.4 for release
#H
#H  Revision 3.1  1996/02/07  13:08:26  sam
#H  fixed calls of 'LogFFE' (specify root now)
#H
#H  Revision 3.0  1996/02/07  13:02:46  sam
#H  initial revision under RCS
#H
##
##  This file contains the interface to my cohomology `C' programs.
##
DeclareInfoClass("InfoCohomolo");

#############################################################################
##
#V  COHOMOLO . . . . . . . . . . . . . . . . . . . . . . . .  global variable
##
COHOMOLO := rec(
                 Multiplier := [],
                 CoDim1     := 0,
                 CoDim2     := 0,
                 RelVals    := [],
                 PermRels   := [],
                 RM_F       := "rm -f ",
                 CALL       := DirectoriesPackagePrograms("cohomolo")
                 #This is the directory of the external executables
                );

#############################################################################
##
#F  COHOMOLO.WritePermGroup( <G>, <deg>, <base>, <filename> )
##
##  <G> should be permutation group of degree <deg> and with base <base>.
##  The group is written to the file <filename> in the format of the
##  cohomology programs.
##
COHOMOLO.WritePermGroup := function ( G, deg, base, filename)

   local  line, gens, ng, nb, perm, i, j;

   gens := GeneratorsOfGroup(G);
   ng := Length(gens);
   nb := Length(base);

   #We output line by line, storing the data in each line in "line".
   line := Concatenation(String(deg)," ",String(ng)," ",String(nb)," 0\n");
   PrintTo(filename,line);
   line := "";
   for i in [1..nb] do
     line := Concatenation(line," ",String(base[i]));
     if Length(line)>70 then
         line := Concatenation(line,"\n");
         AppendTo(filename,line);
         line := "";
     fi;
   od;
   line := Concatenation(line,"\n");
   AppendTo(filename,line);
   for i in [1..ng] do
     perm := gens[i];
     line := "";
     for j in [1..deg] do
         line := Concatenation(line," ",String(j^perm));
         if Length(line)>70 then
             line := Concatenation(line,"\n");
             AppendTo(filename,line);
             line := "";
         fi;
     od;
     line := Concatenation(line,"\n");
     AppendTo(filename,line);
   od;
end;

#############################################################################
##
#F  COHOMOLO.WriteFPGroup( <G>, <filename> )
##
##  <G> should be a finitely presented group.
##  The group is written to the file <filename> in the format of the
##  cohomology programs.
##
COHOMOLO.WriteFPGroup := function ( G, filename)
   local  line, gens, genstring, igens, allgens, ng, rels, nr, LL, LG, ILG, ALG,
          w, i, j, ct, gno, lgno;

   gens := FreeGeneratorsOfFpGroup (G);
   ng := Length(gens);
   rels := RelatorsOfFpGroup(G);
   nr := Length(rels);
   igens := List(gens, x -> x^-1);
   allgens := Concatenation(gens,igens);
   LL := "abcdefghijklmnopqrstuvwxyz";
   LG := List( [ 1 .. ng ],  x -> [LL[x]] );
   ILG := List(LG, x -> Concatenation(x,"-"));
   ALG := Concatenation(LG,ILG);
   #lower case letters will be used as the generator names in the printout.

   line := Concatenation(String(ng)," 0 ",String(nr),"\n");
   PrintTo(filename,line);
   line := "";
   genstring := List( [ 1 .. ng ],  x -> LL[x] );
   line := Concatenation(genstring,"\n");
   AppendTo(filename,line);
   for i in [1..nr] do
      line := "";
      w := rels[i];
      lgno := 0; ct := 0;
      for j in [1..Length(w)] do
        gno := Position(allgens,Subword(w,j,j));
        if gno = lgno then
           ct := ct+1; #current power of this generator
        else
           if ct>1 then line := Concatenation(line,String(ct)); fi;
           ct := 1;
           line := Concatenation(line,ALG[gno]);
           lgno := gno;
        fi;
      od;
      if ct>1 then line := Concatenation(line,String(ct)); fi;
      line := Concatenation(line,"\n");
      AppendTo(filename,line);
   od;
   AppendTo(filename,"n\n");
end;

#############################################################################
##
#F  COHOMOLO.WriteMatrices( <mats>, <filename> )
##
##  <mats> should be a list of square matrices, all of same dimension
##  over the field of order p, for some prime p.
##  They are written to the file <filename> in the format of the
##  cohomology programs.
##
COHOMOLO.WriteMatrices := function ( mats, filename)
   local  line, f, w, p, nm, dim, mat, row, entry, table, n, i, j, k;

   f := Field(Flat(mats));
   p := Size( f );
   w := Z(p);

   #We first make ourselves a little table to convert field entries to integers.
   table := [];
   for i in [1..p-1] do
     table[LogFFE(i*w^0,w)+1] := i;
   od;
   nm := Length(mats);
   dim := Length(mats[1]);
   line := Concatenation(String(p)," ",String(dim)," ",String(nm),"\n");
   PrintTo(filename,line);
   for i in [1..nm] do
     AppendTo(filename,"\n");
     mat := mats[i];
     for j in [1..dim] do
        row := mat[j];
        line := "";
        for k in [1..dim] do
          entry := row[k];
          if entry = 0*w then n := 0; else n := table[LogFFE(entry,w)+1]; fi;
          line := Concatenation(line," ",String(n));
          if Length(line)>70 then
              line := Concatenation(line,"\n");
              AppendTo(filename,line);
              line := "";
          fi;
        od;
        line := Concatenation(line,"\n");
        AppendTo(filename,line);
     od;
   od;
end;


#############################################################################
##
#F  CHR( <G>, <p>, [<F>], [<mats>] ) . . . . . . make a cohomology record
##
##  This function makes a cohomology record. It should be called before
##  using any of the main cohomology functions.
##  <G> must be a permutation group, and <p> a prime (ususally dividing the
##  order of <G>).
##  <F> must either be 0 (if not required, but <mats> is), or a finitely
##  presented group with the same number of generators as <G> and mapping
##  epimorphically onto <G>. (This will be checked.)
##  (To obtain useful results, <F> would normally be isomorphic to <G>.)
##  <mats> (if present) should be a list of matrices, all of the same
##  dimension, over the field of order <p>.
##  The length of the list must be the same as the number of generators of
##  <G>, and the matrices must define a module for <G> over the field of
##  order <p>.
##  Apart from checking validity, a Sylow <p>-subgroup of <G> is calculated.
##  The record (with various components set) is returned.
##
CHR := function ( arg )
   local  chr, na, G, p, F, mats, Ggens, Fgens, ng, deg, dim, error, mat, rels,
          w, i, d;
   na := Number(arg);

   if na<2 or na>4 then
      Error("Number of arguments to CHR must be 2, 3 or 4");
   fi;
   G:=arg[1]; p:=arg[2]; F:=0; mats:=0;
   if na >= 3 then F:=arg[3]; fi;
   if na = 4 then mats:=arg[4]; fi;
   
   chr := rec();
   chr.isCHR := true;

   #First check arguments have correct types
   if not IsPermGroup(G) then
      Error("First argument of CHR must be a permutation group");
   fi;
   chr.permgp := G;
   Ggens := GeneratorsOfGroup(G);
   ng := Length(Ggens);
   if not IsInt(p) or not IsPrime(p) then
      Error("Second argument of CHR must be a prime number");
   fi;
   chr.prime := p;
   if not IsGroup(F) and F<>0 then
      Error("Third argument of CHR must be a finitely presented group or 0");
   fi;
   if IsGroup(F) then
      if not IsFpGroup(F) then
        Error("Third argument of CHR must be a finitely presented group");
      fi;
      Fgens := FreeGeneratorsOfFpGroup(F);
      if Length(Fgens)<>ng then 
        Error(
          "Arguments <G> and <F> of CHR must have same numbers of generators");
      fi;
   fi;
   chr.fpgp := F;
   error := false;
   if mats<>0 then
     if not IsList(mats) or Length(mats)<>ng then
       error := true;
     else
       for i in [1..ng] do
         mat := mats[i];
         if not IsMatrix(mat) or Field(Flat(mat))<>GF(p) then
            error := true;
         else
            d := DimensionsMat(mat);
            if i=1 then dim := d[1]; fi;
            if d[1]<>dim or d[2]<>dim or RankMat(mat)<>dim then
               error := true;
            fi;
         fi;
       od;
     fi;
     if error then
       Error(
     "Fourth argument of CHR must be a list of invertible matrices over GF(p)");
     fi;
   fi;
   chr.matrices := mats;

   #If F is present we now check that the permutations satisfy the relations,
   #and if mats is also present, we check that the matrices also satisfy them.
   if  IsGroup(F) then
      rels := RelatorsOfFpGroup(F);
      for w in rels do
         if MappedWord(w,Fgens,Ggens) <> () then
            Print("Relator: ",w,"\n");
            Error("This relator of F is not satisfied by the generators of G");
         fi;
         if mats<>0 and MappedWord(w,Fgens,mats) <> IdentityMat(dim,GF(p)) then
           Error("A relator of F is not satisfied by the matrices");
         fi;
      od;
   fi;

   #Now calculate some other fields - first degree of group.
   deg := 0;
   for i in Ggens do
      if i<>() and LargestMovedPointPerm(i)>deg then
         deg := LargestMovedPointPerm(i);
      fi;
   od;
   if deg > 4096 then
     Error(
  "Sorry - the cohomology programs are restricted to groups of degree <= 4096");
   fi;
   chr.degree := deg;
   #Next a base, and then a Sylow p-subgroup - and that's it for now!
   chr.base := BaseOfGroup(G);
   chr.sylow := SylowSubgroup(G,p);

   chr.verbose := false;
   
   return chr;
end;

#############################################################################
##
#F  IsCHR( x ) . . . . . . . . checks if the argument is a cohomology record.
##
IsCHR := function(x)
   return IsRecord(x) and IsBound(x.isCHR) and x.isCHR=true;
end;

#############################################################################
##
#F  FindSubSeq( <chr>, <n> ) . . . . . . find a subgroup chain
##
##  <chr> should be a cohomology record, and n a positive integer.
##  Let G=chr.permgp and P=chr.sylow.
##  FindSubSeq calculates a sequence [S1,...,St] of subgroups of G, where
##  Si > S(i+1) S1 = <G>,  S2 = <P> and the indices are a ssmall as possible.
##  It works, by trying to find a subgroup S2 of G with small index, and then
##  repeating with S2 in place of G.
##  The parameter <n> indicates how hard it should try.
##  It selects S2 as soon as it has found an S2 with index <= <n>.
##  The sequence is stored as chr.chain.
##
FindSubSeq := function ( chr, n )
   local  G, P, p, deg, seq, H, N, foundsub, orb, norbs, orbdone,
          sylind, bestind, ind, badind,
          orbsize, bestsub, orbno, orbstab, Z, OZ, OZG, centels, cent,
          el, elno, i, a, b, c;

   if not IsCHR(chr) then
     Error("First argument of FindSubSeq must be a cohomology record");
   fi;
   if IsBound(chr.chain) then
      return; #Must have been called already.
   fi;
   G := chr.permgp;  P := chr.sylow;  deg := chr.degree; p := chr.prime;
   seq := [G];
   sylind := Index(G,P);
   if sylind = 1 then
      chr.chain := seq;
      return;
   fi;

   #We will be trying to find subgroups of P as stabilizers of orbits of P,
   # so we first calculate the orbits.
   if sylind >= n then  
     orb := List(Orbits(P,[1..deg]),AsSet);
     norbs := Length(orb);
     orbdone := [];
     for i in [1..norbs] do
        orbdone[i] := false; # this means orbit number i is not yet processed.
     od;
   fi;

   #We also might try centralizers of central elements, so let's find
   #the centre of P, and then find a small random set of elements
   #of order p.
   Z := Centre(P);
   OZG := [];
   for el in GeneratorsOfGroup(Z) do
     Add(OZG,el^(Order(el)/p));
   od;
   OZ := Subgroup(Z,OZG);
   if IsCyclic(OZ) then
      centels := [OZG[1]];
   elif Length(OZG) = 2 then
     a := OZG[1]; b := OZG[2];
     if p=2 then centels:=[a,b,a*b]; elif p=3 then centels:=[a,b,a*b,a*b^2];
       elif p=5 then centels:=[a,b,a*b,a*b^2,a*b^3,a*b^4];
       else centels:=[a,b,a*b,a*b^2,a*b^3,a*b^4,a*b^5,a*b^6];
     fi;
   else
     a := OZG[1]; b := OZG[2]; c := OZG[3];
     centels := [a,b,c,a*b,a*c,b*c,a*b*c];
   fi;

   #Now we start looking for subgroups.
   H := G;
   foundsub := true;
   while sylind >= n and foundsub do
      foundsub := false;
      bestind := sylind; #The smallest index we have so far.

      #First try the normalizer of P
      N := Normalizer(H,P);
      if H <> N and P <> N then
        ind :=  Index(H,N);
        if ind < n then
           #Found a suitable subgroup!!
           H := N; Add(seq,H);
           foundsub := true; sylind := sylind/ind;
        else
           bestind := ind;  bestsub := N;
        fi;
      fi;

      #We next look for a suitable subgroup as a stabilizer of an orbit
      #of P - we consider the orbits in order of increasing size - we
      #go only up to size 16, since it takes too long to calculate set
      #stabilizers for large sets.
      orbsize := 1;
      while not foundsub and orbsize <= 16 do
         orbno := 1;
         while not foundsub and orbno <= norbs do
            if not orbdone[orbno] and Length(orb[orbno]) = orbsize then
               if orb[orbno] = Set(Orbit(H,orb[orbno][1])) then
                  orbstab := H;
               else
                  orbstab := Stabilizer(H,orb[orbno],OnSets);
               fi;
               if orbstab = H or orbstab = P then
                  orbdone[orbno] := true;
               else
                  ind := Index(H,orbstab);
                  if ind < n then
                     #Found a suitable subgroup!!
                     H := orbstab; Add(seq,H);
                     foundsub := true; sylind := sylind/ind;
                     orbdone[orbno] := true;
                  else   
                     #We could try the nomalizer!
                     N := Normalizer(H,orbstab);
                     if N<>H and N<>orbstab then
                        ind := Index(H,N);
                        orbstab := N;
                        if ind < n then
                           #Found a suitable subgroup!!
                           H := N; Add(seq,H);
                           foundsub := true; sylind := sylind/ind;
                        fi;
                     fi;
                     if ind < bestind then
                       #Not ideal, but the best we have so far, so remember it!
                       bestind := ind;  bestsub := orbstab;
                     fi;
                  fi;
               fi;
            fi;
            orbno := orbno + 1;
         od;
         orbsize := orbsize+1;
      od;

      if not foundsub then
         #Orbit stabilizers wasn't satisfactory, so we'll try
         #centralizers of central elements of P - 
         #We have already collected a few such elements of order p.
         elno := 1;
         while  not foundsub and elno <= Length(centels) do
            el := centels[elno];
            cent := Centralizer(H,el);
            if cent <> H and cent <> P then
              ind := Index(H,cent);
              if ind < n then
                 #Found a suitable subgroup!!
                 H := cent; Add(seq,H);
                 foundsub := true; sylind := sylind/ind;
              else   
                  #We could try the normalizer!
                  N := Normalizer(H,cent);
                  if N<>H and N<>cent then
                     ind := Index(H,N);
                     cent := N;
                     if ind < n then
                        #Found a suitable subgroup!!
                        H := N; Add(seq,H);
                        foundsub := true; sylind := sylind/ind;
                     fi;
                  fi;
                  if ind < bestind then
                 #Not ideal, but the best we have so far, so remember it!
                     bestind := ind;  bestsub := cent;
                  fi;
              fi;
            fi;
            elno := elno+1;
         od;
      fi;

      if not foundsub and bestind < sylind then
         #We make do with the best one we found!
         H := bestsub; Add(seq,H);
         foundsub := true; sylind := sylind/bestind;
      fi;
   od;

   N := Normalizer(H,P);
   if H <> N and P <> N then
     Add(seq,N);
   fi;

   Add(seq,P);

   if InfoLevel(InfoCohomolo)>=1 then
     Print("#I  Indices in the subgroup chain are:  ");
   fi;
   badind := false;
   for i in [1..Length(seq)-1] do
     ind := Index(seq[i],seq[i+1]);
     if ind>50000 then badind := true; fi;
     if InfoLevel(InfoCohomolo)>=1 then
       Print(Index(seq[i],seq[i+1])," ");
     fi;
   od;
   if InfoLevel(InfoCohomolo)>=1 then
     Print("\n");
   fi;

   if badind then
     Print(
  "#WARNING: An index in the subgroup chain found is larger than 50000.\n");
     Print(
            "#This calculation may fail. See manual for possible remedies.\n");
   fi;

   chr.chain := seq;
end;

#############################################################################
##
#F  Cohomolo( <chr>, <mult>, <pres>, <first>, <filename> )
##             . . . . perform cohomology calculation using external package.
##
##  This is the routine that calls the external routines to carry out the
##  cohomology calculations. It is not intended to be called directly, but
##  it is called by the access functions which follow.
##  <chr> must be a cohomology record, created with CHR.
##  <mult> is true for multiplier calculations, otherwise false
##         (and when false, chr.matrices must be defined).
##  <pres> is true if presentations of extensions are to be defined,
##         (and when true, chr.fpgp must be defined).
##  <first> is true for first cohomology group computation, in which case
##         mult and pres must be false.
##  <filename> is the name of the base of the files to be created for use of
##       the external package. The files have names of form <filename>.<suffix>.
##
Cohomolo := function( chr, mult, pres, first, filename )
   local deg, G, P, p, base, lc, nint, ch, F, mats, i, optstring, callstring,
         ct, ok;

   if not IsCHR(chr) then
     Error("First argument of Cohomolo must be a cohomology record");
   fi;

   F := chr.fpgp; mats := chr.matrices;
   if not mult and mats=0 then
     Error("Cohomolo: if mult is false, matrices field must be defined");
   fi;
   if first and mats=0 then
     Error("Cohomolo: if first is true, matrices field must be defined");
   fi;
   if first and (mult or pres) then
     Error("Cohomolo: if first is true, mult and pres must be false");
   fi;
   if pres and not IsFpGroup(F) then
     Error("Cohomolo: if pres is true, fpgp field must be defined");
   fi;

   G := chr.permgp; P := chr.sylow; deg := chr.degree; p := chr.prime;
   #Check that P really is a Sylow-subgroup for safety.
   if Set(FactorsInt(Size(P))) <> [p] or p in FactorsInt(Index(G,P)) then
      Error("Cohomolo: sylow field of <chr> is not a Sylow-subgroup!");
   fi;
   #First check for trivial cases.
   if IsTrivial(P) then
     Print(
"The Sylow p-subgroup of the group is trivial - so all cohomology is trivial.\n"
     );
     if mats<>0 then chr.codim1 := 0; chr.codim2 := 0; fi;
     chr.multiplier := [];
     if IsFpGroup(F) then chr.multrelvals := []; fi;
     if IsFpGroup(F) and mats<>0 then chr.modrelvals:=[]; fi;
     return;
   fi;
   if mult and IsCyclic(P) then
     Print(
"The Sylow p-subgroup of the group is cyclic - so the multiplier is trivial.\n"
     );
     chr.multiplier := []; chr.multrelvals := [];
     return;
   fi;
   
   FindSubSeq(chr,20);

   #We will check the validity of the chain for safety - 
   #just in case the user may have supplied it!

   ct := 1; ok := true;
   ch := chr.chain; lc := Length(ch);
   while ok and ct <= lc do
     if ct=1 and ch[ct]<>G  then ok := false;
     elif ct=lc and ch[ct]<>P then ok := false;
     elif ct<lc and (not IsSubgroup(ch[ct],ch[ct+1]) or ch[ct]=ch[ct+1]) then
        ok := false;
     fi;
     ct := ct+1;
   od;
   if not ok then
      Error(
    "Cohomolo: <chr>.chain must be a strictly decreasing chain from G to P.");
   fi;

   ## chr.norm is true iff the penultimate member of the chain normalises P.
   ## chr.neqg is true iff the chain has length<=2 and P is normal in G.
   chr.norm := lc > 1 and IsNormal(ch[lc-1],ch[lc]);
   chr.neqg := lc=1 or (lc=2 and chr.norm);
   #Now write things to files -
   #but first make sure there is no rubbish left from a previous run.
   Exec( Concatenation( COHOMOLO.RM_F, filename, ".*" ) );
   base := chr.base;
   COHOMOLO.WritePermGroup(G,deg,base,Concatenation(filename,".inperm"));
   if lc > 1 then
     COHOMOLO.WritePermGroup(P,deg,base,Concatenation(filename,".gp"));
     if chr.norm then
       COHOMOLO.WritePermGroup(ch[lc-1],deg,base,Concatenation(filename,".gn"));
       nint := lc-3;
     else
       nint := lc-2;
     fi;
     if nint > 0 then
        for i in [1..nint] do
           COHOMOLO.WritePermGroup(ch[nint+2-i],deg,base,
                                      Concatenation(filename,".g",String(i)));
        od;
     fi;
   fi;

   if pres then
      COHOMOLO.WriteFPGroup(F,Concatenation(filename,".tc"));
      PrintTo(Concatenation(filename,".sg.rel"),Length(base),"\n");
      #The last is a technicality to avoid a silly problem.
   fi;
   if pres and not mult then
      #Write input for the external program nqip. This is the number of
      #the basis of H^2(G,M) that is to be used for corestriction and
      #calculation of an extension. This number always starts at 1.
      #If H^2(G,M) has dimension>1,then we will repeat later with higher values.
      PrintTo(Concatenation(filename,".nqip"),1,"\n");
   fi;

   if not mult then
      COHOMOLO.WriteMatrices(mats,Concatenation(filename,".inmat"));
   fi;
   
   #Now work out the command for the external program.
   callstring := Filename(COHOMOLO.CALL, "cohomology.gap");
   optstring := "-";
   if mult        then Add( optstring, 'm'); fi;
   if pres        then Add( optstring, 'c'); fi;
   if first       then Add( optstring, '1'); fi;
   if chr.norm    then Add( optstring, 'n'); fi;
   if chr.neqg    then Add( optstring, 'e'); fi;
   if chr.verbose then Add( optstring, 'v'); fi;
   if 1 < Length( optstring ) then
      callstring := Concatenation(callstring," ", optstring );
   fi;
   callstring:= Concatenation( callstring,
                               " ", String( chr.prime ), " ", filename );
   if chr.verbose then Print(callstring,"\n"); fi;

   Info(InfoCohomolo,1," Cohomolo package: Calling external program." );
   Exec( callstring );
   Info(InfoCohomolo,1," External program complete." );

   if mult then

      if not READ( Concatenation( filename, ".mult" ) ) then
        Error( "'Cohomolo' failed for some reason.\n" );
      fi;
      chr.multiplier := COHOMOLO.Multiplier;

   elif READ(Concatenation(filename,".cdim")) then
      if first then chr.codim1 := COHOMOLO.CoDim1;
      else  chr.codim2 := COHOMOLO.CoDim2;
      fi;
   else
     Error( "'Cohomolo' failed for some reason.\n" );
   fi;

   if pres then
      if mult then
        if chr.multiplier=[] then chr.multrelvals := [];
        elif READ(Concatenation(filename,".rvals")) then
           chr.multrelvals := COHOMOLO.RelVals;
        else
           Error( "'Cohomolo' failed for some reason.\n" );
        fi;
      else
         if chr.codim2=0 then chr.modrelvals := []; 
         elif READ(Concatenation(filename,".rvals")) then
           chr.modrelvals := [COHOMOLO.RelVals];
         else
           Error( "'Cohomolo' failed for some reason.\n" );
         fi;
      fi;
   fi;

   if pres and not mult and chr.codim2>1 then

     #We have to calculate the other extensions for the basis of H^2(G,M).
      Add( optstring, 'r' );
      callstring := Filename(COHOMOLO.CALL, "cohomology.gap");
      callstring := Concatenation(callstring," ",
                      optstring, " ", String(chr.prime), " ", filename );
            
      for i in [2..chr.codim2] do
         PrintTo(Concatenation(filename,".nqip"),i,"\n");
         if chr.verbose then Print(callstring,"\n"); fi;

         Info(InfoCohomolo,1,
                    " Cohomolo package: Calling external program." );
         Exec( callstring );
         Info(InfoCohomolo,1," External program complete." );

         if not READ(Concatenation(filename,".rvals")) then
           Error( "'Cohomolo' failed for some reason.\n" );
         fi;
         chr.modrelvals[i] := COHOMOLO.RelVals;
      od;
   fi;

   Info(InfoCohomolo,1," Removing temporary files." );
   ##Exec( Concatenation( COHOMOLO.RM_F, filename, ".*" ) );
end;

#############################################################################
##
#F  CalcExtPres( <chr>, <mult>, [<vec>] ) .  calculate presentation of extension
##
##  <chr> must be a cohomology record.
##  Let <chr>.permgp=G, <chr>.prime=p  and  <chr>.fpgp =F. 
##  CalcExtPres calculates a presentation of an extension of M by F,
##  where M is either the p-part of the Schur multiplier of G, when <mult> is
##  true, or the F-module over GF(p) defined by <chr>.matrices, when <mult>
##  is false.
##  In the first case, Cohomolo must already have been called on <chr> with
##  <mult> and <pres> true, and the presentation is of a p-cover of G
##  (assuming F and G are isomorphic).
##  In the second case, <vec> must be a list of integers, and either <vec>=[],
##  in which case the split extension is returned, or Cohomolo must already
##  been called already with <mult> false and <pres> true, and the length of
##  <vec> must be equal to <chr>.codim2. The extension returned is the one
##  corresponding to the element <vec> of H^2(G,M).
##  This function is not intended to be called directly - it is accessed by
##  the individual functions, and by ExtPresentation.
##
CalcExtPres := function( arg )
    local chr, mult, vec, E, EG, p, F, ng, codim, mp, dim, rels, rel, relval, w,
          mats, row, modrv, multrv, f, table, rt, i, j, k, Erels;

   chr := arg[1]; mult := arg[2];
   if (mult and Number(arg)<>2) or (not mult and Number(arg)<>3) then
      Error("Wrong number of arguments to CalcExtPres");
   fi; 
   if not mult then
      vec := arg[3];
      if not IsList(vec) then
         Error("Third argument of CalcExtPres must be a list");
      fi;
   fi;
   if not IsCHR(chr) then
     Error("First argument of CalcExtPres must be a cohomology record");
   fi;

   F := chr.fpgp; mats := chr.matrices; p := chr.prime;
   if not mult and mats=0 then
     Error("CalcExtPres: if mult is false, matrices field must be defined");
   fi;
   if not IsFpGroup(F) then
     Error("CalcExtPres: if pres is true, fpgp field must be defined");
   fi;
   if mult then
      if not IsBound(chr.multiplier) or not IsBound(chr.multrelvals) then
         Error("CalcExtPres: Cohomolo must be run before CalcExtPres");
      fi;
      multrv := chr.multrelvals;
      mp := chr.multiplier;
      dim := Length(mp);
   else
      if Length(vec)<>0 then
         if not IsBound(chr.codim2) or not IsBound(chr.modrelvals) then
            Error("CalcExtPres: Cohomolo must be run before CalcExtPres");
         fi;
         codim := chr.codim2;
         if  Length(vec)<>codim then
            Error("CalcExtPres: Length of third argument wrong");
         fi;
         modrv := chr.modrelvals;
      fi;
      #We make ourselves a little table to convert field entries to integers.
      f := Field(Flat(mats));
      rt := Z(Size(f));
      table := [];
      for i in [1..p-1] do
        table[LogFFE(i*rt^0,rt)+1] := i;
      od;
      dim := Length(mats[1]);
   fi;

   #Now we can start to build up the extension E
   ng := Length(FreeGeneratorsOfFpGroup(F));
   E := FreeGroup(ng+dim);
   EG := GeneratorsOfGroup(E);
   Erels := [];
   #First the relators that say the normal subgroup is finite abelian.
   for i in [1..dim-1] do for j in [i+1..dim] do
      Add(Erels,Comm(EG[j+ng],EG[i+ng]));
   od; od;
   for i in [1..dim] do
      if mult then j:=mp[i]; else j:=p; fi;
      Add(Erels,EG[i+ng]^j);
   od;
   #Now the module action relators
   for i in [1..ng] do for j in [1..dim] do
      w := EG[i]^-1*EG[ng+j]*EG[i];
      if mult then w := w/EG[ng+j];
      else
         row := mats[i][j];
         for k in [1..dim] do
           if row[k]<>0*rt then
             w := w/EG[ng+k]^table[LogFFE(row[k],rt)+1];
           fi;
         od;
      fi;
      Add(Erels,w);
   od; od;
   #And finally the relators, that tell us the values of the relators of
   #F in the normal subgroup.
   rels := RelatorsOfFpGroup(F);
   for i in [1..Length(rels)] do
      rel := rels[i];
      w := MappedWord(rel,FreeGeneratorsOfFpGroup(F),EG{[1..ng]});
      if mult then
         if mp=[] then relval := [];
         else relval :=  multrv[i];
         fi;
      else 
         relval := [];
         for j in [1..dim] do relval[j]:=0; od;
         if vec<>[] then
            for j in [1..dim] do
               for k in [1..codim] do
                  relval[j] := relval[j] + vec[k]*modrv[k][i][j];
               od;
               relval[j] := relval[j] mod p;
            od;
         fi;
      fi;
      for j in [1..dim] do
        if relval[j]<>0 then
          w := w/EG[ng+j]^relval[j];
        fi;
      od;
      Add(Erels,w);
   od; 
   return E/Erels;
end;

#############################################################################
##
#F  SchurMultiplier( <chr> )
##      . . . . calculate the p-part of the Schur multiplier of <chr>.permgp
##
##  <chr> is a cohomology record.
##  The result is returned as a list of the abelian invariants.
##
SchurMultiplier := function( chr )
   if not IsCHR(chr) then
     Error("First argument of Cohomolo must be a cohomology record");
   fi;
   if not IsBound(chr.multiplier) then
      Cohomolo(chr,true,false,false, TmpName() );
   fi;
   return chr.multiplier;
end;

#############################################################################
##
#F  CoveringGroup( <chr> )  . . . calculate p-covering group of <chr>.permgp 
##
##  To use this <chr>.fpgp must be defined, and the answer will be accurate only
##  if <chr>.fpgp and <chr>.permgp are isomorphic.
##  The p-cover is returned as an fp-group.
##
CoveringGroup := function( chr )
   if not IsCHR(chr) then
     Error("First argument of Cohomolo must be a cohomology record");
   fi;
   if not IsBound(chr.multrelvals) then
      Cohomolo(chr,true,true,false, TmpName() );
   fi;
   return CalcExtPres(chr,true);
end;

#############################################################################
##
#F  FirstCohomologyDimension( <chr> ) 
##  . . dimension of H^1(G,M), with G=<chr>.permgp, M=module of <chr>.matrices 
##
##  <chr> is a cohomology record with <chr>.matrices defined.
##
FirstCohomologyDimension := function( chr )
   if not IsCHR(chr) then
     Error("First argument of Cohomolo must be a cohomology record");
   fi;
   if not IsBound(chr.codim1) then
      Cohomolo(chr,false,false,true, TmpName() );
   fi;
   return chr.codim1;
end;

#############################################################################
##
#F  SecondCohomologyDimension( <chr> ) 
##  . . dimension of H^2(G,M), with G=<chr>.permgp, M=module of <chr>.matrices 
##
##  <chr> is a cohomology record with <chr>.matrices defined.
##
SecondCohomologyDimension := function( chr )
   if not IsCHR(chr) then
     Error("First argument of Cohomolo must be a cohomology record");
   fi;
   if not IsBound(chr.codim2) then
     Cohomolo(chr,false,false,false, TmpName() );
   fi;
   return chr.codim2;
end;

#############################################################################
##
#F  SplitExtensionCHR( <chr> )
## . . presentation of split extension of <chr>.matrices by <chr>.fpgp
##
##  To use this <chr>.fpgp and <chr>.matrices must both be define
##  This is a routine calculation and does not call Cohomolo.
##
SplitExtensionCHR := function( chr )
   if not IsCHR(chr) then
     Error("First argument of Cohomolo must be a cohomology record");
   fi;
   return CalcExtPres(chr,false,[]);
end;

#############################################################################
##
#F  NonsplitExtension( <chr> , [<vec>] )
##  . . presentation of a nonsplit extension of <chr>.matrices by <chr>.fpgp
##
##  To use this <chr>.fpgp and <chr>.matrices must both be define
##  The equivalence classes of nonsplit extensions of M by G are in one-one
##  correspondence with the elements of H^2(G,M). Therefore an element of
##  H^2(G,M) needs to be specified. If the optional argument <vec> is
##  present, it must be a list of integers of length Dim(H^2(G,M)), which
##  specifies the element of H^2(G,M) as a vector (the integers may as well
##  be in range [0...p-1] but that is no compulsory).
##  By default, <vec> is taken to be [1,0,...,0], where the length is equal
##  to Dim(H^2(G,M)).
##
NonsplitExtension := function( arg )
   local chr, vec, i;
   if Number(arg)<>1 and Number(arg)<>2 then
     Error("Number of arguments of NonSplitExtension wrong");
   fi;
   chr := arg[1];
   vec := [];
   if Number(arg)=2 then vec := arg[2]; fi;
   if not IsCHR(chr) then
     Error("First argument of Cohomolo must be a cohomology record");
   fi;
   if not IsBound(chr.modrelvals) then
      Cohomolo(chr,false,true,false, TmpName() );
   fi;
   if chr.codim2 = 0 then
      Error("There is no nonsplit extension.");
   fi;
   if vec=[] then
      vec[1]:=1;
      for i in [2..chr.codim2] do vec[i]:=0; od;
   fi;
   return CalcExtPres(chr,false,vec);
end;

#############################################################################
##
#F  PermRep( <F>, <K> ). . . calculate permutation represenation of fp-group
##
##  <F> should be a finitely presented group and <K> a subgroup of finite
##  index.
##  This function return the permutation group giving the action of <F> on
##  right cosets of <K>.
##  Of course, there is no guarantee in general that the perm rep of <F> will
##  be faithful.
##  It is a straightforward application of standard GAP functions.
##
PermRep := function(F,K)
   local  ng, Ggens, GGi, CT, i;
   if not IsFpGroup(F) or not IsSubgroup(F,K) then
     Error("PermRep(F,K): K must be a subgroup of the fp-group F");
   fi;
   ng := Length(FreeGeneratorsOfFpGroup(F));
   CT := CosetTable(F,K);
   GGi := List(CT,PermList); #list of generators and inverses.
   Ggens := [];
   for i in [1..ng] do Ggens[i] := GGi[2*i-1]; od;
   return Group(Ggens,());
end;

#############################################################################
##
#F  CalcPres( <chr> ). . . calculate a presentation of <chr>.permgp
##
##  <chr> must be a cohomology record, created with CHR.
##  This routine can be used to calculate a presentation of <chr>.permgp,
##  when none is known to begin with (i.e. when <chr>.fpgp is zero).
##  The computed presentation is stored as <chr>.fpgp.
##  It calls an external program to do this.
##  It is only really useful for moderately small groups - currently, its
##  use is restricted to groups of order at most 32767.
##
CalcPres := function( chr )
   local deg, G, base, F, Fg, Fr, optstring, callstring, ng, ordgen, o, rel,
   i, l, w, g, h, ct, neg, filename;

   if not IsCHR(chr) then
     Error( "<chr> must be a cohomology record");
   fi;

   F := chr.fpgp;
   if IsFpGroup(F) then
      Print("CalcPres: presentation is already known.\n");
      return;
   fi;

   G := chr.permgp;  deg := chr.degree;

   #Now write group to file -
   base := chr.base;
   filename:= TmpName();
   COHOMOLO.WritePermGroup(G,deg,base,Concatenation( filename, ".inperm" ) );
   
   #Now work out the command for the external program.
   callstring := Filename( COHOMOLO.CALL, "calcpres.gap" );
   optstring := "-";
   if chr.verbose then Add( optstring, 'v' ); fi;
   if 1 < Length( optstring ) then
      callstring := Concatenation(callstring," ", optstring );
   fi;
   callstring := Concatenation(callstring," ", filename );
   if chr.verbose then Print(callstring,"\n"); fi;

   Info(InfoCohomolo,1," Calling external program." );
   Exec(callstring);
   Info(InfoCohomolo,1," External program complete." );

   if not READ(Concatenation( filename, ".reg.relg" ) ) then
      Error( "'CalcPres' failed for some reason.\n" );
   fi;
   chr.permrels := COHOMOLO.PermRels;

   Info(InfoCohomolo,1," Removing temporary files." );
   Exec( Concatenation( COHOMOLO.RM_F, filename, ".*" ) );

   # Now we build the finitely presented group.
   ng := Length(GeneratorsOfGroup(G));
   F := FreeGroup(ng);
   Fg := GeneratorsOfGroup(F);
   Fr := [];
   #first look for those relators that give the orders of generators.
   ordgen := [];
   for i in [1..ng] do ordgen[i] := 0; od;
   for rel in chr.permrels do
      if Size(Set(rel))=1 then
        g := rel[1];
        if g<0 then g:= -g; fi;
        o := Length(rel);
        ordgen[g] := o;
        Add(Fr,Fg[g]^o);
      fi;
   od;
   #Now process the other relators.
   for rel in chr.permrels do
      if Size(Set(rel))>1 then
        w := One(F);
        ct := 0; l := Length(rel); g := 0;
        for i in [1..l+1] do
           if i<=l then h := rel[i]; else h:=0; fi;
           if h<>g and ct>0 then
              neg := g<0;
              if neg then g := -g; fi;
              o := ordgen[g];
              if o=0 then
                 if neg then w:=w*Fg[g]^-ct; else w:=w*Fg[g]^ct; fi;
              elif neg and 2*ct>=o then
                 w:=w*Fg[g]^(o-ct);
              elif not neg and 2*ct>=o+1 then
                 w:=w*Fg[g]^(ct-o);
              else
                 if neg then w:=w*Fg[g]^-ct; else w:=w*Fg[g]^ct; fi;
              fi;
              ct := 0;
           fi;
           g := h; ct := ct+1;
        od;
        if w<> One(F) then
          Add(Fr,w);
        fi;
      fi;
   od;

   Unbind(chr.permrels);
   chr.fpgp := F/Fr;
end;

