Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/GAP/pkg/digraphs/gap/   (Algebra von RWTH Aachen Version 4.15.1©)  Datei vom 27.8.2025 mit Größe 70 kB image not shown  

Quelle  io.gi   Sprache: unbekannt

 
#############################################################################
##
##  io.gi
##  Copyright (C) 2014-19                                James D. Mitchell
##
##  Licensing information can be found in the README file of this package.
##
#############################################################################
##

#############################################################################
## This file contains methods for reading and writing digraphs from and to
## files.
##
## It is organized as follows:
##
##   0. Internal functions
##
##   1. Picklers for IO
##
##   2. Read/WriteDigraphs (the main functions)
##
##   3. Decoders
##
##   4. Encoders
##
#############################################################################

#############################################################################
# 0. Internal functions
#############################################################################

BindGlobal("DIGRAPHS_SplitStringBySubstring",
function(string, substring)
  local m, n, i, j, out, nr;

  if Length(string) = 0 then
    return [];
  elif Length(substring) = 1 then
    return SplitString(string, substring);
  fi;

  m := Length(string);
  n := Length(substring);
  i := 1;
  j := 1;
  out := [];
  nr := 0;

  while m - i >= n do
    if string{[i .. i + n - 1]} = substring then
      if i <> 1 then
        nr := nr + 1;
        out[nr] := string{[j .. i - 1]};
        j := i + n;
        i := i + n;
      fi;
    else
      i := i + 1;
    fi;
  od;
  nr := nr + 1;
  out[nr] := string{[j .. Length(string)]};
  return out;
end);

BindGlobal("DIGRAPHS_Graph6Length",
function(n)
  local list;
  list := [];
  if n < 0 then
    return fail;
  elif n < 63 then
    Add(list, n);
  elif n < 258248 then
    Add(list, 63);
    Add(list, Int(n / 64 ^ 2));
    Add(list, Int(n / 64) mod 64);
    Add(list, n mod 64);
  elif n < 68719476736 then
    Add(list, 63);
    Add(list, 63);
    Add(list, Int(n / 64 ^ 5));
    Add(list, Int(n / 64 ^ 4) mod 64);
    Add(list, Int(n / 64 ^ 3) mod 64);
    Add(list, Int(n / 64 ^ 2) mod 64);
    Add(list, Int(n / 64 ^ 1) mod 64);
    Add(list, n mod 64);
  else
    return fail;
  fi;
  return list;
end);

################################################################################
# 1. Picklers
################################################################################

InstallMethod(IO_Pickle, "for a digraph with known digraph group",
[IsFile, IsDigraph and HasDigraphGroup],
function(file, D)
  local g, out;
  g := DigraphGroup(D);
  if IsTrivial(g) then
    TryNextMethod();
  elif IO_Write(file, "DIGG") = fail then
    return IO_Error;
  fi;
  out := [GeneratorsOfGroup(g),
          RepresentativeOutNeighbours(D),
          DigraphSchreierVector(D)];
  return IO_Pickle(file, out);
end);

IO_Unpicklers.DIGG := function(file)
  local list, gens, rep_out, sch, out, trace, word, D, i, w;

  list := IO_Unpickle(file);
  if list = IO_Error then
    return IO_Error;
  fi;

  gens    := list[1];
  rep_out := list[2];
  sch     := list[3];

  out  := [];
  for i in [1 .. Length(sch)] do
    if sch[i] < 0 then
      out[i] := rep_out[-sch[i]];
    fi;

    trace := DIGRAPHS_TraceSchreierVector(gens, sch, i);
    out[i] := rep_out[trace.representative];
    word := trace.word;
    for w in word do
       out[i] := OnTuples(out[i], gens[w]);
    od;
  od;

  D := ConvertToImmutableDigraphNC(out);
  SetDigraphGroup(D, Group(gens));
  SetDigraphSchreierVector(D, sch);
  SetRepresentativeOutNeighbours(D, rep_out);
  return D;
end;

InstallMethod(IO_Pickle, "for a digraph",
[IsFile, IsDigraph],
function(file, D)
  if IO_Write(file, "DIGT") = fail then
    return IO_Error;
  fi;
  return IO_Pickle(file, OutNeighbours(D));
end);

IO_Unpicklers.DIGT := function(file)
  local out;
  out := IO_Unpickle(file);
  if out = IO_Error then
    return IO_Error;
  fi;
  return ConvertToImmutableDigraphNC(out);
end;

################################################################################
# 2. ReadDigraphs and WriteDigraphs
################################################################################

InstallGlobalFunction(IteratorFromDigraphFile,
function(arg...)
  local filename, decoder, file, record;

  if Length(arg) = 1 then
    filename := arg[1];
    decoder  := fail;
  elif Length(arg) = 2 then
    filename := arg[1];
    decoder  := arg[2];
  else
    ErrorNoReturn("there must be 1 or 2 arguments,");
  fi;

  if not IsString(filename) then
    ErrorNoReturn("the 1st argument must be a string,");
  elif decoder <> fail and not IsFunction(decoder) then
    ErrorNoReturn("the 2nd argument must be a function or fail,");
  fi;

  file := DigraphFile(UserHomeExpand(filename), decoder, "r");

  record := rec(file := file, current := file!.coder(file));

  record.NextIterator := function(iter)
    local next;
    next := iter!.current;
    iter!.current := iter!.file!.coder(iter!.file);
    return next;
  end;

  record.IsDoneIterator := function(iter)
    if iter!.current = IO_Nothing then
      if not iter!.file!.closed then
        IO_Close(iter!.file);
      fi;
      return true;
    else
      return false;
    fi;
  end;

  # TODO store filename, decoder in iter?
  record.ShallowCopy := function(_)
    local file;
    file := DigraphFile(UserHomeExpand(filename), decoder, "r");
    return rec(file := file, current := file!.coder(file));
  end;

  return IteratorByFunctions(record);
end);

BindGlobal("WholeFileDecoders", HashSet());
AddSet(WholeFileDecoders, "DigraphFromDreadnautString");
AddSet(WholeFileDecoders, "DigraphFromDIMACSString");

BindGlobal("WholeFileEncoders", HashSet());
AddSet(WholeFileEncoders, "DreadnautString");
AddSet(WholeFileEncoders, "DIMACSString");

InstallGlobalFunction(IsWholeFileDecoder,
  decoder -> NameFunction(decoder) in WholeFileDecoders);

InstallGlobalFunction(IsWholeFileEncoder,
  encoder -> NameFunction(encoder) in WholeFileEncoders);

# these functions wrap the various line encoders/decoders in this file so that
# they behave like IO_Pickle.

BindGlobal("DIGRAPHS_EncoderWrapper",
function(encoder)
  if encoder = IO_Pickle or NameFunction(encoder) in WholeFileEncoders then
    return encoder;
  elif NameFunction(encoder) = "WriteDIMACSDigraph" then
    return DIMACSString;  # edge case (legacy function)
  fi;
  return {file, D} -> IO_WriteLine(file, encoder(D));
end);

BindGlobal("DIGRAPHS_DecoderWrapper",
function(decoder)
  if decoder = IO_Unpickle or NameFunction(decoder) in WholeFileDecoders then
    return decoder;
  elif NameFunction(decoder) = "ReadDIMACSDigraph" then
    return DigraphFromDIMACSString;  # edge case (legacy function)
  fi;
  return
    function(file)
      local line;
      line := IO_ReadLine(file);
      if line = "" then
        return IO_Nothing;
      fi;
      return decoder(line);
    end;
end);

# if we are choosing the decoder, then the file extension is used.

BindGlobal("DIGRAPHS_ChooseFileDecoder",
function(filename)
  local splitname, extension;

  if not IsString(filename) then
    ErrorNoReturn("the argument <filename> must be a string,");
  fi;

  splitname := SplitString(filename, ".");
  extension := splitname[Length(splitname)];

  if extension in ["gz", "bz2", "xz"] then
    extension := splitname[Length(splitname) - 1];
  fi;

  if extension = "txt" then
    return DigraphPlainTextLineDecoder("  ", " ", 1);
  elif extension = "g6" then
    return DigraphFromGraph6String;
  elif extension = "s6" then
    return DigraphFromSparse6String;
  elif extension = "d6" then
    return DigraphFromDigraph6String;
  elif extension = "ds6" then
    return DigraphFromDiSparse6String;
  elif extension = "p" or extension = "pickle" then
    return IO_Unpickle;
  elif extension = "dre" then
    return DigraphFromDreadnautString;
  elif extension = "dimacs" then
    return DigraphFromDIMACSString;
  fi;

  return fail;
end);

# if we are choosing the decoder, then the file extension is used.

