Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  interact.gi   Sprache: unbekannt

 
Spracherkennung für: .gi vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

#############################################################################
##
#W  interact.gi                ACE Package                        Greg Gamble
##
##  This file  installs  commands for using ACE interactively via IO Streams.
##    
#Y  Copyright (C) 2000  Centre for Discrete Mathematics and Computing
#Y                      Department of Information Technology & Electrical Eng.
#Y                      University of Queensland, Australia.
##

#############################################################################
####
##
#F  ACE_IOINDEX . . . . . . . . . . . .  Get the index of the ACEData.io list
##  . . . . . . . . . . . . . . . . . . . . . for an interactive ACE session.
##
InstallGlobalFunction(ACE_IOINDEX, function(arglist)
local ioIndex;

  if IsEmpty(arglist) then
    # Find the first bound ioIndex
    ioIndex := 1;
    while not(IsBound(ACEData.io[ioIndex])) and ioIndex < Length(ACEData.io) do
      ioIndex := ioIndex + 1;
    od;
    if IsBound(ACEData.io[ioIndex]) then
      return ioIndex;
    else
      Info(InfoACE + InfoWarning, 1, 
           "No interactive ACE sessions are currently active");
      return fail;
    fi;
  elif IsBound(ACEData.io[ arglist[1] ]) then
    return arglist[1];
  else
    Error("no such interactive ACE session\n");
  fi;
end);

#############################################################################
####
##
#F  ACE_IOINDEX_ARG_CHK . . . . . . . . Checks for the right no. of arguments
##  . . . . . . . . . . . . . . . . . . warns user of any  ignored  arguments 
##
InstallGlobalFunction(ACE_IOINDEX_ARG_CHK, function(arglist)
  if Length(arglist) > 1 then
    Info(InfoACE + InfoWarning, 1,
         "Expected 0 or 1 arguments, all but first argument ignored");
  fi;
end);

#############################################################################
##
#F  ACEDataRecord([<i>]) . . . . . . . . returns the data record of a process
##
InstallGlobalFunction(ACEDataRecord, function( arg )
  if not IsEmpty(arg) and arg[1] = 0 and IsBound( ACEData.ni ) then
    return ACEData.ni;
  else
    return ACEData.io[ CallFuncList(ACEProcessIndex, arg) ];
  fi;
end);

#############################################################################
####
##
#F  ACEProcessIndex . . . . . . . . . . . . . . . User version of ACE_IOINDEX
##
##  If given (at least) one integer argument returns the first argument if it
##  corresponds  to  an  active  interactive  process  or  raises  an  error,
##  otherwise it returns the default active interactive process. If the  user
##  provides more than one argument then all arguments other than  the  first
##  argument are ignored (and a warning is issued).
##
InstallGlobalFunction(ACEProcessIndex, function(arg)
local ioIndex;
  ACE_IOINDEX_ARG_CHK(arg);
  ioIndex := ACE_IOINDEX(arg);
  if ioIndex = fail then
    Error( "no currently active interactive ACE sessions" );
  fi;
  return ioIndex;
end);

#############################################################################
####
##
#F  ACEProcessIndices . . . . . . . . . .  Returns the list of indices of all
##  . . . . . . . . . . . . . . . . . . .  active interactive  ACE  processes
##
##
InstallGlobalFunction(ACEProcessIndices, function()
  return Filtered( [1..Length(ACEData.io)], i -> IsBound( ACEData.io[i] ) );
end);

#############################################################################
####
##
#F  IsACEProcessAlive . . . . . . . . . . Returns true if the stream  of  the
##  . . . . . . . . . . . . . . . . . . . interactive ACE process  determined
##  . . . . . . . . . . . . . . . . . . . by arg can be written to  (i.e.  is
##  . . . . . . . . . . . . . . . . . . . .  still alive) and false otherwise
##
InstallGlobalFunction(IsACEProcessAlive, function(arg)
  return not IsEndOfStream( CallFuncList(ACEDataRecord, arg).stream );
end);

#############################################################################
####
##
#F  ACEResurrectProcess . . . . . . . . . Re-generates the stream of the i-th
##  . . . . . . . . . . . . . . . . . . . interactive ACE process, where i is
##  . . . . . . . . . . . . . . . . . . . determined by  arg,  and  tries  to
##  . . . . . . . . . . . . . . . . . . . recover as much as possible of  the
##  . . . . . . . . . . . . . . . . . . . previous state from saved values of
##  . . . . . . . . . . . . . . . . . . . . .  the args and parameter options
##
##  The  args  of  the  i-th  interactive   ACE   process   are   stored   in
##  ACEData.io[i].args (a record with fields fgens, rels and sgens, which are
##  the   GAP   group   generators,   relators   and   subgroup   generators,
##  respectively). Option information is saved in ACEData.io[i].options  when
##  a user uses an interactive ACE interface function with  options  or  uses
##  SetACEOptions. Option information is saved in ACEData.io[i].parameters if
##  ACEParameters is used to extract from ACE the current values of  the  ACE
##  parameter options (this is generally less reliable unless one of the  ACE
##  modes has been run previously).
##
##  By default, ACEResurrectProcess  recovers  parameter  option  information
##  from    ACEData.io[i].options    if    it    is    bound,     or     from
##  ACEData.io[i].parameters if is bound, otherwise. To alter this behaviour,
##  the user is provided two options:
##
##   use := list  . list  may  contain  one  or   both   of   "options"   and
##                  "parameters". By default: use = ["options", "parameters"]
##
##   useboth  . . . (boolean) By default: useboth = false
##
##  If useboth is true, ACEResurrectProcess applies SetACEOptions  with  each
##  ACEData.io[i].(field) for each field ("options" or "parameters") that  is
##  bound and in use's list, in the order implied  by  list.  If  useboth  is
##  false,      ACEResurrectProcess      applies      SetACEOptions      with
##  ACEData.io[i].(field) for only the first field that  is  bound  in  use's
##  list.
##
InstallGlobalFunction(ACEResurrectProcess, function(arg)
local ioIndex, datarec, gens, ToACE, uselist, useone, saved, optname, field;

  ioIndex := CallFuncList(ACEProcessIndex, arg);
  datarec := ACEData.io[ ioIndex ];
  if not IsEndOfStream( datarec.stream ) then
    Info(InfoACE + InfoWarning, 1, 
         "Huh? Stream of interactive ACE process ", ioIndex, " not dead?");
    return fail;
  fi;

  # Restart the stream
  datarec.stream := InputOutputLocalProcess(ACEData.tmpdir, ACEData.binary, []);

  if IsBound(datarec.args) and IsBound(datarec.args.fgens) then
    gens := TO_ACE_GENS(datarec.args.fgens);
    ToACE := function(list) WRITE_LIST_TO_ACE_STREAM(datarec.stream, list); end;
    ToACE([ "Group Generators: ", gens.toace, ";" ]);
    Info(InfoACE, 1, "Group generators:", datarec.args.fgens);
    if IsBound(datarec.args.rels) then
      ToACE([ "Group Relators: ", 
              ACE_WORDS(datarec.args.rels, datarec.args.fgens, gens.acegens), 
              ";" ]);
      Info(InfoACE, 1, "Relators:", datarec.args.rels);
    else
      Info(InfoACE + InfoWarning, 1, "No relators.");
    fi;
    if IsBound(datarec.args.sgens) then
      ToACE([ "Subgroup Generators: ", 
              ACE_WORDS(datarec.args.sgens, datarec.args.fgens, gens.acegens), 
              ";" ]);
      Info(InfoACE, 1, "Subgroup generators:", datarec.args.sgens);
    else
      Info(InfoACE + InfoWarning, 1, "No subgroup generators.");
    fi;
  else
    Info(InfoACE + InfoWarning, 1, "No group generators.");
  fi;
    
  uselist := Filtered(ACE_VALUE_OPTION("use", ["options", "parameters"]),
                      field -> IsBound(datarec.(field)) );
  useone := not ACE_VALUE_OPTION("useboth", false);
  if IsEmpty(uselist) then
    Info(InfoACE + InfoWarning, 1, "Sorry. No parameter options recovered.");
  else
    if useone then
      uselist := uselist{[1]};
    fi;
    if "options" in uselist then
      # Scrub any non{-parameter,-strategy,-echo} options
      for optname in Filtered(
                         RecNames(datarec.options),
                         function(optname)
                           local prefname;
                           prefname := ACEPreferredOptionName(optname);
                           return prefname <> "echo" and
                                  not (prefname in ACEStrategyOptions) and
                                  not (prefname in RecNames(
                                                       ACEParameterOptions
                                                       ));
                         end
                         )
      do
        Unbind( datarec.options.(optname) );
      od;
      saved := rec(options := datarec.options);
      if IsBound(datarec.parameters) then
        saved.parameters := datarec.parameters;
      fi;
    else
      saved := rec( parameters := ShallowCopy(datarec.parameters) );
      if IsBound(datarec.options) then
        for optname in Filtered( 
                           RecNames(datarec.options),
                           optname -> ACEPreferredOptionName(optname) = "echo" 
                           )
        do
          saved.parameters.(optname) := datarec.options.(optname);
        od;
      fi;
    fi;
    Unbind( datarec.options );
    for field in uselist do
      PushOptions( saved.(field) );
      INTERACT_SET_ACE_OPTIONS("ACEResurrectProcess", datarec);
      PopOptions();
    od;
    Info(InfoACE, 1, "Options set to: ", GetACEOptions(ioIndex));
  fi;
  if not IsBound(datarec.options) then
    datarec.options := rec();
  fi;
end);

#############################################################################
####
##
#F  READ_ACE_ERRORS . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . . . . . . . . . reads interactive ACE output from
##  . . . . . . . . . . . . . . . . . . . . stream  when  none  is  expected.
##
##  Writes any output read to Info at InfoACE + InfoWarning level 1.
##
##  This function may miss data output by ACE purely because it wasn't  ready
##  at the time of the call. If it turns out that READ_ACE_ERRORS is used  in
##  a place where it's important that all data be collected  from  ACE,  then
##  the  call  to  READ_ACE_ERRORS  should  be  replaced   by   a   call   to
##  ENSURE_NO_ACE_ERRORS.
##
InstallGlobalFunction(READ_ACE_ERRORS, function(datarec)
local line;

  line := ReadAllLine(datarec.stream);
  while line <> fail do
    if not IsMatchingSublist(line, "** ERROR") and
       Length(line) > 1 and line[ Length(line) - 1 ] = ')' then
      #a `start', `aep' or `rep' option was slipped in
      datarec.enumResult := Chomp(line);
      datarec.stats := ACE_STATS(datarec.enumResult);
    fi;
    Info(InfoACE + InfoWarning, 1, Chomp(line));
    line := ReadAllLine(datarec.stream);
  od;
end);

#############################################################################
####
##
#F  ENSURE_NO_ACE_ERRORS  . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . . . . . . . . . . . .  purges all interactive ACE
##  . . . . . . . . . . . . . . . . . . . . . . . . . . .  output from stream
##
##  Writes any output read to Info at InfoACE + InfoWarning level 1.
##
##  This function is like READ_ACE_ERRORS but makes ACE write "***" which  we
##  use as a sentinel to ensure we get all output due to  be  collected  from
##  ACE.
##
InstallGlobalFunction(ENSURE_NO_ACE_ERRORS, function(datarec)

  PROCESS_ACE_OPTION(datarec.stream, "text", "***"); # Causes ACE to print "***"
  FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE,
                         line -> IsMatchingSublist(line, "***"));
end);

#############################################################################
####
##
#F  INTERACT_TO_ACE_WITH_ERRCHK . . . . . . . . . . . . .  Internal procedure
##  . . . . . . . . . . . . . .  interactive ToACE procedure with error check
##
##  Writes list to the interactive ACE iostream stream and reads from  stream
##  to check for errors. Any output read is written  to  Info  at  InfoACE  +
##  InfoWarning level 1. Used where no output is expected.
##
InstallGlobalFunction(INTERACT_TO_ACE_WITH_ERRCHK, function(datarec, list)

  WRITE_LIST_TO_ACE_STREAM(datarec.stream, list);
  READ_ACE_ERRORS(datarec);
end);

#############################################################################
####
##
#F  ACE_ENUMERATION_RESULT  . . . .  Get and return an ACE enumeration result
##
##
InstallGlobalFunction(ACE_ENUMERATION_RESULT, function(stream, readline)
  # Call LAST_ACE_ENUM_RESULT with first (3rd argument) set to true,
  # so that it returns on the first enumeration result (or error) found
  return  LAST_ACE_ENUM_RESULT(stream, readline, true);
end);

#############################################################################
####
##
#F  LAST_ACE_ENUM_RESULT  . . . .  Get and return the last enumeration result
##
##  Enumeration result lines are recognised by being ones that end  in  ")\n"
##  but not starting with "** " or " " (as ACE error diagnostics do) ... this
##  is potentially flaky.
##
##  Reads and Infos lines from stream via function readline until a  sentinel
##  "***" and returns the last enumeration result (or  error)  found,  unless
##  first = true, in which case, it simply returns on the  first  enumeration
##  result (or error) found (without looking for a sentinel "***").
##
InstallGlobalFunction(LAST_ACE_ENUM_RESULT, function(stream, readline, first)
local errmsg, onbreakmsg, IsLastLine, IsEnumLine, line, enumResult;

  if first = true then
    IsLastLine := line -> true;
    IsEnumLine := line -> Length(line) > 1 and line[ Length(line) - 1 ] = ')';
  else
    IsLastLine := line -> IsMatchingSublist(line, "***");
    IsEnumLine := line -> line = fail or IsMatchingSublist(line, "***") or 
                          Length(line) > 1 and line[ Length(line) - 1 ] = ')';
  fi;
  repeat
    line := Chomp(FLUSH_ACE_STREAM_UNTIL(stream, 3, 10, readline, IsEnumLine));
    if line = fail then
      errmsg := ["expected to find output ...",
                 "possibly, you have reached the limit of what can be",
                 "written to ACEData.tmpdir (temporary directory)."];
      onbreakmsg :=
                ["You can only 'quit;' from here.",
                 "You will have to redo the calculation, but before that",
                 "try running 'ACEDirectoryTemporary(<dir>);' for some",
                 "directory <dir> where you know you will not be so limited."];
      Error(ACE_ERROR(errmsg, onbreakmsg), "\n");
    elif IsMatchingSublist(line, "** ERROR") then
      Info(InfoACE + InfoWarning, 1, line);
      line := Chomp( readline(stream) );
      Info(InfoACE + InfoWarning, 1, line);
      enumResult := Concatenation("ACE Enumeration failed: ", line);
    elif (first = true) or not IsLastLine(line) then
      Info(InfoACE, 2, line);
      enumResult := line;
    else
      Info(InfoACE, 3, line);
    fi;
  until IsLastLine(line);
  if IsMatchingSublist(enumResult, "ACE Enum") and first <> fail then
    Error(enumResult, "\n");
  fi;
  return enumResult;
end);

