gonum / graph

Graph packages for the Go language [DEPRECATED]

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

search: ShortestPaths.AllBetween does not cope with zero length cycles

kortschak opened this issue · comments

Currently calling AllBetween on a ShortestPaths that was created by FloydWarshall on a graph with zero-weight cycles that are on a shortest path may results in a stack exhaustion. Non-halting is formally what the documentation says should happen in this case, but it is not very useful. In addition it is not clear yet why this does not always happen (it is likely due to node ordering in FloydWarshall).

The following code demonstrates the issue:

package main

import (
    "fmt"

    "github.com/gonum/graph/concrete"
    "github.com/gonum/graph/search"
)

func main() {
    edges := []concrete.WeightedEdge{
        // Add a path from 0->5 of weight 4
        {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1},
        {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1},
        {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1},
        {concrete.Edge{concrete.Node(3), concrete.Node(5)}, 1},

        // Add a zero weight cycle.
        {concrete.Edge{concrete.Node(1), concrete.Node(6)}, -1},
        {concrete.Edge{concrete.Node(6), concrete.Node(1)}, 1},
    }

    g := concrete.NewDirectedGraph()
    for _, e := range edges {
        g.AddDirectedEdge(e, e.Cost)
    }

    pt, ok := search.FloydWarshall(g, nil)
    fmt.Printf("no negative cycles=%t\n", ok)

    fmt.Println("recover 10 paths from 0 to 5:")
    for i := 0; i < 10; i++ {
        fmt.Println(pt.Between(concrete.Node(0), concrete.Node(5)))
    }

    fmt.Println("attempt to recover all paths from 0 to 5:")
    fmt.Println(pt.AllBetween(concrete.Node(0), concrete.Node(5)))
}

e.g. output

$ go run Desktop/zero-cycle.go 
no negative cycles=true
recover 10 paths from 0 to 5:
[0 1 6 1 6 1 2 3 5] 4 false
[0 1 2 3 5] 4 false
[0 1 6 1 2 3 5] 4 false
[0 1 2 3 5] 4 false
[0 1 2 3 5] 4 false
[0 1 2 3 5] 4 false
[0 1 2 3 5] 4 false
[0 1 2 3 5] 4 false
[0 1 2 3 5] 4 false
[0 1 6 1 6 1 2 3 5] 4 false
attempt to recover all paths from 0 to 5:
fatal error: runtime: out of memory
...

There are two options:

  1. Fix the FloydWarshall implementation so that it always returns a ShortestPaths with the non-terminating paths and document that graphs with zero-length cycles have infinitely many shortest paths.
  2. Change the behaviour of AllBetween (and probably Between since it is only probabilistically likely to terminate in this case - here FloydWarshall also needs fixing so that we can return that a path is not actually unique since there are infinitely many alternatives) so that zero length cycles are not returned. This entails keeping a []bool of nodes that have been visited and not choosing those for each path being constructed. This exclusion must then be documented.

I think 2 is the better option.