diff --git a/doc/weights.xml b/doc/weights.xml
index 1f98ee26d..119bc1fbf 100644
--- a/doc/weights.xml
+++ b/doc/weights.xml
@@ -122,3 +122,98 @@ gap> EdgeWeights(T);
<#/GAPDoc>
+
+<#GAPDoc Label="EdgeWeightedDigraphShortestPaths">
+
+
+
+ A record.
+
+ If digraph is an edge-weighted digraph, this attribute returns a
+ record describing the paths of lowest total weight (the shortest
+ paths) connecting each pair of vertices. If the optional argument
+ source is specified and is a vertex of digraph, then the
+ output will only contain information on paths originating from that
+ vertex.
+
+ In the two-argument form, the value returned is a record containing three
+ components: distances, parents and edges. Each of
+ these is a list of integers with one entry for each vertex v as
+ follows:
+
+ -
+ distances[v] is the total weight of the shortest path from
+ source to v.
+
+ -
+ parents[v] is the final vertex before v on the shortest
+ path from source to v.
+
+ -
+ edges[v] is the index of the edge of lowest weight going from
+ parents[v] to v.
+
+
+ Using these three components together, you can find the shortest edge
+ weighted path to all other vertices from a starting vertex.
+
+ If no path exists from source to v, then parents[v] and
+ edges[v] will both be fail. The distance from source
+ to itself is considered to be 0, and so both parents[source] and
+ edges[source] are fail.
+ Edge weights can have negative values, but this operation will fail with an
+ error if a negative-weighted cycle exists.
+
+ In the one-argument form, the value returned is also a record containing
+ components distances, parents and edges, but each of
+ these will instead be a list of lists in which the ith entry is the
+ list that corresponds to paths starting at i.
+
+ For a simple way of finding the shortest path between two specific vertices,
+ see . See also the non-weighted
+ operation .
+
+ D := EdgeWeightedDigraph([[2, 3], [4], [4], []],
+> [[5, 1], [6], [11], []]);
+
+gap> EdgeWeightedDigraphShortestPaths(D, 1);
+rec( distances := [ 0, 5, 1, 11 ], edges := [ fail, 1, 2, 1 ],
+ parents := [ fail, 1, 1, 2 ] )
+gap> D := EdgeWeightedDigraph([[2], [3], [1]], [[1], [2], [3]]);
+
+gap> EdgeWeightedDigraphShortestPaths(D);
+rec( distances := [ [ 0, 1, 3 ], [ 5, 0, 2 ], [ 3, 4, 0 ] ],
+ edges := [ [ fail, 1, 1 ], [ 1, fail, 1 ], [ 1, 1, fail ] ],
+ parents := [ [ fail, 1, 1 ], [ 2, fail, 2 ], [ 3, 3, fail ] ] )]]>
+
+
+<#/GAPDoc>
+
+<#GAPDoc Label="EdgeWeightedDigraphShortestPath">
+
+
+ A pair of lists, or fail.
+
+ If digraph is an edge-weighted digraph with vertices source
+ and dest, this operation returns a directed path from source
+ to dest with the smallest possible total weight. The output is a
+ pair of lists [v, a] of the form described in .
+
+ If source = dest or no path exists, then fail is
+ returned.
+
+ See .
+ See also the non-weighted operation .
+ D := EdgeWeightedDigraph([[2, 3], [4], [4], []],
+> [[5, 1], [6], [11], []]);
+
+gap> EdgeWeightedDigraphShortestPath(D, 1, 4);
+[ [ 1, 2, 4 ], [ 1, 1 ] ]
+gap> EdgeWeightedDigraphShortestPath(D, 3, 2);
+fail]]>
+
+
+<#/GAPDoc>
diff --git a/doc/z-chap5.xml b/doc/z-chap5.xml
index 0baeaf83c..3f5c276a4 100644
--- a/doc/z-chap5.xml
+++ b/doc/z-chap5.xml
@@ -29,6 +29,8 @@
<#Include Label="EdgeWeightedDigraph">
<#Include Label="EdgeWeightedDigraphTotalWeight">
<#Include Label="EdgeWeightedDigraphMinimumSpanningTree">
+ <#Include Label="EdgeWeightedDigraphShortestPaths">
+ <#Include Label="EdgeWeightedDigraphShortestPath">
Orders
diff --git a/gap/weights.gd b/gap/weights.gd
index b15f5dd71..6c50101ad 100644
--- a/gap/weights.gd
+++ b/gap/weights.gd
@@ -20,3 +20,17 @@ DeclareOperation("EdgeWeightsMutableCopy", [IsDigraph and HasEdgeWeights]);
# 3. Minimum Spanning Trees
DeclareAttribute("EdgeWeightedDigraphMinimumSpanningTree",
IsDigraph and HasEdgeWeights);
+
+# 4. Shortest Path
+DeclareAttribute("EdgeWeightedDigraphShortestPaths",
+ IsDigraph and HasEdgeWeights);
+DeclareOperation("EdgeWeightedDigraphShortestPaths",
+ [IsDigraph and HasEdgeWeights, IsPosInt]);
+DeclareOperation("EdgeWeightedDigraphShortestPath",
+ [IsDigraph and HasEdgeWeights, IsPosInt, IsPosInt]);
+
+DeclareGlobalFunction("DIGRAPHS_Edge_Weighted_Johnson");
+DeclareGlobalFunction("DIGRAPHS_Edge_Weighted_FloydWarshall");
+DeclareGlobalFunction("DIGRAPHS_Edge_Weighted_Bellman_Ford");
+DeclareGlobalFunction("DIGRAPHS_Edge_Weighted_Dijkstra");
+DeclareGlobalFunction("DIGRAPHS_ShortestPathsIterator");
diff --git a/gap/weights.gi b/gap/weights.gi
index f3ef799da..e5467a363 100644
--- a/gap/weights.gi
+++ b/gap/weights.gi
@@ -166,3 +166,580 @@ function(digraph)
SetEdgeWeightedDigraphTotalWeight(out, total);
return out;
end);
+
+#############################################################################
+# 4. Shortest Path
+#############################################################################
+#
+# Three different "shortest path" problems are solved:
+# - All pairs: DigraphShortestPaths(digraph)
+# - Single source: DigraphShortestPaths(digraph, source)
+# - Source and destination: DigraphShortestPath (digraph, source, dest)
+#
+# The "all pairs" problem has two algorithms:
+# - Johnson: better for sparse digraphs
+# - Floyd-Warshall: better for dense graphs
+#
+# The "single source" problem has three algorithms:
+# - If "all pairs" is already known, extract information for the given source
+# - Dijkstra: faster, but cannot handle negative weights
+# - Bellman-Ford: slower, but handles negative weights
+#
+# The "source and destination" problem calls the "single source" problem and
+# extracts information for the given destination.
+#
+# Justification and benchmarks are in Raiyan's MSci thesis, Chapter 6.
+#
+
+InstallMethod(EdgeWeightedDigraphShortestPaths,
+"for a digraph with edge weights",
+[IsDigraph and HasEdgeWeights],
+function(digraph)
+ local maxNodes, threshold, digraphVertices, nrVertices, nrEdges;
+
+ digraphVertices := DigraphVertices(digraph);
+ nrVertices := Size(digraphVertices);
+ nrEdges := DigraphNrEdges(digraph);
+
+ maxNodes := nrVertices * (nrVertices - 1);
+
+ # the boundary for performance is edge weight 0.125
+ # so if nr edges for vertices v is less
+ # than total number of edges in a connected
+ # graph we use johnson's algorithm
+ # which performs better on sparse graphs, otherwise
+ # we use floyd warshall algorithm.
+ # This information is gathered from benchmarking tests.
+ threshold := Int(maxNodes / 8);
+ if nrEdges <= threshold then
+ return DIGRAPHS_Edge_Weighted_Johnson(digraph);
+ else
+ return DIGRAPHS_Edge_Weighted_FloydWarshall(digraph);
+ fi;
+end);
+
+InstallMethod(EdgeWeightedDigraphShortestPaths,
+"for a digraph with edge weights and known shortest paths and a pos int",
+[IsDigraph and HasEdgeWeights and HasEdgeWeightedDigraphShortestPaths, IsPosInt],
+function(digraph, source)
+ local all_paths;
+ if not source in DigraphVertices(digraph) then
+ ErrorNoReturn("the 2nd argument must be a vertex of the ",
+ "digraph that is the 1st argument,");
+ fi;
+ # Shortest paths are known for all vertices. Extract the one we want.
+ all_paths := EdgeWeightedDigraphShortestPaths(digraph);
+ return rec(distances := all_paths.distances[source],
+ edges := all_paths.edges[source],
+ parents := all_paths.parents[source]);
+end);
+
+InstallMethod(EdgeWeightedDigraphShortestPaths,
+"for a digraph with edge weights and a pos int",
+[IsDigraph and HasEdgeWeights, IsPosInt],
+function(digraph, source)
+ if not source in DigraphVertices(digraph) then
+ ErrorNoReturn("the 2nd argument must be a vertex of the ",
+ "digraph that is the 1st argument,");
+ fi;
+
+ if IsNegativeEdgeWeightedDigraph(digraph) then
+ return DIGRAPHS_Edge_Weighted_Bellman_Ford(digraph, source);
+ else
+ return DIGRAPHS_Edge_Weighted_Dijkstra(digraph, source);
+ fi;
+end);
+
+InstallMethod(EdgeWeightedDigraphShortestPath,
+"for a digraph with edge weights and two pos ints",
+[IsDigraph and HasEdgeWeights, IsPosInt, IsPosInt],
+function(digraph, source, dest)
+ local paths, v, a, current, edge_index;
+ if not source in DigraphVertices(digraph) then
+ ErrorNoReturn("the 2nd argument must be a vertex of the ",
+ "digraph that is the 1st argument,");
+ elif not dest in DigraphVertices(digraph) then
+ ErrorNoReturn("the 3rd argument must be a vertex of the ",
+ "digraph that is the 1st argument,");
+ fi;
+
+ # No trivial paths
+ if source = dest then
+ return fail;
+ fi;
+
+ # Get shortest paths information for this source vertex
+ paths := EdgeWeightedDigraphShortestPaths(digraph, source);
+
+ # Convert to DigraphPath's [v, a] format by exploring backwards from dest
+ v := [dest];
+ a := [];
+ current := dest;
+ while current <> source do
+ edge_index := paths.edges[current];
+ current := paths.parents[current];
+ if edge_index = fail or current = fail then
+ return fail;
+ fi;
+ Add(a, edge_index);
+ Add(v, current);
+ od;
+
+ return [Reversed(v), Reversed(a)];
+end);
+
+InstallGlobalFunction(DIGRAPHS_Edge_Weighted_Johnson,
+function(digraph)
+ local vertices, nrVertices, mutableOuts, mutableWeights, new, v, bellman,
+ bellmanDistances, u, outNeighbours, idx, w, distances, parents, edges,
+ dijkstra;
+ vertices := DigraphVertices(digraph);
+ nrVertices := Size(vertices);
+ mutableOuts := OutNeighborsMutableCopy(digraph);
+ mutableWeights := EdgeWeightsMutableCopy(digraph);
+
+ # add new u that connects to all other v with weight 0
+ new := nrVertices + 1;
+ mutableOuts[new] := [];
+ mutableWeights[new] := [];
+
+ # fill new u
+ for v in [1 .. nrVertices] do
+ mutableOuts[new][v] := v;
+ mutableWeights[new][v] := 0;
+ od;
+
+ # calculate shortest paths from the new vertex (could be negative)
+ digraph := EdgeWeightedDigraph(mutableOuts, mutableWeights);
+ bellman := DIGRAPHS_Edge_Weighted_Bellman_Ford(digraph, new);
+ bellmanDistances := bellman.distances;
+
+ # new copy of neighbours and weights
+ mutableOuts := OutNeighborsMutableCopy(digraph);
+ mutableWeights := EdgeWeightsMutableCopy(digraph);
+
+ # set weight(u, v) equal to weight(u, v) + bell_dist(u) - bell_dist(v)
+ # for each edge (u, v)
+ for u in vertices do
+ outNeighbours := mutableOuts[u];
+ for idx in [1 .. Size(outNeighbours)] do
+ v := outNeighbours[idx];
+ w := mutableWeights[u][idx];
+ mutableWeights[u][idx] := w +
+ bellmanDistances[u] - bellmanDistances[v];
+ od;
+ od;
+
+ Remove(mutableOuts, new);
+ Remove(mutableWeights, new);
+
+ digraph := EdgeWeightedDigraph(mutableOuts, mutableWeights);
+ distances := EmptyPlist(nrVertices);
+ parents := EmptyPlist(nrVertices);
+ edges := EmptyPlist(nrVertices);
+
+ # run dijkstra
+ for u in vertices do
+ dijkstra := DIGRAPHS_Edge_Weighted_Dijkstra(digraph, u);
+ distances[u] := dijkstra.distances;
+ parents[u] := dijkstra.parents;
+ edges[u] := dijkstra.edges;
+ od;
+
+ # correct distances
+ for u in vertices do
+ for v in vertices do
+ if distances[u][v] = fail then
+ continue;
+ fi;
+ distances[u][v] := distances[u][v] +
+ bellmanDistances[v] - bellmanDistances[u];
+ od;
+ od;
+
+ return rec(distances := distances, parents := parents, edges := edges);
+end);
+
+InstallGlobalFunction(DIGRAPHS_Edge_Weighted_FloydWarshall,
+function(digraph)
+ local weights, adjMatrix, vertices, nrVertices, u, v, edges, outs, idx,
+ outNeighbours, w, i, k, distances, parents;
+ weights := EdgeWeights(digraph);
+ vertices := DigraphVertices(digraph);
+ nrVertices := Size(vertices);
+ outs := OutNeighbours(digraph);
+
+ # Create adjacency matrix with (minimum weight, edge index), or a hole
+ adjMatrix := EmptyPlist(nrVertices);
+ for u in vertices do
+ adjMatrix[u] := EmptyPlist(nrVertices);
+ outNeighbours := outs[u];
+ for idx in [1 .. Size(outNeighbours)] do
+ v := outNeighbours[idx]; # the out neighbour
+ w := weights[u][idx]; # the weight to the out neighbour
+ # Use minimum weight edge
+ if (not IsBound(adjMatrix[u][v])) or (w < adjMatrix[u][v][1]) then
+ adjMatrix[u][v] := [w, idx];
+ fi;
+ od;
+ od;
+
+ # Store shortest paths for single edges
+ distances := EmptyPlist(nrVertices);
+ parents := EmptyPlist(nrVertices);
+ edges := EmptyPlist(nrVertices);
+ for u in vertices do
+ distances[u] := EmptyPlist(nrVertices);
+ parents[u] := EmptyPlist(nrVertices);
+ edges[u] := EmptyPlist(nrVertices);
+
+ for v in vertices do
+ distances[u][v] := infinity;
+ parents[u][v] := fail;
+ edges[u][v] := fail;
+
+ if u = v then
+ distances[u][v] := 0;
+ # if the same node, then the node has no parents
+ parents[u][v] := fail;
+ edges[u][v] := fail;
+ elif IsBound(adjMatrix[u][v]) then
+ w := adjMatrix[u][v][1];
+ idx := adjMatrix[u][v][2];
+
+ distances[u][v] := w;
+ parents[u][v] := u;
+ edges[u][v] := idx;
+ fi;
+ od;
+ od;
+
+ # try every triple: distance from u to v via k
+ for k in vertices do
+ for u in vertices do
+ if distances[u][k] < infinity then
+ for v in vertices do
+ if distances[k][v] < infinity then
+ if distances[u][k] + distances[k][v] < distances[u][v] then
+ distances[u][v] := distances[u][k] + distances[k][v];
+ parents[u][v] := parents[u][k];
+ edges[u][v] := edges[k][v];
+ fi;
+ fi;
+ od;
+ fi;
+ od;
+ od;
+
+ # detect negative cycles
+ for i in vertices do
+ if distances[i][i] < 0 then
+ ErrorNoReturn("1st arg contains a negative-weighted cycle,");
+ fi;
+ od;
+
+ # replace infinity with fails
+ for u in vertices do
+ for v in vertices do
+ if distances[u][v] = infinity then
+ distances[u][v] := fail;
+ fi;
+ od;
+ od;
+
+ return rec(distances := distances, parents := parents, edges := edges);
+end);
+
+InstallGlobalFunction(DIGRAPHS_Edge_Weighted_Dijkstra,
+function(digraph, source)
+ local weights, vertices, nrVertices, adj, u, outNeighbours, idx, v, w,
+ distances, parents, edges, visited, queue, node, currDist, neighbour,
+ edgeInfo, distance, i;
+
+ weights := EdgeWeights(digraph);
+ vertices := DigraphVertices(digraph);
+ nrVertices := Size(vertices);
+
+ # Create an adjacency map for the shortest edges: index and weight
+ adj := HashMap();
+ for u in vertices do
+ adj[u] := HashMap();
+ outNeighbours := OutNeighbors(digraph)[u];
+ for idx in [1 .. Size(outNeighbours)] do
+ v := outNeighbours[idx]; # the out neighbour
+ w := weights[u][idx]; # the weight to the out neighbour
+
+ # an edge to v already exists
+ if v in adj[u] then
+ # check if edge weight is less than current weight,
+ # and keep track of edge idx
+ if w < adj[u][v][1] then
+ adj[u][v] := [w, idx];
+ fi;
+ else # edge doesn't exist already, so add it
+ adj[u][v] := [w, idx];
+ fi;
+ od;
+ od;
+
+ distances := ListWithIdenticalEntries(nrVertices, infinity);
+ parents := EmptyPlist(nrVertices);
+ edges := EmptyPlist(nrVertices);
+
+ distances[source] := 0;
+ parents[source] := fail;
+ edges[source] := fail;
+
+ visited := BlistList(vertices, []);
+
+ # make binary heap by priority of
+ # index 1 of each element (the cost to get to the node)
+ queue := BinaryHeap({x, y} -> x[1] > y[1]);
+ Push(queue, [0, source]); # the source vertex with cost 0
+
+ while not IsEmpty(queue) do
+ node := Pop(queue);
+
+ currDist := node[1];
+ u := node[2];
+
+ if visited[u] then
+ continue;
+ fi;
+
+ visited[u] := true;
+
+ for neighbour in KeyValueIterator(adj[u]) do
+ v := neighbour[1];
+ edgeInfo := neighbour[2];
+ w := edgeInfo[1];
+ idx := edgeInfo[2];
+
+ distance := currDist + w;
+
+ if Float(distance) < Float(distances[v]) then
+ distances[v] := distance;
+
+ parents[v] := u;
+ edges[v] := idx;
+
+ if not visited[v] then
+ Push(queue, [distance, v]);
+ fi;
+ fi;
+ od;
+ od;
+
+ # show fail if no path is possible
+ for i in vertices do
+ if distances[i] = infinity then
+ distances[i] := fail;
+ parents[i] := fail;
+ edges[i] := fail;
+ fi;
+ od;
+
+ return rec(distances := distances, parents := parents, edges := edges);
+end);
+
+InstallGlobalFunction(DIGRAPHS_Edge_Weighted_Bellman_Ford,
+function(digraph, source)
+ local edgeList, weights, vertices, nrVertices, distances, u, outNeighbours,
+ idx, v, w, edge, parents, edges, i, flag, _;
+
+ weights := EdgeWeights(digraph);
+ vertices := DigraphVertices(digraph);
+ nrVertices := Size(vertices);
+
+ edgeList := [];
+ for u in DigraphVertices(digraph) do
+ outNeighbours := OutNeighbours(digraph)[u];
+ for idx in [1 .. Size(outNeighbours)] do
+ v := outNeighbours[idx]; # the out neighbour
+ w := weights[u][idx]; # the weight to the out neighbour
+ Add(edgeList, [w, u, v, idx]);
+ od;
+ od;
+
+ distances := ListWithIdenticalEntries(nrVertices, infinity);
+ parents := EmptyPlist(nrVertices);
+ edges := EmptyPlist(nrVertices);
+
+ distances[source] := 0;
+ parents[source] := fail;
+ edges[source] := fail;
+
+ # relax all edges: update weight with smallest edges
+ flag := true;
+ for _ in vertices do
+ for edge in edgeList do
+ w := edge[1];
+ u := edge[2];
+ v := edge[3];
+ idx := edge[4];
+
+ if distances[u] <> infinity
+ and Float(distances[u]) + Float(w) < Float(distances[v]) then
+ distances[v] := distances[u] + w;
+
+ parents[v] := u;
+ edges[v] := idx;
+ flag := false;
+ fi;
+ od;
+
+ if flag then
+ break;
+ fi;
+ od;
+
+ # check for negative cycles
+ for edge in edgeList do
+ w := edge[1];
+ u := edge[2];
+ v := edge[3];
+
+ if distances[u] <> infinity
+ and Float(distances[u]) + Float(w) < Float(distances[v]) then
+ ErrorNoReturn("1st arg contains a negative-weighted cycle,");
+ fi;
+ od;
+
+ # fill lists with fail if no path is possible
+ for i in vertices do
+ if distances[i] = infinity then
+ distances[i] := fail;
+ parents[i] := fail;
+ edges[i] := fail;
+ fi;
+ od;
+
+ return rec(distances := distances, parents := parents, edges := edges);
+end);
+
+#############################################################################
+# 4. Shortest Paths Iterator
+#############################################################################
+#
+# returns an iterator that generates the (possibly empty) sequence of paths
+# between source and dest by increasing weight.
+#
+# the iterator needs to store
+# - found paths
+# - candidates
+# - reference to the digraph
+# rec( found_paths := [],
+
+DIGRAPHS_SPI := function(digraph, source, dest)
+ local iter;
+
+ iter := rec(
+ NextIterator := function(iter)
+ local shortestPath;
+
+ if IsEmpty(iter!.foundPaths) then
+ shortestPath := EdgeWeightedDigraphShortestPath(iter!.digraph, iter!.source, iter!.dest);
+ Add(iter!.foundPaths, shortestPath);
+ else
+
+ fi;
+ end,
+ IsDoneIterator := function(iter)
+ return IsEmpty(iter!.candidatePaths);
+ end,
+ ShallowCopy := function(iter)
+ # TODO
+ return iter;
+ end,
+ PrintObj := function(iter)
+ Print("");
+ end,
+
+ foundPaths := [],
+ candidatePaths := BinaryHeap(),
+ digraph := digraph,
+ source := source,
+ dest := dest
+ );
+ return IteratorByFunctions(iter);
+end;
+
+# FIXME: find out how often paths are used as objects in
+# their own right.
+DIGRAPHS_ConcatenatePaths := function(a, b)
+ local a_length;
+
+ a_length := Length(a);
+
+ if a[1][a_length] <> b[1][1] then
+ ErrorNoReturn("concatenatePaths: last vertex on `a` is not equal to first vertex of `b`");
+ fi;
+
+ if a_length = 0 then
+ return StructuralCopy(b);
+ else
+ return [ Concatenation(a[1]{[1..a_length-1]}, b[1]),
+ Concatenation(b[2], b[2]) ];
+ fi;
+end;
+
+DIGRAPHS_ModifyGraph := function(digraph, root, foundPaths)
+ mutableWeights := EdgeWeightsMutableCopy(digraph);
+ for p in foundPaths do
+ if rootPath = p[1]{[1..i]} then
+ mutableWeights[p[2][i]] := infinity;
+ fi;
+ od;
+
+ rootNodes := currentShortestPath[1]{[1..i-1]};
+
+ o := OutNeighbours(digraph);
+ for i in [1..Length(o)] do
+ for j in [1..Length(o[i])] do
+ if o[i][j] in rootNodes then
+ mutableWeights[i][j] := infinity;
+ fi;
+ od;
+ od;
+ return EdgeWeightedDigraph(digraph, mutableWeights);
+end;
+
+DIGRAPHS_NextShortestPath := function(iter)
+ local currentShortestPath, currentShortestPathLength, spurNode, rootPath,
+ rootPathNode, modifiedGraph, foundPaths, i, p, spurPath, totalPath,
+ nextPath, mutableWeights, mutableOuts, rootNodes, j, o;
+
+ currentShortestPath := Last(iter.foundPaths);
+ currentShortestPathLength := Length(currentShortestPath[1]);
+ foundPaths := iter.foundPaths;
+
+ for i in [1 .. currentShortestPathLength] do
+ spurNode := currentShortestPath[1][i];
+ rootPath := [
+ currentShortestPath[1]{[1..i]},
+ currentShortestPath[2]{[1..i-1]}
+ ];
+
+ modifiedGraph := DIGRAPHS_ModifyGraph(digraph, rootPath, iter.foundPaths);
+ spurPath := EdgeWeightedDigraphShortestPath(modifiedGraph, spurNode, dest);
+
+ if spurPath <> fail then
+ totalPath := DIGRAPHS_ConcatenatePaths(rootPath, spurPath);
+ Push(iter.candidatePaths, totalPath);
+ fi;
+ od;
+
+ if IsEmpty(iter.candidatePaths) then
+ return fail;
+ fi;
+
+ nextPath := Pop(iter.candidatePaths);
+ Add(iter.foundPaths, nextPath);
+
+ return nextPath;
+end;
+
+InstallGlobalFunction(DIGRAPHS_ShortestPathsIterator,
+function(digraph, source, dest)
+ ErrorNoReturn("Not implemented yet");
+end);
diff --git a/tst/standard/weights.tst b/tst/standard/weights.tst
index 6acc69b38..31cfb8c30 100644
--- a/tst/standard/weights.tst
+++ b/tst/standard/weights.tst
@@ -133,10 +133,156 @@ gap> d := EdgeWeightedDigraph([[2, 2, 2], [1]], [[10, 5, 15], [7]]);
gap> EdgeWeightedDigraphMinimumSpanningTree(d);
+# Shortest paths: one node
+gap> d := EdgeWeightedDigraph([[]], [[]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0 ], edges := [ fail ], parents := [ fail ] )
+
+# Shortest paths: early break when path doesn't exist
+gap> d := EdgeWeightedDigraph([[], [1]], [[], [-10]]);;
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0, fail ], edges := [ fail, fail ],
+ parents := [ fail, fail ] )
+
+# Shortest paths: one node and loop
+gap> d := EdgeWeightedDigraph([[1]], [[5]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0 ], edges := [ fail ], parents := [ fail ] )
+
+# Shortest paths: two nodes and loop on second node
+gap> d := EdgeWeightedDigraph([[2], [1, 2]], [[5], [5, 5]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0, 5 ], edges := [ fail, 1 ], parents := [ fail, 1 ] )
+
+# Shortest paths: cycle
+gap> d := EdgeWeightedDigraph([[2], [3], [1]], [[2], [3], [4]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0, 2, 5 ], edges := [ fail, 1, 1 ],
+ parents := [ fail, 1, 2 ] )
+
+# Shortest paths: parallel edges
+gap> d := EdgeWeightedDigraph([[2, 2, 2], [1]], [[10, 5, 15], [7]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0, 5 ], edges := [ fail, 2 ], parents := [ fail, 1 ] )
+
+# Shortest paths: negative edges
+gap> d := EdgeWeightedDigraph([[2], [1]], [[-2], [7]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0, -2 ], edges := [ fail, 1 ], parents := [ fail, 1 ] )
+
+# Shortest paths: parallel negative edges
+gap> d := EdgeWeightedDigraph([[2, 2, 2], [1]], [[-2, -3, -4], [7]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0, -4 ], edges := [ fail, 3 ], parents := [ fail, 1 ] )
+
+# Shortest paths: negative cycle
+gap> d := EdgeWeightedDigraph([[2, 2, 2], [1]], [[-10, 5, -15], [7]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+Error, 1st arg contains a negative-weighted cycle,
+
+# Shortest paths: source not in graph
+gap> d := EdgeWeightedDigraph([[2], [1]], [[2], [7]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 3);
+Error, the 2nd argument must be a vertex of the digraph tha\
+t is the 1st argument,
+gap> EdgeWeightedDigraphShortestPath(d, 3, 1);
+Error, the 2nd argument must be a vertex of the digraph tha\
+t is the 1st argument,
+gap> EdgeWeightedDigraphShortestPath(d, 1, 3);
+Error, the 3rd argument must be a vertex of the digraph that \
+is the 1st argument,
+
+# Shortest paths: no path exists
+gap> d := EdgeWeightedDigraph([[1], [2]], [[5], [10]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0, fail ], edges := [ fail, fail ],
+ parents := [ fail, fail ] )
+gap> EdgeWeightedDigraphShortestPath(d, 1, 2);
+fail
+
+# Shortest paths: no path exists with negative edge weight
+gap> d := EdgeWeightedDigraph([[2], [2], []], [[-5], [10], []]);
+
+gap> r := EdgeWeightedDigraphShortestPaths(d, 1);;
+gap> r.distances = [0, -5, fail];
+true
+gap> r.edges = [fail, 1, fail];
+true
+gap> r.parents = [fail, 1, fail];
+true
+
+# Shortest paths: parallel edges
+gap> d := EdgeWeightedDigraph([[2, 2, 2], []], [[3, 2, 1], []]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0, 1 ], edges := [ fail, 3 ], parents := [ fail, 1 ] )
+gap> EdgeWeightedDigraphShortestPaths(d);
+rec( distances := [ [ 0, 1 ], [ fail, 0 ] ],
+ edges := [ [ fail, 3 ], [ fail, fail ] ],
+ parents := [ [ fail, 1 ], [ fail, fail ] ] )
+gap> EdgeWeightedDigraphShortestPath(d, 1, 2);
+[ [ 1, 2 ], [ 3 ] ]
+
+# Shortest paths: negative cycle
+gap> d := EdgeWeightedDigraph([[2], [3], [1]], [[-3], [-5], [-7]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d);
+Error, 1st arg contains a negative-weighted cycle,
+
+# Shortest paths: source not in graph neg int
+gap> EdgeWeightedDigraphShortestPaths(d, -1);
+Error, no method found! For debugging hints type ?Recovery from NoMethodFound
+Error, no 1st choice method found for `EdgeWeightedDigraphShortestPaths' on 2 \
+arguments
+
+# Shortest path: same vertex
+gap> d := EdgeWeightedDigraph([[2], [3], [1]], [[-3], [-5], [-7]]);;
+gap> EdgeWeightedDigraphShortestPath(d, 2, 2);
+fail
+
+# Shortest paths: Johnson
+gap> d := EdgeWeightedDigraph([[2], [3], [], [], []], [[3], [5], [], [], []]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0, 3, 8, fail, fail ], edges := [ fail, 1, 1, fail, fail ]
+ , parents := [ fail, 1, 2, fail, fail ] )
+gap> EdgeWeightedDigraphShortestPaths(d);
+rec( distances := [ [ 0, 3, 8, fail, fail ], [ fail, 0, 5, fail, fail ],
+ [ fail, fail, 0, fail, fail ], [ fail, fail, fail, 0, fail ],
+ [ fail, fail, fail, fail, 0 ] ],
+ edges := [ [ fail, 1, 1, fail, fail ], [ fail, fail, 1, fail, fail ],
+ [ fail, fail, fail, fail, fail ], [ fail, fail, fail, fail, fail ],
+ [ fail, fail, fail, fail, fail ] ],
+ parents := [ [ fail, 1, 2, fail, fail ], [ fail, fail, 2, fail, fail ],
+ [ fail, fail, fail, fail, fail ], [ fail, fail, fail, fail, fail ],
+ [ fail, fail, fail, fail, fail ] ] )
+gap> EdgeWeightedDigraphShortestPaths(d, 6);
+Error, the 2nd argument must be a vertex of the digraph tha\
+t is the 1st argument,
+gap> EdgeWeightedDigraphShortestPath(d, 1, 3);
+[ [ 1, 2, 3 ], [ 1, 1 ] ]
+
+# K Shortest Paths
+gap> d := EdgeWeightedDigraph([[2], [3], [4], []], [[1], [1], [1], []]);
+
+gap> shortest_path := EdgeWeightedDigraphShortestPath(d, 1, 4);
+[ [ 1, 2, 3, 4 ], [ 1, 1, 1 ] ]
+gap> iter := DIGRAPHS_ShortestPathsIterator(d, 1, 4);
+
# DIGRAPHS_UnbindVariables
gap> Unbind(d);
gap> Unbind(tree);
#
gap> DIGRAPHS_StopTest();
-gap> STOP_TEST("Digraphs package: standard/weights.tst", 0);
\ No newline at end of file
+gap> STOP_TEST("Digraphs package: standard/weights.tst", 0);
diff --git a/tst/testinstall.tst b/tst/testinstall.tst
index e165c78c2..f1a2cb3bd 100644
--- a/tst/testinstall.tst
+++ b/tst/testinstall.tst
@@ -421,6 +421,16 @@ gap> EdgeWeightedDigraphTotalWeight(d);
15
gap> EdgeWeightedDigraphMinimumSpanningTree(d);
+gap> d := EdgeWeightedDigraph([[2], [1, 2]], [[5], [5, 5]]);
+
+gap> EdgeWeightedDigraphShortestPaths(d, 1);
+rec( distances := [ 0, 5 ], edges := [ fail, 1 ], parents := [ fail, 1 ] )
+gap> EdgeWeightedDigraphShortestPaths(d);
+rec( distances := [ [ 0, 5 ], [ 5, 0 ] ],
+ edges := [ [ fail, 1 ], [ 1, fail ] ],
+ parents := [ [ fail, 1 ], [ 2, fail ] ] )
+gap> EdgeWeightedDigraphShortestPath(d, 1, 2);
+[ [ 1, 2 ], [ 1 ] ]
# Issue 617: bug in DigraphRemoveEdge, wasn't removing edge labels
gap> D := DigraphByEdges(IsMutableDigraph, [[1, 2], [2, 3], [3, 4], [4, 1], [1, 1]]);;