#############################################################################
####
##
#F  ACEWrite  . . . . . . . . . . . . . . . . . . . .  Primitive write to ACE
##
##  Writes the last argument to the i-th interactive ACE process, where i  is
##  the first argument if there are 2 arguments or  the  default  process  if
##  there is only 1 argument. The action is echoed via Info at InfoACE  level
##  4 (with a `ToACE> ' prompt). Returns true if successful in writing to the
##  stream and fail otherwise.
##
InstallGlobalFunction(ACEWrite, function(arg)

  if Length(arg) in [1, 2] then
    return WRITE_LIST_TO_ACE_STREAM( 
               CallFuncList(ACEDataRecord, arg{[1..Length(arg) - 1]}).stream,
               arg{[Length(arg)..Length(arg)]} );
  else
    Error("expected 1 or 2 arguments ... not ", Length(arg), " arguments\n");
  fi;
end);

#############################################################################
####
##
#F  ACERead . . . . . . . . . . . . . . . . . . . . . Primitive read from ACE
##
##  Reads a complete line of  ACE  output,  from  the  i-th  interactive  ACE
##  process, if there is output to be read and returns fail otherwise,  where
##  i is the first argument if there is 1 argument or the default process  if
##  there are no arguments.
##
InstallGlobalFunction(ACERead, function(arg)

  return ReadAllLine( CallFuncList(ACEDataRecord, arg).stream );
end);

#############################################################################
####
##
#F  ACEReadAll  . . . . . . . . . . . . . . . . . . . Primitive read from ACE
##
##  Reads and returns as many complete lines of ACE  output,  from  the  i-th
##  interactive ACE process, as there are to be read, as a  list  of  strings
##  with the trailing newlines removed and returns the empty list  otherwise,
##  where i is the first argument if there  is  1  argument  or  the  default
##  process if there are no arguments. Also writes via Info at InfoACE  level
##  3 each line read.
##
InstallGlobalFunction(ACEReadAll, function(arg)
local stream, lines, line;

  stream := CallFuncList(ACEDataRecord, arg).stream;
  lines := [];
  line := ReadAllLine(stream);
  while line <> fail do
    line := Chomp(line);
    Info(InfoACE, 3, line);
    Add(lines, line);
    line := ReadAllLine(stream);
  od;
  return lines;
end);

#############################################################################
####
##
#F  ACEReadUntil  . . . . . . . . . . . . . . . . . . Primitive read from ACE
##
##  Reads complete lines  of  ACE  output,  from  the  i-th  interactive  ACE
##  process, until a line for which IsMyLine(line) is true, where  i  is  the
##  first argument if the first argument is an integer or the default process
##  otherwise, and IsMyLine is the first function argument.  The  lines  read
##  are returned as a list of strings with the trailing newlines removed.  If
##  IsMyLine(line) is never true ACEReadUntil will  wait  indefinitely.  Also
##  writes via Info at InfoACE level 3 each line read. If there is  a  second
##  function argument it is used to modify each returned line; in this  case,
##  each line is emitted to  Info  before  modification,  but  each  line  is
##  modified before the IsMyLine test.
##
InstallGlobalFunction(ACEReadUntil, function(arg)
local idx1stfn, stream, IsMyLine, Modify, lines, line;

  idx1stfn := First([1..Length(arg)], i -> IsFunction(arg[i]));
  if idx1stfn = fail then
    Error("expected at least one function argument\n");
  elif Length(arg) > idx1stfn + 1 then
    Error("expected 1 or 2 function arguments, not ", 
          Length(arg) - idx1stfn + 1, "\n");
  elif idx1stfn > 2  then
    Error("expected 0 or 1 integer arguments, not ", idx1stfn - 1, "\n");
  else
    stream := CallFuncList(ACEDataRecord, arg{[1..idx1stfn - 1]}).stream;
    IsMyLine := arg[idx1stfn];
    if idx1stfn = Length(arg) then
      Modify := line -> line; # The identity function
    else
      Modify := arg[Length(arg)];
    fi;
    lines := [];
    repeat
      line := Chomp( ACE_READ_NEXT_LINE(stream) );
      Info(InfoACE, 3, line);
      line := Modify(line);
      Add(lines, line);
    until IsMyLine(line);
    return lines;
  fi;
end);

#############################################################################
####
##
#F  ACE_STATS . . . . . . . . . . . . . . . . Called by ACEStart and ACEStats
##  
##
InstallGlobalFunction(ACE_STATS, function(line)
local stats;

  # Parse line for statistics and return
  stats := Filtered(line, char -> char in ". " or char in CHARS_DIGITS);
  if not IsMatchingSublist(line, "INDEX") then
    # Enumeration failed so the index is missing 
    # ... shove a 0 index on the front of stats
    stats := Concatenation("0 ", stats);
  fi;
  stats := SplitString(stats, "", " .");

  return rec(index     := Int(stats[1]),
             cputime   := Int(stats[7])*10^Length(stats[8])+Int(stats[8]),
             cputimeUnits := Concatenation("10^-", String(Length(stats[8])),
                                           " seconds"),
             activecosets := Int(stats[2]),
             maxcosets := Int(stats[9]),
             totcosets := Int(stats[10]));
end);

#############################################################################
####
##
#F  ACE_COSET_TABLE
##
##
InstallGlobalFunction(ACE_COSET_TABLE, 
                      function(activecosets, acegens, iostream, readline)
local n, line, genColIndex, invColIndex, table, i, rowi, j, colj, invcolj;

  n := Length(acegens);

  # Skip some header until the ` coset ' line
  line := FLUSH_ACE_STREAM_UNTIL(iostream, 3, 3, readline, 
                                 line -> Length(line)>5 and
                                         line{[1..6]} in [" coset", "** ERR"]);
  if IsMatchingSublist(line, "** ERROR") then
    line := Chomp(readline(iostream));
    Info(InfoACE, 1, line);
    Error(line{[3..Length(line)]}, ". Try running ACEStart first.\n");
  fi;
  # Extract the coset table column headers
  rowi := SplitString(line, "", " |\n");

  # Look at the coset table column headers and determine the column
  # corresponding to each generator:
  #   colIndex[j] = Index of column(acegens[j])
  genColIndex := List(acegens, gen -> Position(rowi, gen));
  invColIndex := List(genColIndex, 
                      i -> ACE_IF_EXPR(
                               i + 1 in genColIndex or i + 1 > Length(rowi),
                               i,
                               i + 1,
                               0 # doesn't occur
                               ));
  # Discard the `---' line
  line := Chomp( readline(iostream) );
  Info(InfoACE, 3, line);

  # Now read the body of the coset table into table as a GAP List
  table := List([1 .. 2*n], j -> []);
  i := 0;
  repeat
    line := Chomp( readline(iostream) );
    Info(InfoACE, 3, line);
    i := i + 1;
    rowi := SplitString(line, "", " :|");
    for j in [1..n] do
      Add(table[2*j - 1], Int(rowi[ genColIndex[j] ]));
      Add(table[2*j],     Int(rowi[ invColIndex[j] ]));
    od;
  until i = activecosets;

  return table;
end);

#############################################################################
####
##
#F  ACE_MODE  . . . . . . . . . . . .  Start, continue or redo an enumeration
##  . . . . . . . . . . . .  also sets enumResult and stats fields of datarec
##
InstallGlobalFunction(ACE_MODE, function(mode, datarec)
  ENSURE_NO_ACE_ERRORS(datarec); # purge any output not yet collected
                                 # e.g. error messages due to unknown 
                                 # or inappropriate options
  WRITE_LIST_TO_ACE_STREAM(datarec.stream, [ mode, ";" ]);
  datarec.enumResult := ACE_ENUMERATION_RESULT(datarec.stream, 
                                               ACE_READ_NEXT_LINE);
  datarec.stats := ACE_STATS(datarec.enumResult);
end);
  
#############################################################################
####
##
#F  ACE_MODE_AFTER_SET_OPTS . . . . . . . Gets ACE stream index, sets options 
##  . . . . . . . . . . . . . . . . . . . and then calls ACE_MODE  to  start,
##  . . . . . . . . . . . . . . . . . . . . . continue or redo an enumeration
##
InstallGlobalFunction(ACE_MODE_AFTER_SET_OPTS, function(mode, arglist)
local ioIndex;
  ioIndex := CallFuncList(ACEProcessIndex, arglist);
  INTERACT_SET_ACE_OPTIONS(Flat( ["ACE", mode] ), ACEData.io[ioIndex]);
  if IsEmpty( ACEGroupGenerators(ioIndex) ) then
    Info(InfoACE + InfoWarning, 1, "ACE", mode, " : No group generators?!");
  else
    ACE_MODE(mode, ACEData.io[ioIndex]);
  fi;
  return ioIndex;
end);
  
#############################################################################
####
##
#F  CHEAPEST_ACE_MODE . . . . . . . . . . . . .  Does ACE_MODE(mode, datarec)
##  . . . . . . . . . . . . . . . . . . . . . for the cheapest mode available
##
InstallGlobalFunction(CHEAPEST_ACE_MODE, function(datarec)
local modes, mode;
  modes := ACE_MODES( datarec );
  mode := First( RecNames(modes), ACEmode -> modes.(ACEmode) );
  if mode = fail then
    Error("none of ACEContinue, ACERedo or ACEStart is possible. Huh???\n");
  else
    ACE_MODE(mode{[4..Length(mode)]}, datarec);
  fi;
end);
  
#############################################################################
####
##
#F  ACE_LENLEX_CHK  . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . for the interactive ACE process indexed by ioIndex,
##  . . . . . . . . . . . determine the coset  table  standardisation  scheme
##  . . . . . . . . . . . desired by the user: if "lenlex" ensure  `asis'  is
##  . . . . . . . . . . . enforced and re-emit the  relators  using  ACE_RELS
##  . . . . . . . . . . . with 4th arg `true' to avoid ACE swapping the first
##  . . . . . . . . . . . two generators, if  necessary;  when  found  to  be
##  . . . . . . . . . . . necessary `start' is invoked and if  dostandard  is
##  . . . . . . . . . . . true, `standard' is invoked. Finally the determined
##  . . . . . . . . . . . . . coset table standardisation scheme is returned.
##
InstallGlobalFunction(ACE_LENLEX_CHK, function(ioIndex, dostandard)
local datarec, standard;
  datarec := ACEData.io[ ioIndex ];  
  standard := ACE_COSET_TABLE_STANDARD( datarec.options );
  if (standard = "lenlex") and IsBound(datarec.enumResult) then
    if (not IsBound(datarec.enforceAsis) or not datarec.enforceAsis) and 
       not IsACEGeneratorsInPreferredOrder(ioIndex) then
      datarec.enforceAsis := true;
      PROCESS_ACE_OPTION(datarec.stream, "relators", 
                         ACE_RELS(ACERelators(ioIndex),
                                  ACEGroupGenerators(ioIndex),
                                  datarec.acegens,
                                  true));
      PROCESS_ACE_OPTION(datarec.stream, "asis", 1);
      ACE_MODE("Start", datarec);
    fi;
    if dostandard then
      PROCESS_ACE_OPTION(datarec.stream, "standard", "");
    fi;
  fi;
  return standard;
end);

#############################################################################
####
##
#F  SET_ACE_ARGS . . . . . . . . . . . . . . . . . . . . .  Set ACEStart args
##
##
InstallGlobalFunction(SET_ACE_ARGS, function(ioIndex, fgens, rels, sgens)
local datarec, gens;
  ioIndex := ACEProcessIndex(ioIndex); # Ensure ioIndex is valid
  fgens := ACE_FGENS_ARG_CHK(fgens);
  rels  := ACE_WORDS_ARG_CHK(fgens, rels, "relators");
  sgens := ACE_WORDS_ARG_CHK(fgens, sgens, "subgp gen'rs");
  
  gens := TO_ACE_GENS(fgens);
  datarec := ACEData.io[ ioIndex ];
  datarec.enforceAsis 
      := ( DATAREC_VALUE_ACE_OPTION(datarec, false, "lenlex") or
           VALUE_ACE_OPTION( ACE_OPT_NAMES(), false, "lenlex") ) and
         not IsACEGeneratorsInPreferredOrder(fgens, rels, "noargchk");
  datarec.echoargs := true; # If echo option is set INTERACT_SET_ACE_OPTIONS
                            # will echo args
  PROCESS_ACE_OPTION(datarec.stream, "group", gens.toace);
  PROCESS_ACE_OPTION(datarec.stream, "relators", 
                     ACE_RELS(rels, fgens, gens.acegens, datarec.enforceAsis));
  PROCESS_ACE_OPTION(datarec.stream, "generators", 
                     ACE_WORDS(sgens, fgens, gens.acegens));
  if datarec.enforceAsis then
    PROCESS_ACE_OPTION(datarec.stream, "asis", 1);
  fi;
  datarec.args := rec(fgens := fgens, rels := rels, sgens := sgens);
  datarec.acegens := gens.acegens;
  return ioIndex;
end);
  
#############################################################################
####
##
#F  NO_START_DO_ACE_OPTIONS . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . . . If set is true, set options for the  ACEStart
##  . . . . . . . . . . . . . . process indexed by ioIndex. If one of the new
##  . . . . . . . . . . . . . . options evokes an enumeration the  enumResult
##  . . . . . . . . . . . . . . and stats fields are re-set. All  ACE  output
##  . . . . . . . . . . . . . . is flushed. Called when no `start'  directive
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  is needed.
##
##
InstallGlobalFunction(NO_START_DO_ACE_OPTIONS, function(ioIndex, set)
local datarec, setEnumResult;
  datarec := ACEDataRecord(ioIndex);
  if not IsEmpty(OptionsStack) then
    setEnumResult := VALUE_ACE_OPTION( ACE_OPT_NAMES(), 
                                       fail, 
                                       ["start", "aep", "rep"] ) <> fail;
    if set then
      INTERACT_SET_ACE_OPTIONS("ACEStart", datarec);
    fi;
    PROCESS_ACE_OPTION(datarec.stream, "text", "***");
    if setEnumResult then
      datarec.enumResult 
          := LAST_ACE_ENUM_RESULT(datarec.stream, ACE_READ_NEXT_LINE, fail);
      if IsEmpty( ACEGroupGenerators(ioIndex) ) then
        Info(InfoACE + InfoWarning, 1, "ACEStart : No group generators?!");
        Unbind(datarec.enumResult);
        Unbind(datarec.stats);
      else
        datarec.stats := ACE_STATS(datarec.enumResult);
      fi;
    else
      FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE,
                             line -> IsMatchingSublist(line, "***"));
    fi;
  fi;
