graph/path: use NaN internally for negative cycle weights

This commit is contained in:
Dan Kortschak
2019-02-08 08:07:52 +10:30
parent ee7dd3742a
commit 97dc6c81b4
2 changed files with 42 additions and 9 deletions

View File

@@ -65,7 +65,10 @@ func FloydWarshall(g graph.Graph) (paths AllShortest, ok bool) {
if !ok { if !ok {
// If we have a negative cycle, mark all // If we have a negative cycle, mark all
// the edges in the cycles with -Inf weight. // the edges in the cycles with NaN(0xdefaced)
// weight. These weights are internal, being
// returned as -Inf in user calls.
d := paths.dist d := paths.dist
for i := range nodes { for i := range nodes {
for j := range nodes { for j := range nodes {
@@ -74,8 +77,10 @@ func FloydWarshall(g graph.Graph) (paths AllShortest, ok bool) {
continue continue
} }
if d.At(k, k) < 0 { if d.At(k, k) < 0 {
d.Set(k, k, math.Inf(-1)) d.Set(k, k, defaced)
d.Set(i, j, math.Inf(-1)) d.Set(i, j, defaced)
} else if math.Float64bits(d.At(k, k)) == defacedBits {
d.Set(i, j, defaced)
} }
} }
} }

View File

@@ -9,6 +9,7 @@ import (
"golang.org/x/exp/rand" "golang.org/x/exp/rand"
"gonum.org/v1/gonum/floats"
"gonum.org/v1/gonum/graph" "gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/internal/ordered" "gonum.org/v1/gonum/graph/internal/ordered"
"gonum.org/v1/gonum/graph/internal/set" "gonum.org/v1/gonum/graph/internal/set"
@@ -175,6 +176,19 @@ type AllShortest struct {
// //
// dist contains the pairwise // dist contains the pairwise
// distances between nodes. // 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 dist *mat.Dense
// next contains the shortest-path // next contains the shortest-path
// tree of the graph. The first index // tree of the graph. The first index
@@ -196,6 +210,16 @@ type AllShortest struct {
forward bool 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 = floats.NaNWith(0xdefaced)
// defacedBits is the bit pattern we look for in AllShortest to
// identify the edges.
defacedBits = math.Float64bits(defaced)
)
func newAllShortest(nodes []graph.Node, forward bool) AllShortest { func newAllShortest(nodes []graph.Node, forward bool) AllShortest {
if len(nodes) == 0 { if len(nodes) == 0 {
return AllShortest{} return AllShortest{}
@@ -246,7 +270,11 @@ func (p AllShortest) Weight(uid, vid int64) float64 {
if !fromOK || !toOK { if !fromOK || !toOK {
return math.Inf(1) return math.Inf(1)
} }
return p.dist.At(from, to) 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 // Between returns a shortest path from u to v and the weight of the path. If more than
@@ -265,8 +293,8 @@ func (p AllShortest) Between(uid, vid int64) (path []graph.Node, weight float64,
} }
weight = p.dist.At(from, to) weight = p.dist.At(from, to)
if math.IsInf(weight, -1) { if math.Float64bits(weight) == defacedBits {
return nil, weight, false return nil, math.Inf(-1), false
} }
seen := make([]int, len(p.nodes)) seen := make([]int, len(p.nodes))
@@ -326,8 +354,8 @@ func (p AllShortest) AllBetween(uid, vid int64) (paths [][]graph.Node, weight fl
} }
weight = p.dist.At(from, to) weight = p.dist.At(from, to)
if math.IsInf(weight, -1) { if math.Float64bits(weight) == defacedBits {
return nil, weight return nil, math.Inf(-1)
} }
var n graph.Node var n graph.Node
@@ -339,7 +367,7 @@ func (p AllShortest) AllBetween(uid, vid int64) (paths [][]graph.Node, weight fl
seen := make([]bool, len(p.nodes)) seen := make([]bool, len(p.nodes))
paths = p.allBetween(from, to, seen, []graph.Node{n}, nil) paths = p.allBetween(from, to, seen, []graph.Node{n}, nil)
return paths, p.dist.At(from, to) return paths, weight
} }
func (p AllShortest) allBetween(from, to int, seen []bool, path []graph.Node, paths [][]graph.Node) [][]graph.Node { func (p AllShortest) allBetween(from, to int, seen []bool, path []graph.Node, paths [][]graph.Node) [][]graph.Node {