Shortest-path and traversal algorithms on a weighted directed graph
stored as a csr_t from pic_csr.
A graph on n vertices is a square n by n csr_t: row u holds
one entry per outgoing edge of u, the column index is the head vertex
and the value is the edge weight. Vertices are numbered 1:n. An
undirected graph is the symmetric case, stored with both directions.
### Reproducibility guarantee
This is the property that makes the module worth having. pic_heap
guarantees that entries with equal keys pop in FIFO order, so
equal-distance vertices settle in a deterministic order rather than in
whatever order a heap happens to rebalance into. On top of that,
dijkstra relaxes each vertex’s outgoing edges in ascending column
order (CSR rows are sorted), and accepts a relaxation only on a strict
improvement. Together those make the entire output a pure function of
the input: not only dist, which is forced by the mathematics, but
also prev and therefore the reconstructed path when several shortest
paths tie. The same graph gives the same tie-broken path on every
compiler and every run. test_pic_graph pins this with a graph that has
two equal-cost shortest paths and asserts which one wins.
Concretely, when two shortest paths to a vertex tie, the predecessor that is discovered first in settle order wins, and settle order among equal distances is the order in which those vertices were pushed.
### Sentinels
GRAPH_INFINITY (huge(1.0_dp)) is the distance of a vertex not
reachable from the source.GRAPH_NO_PREDECESSOR (0) is the prev entry of the source and of
every unreachable vertex; 0 is not a valid vertex index.GRAPH_UNREACHABLE (-1) is the bfs hop count of an unreachable
vertex.### Negative weights are rejected
Dijkstra and A* are simply invalid on negative edge weights. Rather than
quietly returning wrong distances, dijkstra, a_star and path_cost
report ERROR_VALIDATION when any stored weight is negative. Use a
Bellman-Ford style algorithm for graphs with negative edges.
### The priority queue and its key
heap_t is keyed by integer(int64) while distances here are
real(dp), so distances are mapped to keys by distance_key, an
order-preserving and injective encoding of non-negative doubles into
integer(int64) (see its own documentation). Because it is injective,
two vertices tie in the heap exactly when their distances are equal as
reals, which is what makes the FIFO tie-break above meaningful.
The queue is used with the lazy deletion pattern: an improved
distance is pushed as a new entry rather than decreasing an existing
key, and stale pops are skipped by testing whether the vertex has
already settled. heap_t deliberately offers no decrease_key, and
this is its intended usage.
### Usage
type(csr_t) :: g
type(error_t) :: err
real(dp), allocatable :: dist(:)
integer(default_int), allocatable :: prev(:)
call dijkstra(g, 1_default_int, dist, prev, err)
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=dp), | public, | parameter | :: | GRAPH_INFINITY | = | huge(1.0_dp) |
Distance reported for a vertex unreachable from the source |
| integer(kind=default_int), | public, | parameter | :: | GRAPH_NO_PREDECESSOR | = | 0 |
|
| integer(kind=default_int), | public, | parameter | :: | GRAPH_UNREACHABLE | = | -1 |
|
| integer(kind=int64), | private, | parameter | :: | EXPONENT_BIAS | = | int(maxexponent(1.0_dp), int64)-2_int64 |
Bias turning |
| integer(kind=default_int), | private, | parameter | :: | MANTISSA_BITS | = | digits(1.0_dp)-1 |
Explicit-mantissa bit count of |
| integer(kind=int64), | private, | parameter | :: | MANTISSA_SCALE | = | 2_int64**MANTISSA_BITS |
|
Validate the structural preconditions shared by every algorithm here.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(csr_t), | intent(in) | :: | graph |
Adjacency matrix to check |
||
| integer(kind=default_int), | intent(in) | :: | source |
Vertex index that must lie in |
||
| type(error_t), | intent(out), | optional | :: | err |
.true. when the graph and vertex are usable
Reject negative edge weights, on which Dijkstra and A* are invalid.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(csr_t), | intent(in) | :: | graph |
Adjacency matrix whose stored values are the edge weights |
||
| type(error_t), | intent(out), | optional | :: | err |
.true. when every stored weight is non-negative
Map a non-negative real(dp) onto an integer(int64) heap key,
preserving order exactly.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=dp), | intent(in) | :: | x |
Distance to encode; must be non-negative and finite |
Order-preserving encoding of x
A* search for one shortest path from source to target.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(csr_t), | intent(in) | :: | graph |
Square weighted adjacency matrix of the graph |
||
| integer(kind=default_int), | intent(in) | :: | source |
Start vertex, in |
||
| integer(kind=default_int), | intent(in) | :: | target |
Goal vertex, in |
||
| real(kind=dp), | intent(in) | :: | heuristic(:) |
Admissible lower bound on the distance from each vertex to |
||
| integer(kind=default_int), | intent(out), | allocatable | :: | path(:) |
Vertices from |
|
| type(error_t), | intent(out), | optional | :: | err |
Breadth-first search, ignoring edge weights.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(csr_t), | intent(in) | :: | graph |
Square adjacency matrix of the graph; values are ignored |
||
| integer(kind=default_int), | intent(in) | :: | source |
Source vertex, in |
||
| integer(kind=default_int), | intent(out), | allocatable | :: | level(:) |
Hop count to each vertex |
|
| integer(kind=default_int), | intent(out), | allocatable | :: | prev(:) |
Predecessor of each vertex on its shortest hop path |
|
| type(error_t), | intent(out), | optional | :: | err |
Label the weakly connected components of the graph.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(csr_t), | intent(in) | :: | graph |
Square adjacency matrix of the graph; values are ignored |
||
| integer(kind=default_int), | intent(out), | allocatable | :: | component(:) |
Component label of each vertex |
|
| integer(kind=default_int), | intent(out) | :: | n_components |
Number of weakly connected components found |
||
| type(error_t), | intent(out), | optional | :: | err |
Single-source shortest paths with non-negative edge weights.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(csr_t), | intent(in) | :: | graph |
Square weighted adjacency matrix of the graph |
||
| integer(kind=default_int), | intent(in) | :: | source |
Source vertex, in |
||
| real(kind=dp), | intent(out), | allocatable | :: | dist(:) |
Shortest distance to each vertex |
|
| integer(kind=default_int), | intent(out), | allocatable | :: | prev(:) |
Predecessor of each vertex on its shortest path |
|
| type(error_t), | intent(out), | optional | :: | err |
Total weight of walking path through graph.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(csr_t), | intent(in) | :: | graph |
Square weighted adjacency matrix of the graph |
||
| integer(kind=default_int), | intent(in) | :: | path(:) |
Vertices to walk, in order |
||
| real(kind=dp), | intent(out) | :: | cost |
Sum of the weights of the traversed edges |
||
| type(error_t), | intent(out), | optional | :: | err |
Label and enqueue every still-unlabelled neighbour of u.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(csr_t), | intent(in) | :: | adjacency |
Adjacency to read row |
||
| integer(kind=default_int), | intent(in) | :: | u |
Vertex whose neighbours are being visited |
||
| integer(kind=default_int), | intent(in) | :: | label |
Component label to assign |
||
| integer(kind=default_int), | intent(inout) | :: | component(:) |
Component labels, 0 where still unlabelled |
||
| integer(kind=default_int), | intent(inout) | :: | queue(:) |
Visit queue |
||
| integer(kind=default_int), | intent(inout) | :: | tail |
Index of the last used slot of |
Set err to ERROR_VALIDATION with message, when err is present.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(error_t), | intent(out), | optional | :: | err | ||
| character(len=*), | intent(in) | :: | message |
Human-readable description of the violation |
Walk prev from target back to source and return it forwards.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer(kind=default_int), | intent(in) | :: | prev(:) |
Predecessor array, with |
||
| integer(kind=default_int), | intent(in) | :: | source |
First vertex of the path |
||
| integer(kind=default_int), | intent(in) | :: | target |
Last vertex of the path |
||
| integer(kind=default_int), | intent(out), | allocatable | :: | path(:) |
|