end);
  
#############################################################################
####
##
#F  ACEStart . . . . . . . . . . . . . .  Initiate an interactive ACE session
##
##
InstallGlobalFunction(ACEStart, function(arg)
local start, ioIndex, stream, datarec, gens;

  if Length(arg) > 5 then
    Error("expected at most 5 arguments ... not ", Length(arg), 
          " arguments.\n");
  elif Length(arg) = 2 and arg[1] <> 0 then
    Error("when called with 2 arguments, first argument should be 0.\n");
  elif not IsEmpty(arg) and arg[1] = 0 then
    start := false;
    arg := arg{[2..Length(arg)]};
  else
    start := true;
  fi;

  if Length(arg) in [3, 4] then
    if Length(arg) = 3 then #args are: fgens,  rels,  sgens
      ioIndex := CALL_ACE( "ACEStart", arg[1], arg[2], arg[3] );
    else             #arg{[2..4]} are: fgens,  rels,  sgens
      ioIndex := SET_ACE_ARGS( arg[1], arg[2], arg[3], arg[4] );
      NO_START_DO_ACE_OPTIONS(ioIndex, true);
    fi;
    if start then
      if IsEmpty( ACEGroupGenerators(ioIndex) ) then
        Info(InfoACE + InfoWarning, 1, "ACEStart : No group generators?!");
      else
        ACE_MODE( "Start", ACEData.io[ ioIndex ] );
      fi;
    elif Length(arg) = 3 then
      NO_START_DO_ACE_OPTIONS(ioIndex, false);
    fi;
  elif Length(arg) <= 1 and start then
    ioIndex := ACE_MODE_AFTER_SET_OPTS("Start", arg);
  else # start = false
    if Length(arg) = 1 then
      ioIndex := CallFuncList(ACEProcessIndex, arg);
    else
      stream := InputOutputLocalProcess(ACEData.tmpdir, ACEData.binary, []);
      if stream = fail then
        Error("sorry! Run out of pseudo-ttys. Can't initiate stream.\n");
      else
        Add( ACEData.io, rec(stream := stream, options := rec()) );
        ioIndex := Length(ACEData.io);
        ACEData.io[ioIndex].procId := ioIndex;
      fi;
    fi;
    NO_START_DO_ACE_OPTIONS(ioIndex, true);
  fi;
  ACE_LENLEX_CHK(ioIndex, false);
  return ioIndex;
end);

#############################################################################
##
#F  ACEQuit . . . . . . . . . . . . . . . .  Close an interactive ACE session
##
InstallGlobalFunction(ACEQuit, function(arg)
local ioIndex;

  ioIndex := CallFuncList(ACEProcessIndex, arg);
  CloseStream(ACEData.io[ioIndex].stream);
  Unbind(ACEData.io[ioIndex]);
end);

#############################################################################
##
#F  ACEQuitAll . . . . . . . . . . . . . . Close all interactive ACE sessions
##
InstallGlobalFunction(ACEQuitAll, function()
local ioIndex;

  for ioIndex in [1 .. Length(ACEData.io)] do
    if IsBound(ACEData.io[ioIndex]) then
      CloseStream(ACEData.io[ioIndex].stream);
      Unbind(ACEData.io[ioIndex]);
    fi;
  od;
end);

#############################################################################
##
#F  ACE_MODES . . . . . . . . . .  Returns a record of which of the ACE modes
##  . . . . . . . . . . . . . . . . . . Continue, Redo and Start are possible
##
InstallGlobalFunction(ACE_MODES, function(datarec)
local modes;

  READ_ACE_ERRORS(datarec);
  WRITE_LIST_TO_ACE_STREAM(datarec.stream, [ "mode;" ]);
  modes := SplitString(FLUSH_ACE_STREAM_UNTIL(
                           datarec.stream, 3, 2, ACE_READ_NEXT_LINE,
                           line -> IsMatchingSublist(line, "start = ")
                           ),
                       "",
                       " =,\n");
  return rec(ACEContinue := modes[4] = "yes", # Modes in order of `cheapness'
             ACERedo     := modes[6] = "yes",
             ACEStart    := modes[2] = "yes");
end);

#############################################################################
##
#F  ACEModes  . . . . . . . . . . . .  Returns a record of which of the modes
##  . . . . . . . . . . . . .  ACEContinue, ACERedo and ACEStart are possible
##
InstallGlobalFunction(ACEModes, function(arg)
  return ACE_MODES( CallFuncList(ACEDataRecord, arg) );
end);

#############################################################################
####
##
#F  ACEContinue  . . . . . . . . . . . .  Continue an interactive ACE session
##
##
InstallGlobalFunction(ACEContinue, function(arg)
  return ACE_MODE_AFTER_SET_OPTS("Continue", arg);
end);

#############################################################################
####
##
#F  ACERedo . . . . . . . . . . . . . . . . . Redo an interactive ACE session
##
##
InstallGlobalFunction(ACERedo, function(arg)
  return ACE_MODE_AFTER_SET_OPTS("Redo", arg);
end);

#############################################################################
####
##
#F  ACE_EQUIV_PRESENTATIONS . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . . . . . . . called  by   ACEAllEquivPresentations
##  . . . . . . . . . . . . . . . . . . and ACERandomEquivPresentations where
##  . . . . . . . . . . . . . . . . . . . . string matches the last line read
##
InstallGlobalFunction(ACE_EQUIV_PRESENTATIONS, function(ioIndex, string)
local datarec, out, run;
  datarec := ACEData.io[ ioIndex ];
  out := rec(line := FLUSH_ACE_STREAM_UNTIL(
                         datarec.stream, 3, 3, ACE_READ_NEXT_LINE, 
                         line -> Length(line) > 5 and
                                 line{[1..6]} in [ "Group ", "** ERR" ] 
                         ),
             runs := []);
  if IsMatchingSublist(out.line, "** ERROR") then
    # Can only happen for ACERandomEquivPresentations
    out.line := ACEReadUntil(ioIndex, 
                             line -> IsMatchingSublist(line, string))[1];
    Error("ACERandomEquivPresentations:", out.line{[3..Length(out.line)]});
  fi;
  while not IsMatchingSublist(out.line, string) do
    run := rec(rels := ACE_GAP_WORDS(datarec,
                                     ACE_PARAMETER_WITH_LINE(ioIndex, 
                                                             "Group Relators",
                                                             out.line)),
               enumResult := ACE_ENUMERATION_RESULT(datarec.stream,
                                                    ACE_READ_NEXT_LINE));
    run.stats := ACE_STATS(run.enumResult);
    Add(out.runs, run);
    out.line := ACE_READ_NEXT_LINE(datarec.stream);
    Info(InfoACE, 3, Chomp(out.line));
  od;
  return out;
end);
#############################################################################
####
##
#F  ACEAllEquivPresentations . . . . . . . Tests all equivalent presentations
##
##  For the i-th interactive ACE process, generates and tests an  enumeration
##  for combinations of relator  ordering,  relator  rotations,  and  relator
##  inversions, according to the value of optval,  where  i  and  optval  are
##  determined by arg. The argument optval is considered as a binary  number;
##  its three bits are treated as flags, and control relator  rotations  (the
##  2^0 bit), relator inversions (the 2^1 bit) and relator orderings (the 2^2
##  bit), respectively; 1 means `active' and 0 means `inactive'.
##
##  Outputs a record with fields:
##
##    primingResult 
##        the ACE enumeration result message of the priming run;
##
##    primingStats
##        the enumeration result of the priming run as  a  GAP  ACEStats-like
##        record;
##
##    equivRuns
##        a list of data records, one for each run,  where  each  record  has
##        fields:
##
##      rels
##        the relators in the order used for the run,
##
##      enumResult
##        the ACE enumeration result message of the run, and
##
##      stats
##        the enumeration result as a GAP ACEStats-like record;
##
##    summary
##        a record with fields:
##
##      successes
##        the total number of  successful  (i.e.  having  finite  enumeration
##        index) runs,
##
##      runs
##        the total number of equivalent presentation runs executed,
##
##      maxcosetsRange
##        the  range  of  values  as   a   GAP   list   inside   which   each
##        `equivRuns[i].maxcosets' lies, and
##
##      totcosetsRange
##        the  range  of  values  as  a  {\GAP}  list   inside   which   each
##        `equivRuns[i].totcosets' lies.
##
InstallGlobalFunction(ACEAllEquivPresentations, function(arg)
local ioIndexAndOptval, ioIndex, datarec, aep, epRec, line;
  ioIndexAndOptval := ACE_IOINDEX_AND_ONE_VALUE(arg);
  ioIndex := ioIndexAndOptval[1];
  datarec := ACEData.io[ ioIndex ];

  line := EXEC_ACE_DIRECTIVE_OPTION(ioIndexAndOptval, "aep", 3, 
                                    line -> IsMatchingSublist(line, "* P"), 
                                    "", false);
  if not IsMatchingSublist(line, "* P") then
    Error("ACEAllEquivPresentations:", line{[3..Length(line)]});
  fi;

  aep := rec(primingResult := ACE_ENUMERATION_RESULT(datarec.stream,
                                                     ACE_READ_NEXT_LINE));
  aep.primingStats := ACE_STATS(aep.primingResult);

  epRec := ACE_EQUIV_PRESENTATIONS(ioIndex, "* There were");
  aep.equivRuns := epRec.runs;

  line := SplitString(epRec.line, "", "* Therwsucinu:\n");
  aep.summary := rec(successes := Int(line[1]), runs := Int(line[2]));
  line := Chomp( ACE_READ_NEXT_LINE(datarec.stream) );
  Info(InfoACE, 3, line);
  line := SplitString(line, "", "* maxcost=,");
  aep.summary.maxcosetsRange
       := EvalString( Concatenation( "[", line[1], "]" ) );
  aep.summary.totcosetsRange
       := EvalString( Concatenation( "[", line[2], "]" ) );
  return aep;
end);

#############################################################################
####
##
#F  ACERandomEquivPresentations . . . . . Tests a number of random equivalent 
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . presentations
##
##  For the i-th interactive  ACE  process,  generates  and  tests  n  random
##  enumeration for combinations of relator ordering, relator rotations,  and
##  relator inversions,  according  to  the  value  of  optval,  where  n  is
##  determined by optval, and  i  and  optval  are  determined  by  arg.  The
##  argument optval is considered as a binary  number;  its  three  bits  are
##  treated as flags, and control relator rotations (the  2^0  bit),  relator
##  inversions  (the  2^1  bit)  and  relator  orderings   (the   2^2   bit),
##  respectively; 1 means `active' and 0 means `inactive'.
##
##  Outputs a list of records, each record of which has fields:
##
##    rels
##        the relators in the order used for a presentation run,
##
##    enumResult
##        the ACE enumeration result message of the run, and
##
##    stats
##        the enumeration result of the run as a GAP ACEStats-like record.
##
InstallGlobalFunction(ACERandomEquivPresentations, function(arg)
local ioIndexAndOptval, ioIndex, datarec, stream;
  ioIndexAndOptval := ACE_IOINDEX_AND_ONE_VALUE(arg);
  ioIndex := ioIndexAndOptval[1];
  datarec := ACEData.io[ ioIndex ];
  stream := datarec.stream;

  READ_ACE_ERRORS(datarec); # purge any output not yet collected
  PROCESS_ACE_OPTION(stream, "rep", ioIndexAndOptval[2]);
  PROCESS_ACE_OPTION(stream, "text", "------------------------------------");

  return ACE_EQUIV_PRESENTATIONS(ioIndex, "------------").runs;
end);

#############################################################################
####
##
#F  ACEGroupGenerators  . . . . . . . . . . . Return the GAP group generators
##  . . . . . . . . . . . . . . . . . . . . . . of an interactive ACE session
##
##
InstallGlobalFunction(ACEGroupGenerators, function(arg)
local datarec, ioIndex;

  datarec := CallFuncList(ACEDataRecord, arg);
  ioIndex := datarec.procId;
  if not( IsBound( datarec.args ) and IsBound( datarec.args.fgens ) ) then
    Info(InfoACE + InfoWarning, 1, 
         "No group generators saved. Setting value(s) from ACE ...");
    return ACE_ARGS(ioIndex, "fgens");
  else
    return datarec.args.fgens;
  fi;
end);

#############################################################################
####
##
#F  ACERelators . . . . . . . . . . . . . . . . . . . Return the GAP relators
##  . . . . . . . . . . . . . . . . . . . . . . of an interactive ACE session
##
##
InstallGlobalFunction(ACERelators, function(arg)
local datarec, ioIndex;

  datarec := CallFuncList(ACEDataRecord, arg);
  ioIndex := datarec.procId;
  if not( IsBound( datarec.args ) and IsBound( datarec.args.rels ) ) then
    Info(InfoACE + InfoWarning, 1, 
         "No relators saved. Setting value(s) from ACE ...");
    return ACE_ARGS(ioIndex, "rels");
  else
    return datarec.args.rels;
  fi;
end);

#############################################################################
####
##
#F  ACESubgroupGenerators . . . . . . . .  Return the GAP subgroup generators
##  . . . . . . . . . . . . . . . . . . . . . . of an interactive ACE session
##
##
InstallGlobalFunction(ACESubgroupGenerators, function(arg)
local datarec, ioIndex;

  datarec := CallFuncList(ACEDataRecord, arg);
  ioIndex := datarec.procId;
  if not( IsBound( datarec.args ) and IsBound( datarec.args.sgens ) ) then
    Info(InfoACE + InfoWarning, 1, 
         "No subgroup generators saved. Setting value(s) from ACE ...");
    return ACE_ARGS(ioIndex, "sgens");
  else
    return datarec.args.sgens;
  fi;
end);

#############################################################################
####
##
#F  DISPLAY_ACE_REC_FIELD . . . . . . . . . . . . . Displays  a  record  that
##  . . . . . . . . . . . . . . . . . . . . . . . . is itself a record  field
##
##
InstallGlobalFunction(DISPLAY_ACE_REC_FIELD, function(datarec, field)

  if not IsBound(datarec.(field)) or datarec.(field) = rec() then
    Print("No ", field, ".\n");
  else
    Display(datarec.(field));
    Print("\n");
  fi;
end);

