MODULEFor an edge; IMPORT Atom, RefSeq, RefList, AtomVertexTbl, AtomEdgeTbl, Edge, Vertex; IMPORT Math, EdgeSeq, VertexSeq, Rd, Sx; IMPORT Algorithm, Thread, ZeusPanel, Rsrc; IMPORT ShortestPathAlgClass, ShortestPathIE; TYPE ZeusT = ShortestPathAlgClass.T BRANDED OBJECT OVERRIDES run := Run END; PROCEDURE ShortestPath New (): Algorithm.T = BEGIN RETURN NEW(ZeusT (* , data := ZeusPanel.NewForm("input.fv") *)).init() END New; PROCEDURERun (alg: ZeusT) RAISES {Thread.Alerted} = <* FATAL Rsrc.NotFound *> VAR graphRd := Rsrc.Open("graph.sx", ZeusPanel.GetPath()); BEGIN (* LOCK VBT.mu DO N := FormsVBT.GetInteger(alg.data, "N"); K := FormsVBT.GetInteger(alg.data, "K"); END; *) LoadGraph(alg, graphRd) END Run; VAR EdgeSym := Atom.FromText("Edge"); VertexSym := Atom.FromText("Vertex"); QuerySym := Atom.FromText("Find"); PROCEDURELoadGraph (alg: Algorithm.T; rd: Rd.T) RAISES {Thread.Alerted} = VAR t := NEW(T, alg := alg).init(3); BEGIN TRY LOOP VAR sx: RefList.T := Sx.Read(rd); BEGIN IF sx = NIL THEN RAISE Sx.ReadError("Because!") ELSIF sx.head = EdgeSym THEN t.addEdge(sx.tail) ELSIF sx.head = VertexSym THEN t.addVertex(sx.tail) ELSIF sx.head = QuerySym THEN EVAL t.shortestPath(sx.tail.head, sx.tail.tail.head, NARROW(sx.tail.tail.tail.head, REF INTEGER)^); EXIT END END END EXCEPT Rd.EndOfFile => (* SKIP *) | Sx.ReadError => END END LoadGraph; REVEAL Vertex.T = BRANDED REF RECORD name: Atom.T; edges: EdgeSeq.T; numPaths: CARDINAL; paths: REF ARRAY OF PathRec; qpos: INTEGER; x, y: REAL END; TYPE PathRec = RECORD lastEdge: Edge.T; sourceIndex: CARDINAL; cost: REAL; END; REVEAL Edge.T = BRANDED REF RECORD name: Atom.T; cost: REAL; source, target: Vertex.T; END;
e
, e.name
and e.cost
are its name and cost;
and e.source
and e.target
are the vertices it connects.
For a vertex v
, v.name
is its name; v.edges
is the sequence
(in any order) of edges for which v
is the source, v.numPaths
is the number of ranks of paths that have been computed ending
in v
, and for i < v.numPaths
, the PathRec
v.paths[i].lastEdge
specifies the last edge of the ith
shortest path ending in
v
, v.paths[i].sourceIndex
specifies the rank of the maximal
proper prefix of this path, and v.paths[i].cost
specifies the
cost of this path.
The entries in v.paths[k]
for k >= v.numPaths
are also relevant
if their cost
is less than infinity: they are the candidates
that have been computed for the path of rank v.numPaths
, in
order of increasing cost. These are the so-called candidate
paths.
The value of v.qpos
is the index of v
in the priority
queue (described below), or -1
if v
is not in the
queue.
NUMBER(v.paths)
is at least equal to t.ranklimit
, where t
is the unique ShortestPath.T
whose addVertex
method created
v
.
REVEAL T = Public BRANDED OBJECT alg: Algorithm.T := NIL; ranklimit: CARDINAL; source: Vertex.T; vertices: AtomVertexTbl.T; edges: AtomEdgeTbl.T; pqueue: VertexSeq.T; pendingEdges: RefSeq.T; OVERRIDES init := Init; addVertex := AddVertex; addEdge := AddEdge; shortestPath := ShortestPath; weight := Weight END;If
t
is a ShortestPath.T
, then t.ranklimit
is its rank limit,
q.source
is the last vertex that was the source of any shortest
path query, or NIL
if no such queries have been performed,
t.vertices
and t.edges
map vertex and edge names to
vertices and edges; and pqueue
is a priority queue represented
as a Floyd heap. The vertices in the queue are those with a
non-infinite candidate cost (i.e., the priority is
v.paths[v.numPaths].cost
, if this is defined).
The sequence v.pendingEdges
contains the RefList.T
that
describes edges that have been created by addEdge
but not
yet added to the data structure. It is necessary because
edges can be added before their vertices.
PROCEDUREInit (t: T; k: CARDINAL): T = BEGIN t.ranklimit := k; t.source := NIL; t.vertices := NEW(AtomVertexTbl.Default).init(); t.edges := NEW(AtomEdgeTbl.Default).init(); t.pqueue := NEW(VertexSeq.T).init(); t.pendingEdges := NEW(RefSeq.T).init(); RETURN t END Init; PROCEDUREAddVertex (t: T; rl: RefList.T) RAISES {Thread.Alerted} = VAR v := NEW(Vertex.T, name := rl.head, edges := NEW(EdgeSeq.T).init(), numPaths := 0, x := NARROW(rl.tail.head, REF REAL)^, y := NARROW(rl.tail.tail.head, REF REAL)^, paths := NEW(REF ARRAY OF PathRec, t.ranklimit), qpos := -1); BEGIN FOR i := FIRST(v.paths^) TO LAST(v.paths^) DO v.paths[i].cost := LAST(REAL) END; EVAL t.vertices.put(rl.head, v); ShortestPathIE.NewVertex(t.alg, Atom.ToText(v.name), v.x, v.y) END AddVertex; PROCEDUREAddEdge (t: T; rl: RefList.T) = BEGIN t.pendingEdges.addhi(rl) END AddEdge; PROCEDUREProcessPending (t: T) RAISES {Thread.Alerted} = VAR i := 0; BEGIN WHILE i # t.pendingEdges.size() DO (* The first "i" pending edges refer to absent vertices and therefore must be retained. *) VAR rl: RefList.T := t.pendingEdges.get(i); name: Atom.T := rl.head; sourceName: Atom.T := rl.tail.head; targetName: Atom.T := rl.tail.tail.head; orientation: Atom.T := rl.tail.tail.tail.head; source, target: Vertex.T; cost: REAL; e: Edge.T; BEGIN IF t.vertices.get(sourceName, source) AND t.vertices.get(targetName, target) THEN cost := FLOAT(Math.hypot(FLOAT(source.x - target.x, LONGREAL), FLOAT(source.y - target.y, LONGREAL)), REAL); (* IF rl.tail.tail.tail # NIL THEN cost := NARROW(rl.tail.tail.tail.head, REF REAL)^ ELSE cost := 1.0 END; *) e := NEW(Edge.T, name := name, source := source, target := target, cost := cost); ShortestPathIE.NewEdge( t.alg, Atom.ToText(name), Atom.ToText(sourceName), Atom.ToText(targetName), Atom.ToText(orientation)); source.edges.addhi(e); t.source := NIL; IF i # t.pendingEdges.size() - 1 THEN t.pendingEdges.put(i, t.pendingEdges.remhi()) ELSE EVAL t.pendingEdges.remhi() END ELSE INC(i) END END END END ProcessPending; PROCEDUREShortestPath (t : T; sourceName, targetName: Atom.T; rank : CARDINAL): RefList.T RAISES {Thread.Alerted} = VAR source, target: Vertex.T; BEGIN ProcessPending(t); IF NOT t.vertices.get(sourceName, source) OR NOT t.vertices.get(targetName, target) THEN RETURN NIL END; ShortestPathIE.StartFind( t.alg, Atom.ToText(sourceName), Atom.ToText(targetName)); IF source # t.source OR rank >= t.ranklimit THEN t.source := source; t.ranklimit := MAX(rank + 1, t.ranklimit); Reset(t) END; WHILE target.numPaths <= rank AND t.pqueue.size() # 0 DO PromoteCandidate(t) END; IF target.numPaths > rank THEN RETURN ConsPath(t, target, rank) ELSE RETURN NIL END END ShortestPath; PROCEDUREReset (t: T) =
The valuest.source
and/ort.ranklimit
have changed: set thenumPaths
,paths
, andqpos
fields of each vertex, and thepqueue
field oft
, to satisfy the invariants listed above.
VAR iter := t.vertices.iterate(); name: Atom.T; vertex: Vertex.T; BEGIN WHILE iter.next(name, vertex) DO IF vertex.paths = NIL OR NUMBER(vertex.paths^) < t.ranklimit THEN vertex.paths := NEW(REF ARRAY OF PathRec, t.ranklimit) END; FOR i := FIRST(vertex.paths^) TO LAST(vertex.paths^) DO vertex.paths[i].cost := LAST(REAL) END; vertex.numPaths := 0; vertex.qpos := -1 END; t.source.paths[0].cost := 0.0; t.source.paths[0].lastEdge := NIL; t.source.paths[0].sourceIndex := 0; t.pqueue := NEW(VertexSeq.T).init(); t.pqueue.addhi(t.source); t.source.qpos := 0 END Reset; PROCEDUREPromoteCandidate (t: T) RAISES {Thread.Alerted} = (* Promote the lowest-cost candidate path to be an answer path. Requires "t.pqueue" not be empty. *) VAR v := DeleteMin(t); BEGIN IF v.paths[v.numPaths].lastEdge # NIL THEN ShortestPathIE.Promote( t.alg, Atom.ToText(v.paths[v.numPaths].lastEdge.name), v.paths[v.numPaths].sourceIndex, v.numPaths) END; INC(v.numPaths); IF v.numPaths < NUMBER(v.paths^) AND v.paths[v.numPaths].cost < LAST(REAL) THEN SetPriority(t, v) END; FOR i := 0 TO v.edges.size() - 1 DO ConsiderEdge(t, v.edges.get(i)) END END PromoteCandidate; PROCEDUREConsiderEdge (t: T; e: Edge.T) RAISES {Thread.Alerted} = (* Change "e.target.paths" as necessary to reflect the fact that a candidate path ending in "e.source" has just been promoted to an answer path. *) VAR source := e.source; target := e.target; sourceIndex := source.numPaths - 1; newCost := e.cost + source.paths[sourceIndex].cost; BEGIN IF target.numPaths >= t.ranklimit OR newCost >= target.paths[t.ranklimit-1].cost THEN ShortestPathIE.Consider(t.alg, Atom.ToText(e.name), sourceIndex, -1); RETURN END; VAR k := t.ranklimit-1; BEGIN WHILE k > target.numPaths AND target.paths[k-1].cost > newCost DO target.paths[k] := target.paths[k - 1]; k := k - 1 END; WITH pathRec = target.paths[k] DO pathRec.lastEdge := e; pathRec.sourceIndex := sourceIndex; pathRec.cost := newCost END; ShortestPathIE.Consider(t.alg, Atom.ToText(e.name), sourceIndex, k); IF k = target.numPaths THEN SetPriority(t, target) END END END ConsiderEdge; PROCEDUREDeleteMin (t: T): Vertex.T = (* Delete from "t.queue" and return the lowest cost vertex. Requires "t.pqueue" not be empty. *) VAR res: Vertex.T := t.pqueue.get(0); oldLast: Vertex.T := t.pqueue.remhi(); oldCost := oldLast.paths[oldLast.numPaths].cost; i := 0; n := t.pqueue.size(); PROCEDURE Cost(i: INTEGER): REAL = BEGIN IF i >= n THEN RETURN LAST(REAL) ELSE VAR v: Vertex.T := t.pqueue.get(i); BEGIN RETURN v.paths[v.numPaths].cost END END END Cost; BEGIN res.qpos := -1; IF n = 0 THEN RETURN res END; LOOP VAR lCost := Cost(2 * i + 1); rCost := Cost(2 * i + 2); BEGIN IF lCost <= rCost AND lCost < oldCost THEN VAR ch: Vertex.T := t.pqueue.get(2*i+1); BEGIN t.pqueue.put(i, ch); ch.qpos := i; i := 2 * i + 1 END ELSIF rCost <= lCost AND rCost < oldCost THEN VAR ch: Vertex.T := t.pqueue.get(2*i+2); BEGIN t.pqueue.put(i, ch); ch.qpos := i; i := 2 * i + 2 END ELSE EXIT END END END; t.pqueue.put(i, oldLast); oldLast.qpos := i; RETURN res END DeleteMin; PROCEDURESetPriority (t: T; v: Vertex.T) =
Insertv
int
's priority queue if it is not present, or adjust its priority if it is. Ifv
is already present, it is required that its new cost be at most its old cost.
BEGIN IF v.qpos < 0 THEN t.pqueue.addhi(v); v.qpos := t.pqueue.size() - 1 END; VAR i := v.qpos; parent: CARDINAL; vCost := v.paths[v.numPaths].cost; parentV: Vertex.T; BEGIN WHILE i > 0 DO parent := (i - 1) DIV 2; parentV := t.pqueue.get(parent); IF parentV.paths[parentV.numPaths].cost > vCost THEN t.pqueue.put(i, parentV); parentV.qpos := i; i := parent ELSE EXIT END END; t.pqueue.put(i, v); v.qpos := i END END SetPriority; PROCEDUREConsPath (t: T; target: Vertex.T; rank: CARDINAL): RefList.T RAISES {Thread.Alerted} = (* Return the list representing the path from "t.source" to "target" of rank "k". Requires that this path be present in "target.paths". *) VAR cur := target; res := RefList.Cons(cur.name, NIL); nextEdge := cur.paths[rank].lastEdge; prevIndex := rank; nextIndex := cur.paths[rank].sourceIndex; BEGIN WHILE nextEdge # NIL DO (* "res" is the reverse of the path from "cur" to "target", including its vertices. *) ShortestPathIE.Traceback( t.alg, Atom.ToText(nextEdge.name), nextIndex, prevIndex); res := RefList.Cons(nextEdge.name, res); cur := nextEdge.source; res := RefList.Cons(cur.name, res); nextEdge := cur.paths[nextIndex].lastEdge; prevIndex := nextIndex; nextIndex := cur.paths[nextIndex].sourceIndex END; RETURN res END ConsPath; PROCEDUREWeight (t: T; edgeName: Atom.T): REAL = VAR e: Edge.T; BEGIN IF t.edges.get(edgeName, e) THEN RETURN e.cost ELSE RETURN -1.0 END END Weight; BEGIN ZeusPanel.RegisterAlg(New, "Shortest Path", "ShortestPath"); END ShortestPath.