#############################################################################
##
#A  rwssub4.g                  GAP library                  Derek Holt
##
## Created 21/3/00. New to GAP4.
##
## This file contains GAP4 interface functions to the subgroup functionality
## of the KBMAG standalone package.
##
#V  _xg          external variable - a free group for a subgroup presentation
#V  _x_relators  external variable - relators for a subgroup presentations
_xg := FreeGroup(1);
_x_relators:=[];

#############################################################################
##
#F  SubgroupRWS(<rws>,<H>) . . . . . . . create a subgroup of an <rws>
##
##  <rws> should be a rewriting system and <H> a subgroup of G or a list
##  of generators of G defining a subgroup, where G = rws!.GpMonSgp.
##  G must be a group (i.e. all inverses defined) for this to work.
##
##  Public function.
SubgroupRWS := function ( rws, H )
  local G, invH, g, ng, ns, subrec, mnames, i, l, fam;

  if not IsGroupRWS(rws) then
     Error("SubgroupRWS only applies to rewriting systems from groups.");
  fi;
  G := rws!.GpMonSgp;
  if IsGroup(H) then
    H := GeneratorsOfGroup(H);
  fi;

  for g in H do if not g in G and not g in rws!.FreeGpMonSgp then
     Error ("Second argument of SubgroupRWS should define a subgroup.");
  fi; od;
  ## We want to store H as words in the word monoid.
  H := List(H,i->UnderlyingElement(i));
  H := List(H,i->rws!.ExtToInt(rws!.ExtIntCorr,i));
  ## and we need the inverses of these generators.
  invH:=[];
  for g in H do
    l := WordToListRWS(g,rws!.alphabet);
    l := List(Reversed(l),i->rws!.invAlphabet[i]);
    Add(invH,ListToWordRWS(l,rws!.alphabet));
  od;
  ng := Length(H);

  if IsBound(rws!.subgroups) then
    ns := rws!.numSubgroups+1;
    rws!.numSubgroups := ns;
  else
    rws!.subgroups := [];
    rws!.cosrws := [];
    rws!.subrws := [];
    ns :=  1;
    rws!.numSubgroups := ns;
  fi;
  rws!.subgroups[ns] := rec();
  rws!.cosrws[ns] := rec();
  subrec := rws!.subgroups[ns];
  subrec.GpMonSgp := rws!.GpMonSgp;
  subrec.FreeGpMonSgp := rws!.FreeGpMonSgp;
  subrec.ordering := rws!.ordering;
  mnames := [];
  subrec.generatingWords := [];
  subrec.invAlphabet := [];
  for i in [1..ng] do
    subrec.generatingWords[2*i-1] := H[i];
    subrec.generatingWords[2*i] := invH[i];
    mnames[2*i-1] := Concatenation("_x",String(i));
    mnames[2*i] := Concatenation("_X",String(i));
    subrec.invAlphabet[2*i-1] := 2*i;
    subrec.invAlphabet[2*i] := 2*i-1;
  od;
  subrec.equations:=[];
  subrec.options:=rec();
  subrec.WordMonoid := FreeMonoid(mnames);
  subrec.alphabet := GeneratorsOfMonoid(subrec.WordMonoid);

  fam := NewFamily("Family of Knuth-Bendix Rewriting systems",
                IsKnuthBendixRewritingSystem);
  subrec := Objectify(NewType(fam,
                IsMutable and IsKnuthBendixRewritingSystem
                and IsKBMAGRewritingSystemRep),
                subrec);
  rws!.cosrws[ns] := Objectify(NewType(fam,
                IsMutable and IsKnuthBendixRewritingSystem
                and IsKBMAGRewritingSystemRep),
                rws!.cosrws[ns]);
  return subrec;
end;