#############################################################################
####
##
#F  DisplayACEOptions . . . . . . . . . . .  Displays the current ACE options
##
##
InstallGlobalFunction(DisplayACEOptions, function(arg)
  DISPLAY_ACE_REC_FIELD( CallFuncList(ACEDataRecord, arg), "options" );
end);

#############################################################################
####
##
#F  DisplayACEArgs  . . . . . . . . . . . . . . Displays the current ACE args
##
##
InstallGlobalFunction(DisplayACEArgs, function(arg)
  DISPLAY_ACE_REC_FIELD( CallFuncList(ACEDataRecord, arg), "args" );
end);

#############################################################################
####
##
#F  GET_ACE_REC_FIELD . . . . . . . . . . . . . . .  Returns a record that is
##  . . . . . . . . . . . . . . . . . . . . . . . .  itself  a  record  field
##  . . . . . . . . . . . . . . . . . . . . . . . .  associated   with     an
##  . . . . . . . . . . . . . . . . . . . . . . . . . interactive ACE process
##
##
InstallGlobalFunction(GET_ACE_REC_FIELD, function(arglist, field)
local datarec;

  datarec := CallFuncList(ACEDataRecord, arglist);
  if not IsBound(datarec.(field)) or datarec.(field) = rec() then
    Info(InfoACE + InfoWarning, 1, "No ", field, " saved.");
    return rec();
  else
    return datarec.(field);
  fi;
end);

#############################################################################
####
##
#F  GetACEOptions . . . . . . . . . . . . . . Returns the current ACE options
##
##
InstallGlobalFunction(GetACEOptions, function(arg)
  return GET_ACE_REC_FIELD(arg, "options");
end);

#############################################################################
####
##
#F  GetACEArgs . . . . . . . . . . . . . . . . . Returns the current ACE args
##
##
InstallGlobalFunction(GetACEArgs, function(arg)
  return GET_ACE_REC_FIELD(arg, "args");
end);

#############################################################################
####
##
#F  SET_ACE_OPTIONS . . . . . . . . . . . . . . . . . . .  Internal procedure
##  . . . . . . . . . . . . . . . . . . . . . . . . . Called by SetACEOptions
##
##  SetACEOptions has two forms: the  interactive  version  (below)  and  the
##  non-interactive version defined locally  within  ACECosetTable.  For  the
##  interactive version the data record datarec  is  ACEData.io[ioIndex]  for
##  some integer ioIndex. For the non-interactive version, which will only be
##  invoked from within a break-loop, datarec is ACEData.
##
InstallGlobalFunction(SET_ACE_OPTIONS, function(datarec)
local newoptnames;

  datarec.newoptions := NEW_ACE_OPTIONS();
  # First we need to scrub any option names in datarec.options that
  # match those in datarec.newoptions ... to ensure that *all* new
  # options are at the end of the stack
  SANITISE_ACE_OPTIONS(datarec.options, datarec.newoptions);
  PopOptions();
  Add(OptionsStack, datarec.options);
  PushOptions(datarec.newoptions);
  # The following is needed when SetACEOptions is invoked via ACEExample
  Unbind(OptionsStack[ Length(OptionsStack) ].aceexampleoptions);
  datarec.options := ShallowCopy( OptionsStack[ Length(OptionsStack) ] );
  # We ensure OptionsStack is the same length as before the call to 
  # SET_ACE_OPTIONS, and ensure the updated options are on top
  PopOptions();
  PopOptions();
  Add(OptionsStack, datarec.options);
  newoptnames := RecNames(datarec.newoptions);
  Unbind(datarec.newoptions);
  return newoptnames;
end);

#############################################################################
####
##
#F  ECHO_ACE_ARGS . . . . . . . . . . . . . . . . . . . .  Internal procedure
##  . . . . . . . . . . . . . . . . . . . . . . Echoes  the  values  of   the
##  . . . . . . . . . . . . . . . . . . . . . . fields: fgens, rels, sgens of
##  . . . . . . . . . . . . . . . . . . . . . . args  submitted  to  function
##  . . . . . . . . . . . . . . . . . . . . . . ACEfname if echo is positive.
##
InstallGlobalFunction(ECHO_ACE_ARGS, function(echo, ACEfname, args)
  if echo > 0 then
    Print(ACEfname, " called with the following arguments:\n");
    Print(" Group generators : ", args.fgens, "\n");
    Print(" Group relators : ", args.rels, "\n");
    Print(" Subgroup generators : ", args.sgens, "\n");
  fi;
end);

#############################################################################
####
##
#F  INTERACT_SET_ACE_OPTIONS  . . . . . . . . . . . . . .  Internal procedure
##  . . . . . . . . . . . . . . . . . . . . . . .  Passes new options to  ACE
##  . . . . . . . . . . . . . . . . . . . . . . .  and updates stored options
##
##  Called by the ACE function with name ACEfname and with datarec  equal  to
##  ACEData.io[ioIndex] for some integer ioIndex,  the  updated  options  are
##  stored in datarec.options.
##
InstallGlobalFunction(INTERACT_SET_ACE_OPTIONS, function(ACEfname, datarec)
local newoptnames, s, optnames, echo, ignored;
  datarec.modereqd := false;
  if not(IsEmpty(OptionsStack) or
         ForAll(RecNames(OptionsStack[ Length(OptionsStack) ]),
                optname -> optname in ACE_INTERACT_FUNC_OPTIONS)) then
    if IsBound(datarec.options) then
      newoptnames := SET_ACE_OPTIONS(datarec);
    else
      datarec.options := NEW_ACE_OPTIONS();
      newoptnames := RecNames(datarec.options);
    fi;
    optnames := RecNames(datarec.options);
    newoptnames := Filtered(
                       newoptnames,
                       optname -> not(optname in ACE_INTERACT_FUNC_OPTIONS));
    ignored := List(VALUE_ACE_OPTION(newoptnames, [], "aceignore"),
                    optname -> ACEPreferredOptionName(optname));
    datarec.modereqd := ForAny(newoptnames, 
                               function(optname)
                                 local prefname;
                                 
                                 prefname := ACEPreferredOptionName(optname);
                                 return not(prefname in NonACEbinOptions or
                                            prefname in ignored);
                               end);
    if ForAny(newoptnames, 
              optname -> ACEPreferredOptionName(optname)
                         in ["group", "relators", "generators"]) then
      for s in [ "Detected usage of a synonym of one (or more) of the options:",
                 "    `group', `relators', `generators'.",
                 "Discarding current values of args.",
                 "(The new args will be extracted from ACE, later)." ]
      do
        Info(InfoACE + InfoWarning, 1, s);
      od;
      Unbind(datarec.args);
      Unbind(datarec.acegens);
    fi;
    echo := ACE_VALUE_ECHO(optnames);
    if IsBound(datarec.echoargs) then
      if IsBound(datarec.args) then
        ECHO_ACE_ARGS( echo, ACEfname, datarec.args );
      fi;
      Unbind(datarec.echoargs);
    fi;
    PROCESS_ACE_OPTIONS(ACEfname, optnames, newoptnames, echo, datarec,
                        # disallowed (options) ... none
                        rec(),
                        # ignored
                        Concatenation( [ "aceinfile", "aceoutfile" ],
                                       ignored,
                                       ACE_IF_EXPR(
                                           IsBound(datarec.enforceAsis)
                                           and datarec.enforceAsis,
                                                   [ "asis" ], [], []) ));
  fi;
end);
  
#############################################################################
####
##
#F  SetACEOptions . . . . . . . . . . . .  Interactively, passes  new options 
##  . . . . . . . . . . . . . . . . . . .  to ACE and updates stored  options
##
InstallGlobalFunction(SetACEOptions, function(arg)
local datarec;

  if Length(arg) > 2 then
    Error("expected 0, 1 or 2 arguments ... not ", Length(arg), " arguments\n");
  elif Length(arg) in [1, 2] and IsRecord( arg[Length(arg)] ) then
    if not IsEmpty(OptionsStack) then
      Info(InfoACE + InfoWarning, 1,
           "Non-empty OptionsStack: SetACEOptions may have been called with");
      Info(InfoACE + InfoWarning, 1,
           "both a record argument and options. The order options are listed");
      Info(InfoACE + InfoWarning, 1,
           "may be incorrect. Please use separate calls to SetACEOptions,");
      Info(InfoACE + InfoWarning, 1,
           "e.g. 'SetACEOptions(<optionsRec>); SetACEOptions(: <options>);' ");
    fi;
    PushOptions( arg[Length(arg)] );
    datarec := CallFuncList(ACEDataRecord, arg{[1..Length(arg) - 1]});
    INTERACT_SET_ACE_OPTIONS("SetACEOptions", datarec);
    PopOptions();
  elif Length(arg) <= 1 then
    datarec := CallFuncList(ACEDataRecord, arg);
    INTERACT_SET_ACE_OPTIONS("SetACEOptions", datarec);
  else
    Error("2nd argument should have been a record\n");
  fi;
  if datarec.modereqd then
    CHEAPEST_ACE_MODE(datarec); 
  fi;
  ACE_LENLEX_CHK(datarec.procId, false);
end);

#############################################################################
####
##
#F  ACE_PARAMETER_WITH_LINE . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . . . . . . for the ACE process  of  index  ioIndex
##  . . . . . . . . . . . . . . . . . returns ACE's value  of  the  parameter
##  . . . . . . . . . . . . . . . . . identified by string starting with line
##
InstallGlobalFunction(ACE_PARAMETER_WITH_LINE, function(ioIndex, string, line)
  # Remove "<string>: " and trailing newline
  line := line{[Length(string) + 3 .. Length(line) - 1]};
  if line = "" or line[ Length(line) ] <> ';' then
    line := Flat([line,
                  List(ACEReadUntil(ioIndex, line -> line[Length(line)] = ';'),
                       line -> line{[3..Length(line)]}) # Scrub two blanks at
                                                        # beginning of lines
                  ]);
  fi;
  # Remove any blanks after commas and trailing ';'
  return ReplacedString(line{[1..Length(line) - 1]}, ", ", ",");
end);

#############################################################################
####
##
#F  ACE_PARAMETER . . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . . . . . . .  for the ACE process of index ioIndex
##  . . . . . . . . . . . . . . . . . .  returns ACE's value of the parameter
##  . . . . . . . . . . . . . . . . . . . . . . . . . .  identified by string
##
InstallGlobalFunction(ACE_PARAMETER, function(ioIndex, string)
local line;
  line := FLUSH_ACE_STREAM_UNTIL(ACEData.io[ ioIndex ].stream, 3, 3, 
                                 ACE_READ_NEXT_LINE, 
                                 line -> Length(line) >= Length(string) and
                                         line{[1..Length(string)]} = string);
  return ACE_PARAMETER_WITH_LINE(ioIndex, string, line);
end);

#############################################################################
####
##
#F  ACE_GAP_WORDS . . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . .  returns the translation into GAP of an ACE list of words
##
##  ACE stores words according to the BNF:
##      <word>      = <element> <word> | "(" <word> ")^" <power>
##      <power>     = <integer>
##      <element>   = <generator> | <inverse>
##      <generator> = <integer> <space> | <lowercase letter>
##      <inverse>   = "-" <generator> | <uppercase letter> 
##
InstallGlobalFunction(ACE_GAP_WORDS, function(datarec, words)
local GAPWord;
  
  GAPWord := function(word)
  local power, parts, elements;
    if word[1] = '(' then
      parts := SplitString(word, "", "()^");
      word := parts[1];
      power := Int(parts[2]);
    else
      power := 1;
    fi;
    if IsDigitChar(word[1]) or word[1] = '-' then
      elements := List(SplitString(word, " "), Int);
      # Convert to GAP elements
      elements := List(elements, 
                       function(element)
                         if element < 0 then
                           return datarec.args.fgens[ AbsInt(element) ]^-1;
                         else
                           return datarec.args.fgens[element];
                         fi;
                       end);
    else
      elements := List([1..Length(word)], i -> WordAlp(word, i));
      # Convert to GAP elements
      elements := List(elements, 
                       function(element)
                         if IsUpperAlphaChar(element[1]) then
                           return datarec.args.fgens[ 
                                      Position(
                                          datarec.acegens,
                                          LowercaseString(element)
                                          )
                                      ]^-1;
                         else
                           return datarec.args.fgens[ Position(datarec.acegens, 
                                                              element) ];
                         fi;
                       end);
    fi;
    return Product( elements, One(datarec.args.fgens[1]) )^power;
  end;

  return List(SplitString(words, ','), GAPWord);
end);

#############################################################################
####
##
#F  ACE_GENS  . . . . . . . . . . . . . . . . . . . . . .  Internal procedure
##  . . . . . . . . . . . . . . . sets datarec.args.fgens and datarec.acegens
##  . . . . . . . . . . . . . . . from the value of ACE's "Group  Generators"
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . parameter
##
InstallGlobalFunction(ACE_GENS, function(datarec, string)
local line;
  if IsAlphaChar(string[1]) then
    datarec.acegens := List([1..Length(string)], i -> WordAlp(string, i));
    datarec.args.fgens := GeneratorsOfGroup( FreeGroup(datarec.acegens) );
  else
    datarec.acegens := List([1..Int(string)], i -> String(i));
    datarec.args.fgens := GeneratorsOfGroup(FreeGroup(
                                                List(datarec.acegens, 
                                                     s -> Flat(["x", s]))
                                                ));
  fi;
end);