BindGlobal("DIGRAPHS_ChooseFileEncoder",
function(filename)
  local splitname, extension;

  if not IsString(filename) then
    ErrorNoReturn("the argument <filename> must be a string,");
  fi;

  splitname := SplitString(filename, ".");
  extension := splitname[Length(splitname)];

  if extension in ["gz", "bz2", "xz"] then
    extension := splitname[Length(splitname) - 1];
  fi;

  if extension = "txt" then
    return DigraphPlainTextLineEncoder("  ", " ", -1);
  elif extension = "g6" then
    return Graph6String;
  elif extension = "s6" then
    return Sparse6String;
  elif extension = "d6" then
    return Digraph6String;
  elif extension = "ds6" then
    return DiSparse6String;
  elif extension = "p" or extension = "pickle" then
    return IO_Pickle;
  elif extension = "dre" then
    return DreadnautString;
  elif extension = "dimacs" then
    return DIMACSString;
  fi;
  return fail;
end);

InstallGlobalFunction(DigraphFile,
function(arg...)
  local coder, mode, name, file;

  # defaults
  coder       := fail;
  mode        := "r";
  if Length(arg) = 1 then
    name  := arg[1];
  elif Length(arg) = 2 then
    name  := arg[1];
    if IsString(arg[2]) then
      mode := arg[2];
    else
      coder := arg[2];
    fi;
  elif Length(arg) = 3 then
    name  := arg[1];
    coder := arg[2];
    mode  := arg[3];
  else
    ErrorNoReturn("there must be 1, 2, or 3 arguments,");
  fi;

  # TODO check that the mode and the coder are compatible

  if not IsString(name) then
    ErrorNoReturn("the 1st argument <name> must be a string,");
  elif not (IsFunction(coder) or coder = fail) then
    ErrorNoReturn("the 2nd argument <coder> must be a function or fail,");
  elif not mode in ["a", "w", "r"] then
    ErrorNoReturn("the 3rd argument <mode> must be one of \"a\", ",
                  "\"w\", or \"r\"");
  fi;

  if coder = fail then  # <coder> not specified by the user
    if mode = "r" then
      coder := DIGRAPHS_ChooseFileDecoder(name);
    else
      coder := DIGRAPHS_ChooseFileEncoder(name);
    fi;
  fi;

  if coder = fail then
    ErrorNoReturn("cannot determine the file format,");
  elif mode = "r" then
    coder := DIGRAPHS_DecoderWrapper(coder);
  else
    coder := DIGRAPHS_EncoderWrapper(coder);
  fi;

  file := IO_CompressedFile(UserHomeExpand(name), mode);

  if file = fail then
    ErrorNoReturn("cannot open the file given as the 1st argument <name>,");
  fi;
  file!.coder := coder;
  file!.mode := mode;
  return file;
end);

InstallGlobalFunction(ReadDigraphs,
function(arg...)
  local nr, decoder, name, file, i, next, out, line;

  # defaults
  nr      := infinity;
  decoder := fail;

  if Length(arg) = 1 then
    name := arg[1];
  elif Length(arg) = 2 then
    name    := arg[1];
    if IsInt(arg[2]) then
      nr      := arg[2];
    else
      decoder := arg[2];
    fi;
  elif Length(arg) = 3 then
    name    := arg[1];
    decoder := arg[2];
    nr      := arg[3];
  else
    ErrorNoReturn("there must be 1, 2, or 3 arguments,");
  fi;

  if not (IsString(name) or IsFile(name)) then
    ErrorNoReturn("the 1st argument <filename> must be a string or IO ",
                  "file object,");
  elif not (IsFunction(decoder) or decoder = fail) then
    ErrorNoReturn("the argument <decoder> must be a function or fail,");
  elif not (IsPosInt(nr) or IsInfinity(nr)) then
    ErrorNoReturn("the argument <nr> must be a positive integer or ",
                  "infinity");
  fi;

  if IsString(name) then
    file := DigraphFile(name, decoder, "r");
  else
    file := name;
    if file!.closed then
      ErrorNoReturn("the 1st argument <filename> is a closed file,");
    elif file!.rbufsize = false then
      ErrorNoReturn("the mode of the 1st argument <filename> must be \"r\",");
    fi;
  fi;

  decoder := file!.coder;

  if nr < infinity then
    if NameFunction(decoder) in WholeFileDecoders then
      Info(InfoWarning, 1, NameFunction(decoder), " is a whole file decoder ",
           " and so only one digraph should be specified. If possible, the ",
           "final digraph will be returned, or else an error.");
    else
      i := 0;
      next := fail;
      while i < nr - 1 and next <> IO_Nothing do
        i := i + 1;
        next := IO_ReadLine(file);
      od;
      if next <> IO_Nothing then
        out := decoder(file);
      else
        out := IO_Nothing;
      fi;
      if IsString(arg[1]) then
        IO_Close(file);
      fi;
      return out;
    fi;
  fi;

  out := [];
  if NameFunction(decoder) in WholeFileDecoders then
    line := IO_ReadUntilEOF(file);
    if IsString(arg[1]) then
      IO_Close(file);
    fi;
    Add(out, decoder(line));
    return out;
  else
    next := decoder(file);
  fi;

  while next <> IO_Nothing do
    Add(out, next);
    next := decoder(file);
  od;

  if IsString(arg[1]) then
    IO_Close(file);
  fi;

  return out;
end);

InstallGlobalFunction(WriteDigraphs,
function(arg...)
  local name, digraphs, encoder, mode, splitname, compext, g6sum, s6sum, v, e,
  dg6sum, ds6sum, file, i, D, oldOnBreak, CloseAndCleanup, OnBreakWithCleanup;

  # defaults
  encoder := fail;
  mode    := "a";
  if Length(arg) = 2 then
    name     := arg[1];
    digraphs := arg[2];
  elif IsFile(arg[1]) then
    ErrorNoReturn("the 1st argument <filename> is a file, and so there must ",
                  "only be 2 arguments,");
  elif Length(arg) = 3 then
    name     := arg[1];
    digraphs := arg[2];
    if IsString(arg[3]) then
      mode := arg[3];
    else
      encoder := arg[3];
    fi;
  elif Length(arg) = 4 then
    name     := arg[1];
    digraphs := arg[2];
    encoder  := arg[3];
    mode     := arg[4];
  else
    ErrorNoReturn("there must be 2, 3, or 4 arguments,");
  fi;

  if not IsList(digraphs) then
    digraphs := [digraphs];
  fi;

  if not (IsString(name) or IsFile(name)) then
    ErrorNoReturn("the 1st argument <filename> must be a string or a file,");
  elif not ForAll(digraphs, IsDigraph) then
    ErrorNoReturn("the 2nd argument <digraphs> must be a digraph or list of ",
                  "digraphs,");
  elif not (IsFunction(encoder) or encoder = fail) then
    ErrorNoReturn("the argument <encoder> must be a function or fail,");
  elif not mode in ["a", "w"] then
    ErrorNoReturn("the argument <mode> must be \"a\" or \"w\",");
  elif IsString(name) and not IsExistingFile(name) then
    mode := "w";
  fi;

  if IsString(name) then
    if encoder = fail and DIGRAPHS_ChooseFileEncoder(name) = fail then
      # the file encoder was not specified and cannot be deduced from the
      # filename, so we try to make a guess based on the digraphs themselves
      splitname := SplitString(name, ".");
      if splitname[Length(splitname)] in ["xz", "gz", "bz2"] then
        compext := splitname[Length(splitname)];
        splitname := splitname{[1 .. Length(splitname) - 1]};
      fi;

      # Do we know all the graphs to be symmetric?
      if ForAll(digraphs, g -> HasIsSymmetricDigraph(g)
                               and IsSymmetricDigraph(g)) then
        if ForAny(digraphs, IsMultiDigraph) then
          encoder := DiSparse6String;
          Add(splitname, "ds6");
        else
          # Find the sum of length estimates using Graph6 and Sparse6
          g6sum := 0;
          s6sum := 0;
          for D in digraphs do
            v := DigraphNrVertices(D);
            e := DigraphNrEdges(D);
            g6sum := g6sum + (v * (v - 1) / 2);
            s6sum := s6sum + (e / 2 * (Log2Int(v - 1) + 2) * 3 / 2);
          od;
          if g6sum < s6sum and not ForAny(digraphs, DigraphHasLoops) then
            encoder := Graph6String;
            Add(splitname, "g6");
          else
            encoder := Sparse6String;
            Add(splitname, "s6");
          fi;
        fi;
      else
        if ForAny(digraphs, IsMultiDigraph) then
          encoder := DiSparse6String;
          Add(splitname, "ds6");
        else
          # Find the sum of length estimates using Digraph6 and DiSparse6
          dg6sum := 0;
          ds6sum := 0;
          for D in digraphs do
            v := DigraphNrVertices(D);
            e := DigraphNrEdges(D);
            dg6sum := dg6sum + v ^ 2;
            ds6sum := ds6sum + (e * (Log2Int(v) + 2) * 3 / 2);
          od;
          if dg6sum < ds6sum then
            encoder := Digraph6String;
            Add(splitname, "d6");
          else
            encoder := DiSparse6String;
            Add(splitname, "ds6");
          fi;
        fi;
      fi;
      name := JoinStringsWithSeparator(splitname, ".");
      if IsBound(compext) then
        Append(name, ".");
        Append(name, compext);
      fi;
      Info(InfoWarning, 1, "Writing to ", name);
    fi;
    file := DigraphFile(name, encoder, mode);
    if NameFunction(file!.coder) in WholeFileEncoders and file!.mode <> "w" then
      if IsString(arg[1]) then
        IO_Close(file);
      fi;
      ErrorNoReturn(NameFunction(file!.coder), " is a whole file ",
                    "encoder and so the argument <mode> must be \"w\".");
    fi;
  else
    file := name;
    if file!.closed then
      ErrorNoReturn("the 1st argument <filename> is closed,");
    elif file!.wbufsize = false then
      ErrorNoReturn("the mode of the 1st argument <filename> must be ",
                    "\"w\" or \"a\",");
    fi;
  fi;

  oldOnBreak := OnBreak;
  CloseAndCleanup := function()
    if IsString(arg[1]) then
      IO_Close(file);
    fi;
    OnBreak := oldOnBreak;
  end;

  OnBreakWithCleanup := function()
    CloseAndCleanup();
    OnBreak();
  end;
  OnBreak := OnBreakWithCleanup;

  encoder := file!.coder;
  if NameFunction(encoder) in WholeFileEncoders then
    if Length(digraphs) > 1 then
      Info(InfoWarning, 1, "the encoder ", NameFunction(encoder),
          " is a whole file encoder, and so only one digraph should be ",
          "specified. Only the last digraph will be encoded.");
    fi;
    IO_Write(file, encoder(digraphs[Length(digraphs)]));
  else
    for i in [1 .. Length(digraphs)] do
      encoder(file, digraphs[i]);
    od;
  fi;

  CloseAndCleanup();
  return IO_OK;
end);