#############################################################################
##
#F  IsConfluentCosetsRWS(<rws>, <subrws>)
##  .  .  test whether <subrws> has a confluent coset system in <rws>.
##
##  Public function.
IsConfluentCosetsRWS := function ( rws, subrws )
  local ns, cosrws;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error("Second argument of IsConfluentCosetsRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  return IsBound(cosrws!.isConfluent) and cosrws!.isConfluent=true;
end;

#############################################################################
##
#F  IsAvailableNormalFormCosetsRWS(<rws>, <subrws>) 
##    . . test whether <rws> has a cosets normal form in <subrws>
##
##  Public function.
IsAvailableNormalFormCosetsRWS := function (  rws, subrws )
  local ns, cosrws;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error(
 "Second argument of IsAvailableNormalFormCosetsRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  return IsBound(cosrws!.isAvailableNormalForm) and
                 cosrws!.isAvailableNormalForm=true;
end;

#############################################################################
##
#F  IsAvailableReductionCosetsRWS(<rws>, <subrws>)
##      . . test whether <rws> has a cosets reduction algorithm in <subrws>
##
##  Public function.
IsAvailableReductionCosetsRWS := function (  rws, subrws )
  local ns, cosrws;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error(
 "Second argument of IsAvailableReductionCosetsRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  return IsBound(cosrws!.isAvailableReduction)
                 and cosrws!.isAvailableReduction=true;
end;

#############################################################################
##
#F  IsAvailableIndexRWS(<rws>, <subrws>)
##      . . test whether <rws> has a index algorithm in <subrws>
##
##  Public function.
IsAvailableIndexRWS := function (  rws, subrws )
  local ns, cosrws;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error("Second argument of IsAvailableIndexRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  return IsBound(cosrws!.isAvailableIndex)
                 and cosrws!.isAvailableIndex=true;
end;

#############################################################################
##
#F  WriteSubgroupRWS(<rws>, <subrws>, [<filename>], [<endsymbol>])
##       . . . . . .write an rws and a subgroup to files in external format
##
##  WriteSubgroupRWS prints the rws <rws> and its subgroup <subrws> to the
##  files <filename> and <filename>.sub (where <subrws> is subgroup)
## 
##  This is an extension of WriteRWS, but it is the original equations
##  of rws that are written, and the control parameters are not needed.
##
##  Public function.
WriteSubgroupRWS := function ( arg )
  local ns, filename, endsymbol, ng, nsg, rwsgennames, subrwsgens,
        subrwsigens, ig, line,
        geni, genstring,i, rws, subrws, subgens, eqn, eqns;

  if Length(arg) < 2 then
    Error("WriteSubgroupRWS needs at least two arguments.");
  fi;
  rws := arg[1];
  subrws := arg[2];
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error("Second argument of WriteSubgroupRWS is not a subRWS of first.");
  fi;

  filename := "";
  if Length(arg)>=3 then filename := arg[3]; fi;
  if filename="" then endsymbol := ""; else endsymbol := ";"; fi;
  if Length(arg)>=4 then endsymbol := arg[4]; fi;

  ng := Length(rws!.alphabet);
  rwsgennames := List(rws!.alphabet,x->String(x));

  #Now print main <rws> file
  if filename="" then Print("rec(\n");
  else PrintTo(filename,"_RWS := rec (\n");
  fi;

  line := String("isRWS",16);
  line := Concatenation(line," := true,");
  LinePrintRWS(line,filename);

  line := Concatenation(String("generatorOrder",16)," := [");
  for i in [1..ng] do
    if i > 1 then
      line := Concatenation(line,",");
    fi;
    if i=1 or Length(line)+Length(rwsgennames[i]) <= 76 then
      line := Concatenation(line,rwsgennames[i]);
    else
      LinePrintRWS(line,filename);
      line := String("",21);
      line := Concatenation(line,rwsgennames[i]);
    fi;
  od;
  line := Concatenation(line,"],");
  LinePrintRWS(line,filename);

  ig := rws!.invAlphabet;
  line := Concatenation(String("inverses",16)," := [");
  for i in [1..ng] do
    if i > 1 then line := Concatenation(line,","); fi;
    if IsInt(ig[i]) and ig[i]>0 then
      if i=1 or Length(line)+Length(rwsgennames[ig[i]]) <= 76 then
        line := Concatenation(line,rwsgennames[ig[i]]);
      else
        LinePrintRWS(line,filename);
        line := String("",21);
        line := Concatenation(line,rwsgennames[ig[i]]);
      fi;
    fi;
  od;
  line := Concatenation(line,"],");
  LinePrintRWS(line,filename);

  line := String("ordering",16);
  line := Concatenation(line," := \"",rws!.ordering,"\",");
  LinePrintRWS(line,filename);
  if rws!.ordering="wreathprod" and IsBound(rws!.level) then
    line := Concatenation(String("level",16)," := [");
    for i in [1..ng] do
      if i > 1 then
        line := Concatenation(line,",");
      fi;
      line := Concatenation(line,String(rws!.level[i]));
    od;
    line := Concatenation(line,"],");
    LinePrintRWS(line,filename);
  fi;

  if IsBound(rws!.originalEquations) then
    eqns := rws!.originalEquations;
  else
    eqns := rws!.equations;
  fi;
  line := Concatenation(String("equations",16)," := [");
  for i in [1..Length(eqns)] do
    if i > 1 then line := Concatenation(line,","); fi;
    LinePrintRWS(line,filename);
    eqn := eqns[i];
    line := Concatenation(String("[",10),
                  StringRWS(ListToWordRWS(eqn[1],rws!.alphabet)),",");
    if Length(line)>=40 then
      LinePrintRWS(line,filename);
      line := String("",10);
    fi;
    line :=Concatenation( line,
               StringRWS(ListToWordRWS(eqn[2],rws!.alphabet)),"]");
  od;
  LinePrintRWS(line,filename);
  line := String("]",8);
  LinePrintRWS(line,filename);
  line := Concatenation(")",endsymbol);
  LinePrintRWS(line,filename);

  # That ends output of rws - now we do the subgroup

  if filename <> "" then
    filename := Concatenation(filename,".sub");
  fi;
  subgens := rws!.subgroups[ns]!.generatingWords;
  nsg := Length(rws!.subgroups[ns]!.alphabet);
  subrwsgens := rws!.subgroups[ns]!.alphabet;
  ig := rws!.subgroups[ns]!.invAlphabet;
  subrwsigens := [];
  for i in [1..nsg] do
    subrwsigens[i]:= subrwsgens[ig[i]];
  od;

  if filename="" then Print("rec(\n");
  else PrintTo(filename,"_RWS_Sub := rec (\n");
  fi;

  line := Concatenation(String("subGenerators",26)," := [");
  for i in [1..nsg] do
    if i > 1 then
      line := Concatenation(line,",");
    fi;
    genstring := String(subgens[i]);
    if i=1 or Length(line)+Length(genstring) <= 76 then
      line := Concatenation(line,genstring);
    else
      LinePrintRWS(line,filename);
      line := String("",21);
      line := Concatenation(line,genstring);
    fi;
  od;
  line := Concatenation(line,"],");
  LinePrintRWS(line,filename);

  line := Concatenation(String("subGeneratorNames",26)," := [");
  for i in [1..nsg] do
    if i > 1 then
      line := Concatenation(line,",");
    fi;
    genstring := String(subrwsgens[i]);
    if i=1 or Length(line)+Length(genstring) <= 76 then
      line := Concatenation(line,genstring);
    else
      LinePrintRWS(line,filename);
      line := String("",21);
      line := Concatenation(line,genstring);
    fi;
  od;
  line := Concatenation(line,"],");
  LinePrintRWS(line,filename);

  line := Concatenation(String("subGeneratorInverseNames",26)," := [");
  for i in [1..nsg] do
    if i > 1 then
      line := Concatenation(line,",");
    fi;
    genstring := String(subrwsigens[i]);
    if i=1 or Length(line)+Length(genstring) <= 76 then
      line := Concatenation(line,genstring);
    else
      LinePrintRWS(line,filename);
      line := String("",21);
      line := Concatenation(line,genstring);
    fi;
  od;
  line := Concatenation(line,"]");
  LinePrintRWS(line,filename);

  line := Concatenation(")",endsymbol);
  LinePrintRWS(line,filename);
end;


#############################################################################
##
#F  KBCosets(<rws>, <subrws> [,<subgens>]) 
##      . . . . call external Knuth-Bendix cosets program on rws, subrws!.
##
##  This returns true if a confluent coset rewriting system results - otherwise
##  false. In the latter case, words can still be rewritten with respect to
##  the current equations, but they are not guaranteed to reduce to the unique
##  representative of the group element.
##  If the optional third argument is set true then rewriting system generators
##  will be introduced for the subgroup generators, and so the final
##  confluent presentation will include a confluent presentation of the
##  subgroup.
##  If the third argument is missing or false, then these new generators
##  will not be introduced, and the Knuth-Bendix will operate only on
##  cosets.
##  An error message results if the external program aborts without outputting.
##  Public function.
KBCosets := function ( arg )
  local rws, subrws, subgens, ns, ng, nsg, callstring, filename, cosrws,
        M, subfreegp, is, nst, sr, nsgg, i, j, mnames, gf, eq, eqns,
        gtom, igtom, fam;

  if Length(arg) < 2 then
    Error("KBCosets needs at least two arguments.");
  fi;
  rws := arg[1];
  subrws := arg[2];

  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error("Second argument of WriteSubgroupRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  if IsBound(cosrws!.KBRun) and cosrws!.KBRun then
    Print(
      "KBCosets or AutCosets has already been run on <rws>, <subrws>.\n");
    Print("Call - ResetCosetsRWS( <rws>, <subrws> ) before proceeding.\n");
    return;
  fi;

  subgens := false;
  if Length(arg) >= 3 then
    subgens := arg[3];
  fi;

  ng := Length(rws!.alphabet);
  nsg := Length(subrws!.alphabet);

  #We need to set up a few fields in the cosets record.
  cosrws!.isRWS := true;
  cosrws!.isInternalRWS:=true;
  cosrws!.ordering := "wreathprod";
  cosrws!.hasOne := true;
  mnames := [];
  if subgens then
     mnames := Concatenation(List(rws!.alphabet,x->String(x)),["_H"],
                    List(rws!.subgroups[ns]!.alphabet,x->String(x)) );
  else
     mnames := Concatenation(List(rws!.alphabet,x->String(x)),["_H"] );
  fi;
  M := FreeMonoid(mnames);
  cosrws!.alphabet := GeneratorsOfMonoid(M);
  cosrws!.invAlphabet := ShallowCopy(rws!.invAlphabet);
  cosrws!.invAlphabet[ng+1] := false;
  if subgens then
    for i in [1..nsg] do
      cosrws!.invAlphabet[ng+1+i] :=rws!.subgroups[ns]!.invAlphabet[i]+ng+1;
    od;
  fi;
  cosrws!.FreeGpMonSgp := M;
  cosrws!.WordMonoid := M;
  cosrws!.baseAlphabet := rws!.alphabet;
  cosrws!.options := rec();
  
  WriteSubgroupRWS(rws,subrws,_KBTmpFileName);
  callstring := Filename(_KBExtDir,"makecosfile");
  if subgens then
     callstring := Concatenation(callstring," -sg ");
  fi;
  callstring := Concatenation(callstring," ",_KBTmpFileName," sub");
  Exec(callstring);

  callstring :=  Filename(_KBExtDir,"kbprogcos");
  callstring := Concatenation(callstring," ");
  #This time optional parameters will be added in the command-line.
  if IsBound(rws!.options.maxeqns) then
    callstring := Concatenation(callstring,"-me ",
                                  String(rws!.options.maxeqns)," ");
  fi;
  if IsBound(rws!.options.tidyint) then
    callstring := Concatenation(callstring,"-t ",
                                  String(rws!.options.tidyint)," ");
  fi;
  if IsBound(rws!.options.confnum) then
    callstring := Concatenation(callstring,"-cn ",
                                  String(rws!.options.confnum)," ");
  fi;
  if IsBound(rws!.options.maxstoredlen) then
    callstring := Concatenation(callstring,"-mlr ",
        String(rws!.options.maxstoredlen[1])," ",
                                  String(rws!.options.maxstoredlen[2])," ");
  fi;
  if IsBound(rws!.options.maxoverlaplen) then
  callstring := Concatenation(callstring,"-mo ",
                                  String(rws!.options.maxoverlaplen)," ");
  fi;
  if IsBound(rws!.options.maxreducelen) then
  callstring := Concatenation(callstring,"-mrl ",
                                  String(rws!.options.maxreducelen)," ");
  fi;
  if IsBound(rws!.options.maxstates) then
    callstring := Concatenation(callstring,"-ms ",
                           String(rws!.options.maxstates)," ");
  fi;
  if InfoLevel(InfoRWS)=0 then
    callstring := Concatenation(callstring,"-silent ");
  fi;
  if InfoLevel(InfoRWS)>1 then
    callstring := Concatenation(callstring,"-v ");
  fi;
  if InfoLevel(InfoRWS)>2 then
    callstring := Concatenation(callstring,"-vv ");
  fi;

  callstring := Concatenation(callstring,_KBTmpFileName," cos");
  Info(InfoRWS,1,"Calling external Knuth-Bendix cosets program.\n");
  Exec(callstring);
  filename := Concatenation(_KBTmpFileName,".cos");
  UpdateRWS(cosrws,filename,true,true);
  Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,"*"));
  Info(InfoRWS,1,"External Knuth-Bendix cosets program complete.\n");

  if cosrws!.isConfluent then
    Info(InfoRWS,1,"System computed is confluent.\n");
    cosrws!.isAvailableNormalForm := true;
    cosrws!.warningOn := false;
  else
    Info(InfoRWS,1,"System computed is NOT confluent.\n");
    cosrws!.isAvailableNormalForm := false;
    cosrws!.warningOn := true;
  fi;
  cosrws!.KBRun := true;
  cosrws!.isAvailableReduction := true;
  cosrws!.isAvailableReductionPlus := subgens;
  cosrws!.isAvailableIndex := true;
  if subgens and cosrws!.isAvailableReduction then
  #We will make a new rewriting system for the subgroup.
    rws!.subrws[ns]:=rec();
    sr:=rws!.subrws[ns];
    sr.ordering := "shortlex";
    sr.hasOne := true;
    sr.invAlphabet := subrws!.invAlphabet;
    nsgg := 0;
    gtom:=[];
    igtom:=[];
    for i in [1..nsg] do
      if sr.invAlphabet[i] >= i then
        nsgg := nsgg+1;
        Add(gtom,i);
        Add(igtom,sr.invAlphabet[i]);
      fi;
    od;
    sr.FreeGpMonSgp := FreeGroup(nsgg);
    mnames := [];
    for i in [1..nsg] do
      mnames[i] := Concatenation("_g",String(i));
    od;
    sr.WordMonoid := FreeMonoid(mnames);
    sr.ExtIntCorr := CorrespondenceGroupMonoid(
                                sr.FreeGpMonSgp,sr.WordMonoid,gtom,igtom);
    sr.ExtToInt := FreeGroup2FreeMonoid;
    sr.IntToExt := FreeMonoid2FreeGroup;
    sr.alphabet := GeneratorsOfMonoid(sr.WordMonoid);

    sr.options := rec();
    sr.equations := [];
    ## pick up the required equations from cosrws!.eqauations
    eqns := cosrws!.equations;
    for eq in eqns do
      if eq[1][1] > ng+1 then
        Add(sr.equations, [List(eq[1],i->i-ng-1),List(eq[2],i->i-ng-1)] );
      fi;
    od;
    fam := NewFamily("Family of Knuth-Bendix Rewriting systems",
                IsKnuthBendixRewritingSystem);
    sr := Objectify(NewType(fam,
                IsMutable and IsKnuthBendixRewritingSystem
                and IsKBMAGRewritingSystemRep),
                sr);
    FpStructureRWS(sr);
    if cosrws!.isConfluent then
      Info(InfoRWS,1,"Running external program on subgroup presentation.\n");
      KBRWS(sr); #just to generate to reduction fsa.
    fi;
  fi;
  return cosrws!.isConfluent;
end;

#############################################################################
##
#F  RWSOfSubgroup(<rws>, <subrws>) 
##    . . The rewriting system of the subgroup as a separate entity.
##
##  This can be run after a run of KBCosets(<rws>,<subrws>,true);
##
##  Public function.
RWSOfSubgroup := function (  rws, subrws )
  local ns, cosrws;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error(
 "Second argument of RWSOfSubgroup is not a subRWS of first.");
  fi;
  if not IsBound(rws!.subrws[ns]) then
    Error("You did not call KnuthBendixOnCosetsWithSubgroupRewritingSystem.");
  fi;
  return rws!.subrws[ns];
end;

#############################################################################
##
#F  AutCosets(<rws>, <subrws>,
##                         [<subpres>, <large>], [<filestore>], [<diff1>])
##      . . . . call external automatic cosets program on <subrws> in <rws>.
##
##  This returns true if the automatic cosets programs succeed - otherwise
##  false.
##  If <subpres> is present and true, then a subgroup presentation
##  is also computed - but this not on the user generators. 
##
##  The other optional parameters are all boolean, and false by default.
##  <large> means problem is large - the external programs use bigger tables.
##  <filestore> means external programs use less core memory and more external
##         files - they run a little slower.
##  <diff1> is necessary on some examples - see manual for information.
##  Public function.
AutCosets := function ( arg )
  local  narg, rws, subrws, subpres, large, filestore, diff1, callstring,
         optstring, filename, cosrws, ns, ng, nsg, i;
  narg := Number(arg);
  if narg<2 then
     Error("AutCosets needs at least two arguments.");
  fi;
  rws := arg[1];
  if not IsGroupRWS(rws) then
    Error("AutCosets can only be applied to groups.");
  fi;
  subrws := arg[2];

  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error("Second argument of WriteSubgroupRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  if IsBound(cosrws!.KBRun) and cosrws!.KBRun then
    Print(
      "KBCosets or AutCosets has already been run on <rws>, <subrws>.\n");
    Print("Call - ResetCosetsRWS( <rws>, <subrws> ) before proceeding.\n");
    return;
  fi;

  if not rws!.ordering = "shortlex" then
     Error("Ordering must be shortlex for automatic group programs");
  fi;
  subpres := false; large:=false; filestore:=false; diff1:=false;
  if narg>=3 and arg[3]=true then subpres:=true; fi;
  if narg>=4 and arg[4]=true then large:=true; fi;
  if narg>=5 and arg[5]=true then filestore:=true; fi;
  if narg>=6 and arg[6]=true then diff1:=true; fi;

  ng := Length(rws!.alphabet);
  nsg := Length(subrws!.alphabet);

  #We need to set up a few fields in the cosets record.
  cosrws!.isRWS := true;
  cosrws!.isInternalRWS:=true;
  cosrws!.ordering := "wreathprod";
  cosrws!.hasOne := true;
  cosrws!.alphabet := rws!.alphabet;
  cosrws!.invAlphabet := rws!.invAlphabet;
  cosrws!.FreeGpMonSgp := rws!.WordMonoid;
  cosrws!.WordMonoid := rws!.WordMonoid;
  cosrws!.options:=rec();
  cosrws!.baseAlphabet := rws!.alphabet;
  cosrws!.equations := [];

  WriteSubgroupRWS(rws,subrws,_KBTmpFileName);
  callstring := Concatenation(Filename(_KBExtDir,"makecosfile"),"  -sg ");
  callstring := Concatenation(callstring,_KBTmpFileName," sub");
  Exec(callstring);

  callstring := Filename(_KBExtDir,"autcos");
  optstring := " ";
  if subpres then optstring := Concatenation(optstring," -p "); fi;
  if large then optstring := Concatenation(optstring," -l "); fi;
  if filestore then optstring := Concatenation(optstring," -f "); fi;
  if diff1 then optstring := Concatenation(optstring," -d "); fi;
  if InfoLevel(InfoRWS)=0 then
                      optstring := Concatenation(optstring," -s "); fi;
  if InfoLevel(InfoRWS)>1 then
                      optstring := Concatenation(optstring," -v "); fi;
  if InfoLevel(InfoRWS)>2 then
                      optstring := Concatenation(optstring," -vv "); fi;
  callstring := Concatenation(callstring,optstring,_KBTmpFileName);
  Info(InfoRWS,1,"Calling external automatic cosets groups program.\n");
  Exec(callstring);
  if subpres then
  # read subgroup presentation
    if READ(Concatenation(_KBTmpFileName,".sub.pres")) then
      #Presentation is very redundant, so simplfy.
      rws!.subgroups[ns]!.presentation :=
                                   SimplifiedFpGroup(_xg/_x_relators);
    fi;
  fi;
  filename := Concatenation(_KBTmpFileName,".cos");
  if READ(Concatenation(filename,".success")) then
   Info(InfoRWS,1,
      "Computation was successful - automatic coset structure computed.\n");
    UpdateRWS(cosrws,filename,false,true);
    #Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,"*"));
    cosrws!.KBRun := true;
    cosrws!.isAvailableNormalForm := true;
    cosrws!.isAvailableNormalForm := true;
    cosrws!.isAvailableReduction := true;
    cosrws!.isAvailableReductionPlus := true;
    cosrws!.isAvailableIndex := true;
    cosrws!.warningOn := false;
    return true;
  else
    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,"*"));
    Info(InfoRWS,1,"Computation was not successful.\n");
    return false;
  fi;
end;

#############################################################################
##
#F  PresentationOfSubgroupRWS(<rws>, <subrws>) 
##    . . The presentation of the subgroup computed by AutCosets
##
##  This can be run after a run of AutCosets(<rws>,<subrws>,true);
##
##  Public function.
PresentationOfSubgroupRWS := function (  rws, subrws )
  local ns;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error(
 "Second argument of RWSOfSubgroup is not a subRWS of first.");
  fi;
  if not IsBound(rws!.subgroups[ns]!.presentation) then
    Error("RWS or subRWS unavaliable. You must call AutCosets first.");
  fi;
  return rws!.subgroups[ns]!.presentation;
end;

#############################################################################
##
#F  IsReducedWordCosetsRWS(<rws>, <subrws>, <w>) 
##  . . tests if a <w> word represents a reduced coset of <subrws> in <rws>
##
##  IsReducedWordCosetsRWS tests whether the word <w>
##  is reduced as a coset of <subrws> in <rws>.
##  <w> can be given either as a list of integers (internal format) or as
##  a word in the generators of the underlying free group.
##  Either the word-acceptor (automatic group case) or the reduction FSA
##  must be defined.
##  It merely calls the corresponding FSA function.
##
##  Public function.
IsReducedWordCosetsRWS := function ( rws, subrws, w )
    local i, ng, ns, cosrws;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
  Error("Second argument of IsReducedWordCosetsRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  if not IsAvailableReductionCosetsRWS(rws,subrws) then
     Error(
    "Reduction algorithm unavailable. You must run KBCosets or AutomataCosets");
  fi;

  if not IsList(w) and not IsWord(w) then
     Error("Third argument is not a word or list.");
  fi;
  ng := Length(rws!.alphabet);
  if IsWord(w) then
     w:=WordToListRWS(w,rws!.alphabet);
  fi;
  for i in w do
    if not i in [1..ng] then Error("Invalid entry in word or list");fi;
  od;
  if IsBound(cosrws!.wa) then
  # Coset automatic group case - use word-acceptor
    return IsAcceptedWordDFA( cosrws!.wa,w );
  fi;
  if not IsBound(cosrws!.reductionFSA) then
     Error("Coset rewriting system  does not have initialized dfa as field.");
  fi;
  ##Put the subgroup symbol at the beginning of w
  w := Concatenation([ng+1],w);
  return IsAcceptedWordDFA( cosrws!.reductionFSA,w );
end;

#############################################################################
##
#F  ReduceWordCosetsWD(<wd>, <w>, <subgpword>)
##       . . . . reduces a word for a coset using word-difference automaton
##
##  ReduceWordCosetsWD calculates the reduction of the word <w> (list of
##  integers)  using the MIDFA word-difference automaton <wd>.
##  <wd> should be two-variable, where <w> is a list of integers in the range
##  [1..ng], where ng is the size of the base alphabet.
##  The first letter of w should always be ng+1 where ng is the number
##  of generators of the group - this represents the subgroup _H.
##  WARNING: No validity checks are carried out!
##
##  A list of two words [w1,w] is returned, where w is the reduce representative
##  of the coset of w, and, if subgpword is true,  w1 represents the element
##  in the subgroup such that  the original w = w1 * w in the group, or
##  w1 is the empty list if  subgpword is false. Note that w1 is a word in
## the group generators (not any user-supplied subgroup generators).
##
##  Private function.
ReduceWordCosetsWD := function ( wd, w, subgpword )
    local  ndiff, ngens, ng1, identity, level, cf, gpref, gct, gptr,
           diff, newdiff, deqi, gen1, gen2, donesub, donediffs, subvert,dosub,
           g2ltg1, diffct, t, nlen, olen, i, l, table, w1;
    if not IsInitializedFSA(wd) then
       Error("First argument is not an initialized dfa.");
    fi;

    ndiff := wd.states.size;
    ngens := wd.alphabet.base.size;
    ng1 := ngens+1;            
    identity := wd.initial[1];
    if Length(w) <= 0 then
      return w;
    fi;
    cf := [];
    # cf is used as a characteristic function, when constructing a subset of the
    # set  D  of word differences.

    gpref := []; gct := 0; gpref[1] := 0; gptr := [];
    # gpref[n]  is the number of "vertices" that have been defined after
    # reading the first n-1 elements of the word.
    # These vertices are gptr[1],...,gptr[gpref[n]].
    # A vertex is a record with 4 components, backptr, genno, diffno, sublen,
    # It represents a vertex in the graph of strings that may eventually
    # be used as substituted strings in the word w.
    # backptr is either undefined or another vertex.
    # gen is the generator at the vertex.
    # diffno is the word-difference number of the string at which the vertex
    #        is at the end - this string is reconstructed using backptr.
    # sublen is plus or minus the length of this string. sublen is positive
    #        iff the string lexicographically precedes the corresponding
    #        prefix of the word being reduced. sublen is zero if the
    #        substitution starts at the beginning of the word and both strings
    #        are equal up to that point.
    # We put in some immediate vertices at level 1 for the non-identity
    # inital states of the word-difference machine.
    gct := 0;
    for i in [2..Length(wd.initial)] do
      gct := gct+1;
      gptr[gct] := rec();
      gptr[gct].genno := 0;
      gptr[gct].diffno := wd.initial[i];
      gptr[gct].sublen := 0;
    od;
    gpref[2] := gct;
    w1 := [];

    # Now we start scanning the word.
    table := DenseDTableFSA(wd);
    level := 2;
    while level <= Length(w) do
      for i in [1..ndiff] do cf[i] := false; od;
      gen1 := w[level];
      # The next loop is over the identity, and the subset of the set of
      # word-differences (states of wd) defined at the previous level (level-1)

      diff := identity;
      donesub := false;
      donediffs := false;
      while not donesub and not donediffs do
        deqi := diff = identity;
      # First look for a possible substitution of a shorter string
        newdiff := table[diff][ng1*gen1];
        if newdiff=identity then
          #Make substitution  reducing length of word by 1
          SubstitutedListFSA(w,level,level,[]);
          i := level-1;
          if not deqi then
            subvert := gptr[diffct];
	    dosub := true;
            while dosub and i>1 do
              w[i] := subvert.gen;
              i := i-1;
              if IsBound(subvert.backptr) then
	        subvert := subvert.backptr;
              else
                dosub := false;
              fi;
            od;
            if dosub and i=1 and subgpword then
              #We have a prefix
               w1 := Concatenation(w1, ShallowCopy(WordToListRWS(
                         wd.states.names[subvert.diffno],wd.states.alphabet)));
               ReduceWordWD( wd, w1);
            fi;
          fi;
          #Whenever we make a substitution, we have to go back one level more
          #than expected, because of our policy of looking ahead for
          #substitutions that reduce the length by 2.
          if i>1 then level:=i-1; else level:=i; fi;
          gct := gpref[level+1];
          donesub := true;
        elif newdiff>0 and level<Length(w) then
          #See if there is a substitution reducing length by 2.
          gen2 := w[level+1];
          t := table[newdiff][ng1*gen2];
          if t=identity then
            #Make substitution  reducing length of word by 2
            SubstitutedListFSA(w,level,level+1,[]);
            i := level-1;
            if not deqi then
              subvert := gptr[diffct];
  	      dosub := true;
              while dosub and i>1  do
                w[i] := subvert.gen;
                i := i-1;
                if IsBound(subvert.backptr) then
  	          subvert := subvert.backptr;
                else
                  dosub := false;
                fi;
              od;
              if dosub and i=1 and subgpword then
                #We have a prefix
               w1 := Concatenation(w1, ShallowCopy(WordToListRWS(
                         wd.states.names[subvert.diffno],wd.states.alphabet)));
                 ReduceWordWD( wd, w1);
              fi;
            fi;
            if i>1 then level:=i-1; else level:=i; fi;
            gct := gpref[level+1];
            donesub := true;
          fi;
        fi;

        if not donesub then
          #Now we loop over the generator that is a candidate for
          #substitution at this point.
          for gen2 in [1..ngens] do
            g2ltg1 := gen2 < gen1;
            newdiff := table[diff][ng1*(gen1-1)+gen2];
            if donesub then
              donesub := true;
              #i.e. do nothing - we really want to break from the for loop here.
            elif newdiff=identity then
              if deqi then 
                if g2ltg1 then
                  w[level] := gen2;
                  if level>2 then level:=level-2; else level:=level-1; fi;
                  gct := gpref[level+1];
                  donesub := true;
                fi;
              elif gptr[diffct].sublen>0 or
                                (gptr[diffct].sublen=0 and g2ltg1) then
                #Make a substitution by a string of equal length.
                w[level] := gen2;
                i := level-1;
                subvert := gptr[diffct];
    	        dosub := true;
                while dosub and i>1 do
                  w[i] := subvert.gen;
                  i := i-1;
                  if IsBound(subvert.backptr) then
    	            subvert := subvert.backptr;
                  else
                    dosub := false;
                  fi;
                od;
                if dosub and i=1 and subgpword then
                  #We have a prefix
                   w1 := Concatenation(w1, ShallowCopy(WordToListRWS(
                        wd.states.names[subvert.diffno],wd.states.alphabet)));
                   ReduceWordWD( wd, w1);
                fi;
                if i>1 then level:=i-1; else level:=i; fi;
                gct := gpref[level+1];
                donesub := true;
              fi;
            elif newdiff>0 then
              if cf[newdiff] then
                #We have this word difference stored already, but we will check
                #to see if the current string precedes the existing one.
                i := gpref[level];
                repeat
                  i := i+1;
                  subvert := gptr[i];
                until subvert.diffno=newdiff;
                olen := subvert.sublen;
                if not deqi then
                  l := gptr[diffct].sublen;
                fi;
                if deqi or l=0 then 
                  if g2ltg1 then nlen:=1;
                  elif gen2=gen1 then nlen:=0;
                  else  nlen:= -1;
                  fi;
                else
                  if l>0 then nlen:=l+1; else nlen:=l-1; fi;
                fi;
                if nlen > olen then # new string is better than existing one
                  subvert.gen := gen2;
                  subvert.sublen := nlen;
                  if deqi then Unbind(subvert.backptr);
                  else subvert.backptr := gptr[diffct];
                  fi;
                fi;
              else
               # this is a new word-difference at this level, so
               # we define a new vertex.
                gct := gct+1;
                gptr[gct] := rec();
                if not deqi then
                  l := gptr[diffct].sublen;
                fi;
                if deqi or l=0 then 
                  if g2ltg1 then nlen:=1;
                  elif gen2=gen1 then nlen:=0;
                  else  nlen:= -1;
                  fi;
                else
                  if l>0 then nlen:=l+1; else nlen:=l-1; fi;
                fi;
                subvert := gptr[gct];
                subvert.gen := gen2;
                subvert.diffno := newdiff;
                subvert.sublen := nlen;
                if not deqi then subvert.backptr := gptr[diffct]; fi;
                cf[newdiff] := true;
              fi;
            fi;
          od; # End of loop over gen2

          if not donesub then
            #Go on to next word-difference from the previous level
            if diff=identity then
              diffct := gpref[level-1] + 1;
            else
              diffct := diffct+1;
            fi;
            if not donesub and not donediffs then
              if diffct > gpref[level] then
                donediffs := true;
              else
                diff := gptr[diffct].diffno;
              fi;
            fi;
          fi;
        fi;
      od; #end of loop over word-differences at previous level

      level := level+1;
      gpref[level] := gct;
    od;
    w := w{[2..Length(w)]}; #remove subgroup symbol!
    return [w1,w];
end;

#############################################################################
##
#F  ReduceWordCosetsRWS(<rws>, <subrws>, <w> [,<subgpword>])
##   .  .  . reduce a word  as a coset of <subrws> in <rws>
##
##  ReduceWordCosetsRWS reduces the word <w>, as a coset representatibe of
##  <subrws> in <rws>.
##  <w> can be given either as a list of integers (internal format) or as
##  a word in the generators of the underlying group or monoid.
##  Either the reduction FSA, or one of the word-difference automata (in the
##  automatic group case) must be defined for <rws>.
##  In the latter case, the separate function ReduceWordWD is called.
## 
##  If the optional fourth argument is 'true', then a pair of words
##  [w1,w2] is returned, where w = w1*w2 in the group, w2 is the
##  coset reduction of w, and w1 is an element of the subgroup.
##
##  Public function.
ReduceWordCosetsRWS := function ( arg )
  local rws, subrws, w, subgpword, fsa, pos, label, state, history, eqn,
        sublen, table, ng,  i, word, IdWord, ns, cosrws, p, w1, wp, kb, wd;
  if Length(arg)<3 then
     Error("ReduceWordCosetsRWS must have at least 3 arguments.");
  fi;
  rws:=arg[1]; subrws:=arg[2]; w:=arg[3];
  if Length(arg)>=4 then subgpword:=arg[4]; else subgpword:=false; fi;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error("Second argument of ReduceWordCosetsRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  if subgpword and not cosrws!.isAvailableReductionPlus then
    Error("Subgroup word reduction not available.");
  fi;
  if not IsAvailableReductionCosetsRWS(rws,subrws) then
     Error(
    "Reduction algorithm unavailable. You must run KBCosets or AutCosets");
  fi;
  if not IsList(w) and not IsWord(w) then
     Error("Third argument is not a word or list.");
  fi;
  ng := Length(rws!.alphabet);
  if IsWord(w) then
     word :=true;
     w:=ShallowCopy(WordToListRWS(w,rws!.alphabet));
  else word := false;
  fi;
  if IsBound(cosrws!.warningOn) and cosrws!.warningOn then
    if IsBound(cosrws!.KBRun) and cosrws!.KBRun then
        Print(
 "#WARNING: system is not confluent, so reductions may not be to normal form.\n"
      );
      else
        Print(
 "#WARNING: word-difference reduction machine is not proven correct,\n",
 "          so reductions may not be to normal form.\n");
      fi;
      cosrws!.warningOn:=false;
      # only give the warning once, or it could become irritating!
  fi;
  if IsBound(cosrws!.midiff2) then
   # automatic cosets case
    kb := false;
    wd := cosrws!.midiff2;
  elif IsBound(cosrws!.midiff1) then
   # automatic cosets case
    kb := false;
    wd := cosrws!.midiff1;
  else kb := true;
  fi;
  # put the subgroup symbol in front of w.
  w := Concatenation([ng+1],w);
  if kb then
    w := ReduceWordRWS(cosrws,w);
    #remove prefix up to subgroup symbol
    p := Position(w,ng+1);
    if subgpword then
      w1 := List(w{[1..p-1]},i->i-ng-1);
    fi;
    w := w{[p+1..Length(w)]};
  else
    wp := ReduceWordCosetsWD(wd,w,subgpword);
    w1 := wp[1]; w := wp[2];
  fi;

  if word then
     if subgpword then
       if kb then
         w1 := ListToWordRWS(w1,rws!.subrws[ns]!.alphabet);
       else
         w1 := ListToWordRWS(w1,rws!.alphabet);
       fi;
     fi;
     w := ListToWordRWS(w,rws!.alphabet);
  fi;

  if subgpword then
    return [w1,w];
  fi;
  return w;
end;

#############################################################################
##
#F  IndexRWS(<rws>, <subrws>)
##    . . number of reduced coset words of <subrws> in <rws>
##
##  This merely calls the corresponding FSA function.
##
##  Public function.
IndexRWS := function ( rws, subrws )
  local ns, cosrws, rfsa, ng;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error("Second argument of IndexRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  if not IsAvailableIndexRWS(rws,subrws) then
     Error(
 "Index algorithm unavailable. You must run KBCosets or AutCosets first.");
  fi;
  if IsBound(cosrws!.warningOn) and cosrws!.warningOn then
  if cosrws!.KBRun then
        Print(
 "#WARNING: system is not confluent, so index returned may not be correct.\n"
      );
      else
        Print(
 "#WARNING: word-difference reduction machine is not proven correct,\n",
 "          so index returned may not be correct.\n");
      fi;
  cosrws!.warningOn:=false;
     # only give the warning once, or it could become irritating!
  fi;
  if IsBound(cosrws!.wa) then
     # automatic group case
      return LSizeDFA( cosrws!.wa );
  fi;
  rfsa := cosrws!.reductionFSA;
  ng := Length(rws!.alphabet);
  return LSizeDFA(rfsa,TargetDFA(rfsa,ng+1,rfsa.initial[1]));
end;

#############################################################################
##
#F  EnumerateCosetsRWS(<rws>, <subrws>, <min>, <max>)
##         . . enumerate reduced cosets words of <subrws> in <rws>
##
##  This merely calls the corresponding FSA function.
##  Words are converted to words in generators of underlying group
##  before returning.
##
##  Public function.
EnumerateCosetsRWS := function ( rws, subrws, min, max )
  local ret, x, i, ns, cosrws, rfsa, ng;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error("Second argument of EnumerateCosetsRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  if not IsAvailableIndexRWS(rws,subrws) then
     Error(
  "Enumeration algorithm unavailable. You must run KBCosets or AutCosets");
  fi;
  if IsBound(cosrws!.wa) then
   # automatic group case
    ret := LEnumerateDFA( cosrws!.wa, min, max );
  else
    rfsa := cosrws!.reductionFSA;
    ng := Length(rws!.alphabet);
    ret := LEnumerateDFA(
                rfsa, min, max, TargetDFA(rfsa,ng+1,rfsa.initial[1]));
  fi;
  return ret;
end;

#############################################################################
##
#F  SortEnumerateCosetsRWS(<rws>, <subrws>, <min>, <max>)
##         . . enumerate reduced cosets words of <subrws> in <rws> and sort
##
##  This merely calls the corresponding FSA function.
##  Words are converted to words in generators of underlying group
##  before returning.
##
##  Public function.
SortEnumerateCosetsRWS := function ( rws, subrws, min, max )
  local ret, x, i, ns, cosrws, rfsa, ng;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
  Error("Second argument of SortEnumerateCosetsRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  if not IsAvailableIndexRWS(rws,subrws) then
     Error(
  "Enumeration algorithm unavailable. You must run KBCosets or AutCosets");
  fi;
  if IsBound(cosrws!.wa) then
   # automatic group case
    ret := SortLEnumerateDFA( cosrws!.wa,min,max );
  else
    rfsa := cosrws!.reductionFSA;
    ng := Length(rws!.alphabet);
    ret := SortLEnumerateDFA(
                rfsa, min, max, TargetDFA(rfsa,ng+1,rfsa.initial[1]));
  fi;
  return ret;
end;

#############################################################################
##
#F  SizeEnumerateCosetsRWS(<rws>, <subrws>, <min>, <max>)
##         . . number of reduced cosets words of <subrws> in <rws>
##
##  This merely calls the corresponding FSA function.
##  Words are converted to words in generators of underlying group
##  before returning.
##
##  Public function.
SizeEnumerateCosetsRWS := function ( rws, subrws, min, max )
  local ret, x, i, IdWord, ns, cosrws, rfsa, ng;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
  Error("Second argument of SizeEnumerateCosetsRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];
  if not IsAvailableIndexRWS(rws,subrws) then
     Error(
  "Enumeration algorithm unavailable. You must run KBCosets or AutCosets");
  fi;
  if IsBound(cosrws!.wa) then
   # automatic group case
    return SizeLEnumerateDFA( cosrws!.wa,min,max );
  else
    rfsa := cosrws!.reductionFSA;
    ng := Length(rws!.alphabet);
    return SizeLEnumerateDFA(
                rfsa, min, max, TargetDFA(rfsa,ng+1,rfsa.initial[1]));
  fi;
end;


#############################################################################
##
#F  ResetCosetsRWS(<rws>,<subrws>)  .  reset coset rws after a call of KBCosets.
##
##  Public function.
##  This resets a coset rewriting system back to the original, after a
##  call of KBCosets or AutCosets.
##  Perhaps useful if order of alphabet is to be changed.
ResetCosetsRWS := function ( rws, subrws )
  local ns, cosrws;
  ns := NumberSubgroupRWS(rws,subrws);
  if ns = fail then
    Error("Second argument of ResetCosetsRWS is not a subRWS of first.");
  fi;
  cosrws := rws!.cosrws[ns];

  Unbind(cosrws!.KBRun);
  Unbind(cosrws!.isConfluent);
  Unbind(cosrws!.isAvailableNormalForm);
  Unbind(cosrws!.isAvailableReduction);
  Unbind(cosrws!.isAvailableIndex);
  Unbind(cosrws!.warningOn);
  Unbind(cosrws!.reductionFSA);
  Unbind(cosrws!.wa);
  Unbind(cosrws!.midiff1);
  Unbind(cosrws!.midiff2);
  Unbind(cosrws!.migm);
  Unbind(cosrws!.equations);
end;