#############################################################################
####
##
#F  ACE_ARGS  . . . . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . . . for the ACE process indexed by  ioIndex  sets
##  . . . . . . . . . . . . . and returns ACEDataRecord(ioIndex).args.(field)
##  . . . . . . . . . . . . . . . . . . .  according to ACE's parameter value
##
##  If      ACEDataRecord(ioIndex).args      is      unset,      it       and
##  ACEDataRecord(ioIndex).acegens are set according to the  values  held  by
##  the ACE process indexed by ioIndex.
##
InstallGlobalFunction(ACE_ARGS, function(ioIndex, field)
local datarec, line;
  datarec := ACEDataRecord(ioIndex);
  if not IsBound(datarec.args) then
    datarec.args := rec();
  fi;
  if not IsBound(datarec.args.fgens) or field = "fgens" then
    WRITE_LIST_TO_ACE_STREAM(datarec.stream, [ "sr:1;" ]);
    line := FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE,
                                   line -> Length(line) > 8 and
                                           line{[1..9]} in [ "Group Gen",
                                                             "Group Rel" ]);
    if IsMatchingSublist(line, "Group Gen") then
      ACE_GENS(datarec, ACE_PARAMETER_WITH_LINE(ioIndex, 
                                                "Group Generators", 
                                                line));
    else
      datarec.acegens := [];
      datarec.args.fgens := [];
    fi;
  else
    WRITE_LIST_TO_ACE_STREAM(datarec.stream, [ "sr;" ]);
  fi;
  if not IsBound(datarec.args.rels) or field = "rels" then
    if not IsBound(line) or not IsMatchingSublist(line, "Group Rel") then
      line := FLUSH_ACE_STREAM_UNTIL(
                  datarec.stream, 3, 3, ACE_READ_NEXT_LINE,
                  line -> IsMatchingSublist(line, "Group Rel") );
    fi;
    datarec.args.rels := ACE_GAP_WORDS(datarec,
                                       ACE_PARAMETER_WITH_LINE(
                                           ioIndex, "Group Relators", line
                                           ));
  fi;
  if not IsBound(datarec.args.sgens) or field = "sgens" then
    datarec.args.sgens := ACE_GAP_WORDS(datarec,
                                        ACE_PARAMETER(ioIndex, 
                                                      "Subgroup Generators"));
  fi;
  FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE, 
                         line -> IsMatchingSublist(line, "  #--"));
  return datarec.args.(field);
end);

#############################################################################
####
##
#F  ACEParameters . . . . . .  Returns the ACE value of ACE parameter options
##
##  Also ensures for the interactive ACE process indexed by i that  the  args
##  and acegens fields of  ACEData.io[i]  are  set.  If  not,  it  sets  them
##  according to the values held by ACE process i (the assumption being  that
##  the user started the process via 'ACEStart(0);').
##
InstallGlobalFunction(ACEParameters, function(arg)
local ioIndex, datarec, line, fieldsAndValues, parameters, sgens, i, opt, val;

  ioIndex := CallFuncList(ACEProcessIndex, arg);
  datarec := ACEData.io[ ioIndex ];
  READ_ACE_ERRORS(datarec);
  WRITE_LIST_TO_ACE_STREAM(datarec.stream, [ "sr:1;" ]);
  datarec.parameters 
      := rec(enumeration := ACE_PARAMETER(ioIndex, "Group Name"));
  parameters := datarec.parameters;
  if not IsBound(datarec.args) then
    datarec.args := rec();
  fi;
  if not IsBound(datarec.acegens) or not IsBound(datarec.args.fgens) then
    line := FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE,
                                   line -> Length(line) > 8 and
                                           line{[1..9]} in [ "Group Gen",
                                                             "Group Rel" ]);
    if IsMatchingSublist(line, "Group Gen") then
      ACE_GENS(datarec, ACE_PARAMETER_WITH_LINE(ioIndex, 
                                                "Group Generators", 
                                                line));
    else
      datarec.args.fgens := [];
      datarec.acegens := [];
    fi;
  fi;
  if not IsBound(datarec.args.rels) then
    if not IsBound(line) or not IsMatchingSublist(line, "Group Rel") then
      line := FLUSH_ACE_STREAM_UNTIL(
                  datarec.stream, 3, 3, ACE_READ_NEXT_LINE,
                  line -> IsMatchingSublist(line, "Group Rel"));
    fi;
    datarec.args.rels := ACE_GAP_WORDS(datarec,
                                       ACE_PARAMETER_WITH_LINE(
                                           ioIndex, "Group Relators", line
                                           ));
  fi;
  parameters.subgroup := ACE_PARAMETER(ioIndex, "Subgroup Name");
  sgens := ACE_PARAMETER(ioIndex, "Subgroup Generators");
  if not IsBound(datarec.args.sgens) then
    datarec.args.sgens := ACE_GAP_WORDS(datarec, sgens);
  fi;
  fieldsAndValues :=
      SplitString( 
          ReplacedString(
              Flat( ACEReadUntil(ioIndex, 
                                 line -> IsMatchingSublist(line, "C:")) ),
              "Fi:", "Fil:"
              ),
          "", " :;" 
          );
  FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE,
                         line -> IsMatchingSublist(line, "  #---"));
  i := 1;
  while i < Length(fieldsAndValues) do
    val := Int(fieldsAndValues[i + 1]);
    if val = fail then
      # workspace can be an integer or a string
      val := fieldsAndValues[i + 1];
    fi;
    parameters.(ACEOptionData( fieldsAndValues[i] ).synonyms[1]) := val;
    i := i + 2;
  od;
  return parameters;
end);

#############################################################################
####
##
#F  ACEBinaryVersion 
##
##  Infos the version and component compilation details of  the  ACE  binary,
##  and returns the version of the ACE binary.
##
InstallGlobalFunction(ACEBinaryVersion, function(arg)
local ioIndex, datarec;

  ACE_IOINDEX_ARG_CHK(arg);
  ioIndex := ACE_IOINDEX(arg);
  if ioIndex = fail then 
    # Fire up a new stream ... which we'll close when we're finished
    datarec := ACEData.ni;
    datarec.stream 
        := InputOutputLocalProcess( ACEData.tmpdir, ACEData.binary, [] );
  else
    # Use interactive ACE process: ioIndex
    datarec := ACEData.io[ ioIndex ];
  fi;
  READ_ACE_ERRORS(datarec); # purge any output not yet collected
                            # e.g. error messages due to unknown options
  Info(InfoACE, 1, "ACE Binary Version: ", ACEData.version);
  WRITE_LIST_TO_ACE_STREAM(datarec.stream, [ "options;" ]);
  FLUSH_ACE_STREAM_UNTIL(datarec.stream, 1, 1, ACE_READ_NEXT_LINE,
                         line -> IsMatchingSublist(line, "  host info ="));
  if ioIndex = fail then 
    CloseStream(datarec.stream);
  fi;
  return ACEData.version;
end);

#############################################################################
####
##
#F  EXEC_ACE_DIRECTIVE_OPTION . . . . . . . . . . . . . . . Internal Function
##  . . . . . . . . . . . . . . . . . . .  executes an ACE `directive' option
##
##  An ACE `directive' option is an ACE option with name optname that returns
##  output; most are implemented by a function of form: ACEOptname.
##
##  For the stream and option value defined by arglist pass optname (the name
##  of an ACE option that expects a value) to ACE and flush the output  until
##  a line for which IsMyLine(line) is true or an error  is  encountered  and
##  then return the final line. If IsMyLine is the the null string  then  ACE
##  is also directed to print closeline via option  `text'  and  IsMyLine  is
##  defined to be true if a line matches closeline; in this way closeline  is
##  a sentinel. If both IsMyLine and  closeline  are  null  strings  then  we
##  expect no ACE output and  just  check  for  error  output  from  ACE.  If
##  IsMyLine is the null string, closeline is a non-null string and readUntil
##  is true then all lines read are returned rather than just the last line.
##
InstallGlobalFunction(EXEC_ACE_DIRECTIVE_OPTION, 
function(arglist, optname, infoLevel, IsMyLine, closeline, readUntil)
local datarec, optval, line;
  datarec := ACEData.io[ arglist[1] ];
  optval := arglist[2];
  READ_ACE_ERRORS(datarec); # purge any output not yet collected
                            # e.g. error messages due to unknown options
  PROCESS_ACE_OPTION(datarec.stream, optname, optval);

  if IsMyLine = "" then
    if closeline = "" then 
      # We don't expect any ACE output ... just check for errors
      READ_ACE_ERRORS(datarec);
      return;
    else
      PROCESS_ACE_OPTION(datarec.stream, "text", closeline);
      IsMyLine := line -> Chomp(line) = closeline;
      if readUntil then
        return ACEReadUntil(arglist[1], IsMyLine);
      fi;
    fi;
  else
    line := FLUSH_ACE_STREAM_UNTIL(datarec.stream, infoLevel, infoLevel, 
                                   ACE_READ_NEXT_LINE, 
                                   line -> IsMyLine(line) or
                                           IsMatchingSublist(line, "** ERROR"));
    if IsMatchingSublist(line, "** ERROR") then
      IsMyLine := line -> IsMatchingSublist(line, "   "); # 1 more line to flush
    else 
      return line;
    fi;
  fi;

  return FLUSH_ACE_STREAM_UNTIL(datarec.stream, infoLevel, infoLevel, 
                                ACE_READ_NEXT_LINE, IsMyLine);
end);

#############################################################################
####
##
#F  ACE_IOINDEX_AND_NO_VALUE  . . . . . . . . . . . . . . . Internal Function
##  . . . . . . . . . . . . . . . . . . . .  returns a list [ioIndex, optval]
##  . . . . . . . . . . . . . . . . . . . .  for a no-value  ACE  `directive'
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  option
##
InstallGlobalFunction(ACE_IOINDEX_AND_NO_VALUE, function(arglist)
  return [ CallFuncList(ACEProcessIndex, arglist), "" ];
end);

#############################################################################
####
##
#F  ACE_IOINDEX_AND_ONE_VALUE . . . . . . . . . . . . . . . Internal Function
##  . . . . . . . . . . . . . . . . . . . .  returns a list [ioIndex, optval]
##  . . . . . . . . . . . . . . . . . . . .  for a one-value ACE  `directive'
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  option
##
InstallGlobalFunction(ACE_IOINDEX_AND_ONE_VALUE, function(arglist)
  if Length(arglist) in [1,2] then
    return [ CallFuncList(ACEProcessIndex, arglist{[1..Length(arglist) - 1]}),
             arglist[Length(arglist)] ];
  else
    Error("expected 1 or 2 arguments ... not ", 
          Length(arglist), " arguments\n");
  fi;
end);

#############################################################################
####
##
#F  ACE_IOINDEX_AND_ONE_LIST  . . . . . . . . . . . . . . . Internal Function
##  . . . . . . . . . . . . . . . . . . . .  returns a list [ioIndex, optval]
##  . . . . . . . . . . . . . . . . . . . .  for a one-value ACE  `directive'
##  . . . . . . . . . . . . . . . . . . . .  option,  where  that   one-value
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  must be a list
##
InstallGlobalFunction(ACE_IOINDEX_AND_ONE_LIST, function(arglist)
  if not(Length(arglist) in [1,2]) then
    Error("expected 1 or 2 arguments ... not ", 
          Length(arglist), " arguments\n");
  elif IsString(arglist[ Length(arglist) ]) or 
       not IsList(arglist[ Length(arglist) ]) then
    Error("last argument should be a list\n");
  else
    return [ CallFuncList(ACEProcessIndex, arglist{[1..Length(arglist) - 1]}),
             arglist[Length(arglist)] ];
  fi;
end);

#############################################################################
####
##
#F  ACE_IOINDEX_AND_LIST  . . . . . . . . . . . . . . . . . Internal Function
##  . . . . . . . . . . . . . . . . . . . .  returns a list [ioIndex, optval]
##  . . . . . . . . . . . . . . . . . . . .  for a no-value or list-value ACE
##  . . . . . . . . . . . . . . . . . . . . . . . . . . .  `directive' option
##
InstallGlobalFunction(ACE_IOINDEX_AND_LIST, function(arglist)
  if Length(arglist) > 2 then
    Error("expected 0, 1 or 2 arguments ... not ", 
          Length(arglist), " arguments\n");
  elif Length(arglist) in [1, 2] and IsList( arglist[Length(arglist)] ) then
    return [ CallFuncList(ACEProcessIndex, arglist{[1..Length(arglist) - 1]}),
             arglist[Length(arglist)] ];
  elif Length(arglist) <= 1 then
    return [ CallFuncList(ACEProcessIndex, arglist), "" ];
  else
    Error("2nd argument should have been a list\n");
  fi;
end);

#############################################################################
####
##
#F  ACEDumpVariables . . . . . . . . . . . . . Dumps ACE's internal variables
##
##
InstallGlobalFunction(ACEDumpVariables, function(arg)
  EXEC_ACE_DIRECTIVE_OPTION(
      ACE_IOINDEX_AND_LIST(arg), "dump", 1, 
      line -> IsMatchingSublist(line, "  #----"), "", false);
end);

#############################################################################
####
##
#F  ACEDumpStatistics . . . . . . . . . . . . Dumps ACE's internal statistics 
##
##
InstallGlobalFunction(ACEDumpStatistics, function(arg)
  EXEC_ACE_DIRECTIVE_OPTION(
      ACE_IOINDEX_AND_NO_VALUE(arg), "statistics", 1, 
      line -> IsMatchingSublist(line, "  #----"), "", false);
end);

#############################################################################
####
##
#F  ACEStyle . . . . . . . . . . . . .  Returns the current enumeration style
##
##
InstallGlobalFunction(ACEStyle, function(arg)
local splitstyle;
  splitstyle := SplitString(
                    EXEC_ACE_DIRECTIVE_OPTION(
                        ACE_IOINDEX_AND_NO_VALUE(arg), "style", 3, 
                        line -> IsMatchingSublist(line, "style"), "", false
                        ),
                    "", " =\n"
                    );
  if Length(splitstyle) = 2 then
    return splitstyle[2];
  else
    return Flat([ splitstyle[2], " (defaulted)" ]);
  fi;
end);

#############################################################################
####
##
#F  ACEDisplayCosetTable  . . . . . . . .  Prints the current ACE coset table
##  . . . . . . . . . . . . . . . . . .  at its current level of completeness
##
##
InstallGlobalFunction(ACEDisplayCosetTable, function(arg)
local ioIndexAndValue, stream, closeline;
  ioIndexAndValue := ACE_IOINDEX_AND_LIST(arg);
  stream := ACEData.io[ ioIndexAndValue[1] ].stream;
  PROCESS_ACE_OPTION(stream, "print", ioIndexAndValue[2]);
  closeline := "------------------------------------------------------------";
  PROCESS_ACE_OPTION(stream, "text", closeline);
  FLUSH_ACE_STREAM_UNTIL(stream, 3, 3, ACE_READ_NEXT_LINE, 
                         line -> IsMatchingSublist(line, "CO:") or
                                 IsMatchingSublist(line, "co:") or
                                 IsMatchingSublist(line, "** ERROR"));
  FLUSH_ACE_STREAM_UNTIL(stream, 1, 3, ACE_READ_NEXT_LINE, 
                         line -> IsMatchingSublist(line, closeline));
end);