################################################################################
# 3. Decoders
################################################################################

InstallMethod(DigraphFromGraph6StringCons, "for IsMutableDigraph and a string",
[IsMutableDigraph, IsString],
function(filt, s)
  local FindCoord, list, n, start, maxedges, out, pos, nredges, i, bpos, edge,
  j;

  s := Chomp(s);

  # find a position in the adj matrix from the vector
  # knowing a lower bound for pos_y
  FindCoord := function(pos, bound)
    local i, sum;
      i := bound;
      sum := i * (i + 1) / 2;
      while sum < pos do
        i := i + 1;
        sum := sum + i;
      od;
    return [pos - sum + i, i + 1];
  end;

  if Length(s) = 0 then
    ErrorNoReturn("the 2nd argument <s> must be a non-empty string,");
  fi;

  # Convert ASCII chars to integers
  list := List(s, i -> IntChar(i) - 63);

  # Get n the number of vertices of the graph
  if list[1] <> 63 then
    n := list[1];
    start := 2;
  elif Length(list) > 300 then
    if list[2] = 63 then
      n := 0;
      for i in [0 .. 5] do
        n := n + 2 ^ (6 * i) * list[8 - i];
      od;
      start := 9;
    else
      n := 0;
      for i in [0 .. 2] do
        n := n + 2 ^ (6 * i) * list[4 - i];
      od;
      start := 5;
    fi;
  else
    ErrorNoReturn("the 2nd argument <s> is not a valid graph6 string,");
  fi;

  maxedges := n * (n - 1) / 2;
  if list <> [0] and list <> [1] and
      not (Int((maxedges - 1) / 6) + start = Length(list) and
           list[Length(list)] mod 2 ^ ((0 - maxedges) mod 6) = 0) then
    ErrorNoReturn("the 2nd argument <s> is not a valid graph6 string,");
  fi;

  out := List([1 .. n], x -> []);

  # Obtaining the adjacency vector
  pos := 1;
  nredges := 0;
  for j in [start .. Length(list)] do  # Every integer corresponds to 6 bits
    i := list[j];
    bpos := 1;
    while i > 0 do
      if i mod 2 = 0 then
        i := i / 2;
      else
        edge := FindCoord(pos + 6 - bpos, 0);
        out[edge[1]][Length(out[edge[1]]) + 1] := edge[2];
        out[edge[2]][Length(out[edge[2]]) + 1] := edge[1];
        nredges := nredges + 1;
        i := (i - 1) / 2;
      fi;
      bpos := bpos + 1;
    od;
    pos := pos + 6;
  od;
  return DigraphNC(filt, out);
end);

InstallMethod(DigraphFromGraph6StringCons,
"for IsImmutableDigraph and a string",
[IsImmutableDigraph, IsString],
function(_, s)
  local D;
  D := MakeImmutable(DigraphFromGraph6StringCons(IsMutableDigraph, s));
  SetIsSymmetricDigraph(D, true);
  SetIsMultiDigraph(D, false);
  SetDigraphHasLoops(D, false);
  return D;
end);

InstallMethod(DigraphFromGraph6String, "for a function and a string",
[IsFunction, IsString],
DigraphFromGraph6StringCons);

InstallMethod(DigraphFromGraph6String, "for a string", [IsString],
s -> DigraphFromGraph6String(IsImmutableDigraph, s));

InstallMethod(DigraphFromDigraph6StringCons,
"for IsMutableDigraph and a string",
[IsMutableDigraph, IsString],
function(filt, s)
  local legacy, list, n, start, i, range, source, pos, len, j, bpos, tabpos;
  # NOTE: this package originally used a version of digraph6 that reads down
  # the columns of an adjacency matrix, and appends a '+' to the start.  This
  # has been replaced by the Nauty standard, which reads across the rows of the
  # matrix, and appends a '&' to the start.  For backwards compatibility, this
  # now accepts both formats, sending an info warning if the old format is used.

  s := Chomp(s);
  # Check non-emptiness
  if Length(s) = 0 then
    ErrorNoReturn("the 2nd argument <s> must be a non-empty string,");
  fi;

  # Check for the special '&' character (or the deprecated '+')
  if s[1] = '&' then
    legacy := false;
  elif s[1] = '+' then
    legacy := true;
    Info(InfoDigraphs, 1, "Digraph6 strings beginning with '+' use an old");
    Info(InfoDigraphs, 1, "specification of the Digraph6 format that is");
    Info(InfoDigraphs, 1, "incompatible with the present standard.  They can");
    Info(InfoDigraphs, 1, "still be read by the Digraphs package, but are");
    Info(InfoDigraphs, 1, "unlikely to be recognised by other programs.");
    Info(InfoDigraphs, 1, "Please consider re-encoding with the new format.");
  else
    ErrorNoReturn("the 2nd argument <s> is not a valid digraph6 string,");
  fi;

  # Convert ASCII chars to integers
  list := List(s, i -> IntChar(i) - 63);

  # Get n the number of vertices of the graph
  if list[2] <> 63 then
    n := list[2];
    start := 3;
  elif Length(list) > 300 then
    if list[3] = 63 then
      n := 0;
      for i in [0 .. 5] do
        n := n + 2 ^ (6 * i) * list[9 - i];
      od;
      start := 10;
    else
      n := 0;
      for i in [0 .. 2] do
        n := n + 2 ^ (6 * i) * list[5 - i];
      od;
      start := 6;
    fi;
  else
    ErrorNoReturn("the 2nd argument <s> is not a valid digraph6 string,");
  fi;

  range := [];
  source := [];

  # Obtaining the adjacency vector
  pos := 1;
  len := 1;
  for j in [start .. Length(list)] do  # Every integer corresponds to 6 bits
    i := list[j];
    bpos := 1;
    while i > 0 do
      if i mod 2 = 0 then
        i := i / 2;
      else
        tabpos := pos + 6 - bpos;
        range[len] := (tabpos - 1) mod n + 1;
        source[len] := (tabpos - range[len]) / n + 1;
        len := len + 1;
        i := (i - 1) / 2;
      fi;
      bpos := bpos + 1;
    od;
    pos := pos + 6;
  od;

  if legacy then  # source and range are reversed
    return DigraphNC(filt,
                 rec(DigraphNrVertices := n,
                     DigraphSource     := range,
                     DigraphRange      := source));
  fi;
  return DigraphNC(filt,
               rec(DigraphNrVertices := n,
                   DigraphRange      := range,
                   DigraphSource     := source));
end);

InstallMethod(DigraphFromDigraph6StringCons,
"for IsImmutableDigraph and a string",
[IsImmutableDigraph, IsString],
{filt, s} -> MakeImmutable(DigraphFromDigraph6StringCons(IsMutableDigraph, s)));

InstallMethod(DigraphFromDigraph6String, "for a function and a string",
[IsFunction, IsString],
DigraphFromDigraph6StringCons);

InstallMethod(DigraphFromDigraph6String, "for a string",
[IsString], s -> DigraphFromDigraph6String(IsImmutableDigraph, s));

