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]]);;