#############################################################################
####
##
#F  IsCompleteACECosetTable . . . . . Returns true if the current coset table 
##  . . . . . . . . . . . . . . . . . is  complete,  as  determined  by   the
##  . . . . . . . . . . . . . . . . . current value of the enumeration index,
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . and false otherwise
##
InstallGlobalFunction(IsCompleteACECosetTable, function(arg)
local datarec;
  datarec := CallFuncList(ACEDataRecord, arg);
  if not IsBound(datarec.stats) then
    CHEAPEST_ACE_MODE(datarec);
  fi;
  return datarec.stats.index <> 0;
end);

#############################################################################
####
##
#F  ACECosetRepresentative  . . . . . . . . Returns the coset  representative
##  . . . . . . . . . . . . . . . . . . . . of coset n, for the current coset
##  . . . . . . . . . . . . . . . . . . . . table held by interactive process
##  . . . . . . . . . . . . . . . . . . . . . . i, for i, n determined by arg
##
InstallGlobalFunction(ACECosetRepresentative, function(arg)
local ioIndexAndValue, datarec, coset, line, list;
  ioIndexAndValue := ACE_IOINDEX_AND_ONE_VALUE(arg);
  datarec := ACEData.io[ ioIndexAndValue[1] ];
  coset := ioIndexAndValue[2];
  READ_ACE_ERRORS(datarec); # purge any output not yet collected
  if coset = 1 then
    return One(ACEGroupGenerators( ioIndexAndValue[1] )[1]);
  elif coset > datarec.stats.activecosets then
    Error("ACECosetRepresentative: coset table has only ",
          datarec.stats.activecosets, " (<", coset, ") active coset nos.\n");
  fi;
  PROCESS_ACE_OPTION(datarec.stream, "print", [-coset, coset]);
  line := FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE, 
                                 line -> Length(line) > 1 and
                                         line{[1..2]} in ["--", "  "]);
  if IsMatchingSublist(line, "  ") then
    Error("ACECosetRepresentative: ", line{[4..Length(line)]});
  fi;
  list := ACEReadUntil(ioIndexAndValue[1], list -> true, 
                       line -> SplitString(line, "", "| "))[1];
  return ACE_GAP_WORDS(datarec, list[ Length(list) ])[1];
end);

#############################################################################
####
##
#F  ACECosetRepresentatives . . . . . . . . Returns the coset representatives
##  . . . . . . . . . . . . . . . . . . . . . .  of ACE's current coset table
##
##
InstallGlobalFunction(ACECosetRepresentatives, function(arg)
local ioIndex, datarec, line, activecosets, cosetreps;
  ioIndex := CallFuncList(ACEProcessIndex, arg);
  datarec := ACEData.io[ ioIndex ];
  if not IsBound(datarec.stats) then
    Error("ACECosetRepresentatives: no current table?\n");
  fi;
  READ_ACE_ERRORS(datarec); # purge any output not yet collected
  PROCESS_ACE_OPTION(datarec.stream, "print", -datarec.stats.activecosets);
  line := FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE, 
                                 line -> Length(line) > 1 and
                                         line{[1..2]} in ["co", "CO", "  "]);
  if IsMatchingSublist(line, "  ") then
    Error("ACECosetRepresentatives: ", line{[4..Length(line)]});
  fi;
  activecosets := Int( SplitString(line, "", "coCO: a=")[1] );
  FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE, 
                         line -> IsMatchingSublist(line, "     1 "));
  cosetreps := List(ACEReadUntil(
                        ioIndex, 
                        list -> Int(list[1]) = Minimum(
                                                   activecosets,
                                                   datarec.stats.activecosets),
                        line -> SplitString(line, "", "| ")
                        ),
                    list -> ACE_GAP_WORDS(datarec, list[ Length(list) ])[1]
                    );
  if datarec.stats.activecosets < activecosets then
    # We missed some
    PROCESS_ACE_OPTION(datarec.stream, "print", 
                       [-(datarec.stats.activecosets + 1), activecosets]);
    line := FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE, 
                                   line -> IsMatchingSublist(line, "---"));
    return Concatenation([One(ACEGroupGenerators(ioIndex)[1])],
                         cosetreps,
                         List(ACEReadUntil(ioIndex, 
                                           list -> Int(list[1]) = activecosets,
                                           line -> SplitString(line, "", "| ")),
                              list -> ACE_GAP_WORDS(
                                          datarec, list[ Length(list) ])[1]
                              )
                         );
  else
    return Concatenation([One(ACEGroupGenerators(ioIndex)[1])], cosetreps);
  fi;
end);

#############################################################################
####
##
#F  ACETransversal  . . . . . . . . . Returns ACECosetRepresentatives(arg) if
##  . . . . . . . . . . . . . . . . . the current coset  table  is  complete,
##  . . . . . . . . . . . . . . . . . . . . . . . . . . .  and fail otherwise
##
InstallGlobalFunction(ACETransversal, function(arg)
local ioIndex;
  ioIndex := CallFuncList(ACEProcessIndex, arg);  
  if IsCompleteACECosetTable(ioIndex) then
    return ACECosetRepresentatives(ioIndex);
  else
    Info(InfoACE + InfoWarning, 1,
         "ACETransversal: coset table is not complete");
    return fail;
  fi;
end);

#############################################################################
####
##
#F  ACECycles . . . . . . . . . . . . .  Display the cycles (permutations) of
##  . . . . . . . . . . . . . . . . . . . . .  the permutation representation
##
InstallGlobalFunction(ACECycles, function(arg)
local datarec, error, cycles;
  datarec := CallFuncList(ACEDataRecord, arg);
  READ_ACE_ERRORS(datarec); # purge any output not yet collected
                            # e.g. error messages due to unknown options
  PROCESS_ACE_OPTION(datarec.stream, "cycles", "");
  PROCESS_ACE_OPTION(datarec.stream, "text", ""); # Make ACE print a blank line
                                                  # ... that we use as sentinel
  error := IsMatchingSublist(
               FLUSH_ACE_STREAM_UNTIL( 
                   datarec.stream, 3, 3, ACE_READ_NEXT_LINE, 
                   line -> Length(line) > 1 and
                           line{[1..2]} in ["**", "CO", "co"]
                   ),
               "**", 1);
  cycles := ACEReadUntil(datarec.procId, line -> line = "");
  if error then
    Info(InfoACE + InfoWarning, 1,
         ReplacedString(cycles[1], "   ", "ACECycles: "));
    return fail;
  else
    cycles := List(cycles, 
                   function(line)
                     local posEq;
                     posEq := Position(line, '=');
                     if posEq = fail then
                       return line;
                     elif IsMatchingSublist(line, "= identity", posEq) then
                       return ", ()";
                     else
                       return ReplacedString(line, line{[1..posEq]}, ",");
                     fi;
                   end);
    cycles[1][1] := '[';
    Add(cycles, "]");
    return EvalString( Concatenation(cycles) );
  fi;
end);

#############################################################################
####
##
#F  ACETraceWord  . . . . . . . . . . . . Traces word through the coset table
##  . . . . . . . . . . . . . . . . . . . of the i-th interactive ACE process
##  . . . . . . . . . . . . . . . . . . . starting at coset n, for i, n, word
##  . . . . . . . . . . . . . . . . . . . determined by arg, and  return  the
##  . . . . . . . . . . . . . . . . . . . final coset  number  if  the  trace
##  . . . . . . . . . . . . . . . . . . . . . . completes, and fail otherwise
##
InstallGlobalFunction(ACETraceWord, function(arg)
local ioIndex, datarec, twArgs, acegen, expected, line;
  if Length(arg) in [2,3] then
    datarec := CallFuncList(ACEDataRecord, arg{[1..Length(arg) - 2]});
    ioIndex := datarec.procId;
    twArgs := arg{[Length(arg) - 1..Length(arg)]};
    if not IsPosInt(twArgs[1]) then
      Error("ACETraceWord: coset number must be a positive integer\n"); 
    fi;
  else
    Error("expected 2 or 3 arguments ... not ", 
          Length(arg), " arguments\n");
  fi;
  READ_ACE_ERRORS(datarec); # purge any output not yet collected
  if IsOne(twArgs[2]) and twArgs[2] = One( ACEGroupGenerators(ioIndex)[1] ) then
    acegen := datarec.acegens[1];
    # The ACE binary does not recognise the empty string as the identity
    WRITE_LIST_TO_ACE_STREAM(
        datarec.stream, [ "tw:", twArgs[1], ",", acegen, "*", acegen, "^-1;" ]);
  else
    PROCESS_ACE_OPTION(datarec.stream, "tw", twArgs);
  fi;
  expected := Flat([String(twArgs[1]), " * word = "]){[1..8]};
  line := FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE, 
                                 line -> Length(line) > 7 and
                                         line{[1..8]} in [expected,
                                                          "* Trace ",
                                                          "** ERROR"]);
  if IsMatchingSublist(line, expected) then
    return Int(SplitString(line, "", " *word=\n")[2]);
  elif IsMatchingSublist(line, "* Trace ") then
    Info(InfoACE + InfoWarning, 1,
         "ACETraceWord:", line{[2..Length(line) - 1]});
    return fail;
  else
    line := Chomp( ACE_READ_NEXT_LINE(datarec.stream) );
    Info(InfoACE, 3, line);
    Error("ACETraceWord:", line{[3..Length(line)]});
  fi;
end);

#############################################################################
####
##
#F  ACE_ORDER . . . . . . . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . . . . . . . . .  called by ACEOrder and ACEOrders
##
##
InstallGlobalFunction(ACE_ORDER, function(ACEfname, ioIndexAndValue)
local lines, line, datarec;
  lines := EXEC_ACE_DIRECTIVE_OPTION(
               ioIndexAndValue, "order", 3, "", "---------------------", true);
  if lines[Length(lines) - 1][1] = '*' then
    line := lines[Length(lines) - 1];
    Info(InfoACE + InfoWarning, 1, ACEfname, ":", line{[2..Length(line)]});
    if ioIndexAndValue[2] > 0 then
      return fail;
    else
      return [];
    fi;
  elif IsMatchingSublist(lines[Length(lines) - 2], "** ERROR", 1) then
    line := lines[Length(lines) - 1];
    Error(ACEfname, ":", line{[3..Length(line)]}, "\n",
          "(most probably the value passed to ", ACEfname, 
          "\nwas inappropriate)\n");
  else
    datarec := ACEData.io[ ioIndexAndValue[1] ];
    return List(lines{[First([1..Length(lines)], 
                             i -> IsMatchingSublist(lines[i], "--------")) + 1
                       .. Length(lines) - 1]},
                function(line)
                  line := SplitString(line, "", "| ");
                  return rec(coset := Int(line[1]),
                             order := Int(line[2]),
                             rep := ACE_GAP_WORDS(datarec, line[3])[1]);
                end);
  fi;
end);

#############################################################################
####
##
#F  ACEOrders . . . . . . . . . . . . . . . . . . . Returns a list of records
##  . . . . . . . . . . . . . . . . . . rec(coset := n, order := o, rep := r)
##  . . . . . . . . . . . . . . . . . . of   all    coset    numbers    whose
##  . . . . . . . . . . . . . . . . . . representatives' orders  (modulo  the
##  . . . . . . . . . . . . . . . . . . subgroup) are either finite,  or,  if
##  . . . . . . . . . . . . . . . . . . invoked with the  `suborder'  option,
##  . . . . . . . . . . . . . . . . . . are multiples of the  value  assigned
##  . . . . . . . . . . . . . . . . . . to ` suborder', for  the  interactive
##  . . . . . . . . . . . . . . . . . . . . . . ACE process determined by arg
##
InstallGlobalFunction(ACEOrders, function(arg)
local ioIndex, suborder;
  ioIndex := CallFuncList(ACEProcessIndex, arg);
  suborder := ValueOption("suborder");
  if IsPosInt(suborder) then
    return ACE_ORDER("ACEOrders", [ioIndex, -suborder]);
  else
    if suborder <> fail then
      Info(InfoACE + InfoWarning, 1, 
           "ACEOrders: Expected positive integer value of suborder option");
      Info(InfoACE + InfoWarning, 1,
           "but received: ", suborder, ". Ignoring ... giving all orders.");
    fi;
    return ACE_ORDER("ACEOrders", [ioIndex, 0]);
  fi;
end);

#############################################################################
####
##
#F  ACEOrder  . . . . . . . . . . . . . . . . . . . . . . .  Returns a record
##  . . . . . . . . . . . . . . . . . . rec(coset := n, order := o, rep := r)
##  . . . . . . . . . . . . . . . . . . whose representative's order  (modulo
##  . . . . . . . . . . . . . . . . . . the  subgroup)  is  a   multiple   of
##  . . . . . . . . . . . . . . . . . . suborder,  a  positive  integer,   or
##  . . . . . . . . . . . . . . . . . . `fail' if  there  is  no  such  coset
##  . . . . . . . . . . . . . . . . . . number, for the i-th interactive  ACE
##  . . . . . . . . . . . . . . . . . . process, for i,  suborder  determined
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  by arg
##
##  Actually, suborder is also allowed to be a negative integer -n, in  which
##  case, `ACEOrder(i, -n)' is equivalent to `ACEOrders(i : suborder :=  n)';
##  or suborder may be zero, in which case, `ACEOrder(i, 0)' is equivalent to
##  `ACEOrders(i)'.
##
InstallGlobalFunction(ACEOrder, function(arg)
local ioIndexAndValue, orderlist;
  ioIndexAndValue := ACE_IOINDEX_AND_ONE_VALUE(arg);
  orderlist := ACE_ORDER("ACEOrder", ioIndexAndValue);
  if IsList(orderlist) and ioIndexAndValue[2] > 0 then
    return orderlist[1];
  else
    return orderlist;
  fi;
end);

#############################################################################
####
##
#F  ACECosetOrderFromRepresentative( <i>, <cosetrep> )
#F  ACECosetOrderFromRepresentative( <cosetrep> )
##
##  for the <i>-th (or default) interactive {\ACE} process return  the  order
##  (modulo the subgroup) of the coset with representative <cosetrep> a  word
##  in the free group generators.
##
##  *Note:*   
##  `ACECosetOrderFromRepresentative' calls `ACETraceWord' to  determine  the
##  coset (number) to which <cosetrep> belongs, and then scans the output  of
##  `ACEOrders' to determine the order of the coset (number).
##
InstallGlobalFunction(ACECosetOrderFromRepresentative, function(arg)
local ioIndexAndValue, ioIndex, cosetrep, coset, entry;
  ioIndexAndValue := ACE_IOINDEX_AND_ONE_VALUE(arg);
  ioIndex := ioIndexAndValue[1];
  cosetrep := ioIndexAndValue[2];
  if IsOne(cosetrep) and cosetrep = One( ACEGroupGenerators(ioIndex)[1] ) then
    return 1;
  fi;
  ACERecover(ioIndex);
  coset := ACETraceWord(ioIndex, 1, ioIndexAndValue[2]);
  if coset = fail or coset = 1 then
    return coset;
  fi;
  entry := First(ACE_ORDER("ACEOrder", [ioIndex, 0]),
                 entry -> entry.coset = coset);
  if entry = fail then
    return fail;
  fi;
  return entry.order;
end);