InstallMethod(DigraphFromSparse6StringCons, "for IsMutableDigraph and a string",
[IsMutableDigraph, IsString],
function(filt, s)
  local list, n, start, blist, pos, num, bpos, k, range, source, len, v, i,
  finish, x, j;

  s := Chomp(s);
  # Check non-emptiness
  if Length(s) = 0 then
    ErrorNoReturn("the 2nd argument <s> must be a non-empty string,");
  fi;

  # Check for the special ':' character
  if s[1] <> ':' then
    ErrorNoReturn("the 2nd argument <s> is not a valid sparse6 string,");
  fi;

  # Convert ASCII chars to integers
  list := [];
  for i in s do
    Add(list, IntChar(i) - 63);
  od;

  # Get n the number of vertices of the graph
  if list[2] <> 63 then
    n := list[2];
    start := 3;
  elif list[3] = 63 then
    if Length(list) <= 8 then
      ErrorNoReturn("the 2nd argument <s> is not a valid sparse6 string,");
    fi;
    n := 0;
    for i in [0 .. 5] do
      n := n + 2 ^ (6 * i) * list[9 - i];
    od;
    start := 10;
  elif Length(list) > 4 then
      n := 0;
      for i in [0 .. 2] do
        n := n + 2 ^ (6 * i) * list[5 - i];
      od;
      start := 6;
  else
    ErrorNoReturn("the 2nd argument <s> is not a valid sparse6 string,");
  fi;

  # convert list into a list of bits;
  blist := BlistList([1 .. (Length(list) - start + 1) * 6], []);
  pos := 1;
  for i in [start .. Length(list)] do
    num := list[i];
    bpos := 1;
    while num > 0 do
      if num mod 2 = 0 then
        num := num / 2;
      else
        num := (num - 1) / 2;
        blist[pos + 6 - bpos] := true;
      fi;
      bpos := bpos + 1;
    od;
    pos := pos + 6;
  od;

  if n > 1 then
    k := LogInt(n - 1, 2) + 1;
  else
    k := 1;
  fi;

  range := [];
  source := [];

  len := 1;
  v := 0;
  i := 1;
  # remove some of the padding
  finish := Length(blist) - (Length(blist) mod (k + 1));
  while i <= finish - k do
    if blist[i] then
      v := v + 1;
      if v = n then  # We have reached the end
        break;
      fi;
    fi;
    x := 0;
    for j in [1 .. k] do
      if blist[i + j] then
        x := x + 2 ^ (k - j);
      fi;
    od;
    if x = n then  # We have reached the end
      break;
    elif x > v then
      v := x;
    else
      range[len] := x;
      source[len] := v;
      len := len + 1;
      if x <> v then
        range[len] := v;
        source[len] := x;
        len := len + 1;
      fi;
    fi;
    i := i + k + 1;
  od;

  range := range + 1;
  source := source + 1;
  return DigraphNC(filt, (rec(DigraphNrVertices := n,
                                      DigraphRange := range,
                                      DigraphSource := source)));
end);

InstallMethod(DigraphFromSparse6StringCons,
"for IsImmutableDigraph and a string",
[IsImmutableDigraph, IsString],
function(_, s)
  local D;
  D := MakeImmutable(DigraphFromSparse6String(IsMutableDigraph, s));
  SetIsSymmetricDigraph(D, true);
  return D;
end);

InstallMethod(DigraphFromSparse6String, "for a function and a string",
[IsFunction, IsString], DigraphFromSparse6StringCons);

InstallMethod(DigraphFromSparse6String, "for a string",
[IsString],
s -> DigraphFromSparse6String(IsImmutableDigraph, s));

InstallMethod(DigraphFromDiSparse6StringCons,
"for IsMutableDigraph and a string",
[IsMutableDigraph, IsString],
function(func, s)
  local list, n, start, blist, pos, num, bpos, k, range, source, len, v, i, x,
  finish, j;

  s := Chomp(s);

  # Check non-emptiness
  if Length(s) = 0 then
    ErrorNoReturn("the 2nd argument <s> must be a non-empty string,");
  fi;

  # Check for the special ':' character
  if s[1] <> '.' then
    ErrorNoReturn("the 2nd argument <s> is not a valid disparse6 string,");
  fi;

  # Convert ASCII chars to integers
  list := [];
  for i in s do
    Add(list, IntChar(i) - 63);
  od;

  # Get n the number of vertices of the graph
  if list[2] <> 63 then
    n := list[2];
    start := 3;
  elif list[3] = 63 then
    if Length(list) <= 8 then
      ErrorNoReturn("the 2nd argument <s> is not a valid disparse6 string,");
    fi;
    n := 0;
    for i in [0 .. 5] do
      n := n + 2 ^ (6 * i) * list[9 - i];
    od;
    start := 10;
  elif Length(list) > 4 then
      n := 0;
      for i in [0 .. 2] do
        n := n + 2 ^ (6 * i) * list[5 - i];
      od;
      start := 6;
  else
    ErrorNoReturn("the 2nd argument <s> is not a valid disparse6 string,");
  fi;

  # convert list into a list of bits;
  blist := BlistList([1 .. (Length(list) - start + 1) * 6], []);
  pos := 1;
  for i in [start .. Length(list)] do
    num := list[i];
    bpos := 1;
    while num > 0 do
      if num mod 2 = 0 then
        num := num / 2;
      else
        num := (num - 1) / 2;
        blist[pos + 6 - bpos] := true;
      fi;
      bpos := bpos + 1;
    od;
    pos := pos + 6;
  od;

  if n > 1 then
    k := LogInt(n, 2) + 1;
  else
    k := 1;
  fi;

  range := [];
  source := [];
  # Get the decreasing edges first
  len := 1;
  v := 0;
  i := 1;
  while true do
    if blist[i] then
      v := v + 1;
    fi;
    x := 0;
    for j in [1 .. k] do
      if blist[i + j] then
        x := x + 2 ^ (k - j);
      fi;
    od;
    if x >= n then
      break;
    elif x > v then
      v := x;
    else
      range[len] := x;
      source[len] := v;
      len  := len + 1;
    fi;
    i := i + k + 1;
  od;

  i := i + k + 1;

  # Get the increasing edges
  finish := Length(blist) - (Length(blist) mod (k + 1));
  v := 0;
  while i <= finish - k do
    if blist[i] then
      v := v + 1;
    fi;
    x := 0;
    for j in [1 .. k] do
      if blist[i + j] then
        x := x + 2 ^ (k - j);
      fi;
    od;
    if x >= n then
      break;
    elif x > v then
      v := x;
    else
      range[len] := v;
      source[len] := x;
      len := len + 1;
    fi;
    i := i + k + 1;
  od;
  range := range + 1;
  source := source + 1;
  return DigraphNC(func, (rec(DigraphNrVertices := n,
                              DigraphRange      := range,
                              DigraphSource     := source)));
end);

InstallMethod(DigraphFromDiSparse6StringCons,
"for IsImmutableDigraph and a string",
[IsImmutableDigraph, IsString],
{filt, s} -> MakeImmutable(DigraphFromDiSparse6String(IsMutableDigraph, s)));

InstallMethod(DigraphFromDiSparse6String,
"for a function and a string",
[IsFunction, IsString],
DigraphFromDiSparse6StringCons);

InstallMethod(DigraphFromDiSparse6String,
"for a string",
[IsString],
s -> DigraphFromDiSparse6String(IsImmutableDigraph, s));

# one graph per line
BindGlobal("DIGRAPHS_PlainTextLineDecoder",
function(func, delimiter1, delimiter2, offset)
  local retval;
  retval := function(s)
    local edges, x;
    s := DIGRAPHS_SplitStringBySubstring(Chomp(s), delimiter1);
    Apply(s, x -> DIGRAPHS_SplitStringBySubstring(x, delimiter2));
    edges := EmptyPlist(Length(s));
    for x in s do
      Apply(x, Int);
      x := x + offset;
      Add(edges, x);
    od;
    return func(edges);
  end;
  return retval;
end);

InstallMethod(DigraphFromPlainTextStringCons,
"for IsMutableDigraph and a string", [IsMutableDigraph, IsString],
function(filt, s)
  local f;
  f := x -> DigraphByEdges(filt, x);
  return DIGRAPHS_PlainTextLineDecoder(f, "  ", " ", 1)(Chomp(s));
end);

InstallMethod(DigraphFromPlainTextStringCons,
"for IsImmutableDigraph and a string", [IsImmutableDigraph, IsString],
{_, s} -> MakeImmutable(DigraphFromPlainTextStringCons(IsMutableDigraph, s)));

InstallMethod(DigraphFromPlainTextString, "for a function and a string",
[IsFunction, IsString], DigraphFromPlainTextStringCons);

InstallMethod(DigraphFromPlainTextString, "for a string",
[IsString], s -> DigraphFromPlainTextString(IsImmutableDigraph, s));

# DIMACS format: for symmetric digraphs, one per file, can have loops and
# multiple edges.,

