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


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.86 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


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