#############################################################################
####
##
#F  ACECosetsThatNormaliseSubgroup  . . . . . . .  Determine  coset   numbers
##  . . . . . . . . . . . . . . . . . . . . . . .  whose      representatives
##  . . . . . . . . . . . . . . . . . . . . . . .  normalise   the   subgroup
##
##  For the i-th interactive ACE process and n, where i and n are  determined
##  by arg:
##
##  * If n > 0, the list of the first n non-trivial (i.e.  excluding coset 1)
##    coset numbers whose representatives normalise the subgroup is returned.
##  * If n < 0, a list  of  records  with  fields  `coset'  and  `rep'  which
##    represent the coset number and a representative, respectively,  of  the
##    first n non-trivial coset numbers whose representatives  normalise  the  
##    subgroup is returned.
##  * If n = 0, a list  of  records  with  fields  `coset'  and  `rep'  which
##    represent the coset number and a representative, respectively,  of  all
##    non-trivial coset numbers whose representatives normalise the  subgroup
##    is returned.
##
InstallGlobalFunction(ACECosetsThatNormaliseSubgroup, function(arg)
local ACEfname, ioIndexAndValue, lines, line, datarec;
  ACEfname := "ACECosetsThatNormaliseSubgroup";
  ioIndexAndValue := ACE_IOINDEX_AND_ONE_VALUE(arg);
  lines := EXEC_ACE_DIRECTIVE_OPTION(
               ioIndexAndValue, "sc", 3, "", "---------------------", true);
  if Length(lines) > 2 and 
     IsMatchingSublist(lines[Length(lines) - 2], "** ERROR", 1) then
    line := lines[Length(lines) - 1];
    Error(ACEfname, ":", line{[3..Length(line)]}, "\n",
          "(most probably the value passed to ", ACEfname, 
          "\nwas inappropriate)\n");
  else
    if IsMatchingSublist(lines[Length(lines) - 1], "* Nothing found", 1) then
      lines := [];
      Info(InfoACE + InfoWarning, 1, "no nontrivial normalising cosets found");
    else
      lines := lines{[First([1..Length(lines)], 
                            i -> IsMatchingSublist(lines[i], "Stabil")) + 1 ..
                      Length(lines) - 1]};
    fi;
    if ioIndexAndValue[2] > 0 then
      return List(lines, line -> Int( SplitString(line, "", " ")[1] ));
    else
      datarec := ACEData.io[ ioIndexAndValue[1] ];
      return List(lines,
                  function(line)
                    line := SplitString(line, "", " ");
                    return rec(coset := Int(line[1]),
                               rep := ACE_GAP_WORDS(datarec, line[2])[1]);
                  end);
    fi;
  fi;
end);

#############################################################################
##
#F  ACECosetTable  . . . . . . . . . . . .  Extracts the coset table from ACE
##
InstallGlobalFunction(ACECosetTable, function(arg)
local ioIndex, iostream, datarec, fgens, standard, incomplete,
      cosettable, errmsg, onbreakmsg, SetACEOptions, DisplayACEOptions;

  if Length(arg) = 2 or Length(arg) > 3 then
    Error("expected 0, 1 or 3 arguments ... not ", Length(arg), " arguments\n");
  elif Length(arg) <= 1 then
    # Called as an interactive ACE command
    ioIndex := CallFuncList(ACEProcessIndex, arg);
    datarec := ACEData.io[ ioIndex ];
    INTERACT_SET_ACE_OPTIONS("ACECosetTable", datarec);
    if not IsEmpty(OptionsStack) or not IsBound(datarec.stats) then
      CHEAPEST_ACE_MODE(datarec); 
    fi;
    standard := ACE_LENLEX_CHK(ioIndex, true);
    incomplete := datarec.stats.index = 0 and
                  DATAREC_VALUE_ACE_OPTION(datarec, false, "incomplete");
    if not incomplete and datarec.stats.index = 0 then
      Info(InfoACE + InfoWarning, 1, 
           "The `ACE' coset enumeration failed with the result:");
      Info(InfoACE + InfoWarning, 1, datarec.enumResult);
      Info(InfoACE + InfoWarning, 1, "Try relaxing any restrictive options.");
      Info(InfoACE + InfoWarning, 1, "For interactive ACE process <i>,");
      Info(InfoACE + InfoWarning, 1, 
           "type: 'DisplayACEOptions(<i>);' to see current ACE options.");
      return fail;
    else
      WRITE_LIST_TO_ACE_STREAM(datarec.stream, [ "Print Table;" ]);
      cosettable := ACE_COSET_TABLE(datarec.stats.activecosets, 
                                    datarec.acegens, 
                                    datarec.stream, 
                                    ACE_READ_NEXT_LINE);
    fi;
  else
    # Called non-interactively
    ACEData.ni := rec();

    onbreakmsg := ["Try relaxing any restrictive options",
                   "e.g. try the `hard' strategy or increasing `workspace'",
                   "type: '?strategy options' for info on strategies",
                   "type: '?options for ACE' for info on options",
                   "type: 'DisplayACEOptions();' to see current ACE options;",
                   "type: 'SetACEOptions(:<option1> := <value1>, ...);'",
                   "to set <option1> to <value1> etc.",
                   "(i.e. pass options after the ':' in the usual way)",
                   "... and then, type: 'return;' to continue.",
                   "Otherwise, type: 'quit;' to quit to outer loop."];

    SetACEOptions := function()
      if not IsEmpty(OptionsStack) and 
         datarec.optionsStackDepth in [0, Length(OptionsStack)] then
        SET_ACE_OPTIONS(datarec);
      fi;
    end;

    DisplayACEOptions := function()
      DISPLAY_ACE_REC_FIELD( datarec, "options" );
    end;

    repeat
      datarec :=
          CALL_ACE( "ACECosetTableFromGensAndRels", arg[1], arg[2], arg[3] );
      standard := ACE_COSET_TABLE_STANDARD( ACE_OPTIONS() );
      if IsBound(datarec.infile) then
        # User only wanted an ACE input file to use directly with standalone
        Info(InfoACE, 1, "ACE standalone input file: ", datarec.infile);
        return;
      fi;
      incomplete := datarec.stats.index = 0 and
                    VALUE_ACE_OPTION(ACE_OPT_NAMES(), false, "incomplete");
      if not incomplete and datarec.stats.index = 0 then
        CloseStream(datarec.stream);
        if datarec.silent then
          return fail;
        else
          datarec.options := ACE_OPTIONS();
          datarec.optionsStackDepth := Length(OptionsStack);
          if not IsBound(datarec.origOptionsStackDepth) then
            datarec.origOptionsStackDepth := datarec.optionsStackDepth;
          fi;
          if datarec.optionsStackDepth > 0 then
            # We pop options here, in case the user decides to quit
            PopOptions();
          fi;
          errmsg := ["no coset table ...",
                     "the `ACE' coset enumeration failed with the result:",
                      datarec.enumResult];
          Error(ACE_ERROR(errmsg, onbreakmsg), "\n");
          if datarec.options <> rec() then
            Add(OptionsStack, datarec.options);
            Unbind(datarec.options);
          fi;
        fi;
      else
        if IsBound(datarec.cosettable) then
          cosettable := datarec.cosettable;
          Unbind(datarec.cosettable);
        else
          WRITE_LIST_TO_ACE_STREAM(datarec.stream, [ "Print Table;" ]);
          cosettable := ACE_COSET_TABLE(datarec.stats.activecosets,
                                        datarec.acegens, 
                                        datarec.stream, 
                                        ACE_READ_NEXT_LINE);
        fi;
        CloseStream(datarec.stream);
        if IsBound(datarec.origOptionsStackDepth) and
           (datarec.origOptionsStackDepth = 0) and 
           not IsEmpty(OptionsStack) 
        then
          PopOptions();
        fi;
        Unbind(datarec.optionsStackDepth);
        Unbind(datarec.origOptionsStackDepth);
        break;
      fi;
    until false;
  fi;
  if incomplete then
    StandardizeTable(cosettable, "lenlex");
    Info(InfoACE + InfoWarning, 1, 
         "ACECosetTable: Coset table is incomplete, reduced ",
         "& lenlex standardised.");
  elif standard = "semilenlex" then
    StandardizeTable(cosettable, "semilenlex");
  elif IsMatchingSublist(standard, "GAP") or standard = "semilenlex" then
    StandardizeTable(cosettable);
  fi;
  return cosettable;
end);

#############################################################################
####
##
#F  ACEStats  . . . Get the subgroup index, time and number of cosets defined
##  . . . . . . . . . .  during an interactive or non-interactive ACE session
##
InstallGlobalFunction(ACEStats, function(arg)
local datarec, iostream, line, stats;

  if Length(arg) <= 1 then 
    # Called as an interactive ACE command
    datarec := CallFuncList(ACEDataRecord, arg);
    INTERACT_SET_ACE_OPTIONS("ACEStats", datarec);
    if not IsEmpty(OptionsStack) then
      CHEAPEST_ACE_MODE(datarec);
    fi;
    return datarec.stats;
  elif Length(arg) = 3 then              # args are: fgens,   rels,  sgens
    # Called non-interactively
    datarec := CALL_ACE("ACEStats", arg[1], arg[2], arg[3]);
    CloseStream( datarec.stream );
    return datarec.stats;
  else
    Error("expected 0, 1 or 3 arguments ... not ", Length(arg), " arguments\n");
  fi;
end);

#############################################################################
####
##
#F  ACERecover  . . . . . . . . . . . . Recover space from dead coset numbers
##  . . . . . . . . . . . . . . for interactive ACE process determined by arg
##
InstallGlobalFunction(ACERecover, function(arg)
  EXEC_ACE_DIRECTIVE_OPTION(
      ACE_IOINDEX_AND_NO_VALUE(arg), "recover", 3, 
      line -> Length(line) > 1 and line{[1..2]} in ["CO", "co"], "", false);
end);

#############################################################################
####
##
#F  ACEStandardCosetNumbering . . Reassigns coset numbers in lenlex  standard
##  . . . . . . . . . . . . . . . order   for   interactive    ACE    process
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . determined by arg
##
InstallGlobalFunction(ACEStandardCosetNumbering, function(arg)
  EXEC_ACE_DIRECTIVE_OPTION(
      ACE_IOINDEX_AND_NO_VALUE(arg), "standard", 3, 
      line -> Length(line) > 1 and line{[1..2]} in ["CO", "co"], "", false);
end);

#############################################################################
####
##
#F  ACEAddRelators  . . . . . . . . . . . . . . . Add relatorlist to relators 
##  . . . . . . . . . . . . . . . for interactive ACE process and relatorlist
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . determined by arg
##
##  Also sets and returns ACEData.io[i].args.rels, where i is  the  index  of
##  the interactive ACE process.
##
InstallGlobalFunction(ACEAddRelators, function(arg)
local ioIndexAndOptval, ioIndex, datarec;
  ioIndexAndOptval := ACE_IOINDEX_AND_ONE_LIST(arg);
  ioIndex := ioIndexAndOptval[1];
  datarec := ACEData.io[ ioIndex ];
  if not IsBound(datarec.enforceAsis) then
    datarec.enforceAsis := false;
  fi;
  EXEC_ACE_DIRECTIVE_OPTION(
      [ ioIndex, ACE_RELS(ioIndexAndOptval[2], # relatorlist
                          ACEGroupGenerators(ioIndex),
                          datarec.acegens,
                          datarec.enforceAsis) ],
      "rl", 3, "", "", false
      );
  CHEAPEST_ACE_MODE(datarec);
  return ACE_ARGS(ioIndex, "rels");
end);

#############################################################################
####
##
#F  ACEAddSubgroupGenerators  . . . . . . . . . Add generatorlist to relators 
##  . . . . . . . . . . . . . . for interactive ACE process and generatorlist
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . determined by arg
##
##  Also sets and returns ACEData.io[i].args.sgens, where i is the  index  of
##  the interactive ACE process.
##
InstallGlobalFunction(ACEAddSubgroupGenerators, function(arg)
local ioIndexAndOptval, ioIndex, datarec;
  ioIndexAndOptval := ACE_IOINDEX_AND_ONE_LIST(arg);
  ioIndex := ioIndexAndOptval[1];
  datarec := ACEData.io[ ioIndex ];
  EXEC_ACE_DIRECTIVE_OPTION(
      [ ioIndex, ACE_WORDS(ioIndexAndOptval[2], # generatorlist
                           ACEGroupGenerators(ioIndex),
                           datarec.acegens) ],
      "sg", 3, "", "", false
      );
  CHEAPEST_ACE_MODE(datarec);
  return ACE_ARGS(ioIndex, "sgens");
end);

#############################################################################
####
##
#F  ACE_WORDS_OR_UNSORTED . . . . . . . . . . . . . . . . . Internal function
##  . . . . . . . . . . . . . . check val is a word list of goodwords, if  so
##  . . . . . . . . . . . . . . return the sorted list  of  indices  of  word
##  . . . . . . . . . . . . . . list in goodwords or report that  some  words
##  . . . . . . . . . . . . . . are not of wordtype. If  val  is  an  integer
##  . . . . . . . . . . . . . . list  a  sorted  integer  list  is  returned.
##  . . . . . . . . . . . . . . Otherwise, if  val  is  not  a  list  or  not
##  . . . . . . . . . . . . . . . . . . . . . . homogeneous, val is returned.
##
InstallGlobalFunction(ACE_WORDS_OR_UNSORTED, function(val, goodwords, wordtype)
local badwords;
  if IsList(val) then
    if ForAll(val, IsWord) then
      badwords := Filtered(val, w -> not(w in goodwords));
      if IsEmpty(badwords) then
        return SortedList(List(val, w -> Position(goodwords, w)));
      else
        Error(badwords, " are not ", wordtype, "\n");
      fi;
    elif ForAll(val, IsInt) then
      return SortedList(val);
    fi;
  fi;
  # Let the default error message sort it out
  return val;
end);

