blob: 4fcd239780419187d33184d07d571f31865d8002 [file] [log] [blame]
// Copyright ©2015 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package path
import (
"math"
"golang.org/x/exp/rand"
"gonum.org/v1/gonum/floats/scalar"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/internal/ordered"
"gonum.org/v1/gonum/graph/internal/set"
"gonum.org/v1/gonum/mat"
)
// Shortest is a shortest-path tree created by the BellmanFordFrom, DijkstraFrom
// or AStar single-source shortest path functions.
type Shortest struct {
// from holds the source node given to
// the function that returned the
// Shortest value.
from graph.Node
// nodes hold the nodes of the analysed
// graph.
nodes []graph.Node
// indexOf contains a mapping between
// the id-dense representation of the
// graph and the potentially id-sparse
// nodes held in nodes.
indexOf map[int64]int
// dist and next represent the shortest
// paths between nodes.
//
// Indices into dist and next are
// mapped through indexOf.
//
// dist contains the distances
// from the from node for each
// node in the graph.
dist []float64
// next contains the shortest-path
// tree of the graph. The index is a
// linear mapping of to-dense-id.
next []int
// hasNegativeCycle indicates
// whether the Shortest includes
// a negative cycle. This should
// be set by the function that
// returned the Shortest value.
hasNegativeCycle bool
// negCosts holds negative costs
// between pairs of nodes to report
// negative cycles.
// negCosts must be initialised by
// routines that can handle negative
// edge weights.
negCosts map[negEdge]float64
}
// newShortestFrom returns a shortest path tree for paths from u
// initialised with the given nodes. The nodes held by the returned
// Shortest may be lazily added.
func newShortestFrom(u graph.Node, nodes []graph.Node) Shortest {
indexOf := make(map[int64]int, len(nodes))
uid := u.ID()
for i, n := range nodes {
indexOf[n.ID()] = i
if n.ID() == uid {
u = n
}
}
p := Shortest{
from: u,
nodes: nodes,
indexOf: indexOf,
dist: make([]float64, len(nodes)),
next: make([]int, len(nodes)),
}
for i := range nodes {
p.dist[i] = math.Inf(1)
p.next[i] = -1
}
p.dist[indexOf[uid]] = 0
return p
}
// add adds a node to the Shortest, initialising its stored index and returning, and
// setting the distance and position as unconnected. add will panic if the node is
// already present.
func (p *Shortest) add(u graph.Node) int {
uid := u.ID()
if _, exists := p.indexOf[uid]; exists {
panic("shortest: adding existing node")
}
idx := len(p.nodes)
p.indexOf[uid] = idx
p.nodes = append(p.nodes, u)
p.dist = append(p.dist, math.Inf(1))
p.next = append(p.next, -1)
return idx
}
// set sets the weight of the path from the node in p.nodes indexed by mid to the node
// indexed by to.
func (p Shortest) set(to int, weight float64, mid int) {
p.dist[to] = weight
p.next[to] = mid
if weight < 0 {
e := negEdge{from: mid, to: to}
c, ok := p.negCosts[e]
if !ok {
p.negCosts[e] = weight
} else if weight < c {
// The only ways that we can have a new weight that is
// lower than the previous weight is if either the edge
// has already been traversed in a negative cycle, or
// the edge is reachable from a negative cycle.
// Either way the reported path is returned with a
// negative infinite path weight.
p.negCosts[e] = math.Inf(-1)
}
}
}
// From returns the starting node of the paths held by the Shortest.
func (p Shortest) From() graph.Node { return p.from }
// WeightTo returns the weight of the minimum path to v. If the path to v includes
// a negative cycle, the returned weight will not reflect the true path weight.
func (p Shortest) WeightTo(vid int64) float64 {
to, toOK := p.indexOf[vid]
if !toOK {
return math.Inf(1)
}
return p.dist[to]
}
// To returns a shortest path to v and the weight of the path. If the path
// to v includes a negative cycle, one pass through the cycle will be included
// in path, but any path leading into the negative cycle will be lost, and
// weight will be returned as -Inf.
func (p Shortest) To(vid int64) (path []graph.Node, weight float64) {
to, toOK := p.indexOf[vid]
if !toOK || math.IsInf(p.dist[to], 1) {
return nil, math.Inf(1)
}
from := p.indexOf[p.from.ID()]
path = []graph.Node{p.nodes[to]}
weight = math.Inf(1)
if p.hasNegativeCycle {
seen := make(set.Ints)
seen.Add(from)
for to != from {
next := p.next[to]
if math.IsInf(p.negCosts[negEdge{from: next, to: to}], -1) {
weight = math.Inf(-1)
}
if seen.Has(to) {
break
}
seen.Add(to)
path = append(path, p.nodes[next])
to = next
}
} else {
n := len(p.nodes)
for to != from {
to = p.next[to]
path = append(path, p.nodes[to])
if n < 0 {
panic("path: unexpected negative cycle")
}
n--
}
}
ordered.Reverse(path)
return path, math.Min(weight, p.dist[p.indexOf[vid]])
}
// ShortestAlts is a shortest-path tree created by the BellmanFordAllFrom or DijkstraAllFrom
// single-source shortest path functions.
type ShortestAlts struct {
// from holds the source node given to
// the function that returned the
// ShortestAlts value.
from graph.Node
// nodes hold the nodes of the analysed
// graph.
nodes []graph.Node
// indexOf contains a mapping between
// the id-dense representation of the
// graph and the potentially id-sparse
// nodes held in nodes.
indexOf map[int64]int
// dist and next represent the shortest
// paths between nodes.
//
// Indices into dist and next are
// mapped through indexOf.
//
// dist contains the distances
// from the from node for each
// node in the graph.
dist []float64
// next contains the shortest-path
// tree of the graph. The index is a
// linear mapping of to-dense-id.
next [][]int
// hasNegativeCycle indicates
// whether the ShortestAlts includes
// a negative cycle. This should
// be set by the function that
// returned the ShortestAlts value.
hasNegativeCycle bool
// negCosts holds negative costs
// between pairs of nodes to report
// negative cycles.
// negCosts must be initialised by
// routines that can handle negative
// edge weights.
negCosts map[negEdge]float64
}
// newShortestAltsFrom returns a shortest path tree for all paths from u
// initialised with the given nodes. The nodes held by the returned
// Shortest may be lazily added.
func newShortestAltsFrom(u graph.Node, nodes []graph.Node) ShortestAlts {
indexOf := make(map[int64]int, len(nodes))
uid := u.ID()
for i, n := range nodes {
indexOf[n.ID()] = i
if n.ID() == uid {
u = n
}
}
p := ShortestAlts{
from: u,
nodes: nodes,
indexOf: indexOf,
dist: make([]float64, len(nodes)),
next: make([][]int, len(nodes)),
}
for i := range nodes {
p.dist[i] = math.Inf(1)
p.next[i] = nil
}
p.dist[indexOf[uid]] = 0
return p
}
// add adds a node to the ShortestAlts, initialising its stored index and returning, and
// setting the distance and position as unconnected. add will panic if the node is
// already present.
func (p *ShortestAlts) add(u graph.Node) int {
uid := u.ID()
if _, exists := p.indexOf[uid]; exists {
panic("shortest: adding existing node")
}
idx := len(p.nodes)
p.indexOf[uid] = idx
p.nodes = append(p.nodes, u)
p.dist = append(p.dist, math.Inf(1))
p.next = append(p.next, nil)
return idx
}
// set sets the weight of the path from the node in p.nodes indexed by mid to the node
// indexed by to.
func (p ShortestAlts) set(to int, weight float64, mid int) {
p.dist[to] = weight
p.next[to] = []int{mid}
if weight < 0 {
e := negEdge{from: mid, to: to}
c, ok := p.negCosts[e]
if !ok {
p.negCosts[e] = weight
} else if weight < c {
// The only ways that we can have a new weight that is
// lower than the previous weight is if either the edge
// has already been traversed in a negative cycle, or
// the edge is reachable from a negative cycle.
// Either way the reported path is returned with a
// negative infinite path weight.
p.negCosts[e] = math.Inf(-1)
}
}
}
// addPath adds a new path from the node in p.nodes indexed by mid to the node indexed
// by to. The weight of the path is expected to be the same as already existing paths
// between these nodes, but no check is made for this.
func (p ShortestAlts) addPath(to, mid int) {
// These are likely to be rare, so just loop over collisions.
for _, v := range p.next[to] {
if mid == v {
return
}
}
p.next[to] = append(p.next[to], mid)
}
// From returns the starting node of the paths held by the ShortestAlts.
func (p ShortestAlts) From() graph.Node { return p.from }
// WeightTo returns the weight of the minimum path to v. If the path to v includes
// a negative cycle, the returned weight will not reflect the true path weight.
func (p ShortestAlts) WeightTo(vid int64) float64 {
to, toOK := p.indexOf[vid]
if !toOK {
return math.Inf(1)
}
return p.dist[to]
}
// To returns a shortest path to v and the weight of the path. If more than
// one shortest path exists between u and v, a randomly chosen path will be
// returned and unique is returned false. If a cycle with zero weight exists
// in the path, it will not be included, but unique will be returned false.
// If the path to v includes a negative cycle, one pass through the cycle will
// be included in path, but any path leading into the negative cycle will be
// lost, and weight will be returned as -Inf.
func (p ShortestAlts) To(vid int64) (path []graph.Node, weight float64, unique bool) {
to, toOK := p.indexOf[vid]
if !toOK || math.IsInf(p.dist[to], 1) {
return nil, math.Inf(1), false
}
from := p.indexOf[p.from.ID()]
unique = true
path = []graph.Node{p.nodes[to]}
if p.hasNegativeCycle {
weight = math.Inf(1)
seen := make(set.Ints)
seen.Add(from)
for to != from {
c := p.next[to]
var next int
if len(c) != 1 {
unique = false
next = c[rand.Intn(len(c))]
} else {
next = c[0]
}
if math.IsInf(p.negCosts[negEdge{from: next, to: to}], -1) {
weight = math.Inf(-1)
unique = false
}
if seen.Has(to) {
break
}
seen.Add(to)
path = append(path, p.nodes[next])
to = next
}
weight = math.Min(weight, p.dist[p.indexOf[vid]])
} else {
seen := make([]int, len(p.nodes))
for i := range seen {
seen[i] = -1
}
seen[to] = 0
var next int
for from != to {
c := p.next[to]
if len(c) != 1 {
unique = false
next = c[rand.Intn(len(c))]
} else {
next = c[0]
}
if seen[next] >= 0 {
path = path[:seen[next]]
}
seen[next] = len(path)
path = append(path, p.nodes[next])
to = next
}
weight = p.dist[p.indexOf[vid]]
}
ordered.Reverse(path)
return path, weight, unique
}
// AllTo returns all shortest paths to v and the weight of the paths. Paths
// containing zero-weight cycles are not returned. If a negative cycle exists between
// u and v, paths is returned nil and weight is returned as -Inf.
func (p ShortestAlts) AllTo(vid int64) (paths [][]graph.Node, weight float64) {
from := p.indexOf[p.from.ID()]
to, toOK := p.indexOf[vid]
if !toOK || len(p.next[to]) == 0 {
if p.from.ID() == vid {
return [][]graph.Node{{p.nodes[from]}}, 0
}
return nil, math.Inf(1)
}
_, weight, unique := p.To(vid)
if math.IsInf(weight, -1) && !unique {
return nil, math.Inf(-1)
}
seen := make([]bool, len(p.nodes))
p.allTo(from, to, seen, []graph.Node{p.nodes[to]}, func(path []graph.Node) {
paths = append(paths, append([]graph.Node(nil), path...))
})
weight = p.dist[to]
return paths, weight
}
// AllToFunc calls fn on all shortest paths to v. Paths containing zero-weight
// cycles are not considered. If a negative cycle exists between u and v, no
// path is considered. The fn closure must not retain the path parameter.
func (p ShortestAlts) AllToFunc(vid int64, fn func(path []graph.Node)) {
from := p.indexOf[p.from.ID()]
to, toOK := p.indexOf[vid]
if !toOK || len(p.next[to]) == 0 {
if p.from.ID() == vid {
fn([]graph.Node{p.nodes[from]})
}
return
}
_, weight, unique := p.To(vid)
if math.IsInf(weight, -1) && !unique {
return
}
seen := make([]bool, len(p.nodes))
p.allTo(from, to, seen, []graph.Node{p.nodes[to]}, fn)
}
// allTo recursively constructs a slice of paths extending from the node
// indexed into p.nodes by from to the node indexed by to. len(seen) must match
// the number of nodes held by the receiver. The path parameter is the current
// working path and the results passed to fn.
func (p ShortestAlts) allTo(from, to int, seen []bool, path []graph.Node, fn func(path []graph.Node)) {
seen[to] = true
if from == to {
if path == nil {
return
}
ordered.Reverse(path)
fn(path)
ordered.Reverse(path)
return
}
first := true
var seenWork []bool
for _, to := range p.next[to] {
if seen[to] {
continue
}
if first {
p := make([]graph.Node, len(path), len(path)+1)
copy(p, path)
path = p
seenWork = make([]bool, len(seen))
first = false
}
copy(seenWork, seen)
p.allTo(from, to, seenWork, append(path, p.nodes[to]), fn)
}
}
// negEdge is a key into the negative costs map used by Shortest and ShortestAlts.
type negEdge struct{ from, to int }
// AllShortest is a shortest-path tree created by the DijkstraAllPaths, FloydWarshall
// or JohnsonAllPaths all-pairs shortest paths functions.
type AllShortest struct {
// nodes hold the nodes of the analysed
// graph.
nodes []graph.Node
// indexOf contains a mapping between
// the id-dense representation of the
// graph and the potentially id-sparse
// nodes held in nodes.
indexOf map[int64]int
// dist, next and forward represent
// the shortest paths between nodes.
//
// Indices into dist and next are
// mapped through indexOf.
//
// dist contains the pairwise
// distances between nodes.
//
// Internally, edges on negative
// cycles are given a special NaN
// weight, NaN(0xdefaced).
// This is returned to the user as
// -Inf. This approach allows -Inf
// weight edges on simple paths to be
// distinguished from -Inf weight
// paths that contain negative cycles.
// The distinction is visible to the
// user through whether then path
// returned with a -Inf weight is
// nil or contains a set of nodes.
dist *mat.Dense
// next contains the shortest-path
// tree of the graph. The first index
// is a linear mapping of from-dense-id
// and to-dense-id, to-major with a
// stride equal to len(nodes); the
// slice indexed to is the list of
// intermediates leading from the 'from'
// node to the 'to' node represented
// by dense id.
// The interpretation of next is
// dependent on the state of forward.
next [][]int
// forward indicates the direction of
// path reconstruction. Forward
// reconstruction is used for Floyd-
// Warshall and reverse is used for
// Dijkstra.
forward bool
}
var (
// defaced is NaN(0xdefaced) used as a marker for -Inf weight edges
// within paths containing negative cycles. Routines marking these
// edges should use this value.
defaced = scalar.NaNWith(0xdefaced)
// defacedBits is the bit pattern we look for in AllShortest to
// identify the edges.
defacedBits = math.Float64bits(defaced)
)
// newAllShortest returns an all-pairs shortest path forest for paths with the
// given nodes. The forward flag indicates whether the path reconstruction is
// performed in the forward (Floyd-Warshall) or reverse (Dijkstra/Johnson's) order.
func newAllShortest(nodes []graph.Node, forward bool) AllShortest {
if len(nodes) == 0 {
return AllShortest{}
}
indexOf := make(map[int64]int, len(nodes))
for i, n := range nodes {
indexOf[n.ID()] = i
}
dist := make([]float64, len(nodes)*len(nodes))
for i := range dist {
dist[i] = math.Inf(1)
}
return AllShortest{
nodes: nodes,
indexOf: indexOf,
dist: mat.NewDense(len(nodes), len(nodes), dist),
next: make([][]int, len(nodes)*len(nodes)),
forward: forward,
}
}
// at returns a slice of node indexes into p.nodes for nodes that are mid points
// between nodes indexed by from and to.
func (p AllShortest) at(from, to int) (mid []int) {
return p.next[from+to*len(p.nodes)]
}
// set sets the weights of paths between node indexes into p.nodes for from and to
// passing through the nodes indexed by mid.
func (p AllShortest) set(from, to int, weight float64, mid ...int) {
p.dist.Set(from, to, weight)
p.next[from+to*len(p.nodes)] = append(p.next[from+to*len(p.nodes)][:0], mid...)
}
// add adds paths between node indexed in p.nodes by from and to passing through
// the nodes indexed by mid.
func (p AllShortest) add(from, to int, mid ...int) {
loop: // These are likely to be rare, so just loop over collisions.
for _, k := range mid {
for _, v := range p.next[from+to*len(p.nodes)] {
if k == v {
continue loop
}
}
p.next[from+to*len(p.nodes)] = append(p.next[from+to*len(p.nodes)], k)
}
}
// Weight returns the weight of the minimum path between u and v.
func (p AllShortest) Weight(uid, vid int64) float64 {
from, fromOK := p.indexOf[uid]
to, toOK := p.indexOf[vid]
if !fromOK || !toOK {
return math.Inf(1)
}
w := p.dist.At(from, to)
if math.Float64bits(w) == defacedBits {
return math.Inf(-1)
}
return w
}
// Between returns a shortest path from u to v and the weight of the path. If more than
// one shortest path exists between u and v, a randomly chosen path will be returned and
// unique is returned false. If a cycle with zero weight exists in the path, it will not
// be included, but unique will be returned false. If a negative cycle exists on the path
// from u to v, path will be returned nil, weight will be -Inf and unique will be false.
func (p AllShortest) Between(uid, vid int64) (path []graph.Node, weight float64, unique bool) {
from, fromOK := p.indexOf[uid]
to, toOK := p.indexOf[vid]
if !fromOK || !toOK || len(p.at(from, to)) == 0 {
if uid == vid {
if !fromOK {
return []graph.Node{node(uid)}, 0, true
}
return []graph.Node{p.nodes[from]}, 0, true
}
return nil, math.Inf(1), false
}
weight = p.dist.At(from, to)
if math.Float64bits(weight) == defacedBits {
return nil, math.Inf(-1), false
}
seen := make([]int, len(p.nodes))
for i := range seen {
seen[i] = -1
}
var n graph.Node
if p.forward {
n = p.nodes[from]
seen[from] = 0
} else {
n = p.nodes[to]
seen[to] = 0
}
path = []graph.Node{n}
unique = true
var next int
for from != to {
c := p.at(from, to)
if len(c) != 1 {
unique = false
next = c[rand.Intn(len(c))]
} else {
next = c[0]
}
if seen[next] >= 0 {
path = path[:seen[next]]
}
seen[next] = len(path)
path = append(path, p.nodes[next])
if p.forward {
from = next
} else {
to = next
}
}
if !p.forward {
ordered.Reverse(path)
}
return path, weight, unique
}
// AllBetween returns all shortest paths from u to v and the weight of the paths. Paths
// containing zero-weight cycles are not returned. If a negative cycle exists between
// u and v, paths is returned nil and weight is returned as -Inf.
func (p AllShortest) AllBetween(uid, vid int64) (paths [][]graph.Node, weight float64) {
from, fromOK := p.indexOf[uid]
to, toOK := p.indexOf[vid]
if !fromOK || !toOK || len(p.at(from, to)) == 0 {
if uid == vid {
if !fromOK {
return [][]graph.Node{{node(uid)}}, 0
}
return [][]graph.Node{{p.nodes[from]}}, 0
}
return nil, math.Inf(1)
}
weight = p.dist.At(from, to)
if math.Float64bits(weight) == defacedBits {
return nil, math.Inf(-1)
}
var n graph.Node
if p.forward {
n = p.nodes[from]
} else {
n = p.nodes[to]
}
seen := make([]bool, len(p.nodes))
p.allBetween(from, to, seen, []graph.Node{n}, func(path []graph.Node) {
paths = append(paths, append([]graph.Node(nil), path...))
})
return paths, weight
}
// AllBetweenFunc calls fn on all shortest paths from u to v. Paths containing
// zero-weight cycles are not considered. If a negative cycle exists between u
// and v, no path is considered. The fn closure must not retain the path
// parameter.
func (p AllShortest) AllBetweenFunc(uid, vid int64, fn func(path []graph.Node)) {
from, fromOK := p.indexOf[uid]
to, toOK := p.indexOf[vid]
if !fromOK || !toOK || len(p.at(from, to)) == 0 {
if uid == vid {
if !fromOK {
fn([]graph.Node{node(uid)})
return
}
fn([]graph.Node{p.nodes[from]})
return
}
return
}
if math.Float64bits(p.dist.At(from, to)) == defacedBits {
return
}
var n graph.Node
if p.forward {
n = p.nodes[from]
} else {
n = p.nodes[to]
}
seen := make([]bool, len(p.nodes))
p.allBetween(from, to, seen, []graph.Node{n}, fn)
}
// allBetween recursively constructs a set of paths extending from the node
// indexed into p.nodes by from to the node indexed by to. len(seen) must match
// the number of nodes held by the receiver. The path parameter is the current
// working path and the results passed to fn.
func (p AllShortest) allBetween(from, to int, seen []bool, path []graph.Node, fn func([]graph.Node)) {
if p.forward {
seen[from] = true
} else {
seen[to] = true
}
if from == to {
if path == nil {
return
}
if !p.forward {
ordered.Reverse(path)
}
fn(path)
if !p.forward {
ordered.Reverse(path)
}
return
}
first := true
var seenWork []bool
for _, n := range p.at(from, to) {
if seen[n] {
continue
}
if first {
p := make([]graph.Node, len(path), len(path)+1)
copy(p, path)
path = p
seenWork = make([]bool, len(seen))
first = false
}
if p.forward {
from = n
} else {
to = n
}
copy(seenWork, seen)
p.allBetween(from, to, seenWork, append(path, p.nodes[n]), fn)
}
}
type node int64
func (n node) ID() int64 { return int64(n) }