InstallMethod(DigraphFromDIMACSString, "for a string", [IsString],
function(s)
  local parts, x, int_from_string, next, split, first_char,
  nr_vertices, vertices, vertex_labels, nr_edges, directed_edges,
  symmetric_edges, nbs, vertex, label, i, j, D;

  if s = "" then
    ErrorNoReturn("the argument <s> must be a non-empty string");
  fi;

  # Helper function to read a string into a non-negative integer
  int_from_string := function(string)
    local int;
    int := Int(string);
    if int = fail or int < 0 then
      ErrorNoReturn("the format of the string <s> cannot be determined");
    fi;
    return int;
  end;

  parts := SplitString(s, "\n");
  x := 1;
  next := parts[x];

  while not IsEmpty(next) do
    NormalizeWhitespace(next);

    # the line is entirely whitespace or a comment
    if IsEmpty(next) or next[1] = 'c' then
      x := x + 1;
      if x > Length(parts) then
        next := "";
      else
        next := parts[x];
      fi;
      continue;
    fi;

    split := SplitString(next, " ");

    # the line doesn't have a `type'
    if Length(split[1]) <> 1 then
      ErrorNoReturn("the format of the string <s> cannot be determined");
    fi;

    first_char := next[1];

    # digraph definition line
    if first_char = 'p' then
      if IsBound(vertices) or Length(split) <> 4 or split[2] <> "edge" then
        ErrorNoReturn("the format of the string <s>",
                             " cannot be determined");
      fi;
      nr_vertices     := int_from_string(split[3]);
      vertices        := [1 .. nr_vertices];
      vertex_labels   := vertices * 0 + 1;
      nr_edges        := int_from_string(split[4]);
      directed_edges  := 0;
      symmetric_edges := 0;
      nbs := List(vertices, x -> []);
      x := x + 1;
      if x > Length(parts) then
        next := "";
      else
        next := parts[x];
      fi;
      continue;
    fi;

    if not IsBound(vertices) then
      # the problem definition line must precede all other types
      ErrorNoReturn("the format of the string <s> cannot be determined");
    elif first_char = 'n' then
      # type: vertex label
      if Length(split) <> 3 then
        ErrorNoReturn("the format of the string <s> cannot be determined");
      fi;
      vertex := int_from_string(split[2]);
      if not vertex in vertices then
        ErrorNoReturn("the format of the string <s> cannot be determined");
      fi;
      label := Int(split[3]);
      if label = fail then
        ErrorNoReturn("the format of the string <s> cannot be determined");
      fi;
      vertex_labels[vertex] := label;
    elif first_char = 'e' then
      # type: edge
      if Length(split) <> 3 then
        ErrorNoReturn("the format of the string <s> cannot be determined");
      fi;
      i := int_from_string(split[2]);
      j := int_from_string(split[3]);
      if not (i in vertices and j in vertices) then
        ErrorNoReturn("the format of the string <s> cannot be determined");
      fi;
      Add(nbs[i], j);
      directed_edges := directed_edges + 1;
      symmetric_edges := symmetric_edges + 1;
      if i <> j then
        Add(nbs[j], i);
        directed_edges := directed_edges + 1;
      fi;
    elif first_char in "dvx" then
      # type: unsupported lines
      Info(InfoDigraphs, 1,
           "Lines beginning with 'd', 'v', or 'x' are not supported,");
    else
      # type: unknown
      ErrorNoReturn("the format of the string <s> cannot be determined");
    fi;
    x := x + 1;
    if x > Length(parts) then
      next := "";
    else
      next := parts[x];
    fi;
  od;

  if not IsBound(vertices) then
    ErrorNoReturn("the format of the string <s> cannot be determined");
  fi;

  if not nr_edges in [directed_edges, 2 * directed_edges, symmetric_edges] then
    Info(InfoDigraphs, 1, "An unexpected number of edges was found,");
  fi;

  D := ConvertToImmutableDigraphNC(nbs);
  if IsImmutableDigraph(D) then
    SetDigraphVertexLabels(D, vertex_labels);
  fi;
  return D;
end);

InstallMethod(ReadDIMACSDigraph, "for a string", [IsString],
s -> ReadDigraphs(s, DigraphFromDIMACSString)[1]);

BindGlobal("DIGRAPHS_TournamentLineDecoder",
function(func, s)
  local out, pos, n, i, j;
  pos := 0;
  s := Chomp(s);
  n := (Sqrt(8 * Length(s) + 1) + 1) / 2;
  out := List([1 .. n], x -> []);
  for i in [1 .. n - 1] do
    for j in [i + 1 .. n] do
      pos := pos + 1;
      if s[pos] = '1' then
        Add(out[i], j);
      else
        Add(out[j], i);
      fi;
    od;
  od;
  return func(out);
end);

InstallMethod(TournamentLineDecoder, "for a string", [IsString],
s -> DIGRAPHS_TournamentLineDecoder(ConvertToImmutableDigraphNC, s));

InstallMethod(DigraphPlainTextLineDecoder,
"for a string, string, and integer",
[IsString, IsString, IsInt],
function(delimiter1, delimiter2, offset)
  return DIGRAPHS_PlainTextLineDecoder(DigraphByEdges,
                                       delimiter1,
                                       delimiter2,
                                       offset);
end);

# one edge per line, one graph per file
BindGlobal("DIGRAPHS_ReadPlainTextDigraph",
function(func, name, delimiter, offset, ignore)
  local file, lines, edges, nr, decoder, line;

  file := IO_CompressedFile(UserHomeExpand(name), "r");
  if file = fail then
    ErrorNoReturn("cannot open the file given as the 2nd argument <name>,");
  fi;

  lines := IO_ReadLines(file);
  IO_Close(file);
  edges := EmptyPlist(Length(lines));
  nr := 0;

  decoder := function(string)
    string := DIGRAPHS_SplitStringBySubstring(Chomp(string), delimiter);
    Apply(string, Int);
    return string + offset;
  end;

  for line in lines do
    if Length(line) > 0 and not (line[1] in ignore) then
      nr := nr + 1;
      edges[nr] := decoder(Chomp(line));
    fi;
  od;

  return func(edges);
end);

InstallMethod(ReadPlainTextDigraph,
"for a string, string, integer, and string",
[IsString, IsString, IsInt, IsString],
function(name, delimiter, offset, ignore)
  return DIGRAPHS_ReadPlainTextDigraph(DigraphByEdges,
                                       name,
                                       delimiter,
                                       offset,
                                       ignore);
end);

BindGlobal("DIGRAPHS_AdjacencyMatUpperTriLineDecoder",
function(func, s)
  local out, pos, n, i, j;
  s := Chomp(s);
  pos := 0;
  n := (Sqrt(8 * Length(s) + 1) + 1) / 2;
  out := List([1 .. n], x -> []);
  for i in [1 .. n - 1] do
    for j in [i + 1 .. n] do
      pos := pos + 1;
      if s[pos] = '1' then
        Add(out[i], j);
      fi;
    od;
  od;
  return func(out);
end);

InstallMethod(AdjacencyMatrixUpperTriangleLineDecoder, "for a string",
[IsString],
s -> DIGRAPHS_AdjacencyMatUpperTriLineDecoder(ConvertToImmutableDigraphNC,
                                              s));

BindGlobal("DIGRAPHS_TCodeDecoder",
function(func, s)
  local out, i;

  s := SplitString(Chomp(s), " ");

  if not ForAll(s, x -> ForAll(x, IsDigitChar)) or Length(s) < 2 then
    ErrorNoReturn("the 2nd argument <s> must be a string of ",
                  "at least two space-separated non-negative integers,");
  fi;

  Apply(s, Int);

  if not ForAll([3 .. Length(s)], i -> s[i] < s[1]) then
    ErrorNoReturn("all integers in the 2nd argument <s>, ",
                  "except for the first two, must be strictly less than ",
                  "the first integer, which is ", s[1], ",");
  elif Length(s) < 2 * s[2] + 2 then
    ErrorNoReturn("the 2nd argument <s> must be a string of length ",
                  "at least ", 2 * s[2] + 2);
  fi;

  out := List([1 .. s[1]], x -> []);
  for i in [1 .. s[2]] do
    Add(out[s[2 * i + 1] + 1], s[2 * i + 2] + 1);
  od;

  return func(out);
end);

InstallMethod(TCodeDecoder, "for a string", [IsString],
s -> DIGRAPHS_TCodeDecoder(ConvertToImmutableDigraphNC, s));

InstallGlobalFunction(TCodeDecoderNC,
function(str)
  local out, i;
  str := SplitString(Chomp(str), " ");
  Apply(str, Int);
  out := List([1 .. str[1]], x -> []);
  for i in [1 .. str[2]] do
    Add(out[str[2 * i + 1] + 1], str[2 * i + 2] + 1);
  od;
  return ConvertToImmutableDigraphNC(out);
end);

# helper function for DigraphFromDreadnautString
# gets or ungets (ug = 1 or -1 respectively)
# the next character in the string and updates
# the line number if necessary
# returns the character or fail for getchar
# returns nothing for ungetchar
BindGlobal("DIGRAPHS_GetUngetChar",
function(r, D, ug)
  if ug = 1 then  # GetChar
    r.i := r.i + 1;
    if r.i > Length(D) then
      return fail;
    else
      if D[r.i] = '\n' then
        r.newline := r.newline + 1;
      fi;
      return D[r.i];
    fi;
  else  # ug = -1, UngetChar
    if r.i > Length(D) then
      r.i := r.i - 1;
      return;
    fi;
    if D[r.i] = '\n' then
      r.newline := r.newline - 1;
    fi;
    r.i := r.i - 1;
    return;
  fi;
end);

# helper function for DigraphFromDreadnautString
# (returns the first character not in s)
BindGlobal("DIGRAPHS_GETNWCL",
function(r, D, s)
  local char;
  char := DIGRAPHS_GetUngetChar(r, D, 1);
  while char <> fail and char in s do
    char := DIGRAPHS_GetUngetChar(r, D, 1);
  od;

  return char;
end);

#  helper function for DigraphFromDreadnautString
#  return the next full integer in the string
BindGlobal("DIGRAPHS_readinteger",
function(r, D)
  local char, res, minus;
  char := DIGRAPHS_GETNWCL(r, D, " \n\t\r");

  if not IsDigitChar(char) and char <> '-' and char <> '+' then
    DIGRAPHS_GetUngetChar(r, D, -1);
    return fail;
  fi;

  minus := char = '-';
  if char = '-' or char = '+' then
    res := 0;
  else
    res := Int([char]);
  fi;

  char := DIGRAPHS_GetUngetChar(r, D, 1);

  while IsDigitChar(char) do
    res := res * 10 + Int([char]);
    char := DIGRAPHS_GetUngetChar(r, D, 1);
  od;

  if char <> fail then
    DIGRAPHS_GetUngetChar(r, D, -1);
  fi;

  if minus then
    res := -res;
  fi;

  return res;
end);

# helper function for DigraphFromDreadnautString
# reads in and stores graph adjacency data
# returns nothing
BindGlobal("DIGRAPHS_readgraph",
function(r, D)
  local c, w, v, neg;

  v := 1;
  neg := false;
  c := ' ';

  if r.n = fail then
    ErrorNoReturn("Vertex number must be declared before `g' ",
                  "on line ", r.newline);
  else
    r.edgeList := List([1 .. r.n], x -> []);
  fi;

  while c <> fail do
    c := DIGRAPHS_GETNWCL(r, D, " ,\t");
    if IsDigitChar(c) then
      DIGRAPHS_GetUngetChar(r, D, -1);
      w := DIGRAPHS_readinteger(r, D);
      w := w - r.labelorg + 1;

      if neg then
        neg := false;

        if (w < 1) or (w > r.n) or (w = v and not r.digraph) then
          Info(InfoWarning, 1, StringFormatted(
               Concatenation("Ignoring illegal edge ({}, {}) ",
               "(vertices {}-indexed, on line {}) "),
               v + r.labelorg - 1, w + r.labelorg - 1,
               r.labelorg, r.newline));
        else
          RemoveSet(r.edgeList[v], w);
          if not r.digraph then
            RemoveSet(r.edgeList[w], v);
          fi;
        fi;
      else
        c := DIGRAPHS_GETNWCL(r, D, " ,\t");
        if c = ':' then
          if w < 1 or w > r.n then
            Info(InfoWarning, 1, StringFormatted(
                 Concatenation("Ignoring illegal vertex {} ",
                 "({}-indexed), on line {})"),
                 w + r.labelorg - 1, r.labelorg, r.newline));
          else
            v := w;
          fi;
        else
          DIGRAPHS_GetUngetChar(r, D, -1);
          if w < 1 or w > r.n or (w = v and not r.digraph) then
            Info(InfoWarning, 1, StringFormatted(
                 Concatenation("Ignoring illegal edge ({}, {}) ",
                 "(vertices {}-indexed, on line {}) "),
                 v + r.labelorg - 1, w + r.labelorg - 1,
                 r.labelorg, r.newline));
          else
            AddSet(r.edgeList[v], w);
            if not r.digraph then
              AddSet(r.edgeList[w], v);
            fi;
          fi;
        fi;
      fi;
    elif c = ';' then
      neg := false;
      v := v + 1;
      if v > r.n then
        return;
      fi;
    elif c in "?\n" then
      neg := false;
    elif c = '.' then
      return;
    elif c = '-' then
      neg := true;
    elif c = '!' then
      while c <> '\n' and c <> fail do
        c := DIGRAPHS_GetUngetChar(r, D, 1);
      od;
    else
      if c = fail then
        return;
      else
        ErrorNoReturn("Illegal character ", c, " on line ", r.newline);
      fi;
    fi;
  od;
end);

# helper function for DigraphFromDreadnautString
# returns the next full integer from the string,
# allowing for an optional '=' before the integer
BindGlobal("DIGRAPHS_GetInt",
function(r, D)
  local ch;

  ch := DIGRAPHS_GETNWCL(r, D, " \n\t\r");
  if ch <> '=' then
    DIGRAPHS_GetUngetChar(r, D, -1);
  fi;
  return DIGRAPHS_readinteger(r, D);
end);

# helper function for DigraphFromDreadnautString
# parses partitions and stores them as vertex labels
# returns nothing
BindGlobal("DIGRAPHS_ParsePartition",
function(r, minus, D)
  local c, x, tempNewline, v1, v2, part, partition;

  part := 1;
  if r.n = fail then
    ErrorNoReturn("Vertex number must be declared ",
                  "before partition on line ", r.newline);
  fi;
  partition := List([1 .. r.n], x -> 0);

  if minus then
    return;
  fi;

  c := DIGRAPHS_GETNWCL(r, D, " ,\t");
  if c = '=' then
    c := DIGRAPHS_GETNWCL(r, D, " ,\t");
  fi;

  if IsDigitChar(c) then
    DIGRAPHS_GetUngetChar(r, D, -1);
    v1 := DIGRAPHS_readinteger(r, D);
    if v1 - r.labelorg + 1 < 1 or v1 - r.labelorg + 1 > r.n then
      Info(InfoWarning, 1, "Ignoring illegal vertex in partition", v1,
           " on line ", r.newline);
    else
      partition[v1] := 1;
    fi;
    r.partition := partition;
    return;
  elif c <> '[' then
    ErrorNoReturn("Partitions should be specified",
                  " as either a single number (e.g. 'f=3')",
                  " a list of cells (e.g. 'f=[...]') or",
                  " using '-f'");
  else
    tempNewline := r.newline;
    while c <> fail and c <> ']' do
      c := DIGRAPHS_GETNWCL(r, D, " \n\t\r");
      if c = '|' then
        part := part + 1;
      elif c = ',' then
        continue;
      elif IsDigitChar(c) then
        DIGRAPHS_GetUngetChar(r, D, -1);
        v1 := DIGRAPHS_readinteger(r, D);
        if v1 - r.labelorg + 1 < 1 or v1 - r.labelorg + 1 > r.n then
          ErrorNoReturn("Vertex ", v1,
                        " out of range in partition specification (line ",
                        tempNewline, ")");
        fi;

        v2 := DIGRAPHS_GETNWCL(r, D, " \n\t\r");
        if v2 = ':' then
          v2 := DIGRAPHS_GETNWCL(r, D, " \n\t\r");
          if not IsDigitChar(v2) then
            ErrorNoReturn("Invalid range ", v1, " : ", v2,
                          " in partition specification (line ",
                          tempNewline, ")");
          fi;
          DIGRAPHS_GetUngetChar(r, D, -1);
          v2 := DIGRAPHS_readinteger(r, D);
          if v2 < r.labelorg or v2 > r.n - r.labelorg + 1 then
            ErrorNoReturn("Vertex ", v2,
                          " out of range in partition specification (line ",
                          tempNewline, ")");
          fi;
          for x in [v1 - r.labelorg + 1 .. v2 - r.labelorg + 1] do
            if partition[x] = 0 then
              partition[x] := part;
            else
              Info(InfoWarning, 1, "Vertex ", x + r.labelorg - 1,
                   " (", r.labelorg,
                   "-indexed is in multiple partitions (line ",
                   tempNewline, ")");
            fi;
          od;
          else
            DIGRAPHS_GetUngetChar(r, D, -1);
            if partition[v1 - r.labelorg + 1] = 0 then
              partition[v1 - r.labelorg + 1] := part;
            else
              Info(InfoWarning, 1, "Vertex ", v1,
                   " (", r.labelorg, "-indexed) ",
                   " is in multiple partitions (line ",
                   tempNewline, ")");
            fi;
          fi;
      else
        if c = fail then
          ErrorNoReturn("Unterminated partition specification (line ",
                        tempNewline, ")");
        elif c = ']' then
          break;
        else
          ErrorNoReturn("Unexpected character ", c,
                        " in partition specification (line ", r.newline, ")");
        fi;
      fi;
    od;
  fi;
  r.partition := partition;
  return;
end);

InstallMethod(DigraphFromDreadnautString, "for a digraph", [IsString],
function(D)
  local r, c, minus, backslash, temp, out, underscore, doubleUnderscore;
  r := rec(n := fail, digraph := false, labelorg := 0, i := 0,
           newline := 1, edgeList := fail, partition := fail);
  underscore := false;
  doubleUnderscore := false;

  minus := false;
  c := ' ';

  if D = "" then
    ErrorNoReturn("the argument <s> must be a non-empty string");
  fi;

  while c <> fail do
    c := DIGRAPHS_GetUngetChar(r, D, 1);
    if c = fail then
      break;
    elif c in " \t\r" then
    elif c = '-' then
      minus := true;
    elif c in "+,;\n" then
      minus := false;
    elif c = 'A' then
      minus := false;
      temp := DIGRAPHS_GetUngetChar(r, D, 1);
      if (temp in "nNdDtTsS") = false then
        ErrorNoReturn("Operation 'A' (line ", r.newline,
                      ") is not recognised");
      fi;
      temp := DIGRAPHS_GetUngetChar(r, D, 1);
      if temp <> '+' then
        DIGRAPHS_GetUngetChar(r, D, -1);
      fi;
    elif c in "B@#jv\%IixtTobzamp?Hc" then
      minus := false;
      Info(InfoWarning, 1, "Operation ", c, " (line ", r.newline,
           ") is not supported");
    elif c = '_' then
      temp := DIGRAPHS_GetUngetChar(r, D, 1);
      if temp = '_' then
        doubleUnderscore := true;
      else
        underscore := true;
        DIGRAPHS_GetUngetChar(r, D, -1);
      fi;
    elif c in "<eq" then
      ErrorNoReturn("Operation ", c, " (line ", r.newline,
                    ") is not supported");
    elif c = 'd' then
      if not minus then
        r.digraph := true;
      else
        minus := false;
        r.digraph := false;
      fi;
    elif c in ">" then
      ErrorNoReturn("Operation '>' (line ", r.newline,
                    ") is not supported.",
                    " Please use 'WriteDigraphs'.");
    elif c = '!' then
      while c <> '\n' and c <> fail do
        c := DIGRAPHS_GetUngetChar(r, D, 1);
      od;
    elif c = 'n' then
      minus := false;
      r.n := DIGRAPHS_GetInt(r, D);
      if r.n = fail then
        ErrorNoReturn("Expected integer on line ", r.newline,
                      " following ", c, " but was not found");
      elif r.n <= 0 then
        ErrorNoReturn("Vertex number given as ", r.n, " (line ",
                      r.newline,
                      "), but should be positive.");
      fi;
    elif c = 'g' then
      minus := false;
      if r.edgeList <> fail then
        Info(InfoWarning, 1, "Multiple graphs have been declared.",
             " Only the last one will be read in.");
      fi;
      DIGRAPHS_readgraph(r, D);
    elif c = 's' then
      minus := false;
      if DIGRAPHS_GetUngetChar(r, D, 1) <> 'r' then
        DIGRAPHS_GetUngetChar(r, D, -1);
      fi;
      Info(InfoWarning, 1, "Operation 's' or 'sr' (line ", r.newline,
           ") is not supported");
      DIGRAPHS_GetInt(r, D);
    elif c = '\"' then
      minus := false;
      c := DIGRAPHS_GetUngetChar(r, D, 1);
      temp := r.newline;
      backslash := false;
      while c <> fail do
        c := DIGRAPHS_GetUngetChar(r, D, 1);
        if c = '\\' then
          backslash := true;
        elif backslash then
          backslash := false;
        elif c = '\"' then
          if backslash then
            backslash := false;
          else
            break;
          fi;
        fi;
      od;
      if c = fail then
        ErrorNoReturn("Unterminated comment beginning on line ",
                      temp);
      fi;
    elif c = 'f' then
      DIGRAPHS_ParsePartition(r, minus, D);
      if minus then
        minus := false;
      fi;
    elif c in "lwyK*" then
      Info(InfoWarning, 1, "Operation ", c, " (line ",
           r.newline, ") is not supported");
      minus := false;
      DIGRAPHS_GetInt(r, D);
    elif c = 'F' then
      minus := false;
      temp := DIGRAPHS_GetUngetChar(r, D, 1);
      if temp = 'F' then
        Info(InfoWarning, 1, "Operation 'FF' (line ", r.newline,
             ") is not supported");
      else
        DIGRAPHS_GetUngetChar(r, D, -1);
        Info(InfoWarning, 1, "Operation 'F' (line ", r.newline,
             ") is not supported");
        DIGRAPHS_GetInt(r, D);
      fi;
    elif c = 'O' then
      if DIGRAPHS_GetUngetChar(r, D, 1) <> 'O' then
        DIGRAPHS_GetUngetChar(r, D, -1);
        Info(InfoWarning, 1, "Operation 'O' (line ", r.newline,
             ") is not supported");
      else
        Info(InfoWarning, 1, "Operation 'OO' (line ", r.newline,
             ") is not supported");
      fi;
    elif c = 'M' then
      Info(InfoWarning, 1, "Operation 'M' (line ",
           r.newline, ") is not supported");
      if minus then
        minus := false;
      else
        DIGRAPHS_GetInt(r, D);
        if DIGRAPHS_GetUngetChar(r, D, 1) = '/' then
          DIGRAPHS_GetInt(r, D);
        else
          DIGRAPHS_GetUngetChar(r, D, -1);
        fi;
      fi;
    elif c = 'k' then
      Info(InfoWarning, 1, "Operation 'k' (line ",
           r.newline, ") is not supported");
      minus := false;
      DIGRAPHS_GetInt(r, D);
      DIGRAPHS_GetInt(r, D);
    elif c in "VSGu" then
      Info(InfoWarning, 1, "Operation ", c,
           " (line ", r.newline, ") is not supported");
      if minus then
        minus := false;
      else
        DIGRAPHS_GetInt(r, D);
      fi;
    elif c = 'P' then
      if minus then
        minus := false;
        Info(InfoWarning, 1, "Operation -", c,
             " (line ", r.newline, ") is not supported");
      else
        if DIGRAPHS_GetUngetChar(r, D, 1) = 'P' then
          temp := r.newline;
          while c <> fail and c <> ';' do
            c := DIGRAPHS_GetUngetChar(r, D, 1);
          od;
          if c = fail then
            ErrorNoReturn("Unterminated 'PP' operation beginning ",
                          "on line ", temp);
          fi;
          Info(InfoWarning, 1, "Operation PP (line ",
               r.newline, ") is not supported");
        else
          DIGRAPHS_GetUngetChar(r, D, -1);
          Info(InfoWarning, 1, "Operation ", c, " (line ",
               r.newline, ") is not supported");
        fi;
      fi;
    elif c = '$' then
      temp := DIGRAPHS_GetUngetChar(r, D, 1);
      if temp <> '$' then
        DIGRAPHS_GetUngetChar(r, D, -1);
        r.labelorg := DIGRAPHS_GetInt(r, D);
        if r.labelorg < 0 then
          ErrorNoReturn("Label origin ", r.labelorg, " on line ",
                        r.newline, " must be non-negative.");
        elif r.labelorg = fail then
          ErrorNoReturn("Expected integer on line ", r.newline,
                        " following $ but was not found");
        fi;
      fi;
      if r.labelorg <> 1 then
        Info(InfoWarning, 1, "Vertices will be 1-indexed");
      fi;
    elif c in "rR" then
      Info(InfoWarning, 1, "Operations ['r', 'r&', 'R']",
           " (line ", r.newline, ") is not supported");
      minus := false;
      if (c = 'r' and DIGRAPHS_GetUngetChar(r, D, 1) <> '&') or c = 'R' then
        DIGRAPHS_GetUngetChar(r, D, -1);
        temp := r.newline;
        while c <> fail and c <> ';' do
          c := DIGRAPHS_GetUngetChar(r, D, 1);
        od;
        if c = fail then
          ErrorNoReturn("Unterminated operation (either 'r' or 'R')",
                        " beginning on line ", temp);
        fi;
      fi;
    elif c = '&' then
      minus := false;
      temp := DIGRAPHS_GetUngetChar(r, D, 1);
      if temp <> '&' then
        DIGRAPHS_GetUngetChar(r, D, -1);
        Info(InfoWarning, 1, "Operation '& (line ",
             r.newline, ") is not supported");
      else
        Info(InfoWarning, 1, "Operation '&&' (line ",
             r.newline, ") is not supported");
      fi;
    else
      ErrorNoReturn("Illegal character ", c, " on line ", r.newline);
    fi;
  od;

  if r.edgeList = fail then
    ErrorNoReturn("No graph was declared.");
  fi;
  out := Digraph(r.edgeList);
  if not r.digraph then
    Info(InfoWarning, 1, "Graph is not directed and",
         " so will be symmetrised.");
  fi;
  if r.partition <> fail then
    SetDigraphVertexLabels(out, r.partition);
  fi;
  if doubleUnderscore then
    if not r.digraph then
      underscore := true;
    else
      out := DigraphReverse(out);
    fi;
  fi;
  if underscore then
    if DigraphHasLoops(out) then
      out := DigraphDual(out);
    else
      out := DigraphRemoveLoops(DigraphDual(out));
    fi;
  fi;
  return out;
end);

################################################################################
# 4. Encoders
################################################################################
InstallMethod(WriteDIMACSDigraph, "for a digraph", [IsString, IsDigraph],
              {name, D} -> WriteDigraphs(name, D, DIMACSString, "w"));

InstallMethod(DIMACSString, "for a digraph", [IsDigraph],
function(D)
  local n, verts, nbs, nr_loops, m, labels, i, j, out;

  if not IsSymmetricDigraph(D) then
    ErrorNoReturn("the argument <D> must be a symmetric digraph,");
  fi;

  n := DigraphNrVertices(D);
  verts := DigraphVertices(D);
  nbs := OutNeighbours(D);

  nr_loops := 0;
  if not HasDigraphHasLoops(D) or DigraphHasLoops(D) then
    for i in verts do
      for j in nbs[i] do
        if i = j then
          nr_loops := nr_loops + 1;
        fi;
      od;
    od;
    if IsImmutableDigraph(D) then
      SetDigraphHasLoops(D, nr_loops <> 0);
    fi;
  fi;
  m := ((DigraphNrEdges(D) - nr_loops) / 2) + nr_loops;

  # Problem definition
  out := Concatenation("p edge ", String(n), " ", String(m));

  # Edges
  for i in verts do
    for j in nbs[i] do
      if i <= j then
        out := Concatenation(out, "\ne ", String(i), " ", String(j));
      fi;
      # In the case that j < i, the edge will be written elsewhere in the file
    od;
  od;

  # Vertex labels
  if n > 0 then
    labels := DigraphVertexLabels(D);
    if not (IsHomogeneousList(labels) and IsInt(labels[1])) then
      Info(InfoDigraphs, 1,
           "Only integer vertex labels are supported by the DIMACS format.");
      Info(InfoDigraphs, 1,
           "The vertex labels of the argument <D> will not be",
           " saved.");
    else
      for i in verts do
        out := Concatenation(out, "\nn ", String(i), " ", String(labels[i]));
      od;
    fi;
  fi;

  return out;
end);

InstallGlobalFunction(DreadnautString,
function(args...)
  local n, verts, nbs, i, degs, filteredVerts, partition, partitionString,
        out, positions, joinedPositions, adj, D, dflabels;

  if Length(args) = 1 then
    D := args[1];
  elif Length(args) = 2 then
    D := args[1];
    partition := args[2];
  else
    ErrorNoReturn("there must be 1 or 2 arguments");
  fi;

  if not IsDigraph(D) then
    ErrorNoReturn("the first argument <D> must be a digraph");
  elif IsBound(partition) then
    if not IsList(partition) then
      ErrorNoReturn("the second argument <partition> must be a list");
    elif Length(partition) <> DigraphNrVertices(D) then
      ErrorNoReturn("the second argument <partition> must be a list of ",
                    "length equal to the number of vertices in <D>");
    fi;
  fi;

  if IsMultiDigraph(D) then
    Info(InfoWarning, 1,
         "Multidigraphs are not supported in this file format.",
         " Multiple edges will be reduced to a single edge.");
    D := DigraphRemoveAllMultipleEdges(D);
  fi;

  n := DigraphNrVertices(D);
  verts := DigraphVertices(D);
  nbs := OutNeighbours(D);
  degs := OutDegrees(D);

  if n = 0 then
    ErrorNoReturn("the argument <D> must be a digraph with at",
                  " least one vertex");
  fi;

  out := Concatenation("n=", String(n));
  out := Concatenation(out, " $=1 d g");

  filteredVerts := Filtered(verts, x -> degs[x] > 0);
  for i in filteredVerts do
    adj := List(nbs[i], String);
    if i <> n then
      out := Concatenation(out, "\n", String(i), " : ",
                           JoinStringsWithSeparator(adj, " "), ";");
    else
      out := Concatenation(out, "\n", String(i), " : ",
                           JoinStringsWithSeparator(adj, " "));
    fi;
  od;
  out := Concatenation(out, ".");
  if IsBound(partition) then
    dflabels := DuplicateFreeList(partition);
    partitionString := "\nf = [";

    for i in dflabels do
      positions := PositionsProperty(partition, x -> x = i);
      joinedPositions := JoinStringsWithSeparator(positions, " ");
      partitionString := Concatenation(partitionString, joinedPositions);
      if i <> dflabels[Length(dflabels)] then
        partitionString := Concatenation(partitionString, " | ");
      fi;
    od;
    out := Concatenation(out, partitionString, "]");
  fi;

  return out;
end);

InstallGlobalFunction(DigraphPlainTextLineEncoder,
{delimiter1, delimiter2, offset} ->
function(D)
  local str, i, edges;
  edges := DigraphEdges(D);

  if Length(edges) = 0 then
    return "";
  fi;

  str := Concatenation(String(edges[1][1] + offset), delimiter2,
                       String(edges[1][2] + offset));

  for i in [2 .. Length(edges)] do
    Append(str, Concatenation(delimiter1, String(edges[i][1] + offset),
                              delimiter2, String(edges[i][2] + offset)));
  od;
  return str;
end);

InstallGlobalFunction(WritePlainTextDigraph,
function(name, D, delimiter, offset)
  local file, edge;

  if not IsString(name) then
    ErrorNoReturn("the 1st argument <name> must be a string,");
  elif not IsString(delimiter) then
    ErrorNoReturn("the 3rd argument <delimiter> must be a string,");
  elif not IsInt(offset) then
    ErrorNoReturn("the 4th argument <offset> must be an integer,");
  fi;
  file := IO_CompressedFile(UserHomeExpand(name), "w");

  if file = fail then
    ErrorNoReturn("cannot open the file given as the 1st argument <name>,");
  fi;

  for edge in DigraphEdges(D) do
    IO_WriteLine(file, Concatenation(String(edge[1] + offset),
                                     delimiter,
                                     String(edge[2] + offset)));
  od;
  IO_Close(file);
end);

InstallMethod(Graph6String, "for a digraph by out-neighbours",
[IsDigraphByOutNeighboursRep],
function(D)
  local list, adj, n, lenlist, tablen, blist, i, j, pos, block;
  if (IsMultiDigraph(D) or not IsSymmetricDigraph(D)
      or DigraphHasLoops(D)) then
    ErrorNoReturn("the argument <D> must be a symmetric digraph ",
                  "with no loops or multiple edges,");
  fi;

  list := [];
  adj := OutNeighbours(D);
  n := Length(DigraphVertices(D));

  # First write the number of vertices
  lenlist := DIGRAPHS_Graph6Length(n);
  if lenlist = fail then
    ErrorNoReturn("the argument <D> must be a digraph with between 0 and ",
                  "68719476736 vertices,");
  fi;
  Append(list, lenlist);

  # Find adjacencies (non-directed)
  tablen := n * (n - 1) / 2;
  blist := BlistList([1 .. tablen + 6], []);
  for i in DigraphVertices(D) do
    for j in adj[i] do
      # Loops not allowed
      if j > i then
        blist[i + (j - 2) * (j - 1) / 2] := true;
      elif i > j then
        blist[j + (i - 2) * (i - 1) / 2] := true;
      fi;
    od;
  od;

  # Read these into list, 6 bits at a time
  pos := 0;
  while pos < tablen do
    block := 0;
    for i in [1 .. 6] do
      if blist[pos + i] then
        block := block + 2 ^ (6 - i);
      fi;
    od;
    Add(list, block);
    pos := pos + 6;
  od;

  # Create string to return
  return List(list, i -> CharInt(i + 63));
end);

InstallMethod(Digraph6String, "for a digraph by out-neighbours",
[IsDigraphByOutNeighboursRep],
function(D)
  local list, adj, n, lenlist, tablen, blist, i, j, pos, block;
  # NOTE: this package originally used a version of digraph6 that reads down
  # the columns of an adjacency matrix, and appends a '+' to the start.  This
  # has been replaced by the Nauty standard, which reads across the rows of the
  # matrix, and appends a '&' to the start.  The old '+' format can be read by
  # DigraphFromDigraph6String, but can no longer be written by this function.

  list := [];
  adj := OutNeighbours(D);
  n := Length(DigraphVertices(D));

  # First write the special character '&'
  Add(list, -25);

  # Now write the number of vertices
  lenlist := DIGRAPHS_Graph6Length(n);
  if lenlist = fail then
    ErrorNoReturn("the argument <D> must be a digraph with between 0 and ",
                  "68719476736 vertices,");
  fi;
  Append(list, lenlist);

  # Find adjacencies
  tablen := n ^ 2;
  blist := BlistList([1 .. tablen + 6], []);
  for i in DigraphVertices(D) do
    for j in adj[i] do
      blist[j + n * (i - 1)] := true;
    od;
  od;

  # Read these into list, 6 bits at a time
  pos := 0;
  while pos < tablen do
    block := 0;
    for i in [1 .. 6] do
      if blist[pos + i] then
        block := block + 2 ^ (6 - i);
      fi;
    od;
    Add(list, block);
    pos := pos + 6;
  od;

  # Create string to return
  return List(list, i -> CharInt(i + 63));
end);

BindGlobal("DIGRAPHS_AddBinary",
function(blist, k, nextbit, i)
  local b;
  for b in [1 .. k] do
--> --------------------

--> maximum size reached

--> --------------------

[ Dauer der Verarbeitung: 0.81 Sekunden  (vorverarbeitet)  ]