#############################################################################
####
##
#F  ACEDeleteRelators . . . . . . . . . . .  Delete relatorlist from relators 
##  . . . . . . . . . . . . . . . for interactive ACE process and relatorlist
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . determined by arg
##
##  Also sets and returns ACEData.io[i].args.rels, where i is  the  index  of
##  the interactive ACE process.
##
InstallGlobalFunction(ACEDeleteRelators, function(arg)
local ioIndexAndOptval;
  ioIndexAndOptval := ACE_IOINDEX_AND_ONE_LIST(arg);
  ioIndexAndOptval[2] := ACE_WORDS_OR_UNSORTED(ioIndexAndOptval[2],
                                               ACERelators(ioIndexAndOptval[1]),
                                               "relators");
  EXEC_ACE_DIRECTIVE_OPTION(ioIndexAndOptval, "dr", 3, "", "", false);
  CHEAPEST_ACE_MODE(ACEData.io[ ioIndexAndOptval[1] ]);
  return ACE_ARGS(ioIndexAndOptval[1], "rels");
end);

#############################################################################
####
##
#F  ACEDeleteSubgroupGenerators . . . . .  Delete generatorlist from relators 
##  . . . . . . . . . . . . . . for interactive ACE process and generatorlist
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . determined by arg
##
##  Also sets and returns ACEData.io[i].args.sgens, where i is the  index  of
##  the interactive ACE process.
##
InstallGlobalFunction(ACEDeleteSubgroupGenerators, function(arg)
local ioIndexAndOptval;
  ioIndexAndOptval := ACE_IOINDEX_AND_ONE_LIST(arg);
  ioIndexAndOptval[2] := ACE_WORDS_OR_UNSORTED(ioIndexAndOptval[2],
                                               ACESubgroupGenerators(
                                                   ioIndexAndOptval[1]
                                                   ),
                                               "subgroup generators");
  EXEC_ACE_DIRECTIVE_OPTION(ioIndexAndOptval, "ds", 3, "", "", false);
  CHEAPEST_ACE_MODE(ACEData.io[ ioIndexAndOptval[1] ]);
  return ACE_ARGS(ioIndexAndOptval[1], "sgens");
end);

#############################################################################
####
##
#F  ACECosetCoincidence . . . . . . . . . . Force the coincidence of coset  n 
##  . . . . . . . . . . . . . . . . . . . . with coset 1, for the interactive
##  . . . . . . . . . . . . . . . . . . . . ACE  process  i  and  integer   n
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . determined by arg
##
##  Essentially, the coset representative of coset n is added to the subgroup
##  generators, ACERedo and ACESubgroupGenerators are invoked, and the  coset
##  representative of coset n is returned.
##
InstallGlobalFunction(ACECosetCoincidence, function(arg)
local ioIndexAndOptval, cosetrep, datarec;
  ioIndexAndOptval := ACE_IOINDEX_AND_ONE_VALUE(arg);
  cosetrep := EXEC_ACE_DIRECTIVE_OPTION(
                  ioIndexAndOptval, "cc", 3, 
                  line -> IsMatchingSublist(line, "Coset"), "", false);
  if IsMatchingSublist(cosetrep, "  ") then
    return fail; # Error in input
  fi;
  datarec := ACEData.io[ ioIndexAndOptval[1] ];
  FLUSH_ACE_STREAM_UNTIL(datarec.stream, 3, 3, ACE_READ_NEXT_LINE,
                         line -> IsMatchingSublist(line, "*"));
  CHEAPEST_ACE_MODE(datarec);
  ACE_ARGS(ioIndexAndOptval[1], "sgens");
  return ACE_GAP_WORDS(
             datarec,
             cosetrep{[Position(cosetrep, ':') + 2..Length(cosetrep) - 1]}
             )[1];
end);

#############################################################################
####
##
#F  ACERandomCoincidences( <i>, <subindex> )
#F  ACERandomCoincidences( <subindex>)
#F  ACERandomCoincidences( <i>, [<subindex>] )
#F  ACERandomCoincidences( [<subindex>] )
#F  ACERandomCoincidences( <i>, [<subindex>, <attempts>] )
#F  ACERandomCoincidences( [<subindex>, <attempts>] )
##
##  for  the  <i>th  (or  default)  interactive  {\ACE}  process  started  by
##  `ACEStart', attempt up to <attempts> (or, in the  first  four  forms,  8)
##  times to find nontrivial subgroups with index a multiple of <subindex> by
##  repeatedly making random coset numbers coincident with coset 1 and seeing
##  what happens. The starting coset table must be non-empty, but must  *not*
##  be        complete        (use         `ACERandomlyApplyCosetCoincidence'
##  (see~"ACERandomlyApplyCosetCoincidence")  if  your   table   is   already
##  complete).  For  each   attempt,   we   repeatedly   add   random   coset
##  representatives to the subgroup and `redo' the enumeration. If the  table
##  becomes  too  small,  the  attempt  is  aborted,  the  original  subgroup
##  generators restored, and another attempt made. If  an  attempt  succeeds,
##  then   the   new   set    of    subgroup    generators    is    retained.
##  `ACERandomCoincidences' returns  the  list  of  new  subgroup  generators
##  added.  Use  `ACESubgroupGenerators'   (see~"ACESubgroupGenerators")   to
##  determine the current subgroup generator list.
##
InstallGlobalFunction(ACERandomCoincidences, function(arg)
local ioIndexAndOptval, datarec, index, sgens, lines, newsgens;
  ioIndexAndOptval := ACE_IOINDEX_AND_ONE_VALUE(arg);
  datarec := ACEData.io[ ioIndexAndOptval[1] ];
  sgens := ACE_ARGS(ioIndexAndOptval[1], "sgens");
  READ_ACE_ERRORS(datarec); # purge any output not yet collected
  if not IsBound(datarec.stats) then
    CHEAPEST_ACE_MODE(datarec);
  fi;
  index := datarec.stats.index;
  if index <> 0 then
    Error("ACERandomCoincidences: enumeration index is already finite!\n");
  fi;
  PROCESS_ACE_OPTION(datarec.stream, "rc", ioIndexAndOptval[2]);
  # Perhaps it's wasteful to use ACEReadUntil here ...
  lines := ACEReadUntil(ioIndexAndOptval[1],
                        line -> Length(line)>12 and
                                line{[1..13]} in ["* No success;",
                                                  "* An appropri",
                                                  "   finite ind",
                                                  "   * Unable t"]);
  if IsMatchingSublist(lines[Length(lines)], "* An appropri", 1) then
    datarec.enumResult := lines[Length(lines) - 1];
    datarec.stats := ACE_STATS(datarec.enumResult);
  else
    Info(InfoACE + InfoWarning, 1, "ACERandomCoincidences: Unsuccessful!");
    newsgens := Difference(ACE_ARGS(ioIndexAndOptval[1], "sgens"), sgens);
    if not IsEmpty(newsgens) then
      ACEDeleteSubgroupGenerators(ioIndexAndOptval[1], newsgens);
      Info(InfoACE + InfoWarning, 1, "Subgroup generators restored.");
    fi;
  fi;
  return Difference(ACE_ARGS(ioIndexAndOptval[1], "sgens"), sgens);
end);

#############################################################################
####
##
#F  ACERandomlyApplyCosetCoincidence( <i> [: subindex := <subindex>, 
##                                           hibound := <hibound>,
##                                           lobound := <lobound>,
##                                           attempts := <attempts>] )
#F  ACERandomlyApplyCosetCoincidence( [: subindex := <subindex>, 
##                                       hibound := <hibound>,
##                                       lobound := <lobound>,
##                                       attempts := <attempts>] )
##
##  for  the  <i>th  (or  default)  interactive  {\ACE}  process  started  by
##  `ACEStart', attempt up to <attempts> (or, by default, 8) times to find  a
##  larger proper  subgroup,  by  repeatedly  applying  `ACECosetCoincidence'
##  (see~"ACECosetCoincidence") and seeing what happens. The  starting  coset
##  table   must   already   be   complete    (use    `ACERandomCoincidences'
##  (see~"ACERandomCoincidences") if your table is not already complete).  By
##  default, `<subindex> = 1', <hibound> is the existing subgroup  index  and
##  `<lobound> = 1'. If after an attempt the  new  index  is  a  multiple  of
##  <subindex>, less than <hibound> and greater than <lobound> then  the  the
##  process terminates and  the  list  of  new  subgroup  representatives  is
##  returned. Otherwise, if an attempt reaches a  stage  where  the  criteria
##  cannot be satisfied,  the  attempt  is  aborted,  the  original  subgroup
##  generators    restored,     and     another     attempt     made.     Use
##  `ACESubgroupGenerators' (see~"ACESubgroupGenerators")  to  determine  the
##  current subgroup generator list.
##
InstallGlobalFunction(ACERandomlyApplyCosetCoincidence, function(arg)
local ioIndex, datarec, index, opt, sgens, try, trycosetrep, tries, newsgens;
  ioIndex := CallFuncList(ACEProcessIndex, arg);
  datarec := ACEData.io[ ioIndex ];
  index := ACEStats(ioIndex).index;
  if index = 0 then
    Error("ACERandomlyApplyCosetCoincidence: coset table must be complete.\n");
  fi;
  opt := rec();
  if ACE_VALUE_OPTION_ERROR(
         opt, "subindex", 1, d -> IsPosInt(d) and (index mod d = 0),
         "option `subindex' must be a positive divisor of current index"
         ) or
     ACE_VALUE_OPTION_ERROR(
         opt, "hibound", index, h -> 1 < h and h <= index,
         "option `hibound' must be > 1 and at most the current index"
         ) or
     ACE_VALUE_OPTION_ERROR(
         opt, "lobound", 1, lo -> 1 <= lo and lo < index,
         "option `lobound' must be at least 1 and less than the current index"
         ) or
     ACE_VALUE_OPTION_ERROR(
         opt, "attempts", 8, IsPosInt,
         "option `attempts' must be a positive integer"
         )
  then
     opt.onbreakmsg := ["You can only 'quit;' from here."];
     PopOptions();
     Error(ACE_ERROR(opt.errmsg, opt.onbreakmsg), "\n");
  fi;
  if opt.attempts > index - 1 then
    opt.attempts := index - 1;
  fi;

  sgens := ACESubgroupGenerators(ioIndex);
  tries := [];
  newsgens := [];
  ACERecover(ioIndex);
  while Length(tries) < opt.attempts and (datarec.stats.index >= opt.hibound) do
    repeat
      try := Random([2 .. datarec.stats.index]);
      trycosetrep := ACECosetRepresentative(ioIndex, try);
    until not(trycosetrep in tries);
    Add(tries, try);
    Add(newsgens, ACECosetCoincidence(ioIndex, try));
    Info(InfoACE, 1, "Added new subgroup gen'r:");
    Info(InfoACE, 1, "  ", newsgens[ Length(newsgens) ]);
    if datarec.stats.index <= opt.lobound or 
       (datarec.stats.index mod opt.subindex <> 0) then
      # abort
      Info(InfoACE, 1, "Subgroup index (", datarec.stats.index, ") ",
                       "has become too small ...");
      Info(InfoACE, 1, "restoring original subgroup gen'rs.");
      ACEDeleteSubgroupGenerators(ioIndex, newsgens);
      newsgens := [];
    else
      Info(InfoACE, 1, "New subgroup index = ", datarec.stats.index);
    fi;
    ACERecover(ioIndex);
  od;
  if ACEStats(ioIndex).index >= opt.hibound then
    Info(InfoACE, 1, "ACERandomlyApplyCosetCoincidence: Unsuccessful!");
  fi;
  return Difference(ACESubgroupGenerators(ioIndex), sgens);
end);

#############################################################################
####
##
#F  ACEConjugatesForSubgroupNormalClosure . .  Returns conjugates of subgroup  
##  . . . . . . . . . . . . . . . . . . . . .  generators by generators (that
##  . . . . . . . . . . . . . . . . . . . . .  can  be  determined   to   be)
##  . . . . . . . . . . . . . . . . . . . . .  needed for normal  closure  of
##  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  the subgroup
##
##  Tests that each conjugate of a subgroup generator by  a  group  generator
##  can be traced from coset 1 to a coset number other than coset 1, for  the
##  i-th interactive ACE process, where i is determined by arg. The  list  of
##  conjugates that were determined to belong to cosets other  than  coset  1
##  (the subgroup) is returned; and, if called with the `add'  option,  these
##  conjugates are also added to the existing list of subgroup generators.
##
InstallGlobalFunction(ACEConjugatesForSubgroupNormalClosure, function(arg)
local ACEfname, ioIndex, add, lines, line, datarec;
  ACEfname := "ACEConjugatesForSubgroupNormalClosure";
  ioIndex := CallFuncList(ACEProcessIndex, arg);
  add := ValueOption("add");
  if not IsBool(add) then
    Info(InfoACE + InfoWarning, 1,
         ACEfname, ": Expected boolean value of add option");
    Info(InfoACE + InfoWarning, 1,
         "but received: ", add, ". Ignoring ... no new generators will be.");
    Info(InfoACE + InfoWarning, 1,
         "added to the subgroup");
    add := "";
  elif add <> true then
    add := "";
  else
    add := 1;
  fi;
  lines := EXEC_ACE_DIRECTIVE_OPTION(
               [ioIndex, add], "nc", 3, "", "---------------------", true);
  if lines[Length(lines) - 1] = "* All (traceable) conjugates in subgroup" then
    Info(InfoACE + InfoWarning, 1, 
         ACEfname, ": All (traceable) conjugates in subgp");
    return [];
  elif IsMatchingSublist(lines[Length(lines) - 2], "** ERROR", 1) then
    line := lines[Length(lines) - 1];
    Error(ACEfname, ":", line{[3..Length(line)]}, "\n",
          "(most probably the value passed to ", ACEfname, 
          "\nwas inappropriate)\n");
  else
    datarec := ACEData.io[ ioIndex ];
    if add = 1 then
      CHEAPEST_ACE_MODE(datarec);
      ACE_ARGS(ioIndex, "sgens"); # Update saved subgroup generators
    fi;
    return List(Filtered(lines, 
                         line -> IsMatchingSublist(line, "Conjugate by grp") or
                                 # in case we have an old src for ACE
                                 IsMatchingSublist(line, "Grp")),
                function(line)
                  line := SplitString(line, '"');
                  return ACE_GAP_WORDS(datarec, line[4])[1]
                         ^ ACE_GAP_WORDS(datarec, line[2])[1];
                end);
  fi;
end);

#E  interact.gi . . . . . . . . . . . . . . . . . . . . . . . . .  ends here 

[zur Elbe Produktseite wechseln0.106QuellennavigatorsAnalyse erneut starten2026-04-26]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge