mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 15:16:59 +08:00

│ old.bench │ new.bench │ │ sec/op │ sec/op vs base │ ShortestAlts/AllTo_100×2|0.5(17)-8 12.380µ ± 9% 8.577µ ± 6% -30.72% (p=0.000 n=10) ShortestAlts/AllTo_1000×2|0.5(51)-8 74.87µ ± 17% 47.66µ ± 1% -36.35% (p=0.000 n=10) ShortestAlts/AllTo_100×4|0.25(53)-8 25.50µ ± 3% 15.35µ ± 3% -39.82% (p=0.000 n=10) ShortestAlts/AllTo_1000×4|0.25(574)-8 556.5µ ± 5% 279.2µ ± 4% -49.83% (p=0.000 n=10) ShortestAlts/AllTo_100×16|0.1(76)-8 35.79µ ± 9% 21.71µ ± 3% -39.35% (p=0.000 n=10) ShortestAlts/AllTo_1000×16|0.1(822)-8 851.8µ ± 6% 400.5µ ± 6% -52.98% (p=0.000 n=10) AllShortest/AllBetween_100×2|0.5(17)-8 12.482µ ± 6% 8.573µ ± 3% -31.32% (p=0.000 n=10) AllShortest/AllBetween_1000×2|0.5(51)-8 80.74µ ± 2% 52.90µ ± 4% -34.48% (p=0.000 n=10) AllShortest/AllBetween_100×4|0.25(53)-8 22.54µ ± 4% 16.17µ ± 4% -28.26% (p=0.000 n=10) AllShortest/AllBetween_1000×4|0.25(574)-8 543.8µ ± 3% 326.7µ ± 5% -39.93% (p=0.000 n=10) AllShortest/AllBetween_100×16|0.1(76)-8 31.21µ ± 1% 22.45µ ± 3% -28.07% (p=0.000 n=10) AllShortest/AllBetween_1000×16|0.1(822)-8 678.4µ ± 1% 402.5µ ± 3% -40.67% (p=0.000 n=10) ShortestAlts/AllToFunc_100×2|0.5(17)-8 5.892µ ± 2% ShortestAlts/AllToFunc_1000×2|0.5(51)-8 40.81µ ± 2% ShortestAlts/AllToFunc_100×4|0.25(53)-8 9.178µ ± 2% ShortestAlts/AllToFunc_1000×4|0.25(574)-8 214.6µ ± 2% ShortestAlts/AllToFunc_100×16|0.1(76)-8 12.62µ ± 3% ShortestAlts/AllToFunc_1000×16|0.1(822)-8 310.7µ ± 3% AllShortest/AllBetweenFunc_100×2|0.5(17)-8 5.713µ ± 1% AllShortest/AllBetweenFunc_1000×2|0.5(51)-8 45.68µ ± 3% AllShortest/AllBetweenFunc_100×4|0.25(53)-8 9.775µ ± 1% AllShortest/AllBetweenFunc_1000×4|0.25(574)-8 245.2µ ± 5% AllShortest/AllBetweenFunc_100×16|0.1(76)-8 13.76µ ± 1% AllShortest/AllBetweenFunc_1000×16|0.1(822)-8 314.7µ ± 1% geomean 82.87µ 43.03µ -38.14% │ old.bench │ new.bench │ │ B/op │ B/op vs base │ ShortestAlts/AllTo_100×2|0.5(17)-8 14.148Ki ± 0% 9.727Ki ± 0% -31.25% (p=0.000 n=10) ShortestAlts/AllTo_1000×2|0.5(51)-8 189.8Ki ± 0% 127.5Ki ± 0% -32.80% (p=0.000 n=10) ShortestAlts/AllTo_100×4|0.25(53)-8 22.32Ki ± 0% 14.99Ki ± 0% -32.83% (p=0.000 n=10) ShortestAlts/AllTo_1000×4|0.25(574)-8 1272.9Ki ± 0% 681.9Ki ± 0% -46.42% (p=0.000 n=10) ShortestAlts/AllTo_100×16|0.1(76)-8 33.23Ki ± 0% 22.66Ki ± 0% -31.79% (p=0.000 n=10) ShortestAlts/AllTo_1000×16|0.1(822)-8 1799.9Ki ± 0% 953.2Ki ± 0% -47.04% (p=0.000 n=10) AllShortest/AllBetween_100×2|0.5(17)-8 13.039Ki ± 0% 8.617Ki ± 0% -33.91% (p=0.000 n=10) AllShortest/AllBetween_1000×2|0.5(51)-8 181.5Ki ± 0% 119.3Ki ± 0% -34.29% (p=0.000 n=10) AllShortest/AllBetween_100×4|0.25(53)-8 21.34Ki ± 0% 14.01Ki ± 0% -34.35% (p=0.000 n=10) AllShortest/AllBetween_1000×4|0.25(574)-8 1264.8Ki ± 0% 673.8Ki ± 0% -46.72% (p=0.000 n=10) AllShortest/AllBetween_100×16|0.1(76)-8 32.24Ki ± 0% 21.68Ki ± 0% -32.76% (p=0.000 n=10) AllShortest/AllBetween_1000×16|0.1(822)-8 1791.8Ki ± 0% 945.1Ki ± 0% -47.25% (p=0.000 n=10) ShortestAlts/AllToFunc_100×2|0.5(17)-8 6.922Ki ± 0% ShortestAlts/AllToFunc_1000×2|0.5(51)-8 119.8Ki ± 0% ShortestAlts/AllToFunc_100×4|0.25(53)-8 9.531Ki ± 0% ShortestAlts/AllToFunc_1000×4|0.25(574)-8 611.1Ki ± 0% ShortestAlts/AllToFunc_100×16|0.1(76)-8 13.12Ki ± 0% ShortestAlts/AllToFunc_1000×16|0.1(822)-8 870.7Ki ± 0% AllShortest/AllBetweenFunc_100×2|0.5(17)-8 5.812Ki ± 0% AllShortest/AllBetweenFunc_1000×2|0.5(51)-8 111.5Ki ± 0% AllShortest/AllBetweenFunc_100×4|0.25(53)-8 8.547Ki ± 0% AllShortest/AllBetweenFunc_1000×4|0.25(574)-8 603.0Ki ± 0% AllShortest/AllBetweenFunc_100×16|0.1(76)-8 12.14Ki ± 0% AllShortest/AllBetweenFunc_1000×16|0.1(822)-8 862.6Ki ± 0% geomean 126.5Ki 68.27Ki -37.99% │ old.bench │ new.bench │ │ allocs/op │ allocs/op vs base │ ShortestAlts/AllTo_100×2|0.5(17)-8 141.00 ± 0% 94.00 ± 0% -33.33% (p=0.000 n=10) ShortestAlts/AllTo_1000×2|0.5(51)-8 420.0 ± 0% 269.0 ± 0% -35.95% (p=0.000 n=10) ShortestAlts/AllTo_100×4|0.25(53)-8 279.0 ± 0% 174.0 ± 0% -37.63% (p=0.000 n=10) ShortestAlts/AllTo_1000×4|0.25(574)-8 2.888k ± 0% 1.741k ± 0% -39.72% (p=0.000 n=10) ShortestAlts/AllTo_100×16|0.1(76)-8 395.0 ± 0% 244.0 ± 0% -38.23% (p=0.000 n=10) ShortestAlts/AllTo_1000×16|0.1(822)-8 4.128k ± 0% 2.485k ± 0% -39.80% (p=0.000 n=10) AllShortest/AllBetween_100×2|0.5(17)-8 136.00 ± 0% 89.00 ± 0% -34.56% (p=0.000 n=10) AllShortest/AllBetween_1000×2|0.5(51)-8 415.0 ± 0% 264.0 ± 0% -36.39% (p=0.000 n=10) AllShortest/AllBetween_100×4|0.25(53)-8 275.0 ± 0% 170.0 ± 0% -38.18% (p=0.000 n=10) AllShortest/AllBetween_1000×4|0.25(574)-8 2.884k ± 0% 1.737k ± 0% -39.77% (p=0.000 n=10) AllShortest/AllBetween_100×16|0.1(76)-8 391.0 ± 0% 240.0 ± 0% -38.62% (p=0.000 n=10) AllShortest/AllBetween_1000×16|0.1(822)-8 4.124k ± 0% 2.481k ± 0% -39.84% (p=0.000 n=10) ShortestAlts/AllToFunc_100×2|0.5(17)-8 71.00 ± 0% ShortestAlts/AllToFunc_1000×2|0.5(51)-8 211.0 ± 0% ShortestAlts/AllToFunc_100×4|0.25(53)-8 114.0 ± 0% ShortestAlts/AllToFunc_1000×4|0.25(574)-8 1.156k ± 0% ShortestAlts/AllToFunc_100×16|0.1(76)-8 160.0 ± 0% ShortestAlts/AllToFunc_1000×16|0.1(822)-8 1.652k ± 0% AllShortest/AllBetweenFunc_100×2|0.5(17)-8 66.00 ± 0% AllShortest/AllBetweenFunc_1000×2|0.5(51)-8 206.0 ± 0% AllShortest/AllBetweenFunc_100×4|0.25(53)-8 110.0 ± 0% AllShortest/AllBetweenFunc_1000×4|0.25(574)-8 1.152k ± 0% AllShortest/AllBetweenFunc_100×16|0.1(76)-8 156.0 ± 0% AllShortest/AllBetweenFunc_1000×16|0.1(822)-8 1.648k ± 0% geomean 649.3 336.5 -37.70%
802 lines
21 KiB
Go
802 lines
21 KiB
Go
// 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) }
|