diff --git a/doc/weights.xml b/doc/weights.xml
index 1f98ee26d..3a669ce68 100644
--- a/doc/weights.xml
+++ b/doc/weights.xml
@@ -122,3 +122,101 @@ 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 there is currently no implemented
+ method for this operation 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.
+
+ If digraph contains a negative-weighted cycle, then there is
+ currently no applicable method for this attribute.
+
+ 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 22f039556..c12da5479 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..071463c21 100644
--- a/gap/weights.gd
+++ b/gap/weights.gd
@@ -20,3 +20,16 @@ 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");
diff --git a/gap/weights.gi b/gap/weights.gi
index f3ef799da..859b0d3bb 100644
--- a/gap/weights.gi
+++ b/gap/weights.gi
@@ -166,3 +166,481 @@ function(digraph)
SetEdgeWeightedDigraphTotalWeight(out, total);
return out;
end);
+
+#############################################################################
+# 4. Shortest Path
+#############################################################################
+#
+# Three different "shortest path" problems are solved:
+# - All pairs: EdgeWeightedDigraphShortestPaths(digraph)
+# - Single source: EdgeWeightedDigraphShortestPaths(digraph, source)
+# - Source and dest: EdgeWeightedDigraphShortestPath (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.
+#
+# Some of the algorithms handle negative weights, but none of them handles
+# negative *cycles*. Negative cycles are checked, and if there are any the
+# inner function returns fail and we call TryNextMethod.
+#
+# Justification and benchmarks are in the experimental part of Raiyan's MSci
+# thesis, https://github.com/RaiyanC/CS5199-Proj-Code
+#
+
+InstallMethod(EdgeWeightedDigraphShortestPaths,
+"for a digraph with edge weights",
+[IsDigraph and HasEdgeWeights],
+function(digraph)
+ local maxNodes, threshold, digraphVertices, nrVertices, nrEdges, result;
+
+ digraphVertices := DigraphVertices(digraph);
+ nrVertices := Size(digraphVertices);
+ nrEdges := DigraphNrEdges(digraph);
+
+ maxNodes := nrVertices * (nrVertices - 1);
+
+ # For dense graphs we use Floyd-Warshall; for sparse graphs Johnson. The
+ # threshold for "dense", based on experiments, is n(n-1)/8 edges.
+ threshold := Int(maxNodes / 8);
+ if nrEdges <= threshold then
+ result := DIGRAPHS_Edge_Weighted_Johnson(digraph);
+ else
+ result := DIGRAPHS_Edge_Weighted_FloydWarshall(digraph);
+ fi;
+
+ # Currently we have no method that works for digraphs with negative cycles.
+ # It may be possible if we implement a further algorithm in future.
+ if result = fail then
+ Info(InfoDigraphs, 1, " contains a negative cycle, so there is ",
+ "currently no suitable method for EdgeWeightedDigraphShortestPaths");
+ TryNextMethod();
+ fi;
+
+ return result;
+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 ",
+ "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)
+ local result;
+ if not source in DigraphVertices(digraph) then
+ ErrorNoReturn("the 2nd argument must be a vertex of the ",
+ "1st argument ,");
+ fi;
+
+ if IsNegativeEdgeWeightedDigraph(digraph) then
+ result := DIGRAPHS_Edge_Weighted_Bellman_Ford(digraph, source);
+ if result = fail then
+ Info(InfoDigraphs, 1, " contains a negative cycle, so there is ",
+ "currently no suitable method for EdgeWeightedDigraphShortestPaths");
+ TryNextMethod();
+ fi;
+ return result;
+ 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 ",
+ "1st argument ,");
+ elif not dest in DigraphVertices(digraph) then
+ ErrorNoReturn("the 3rd argument must be a vertex of 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);
+ if bellman = fail then
+ # negative cycle detected: this algorithm fails
+ return fail;
+ fi;
+ 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)
+ # Note: we aren't using the C function FLOYD_WARSHALL here since it is set up
+ # for a different task: it always creates a distance matrix consisting of two
+ # values (edge and non-edge) rather than multiple values; and it only stores
+ # distances, and doesn't remember info about the actual paths. In the future
+ # we might want to refactor the C function so we can use it here.
+ 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
+ # digraph contains a negative cycle: this algorithm fails
+ return fail;
+ 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
+ # negative cycle detected: this algorithm fails
+ return fail;
+ 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);
diff --git a/tst/standard/weights.tst b/tst/standard/weights.tst
index 6acc69b38..2ce1d6eaf 100644
--- a/tst/standard/weights.tst
+++ b/tst/standard/weights.tst
@@ -133,6 +133,148 @@ 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, no method found! For debugging hints type ?Recovery from NoMethodFound
+Error, no 2nd choice method found for `EdgeWeightedDigraphShortestPaths' on 2 \
+arguments
+
+# 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 1st argument ,
+gap> EdgeWeightedDigraphShortestPath(d, 3, 1);
+Error, the 2nd argument must be a vertex of the 1st argument ,
+gap> EdgeWeightedDigraphShortestPath(d, 1, 3);
+Error, the 3rd argument must be a vertex of 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, no method found! For debugging hints type ?Recovery from NoMethodFound
+Error, no 2nd choice method found for `EdgeWeightedDigraphShortestPaths' on 1 \
+arguments
+
+# 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 1st argument ,
+gap> EdgeWeightedDigraphShortestPath(d, 1, 3);
+[ [ 1, 2, 3 ], [ 1, 1 ] ]
+
# DIGRAPHS_UnbindVariables
gap> Unbind(d);
gap> Unbind(tree);
diff --git a/tst/testinstall.tst b/tst/testinstall.tst
index 0012bcd8e..e7d891de3 100644
--- a/tst/testinstall.tst
+++ b/tst/testinstall.tst
@@ -425,6 +425,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]]);;