Repository: gonum/gonum (Go)

Issue:
graph/path: YenKShortestPaths does not find K-n shortest paths
```
package main

import (
	"fmt"
	"log"
	"math"

	"gonum.org/v1/gonum/graph"
	"gonum.org/v1/gonum/graph/encoding"
	"gonum.org/v1/gonum/graph/encoding/dot"
	"gonum.org/v1/gonum/graph/path"
	"gonum.org/v1/gonum/graph/simple"
)

var edges = []edge{
	{simple.WeightedEdge{simple.Node(1), simple.Node(2), 450}},
	{simple.WeightedEdge{simple.Node(3), simple.Node(4), 450}},
	{simple.WeightedEdge{simple.Node(3), simple.Node(1), 20}},
	{simple.WeightedEdge{simple.Node(5), simple.Node(2), 450}},
	{simple.WeightedEdge{simple.Node(2), simple.Node(4), 20}},
	{simple.WeightedEdge{simple.Node(4), simple.Node(6), 610}},
	{simple.WeightedEdge{simple.Node(5), simple.Node(7), 20}},
	{simple.WeightedEdge{simple.Node(2), simple.Node(8), 610}},
	{simple.WeightedEdge{simple.Node(7), simple.Node(3), 40}},
	{simple.WeightedEdge{simple.Node(1), simple.Node(5), 40}},
}

type edge struct {
	simple.WeightedEdge
}

func (e edge) Attributes() []encoding.Attribute {
	return []encoding.Attribute{{
		Key:   "weight",
		Value: fmt.Sprint(e.W),
	}}
}

func (e edge) ReversedEdge() graph.Edge {
	e.WeightedEdge = e.WeightedEdge.ReversedEdge().(simple.WeightedEdge)
	return e
}

func main() {
	g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
	for _, e := range edges {
		g.SetWeightedEdge(e)
	}
	paths := path.YenKShortestPaths(g, 5, simple.Node(8), simple.Node(5))
	fmt.Println("total paths", len(paths))
	fmt.Println(paths)

	b, err := dot.Marshal(g, "case", "", "\t")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", b)
}
-- go.mod --
module yenk

go 1.17

require gonum.org/v1/gonum v0.9.1-0.20220225092540-809af9333526

require golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 // indirect
```
-------

There are five valid paths, but the code returns only four and one with loop. 

```
total paths 5
[[8 2 5] [8 2 1 5] [8 2 4 3 1 5] [8 2 4 2 5] [8 2 1 3 7 5]]
strict graph case {
	// Node definitions.
	1;
	2;
	3;
	4;
	5;
	6;
	7;
	8;

	// Edge definitions.
	1 -- 2 [weight=450];
	1 -- 3 [weight=20];
	1 -- 5 [weight=40];
	2 -- 4 [weight=20];
	2 -- 5 [weight=450];
	2 -- 8 [weight=610];
	3 -- 4 [weight=450];
	3 -- 7 [weight=40];
	4 -- 6 [weight=610];
	5 -- 7 [weight=20];
}
```
The path [8 2 4 2 5] contains loop. Also if we increase k>5, then the API should return only <=5 loop less paths. API should never return path with loops. 


Discussion:
The issue here is more complicated than presented in the issue above, the current release has the behaviour described, but master (with the previous fix to Yen) **does not** return any loops, but it also fails to return the five expected paths, but only the one shortest path (825). I was going to write up a complete analysis this weekend, and I will get to it.

This is the graph in question.

![yenk](https://user-images.githubusercontent.com/275221/156282418-394af7cb-a23a-4165-b063-11111eb5e301.png)
Thanks Dan. 

The correct revision where the above stated issue was seen is 
module go-yenk

go 1.17

require gonum.org/v1/gonum v0.9.3

require golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 // indirect
~    

but with revision 

module yenk

go 1.17

require gonum.org/v1/gonum v0.9.1-0.20220225092540-809af9333526

require golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3

I see only one path. 

total paths 1
[[8 2 5]]

I am not sure how you are seeing the paths as you said in your email.

>> You are still using the old buggy version of the code, here is the
program built against master, https://go.dev/play/p/EQHYNpOxEF0.
Looking at the graph, the paths from 8 to 5 should be 825, 8215,
824315, 824375 and 8243125. The current code does not find all of them,
so there is presumably another bug there. I'm not sure when I will be
able to take a look at this.




Minimal reproducer:
```
package main

import (
	"fmt"
	"math"

	"gonum.org/v1/gonum/graph/path"
	"gonum.org/v1/gonum/graph/simple"
)

var edges = []simple.WeightedEdge{
	{simple.Node(1), simple.Node(2), 4},
	{simple.Node(1), simple.Node(3), 1},
	{simple.Node(2), simple.Node(3), 4},
}

func main() {
	g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
	for _, e := range edges {
		g.SetWeightedEdge(e)
	}
	paths := path.YenKShortestPaths(g, 3, simple.Node(1), simple.Node(2))
	fmt.Println(paths)
}
```
@krishnapreddy Sorry this has taken so long to get to. I have a fix that makes your program print the following, even with k > 5. #1845
```
[[8 2 5] [8 2 1 5] [8 2 4 3 1 5] [8 2 1 3 7 5] [8 2 4 3 7 5]]
strict graph case {
	// Node definitions.
	1;
	2;
	3;
	4;
	5;
	6;
	7;
	8;

	// Edge definitions.
	1 -- 2 [weight=450];
	1 -- 3 [weight=20];
	1 -- 5 [weight=40];
	2 -- 4 [weight=20];
	2 -- 5 [weight=450];
	2 -- 8 [weight=610];
	3 -- 4 [weight=450];
	3 -- 7 [weight=40];
	4 -- 6 [weight=610];
	5 -- 7 [weight=20];
}
```

There were a number of issues. The main one was an omission of removing the nodes from the root of the current search.



Patch:
diff --git a/graph/path/yen_ksp.go b/graph/path/yen_ksp.go
index 8784133034..ddf4109680 100644
--- a/graph/path/yen_ksp.go
+++ b/graph/path/yen_ksp.go
@@ -5,6 +5,7 @@
 package path
 
 import (
+	"math"
 	"sort"
 
 	"gonum.org/v1/gonum/graph"
@@ -41,7 +42,9 @@ func YenKShortestPaths(g graph.Graph, k int, s, t graph.Node) [][]graph.Node {
 	var pot []yenShortest
 	var root []graph.Node
 	for i := int64(1); i < int64(k); i++ {
-		for n := 0; n < len(paths[i-1])-2; n++ {
+		// The spur node ranges from the first node to the next
+		// to last node in the previous k-shortest path.
+		for n := 0; n < len(paths[i-1])-1; n++ {
 			yk.reset()
 
 			spur := paths[i-1][n]
@@ -62,19 +65,24 @@ func YenKShortestPaths(g graph.Graph, k int, s, t graph.Node) [][]graph.Node {
 					yk.removeEdge(path[n].ID(), path[n+1].ID())
 				}
 			}
+			for _, u := range root[:len(root)-1] {
+				yk.removeNode(u.ID())
+			}
 
 			spath, weight := DijkstraFrom(spur, yk).To(t.ID())
+			if math.IsInf(weight, 1) {
+				continue
+			}
 			if len(root) > 1 {
 				var rootWeight float64
 				for x := 1; x < len(root); x++ {
 					w, _ := yk.weight(root[x-1].ID(), root[x].ID())
 					rootWeight += w
 				}
-				root = append(root[:len(root)-1], spath...)
-				pot = append(pot, yenShortest{root, weight + rootWeight})
-			} else {
-				pot = append(pot, yenShortest{spath, weight})
+				spath = append(root[:len(root)-1], spath...)
+				weight += rootWeight
 			}
+			pot = append(pot, yenShortest{spath, weight})
 		}
 
 		if len(pot) == 0 {
@@ -115,12 +123,19 @@ type yenKSPAdjuster struct {
 	// used for shortest path calculation.
 	weight Weighting
 
+	// visitedNodes holds the nodes that have
+	// been removed by Yen's algorithm.
+	visitedNodes map[int64]struct{}
+
 	// visitedEdges holds the edges that have
 	// been removed by Yen's algorithm.
 	visitedEdges map[[2]int64]struct{}
 }
 
 func (g yenKSPAdjuster) From(id int64) graph.Nodes {
+	if _, blocked := g.visitedNodes[id]; blocked {
+		return graph.Empty
+	}
 	nodes := graph.NodesOf(g.Graph.From(id))
 	for i := 0; i < len(nodes); {
 		if g.canWalk(id, nodes[i].ID()) {
@@ -130,22 +145,33 @@ func (g yenKSPAdjuster) From(id int64) graph.Nodes {
 		nodes[i] = nodes[len(nodes)-1]
 		nodes = nodes[:len(nodes)-1]
 	}
+	if len(nodes) == 0 {
+		return graph.Empty
+	}
 	return iterator.NewOrderedNodes(nodes)
 }
 
 func (g yenKSPAdjuster) canWalk(u, v int64) bool {
-	_, ok := g.visitedEdges[[2]int64{u, v}]
-	return !ok
+	if _, blocked := g.visitedNodes[v]; blocked {
+		return false
+	}
+	_, blocked := g.visitedEdges[[2]int64{u, v}]
+	return !blocked
+}
+
+func (g yenKSPAdjuster) removeNode(u int64) {
+	g.visitedNodes[u] = struct{}{}
 }
 
 func (g yenKSPAdjuster) removeEdge(u, v int64) {
 	g.visitedEdges[[2]int64{u, v}] = struct{}{}
-	if g.isDirected {
+	if !g.isDirected {
 		g.visitedEdges[[2]int64{v, u}] = struct{}{}
 	}
 }
 
 func (g *yenKSPAdjuster) reset() {
+	g.visitedNodes = make(map[int64]struct{})
 	g.visitedEdges = make(map[[2]int64]struct{})
 }
 


Test patch:
diff --git a/graph/path/yen_ksp_test.go b/graph/path/yen_ksp_test.go
index aba6f6058c..8750c28352 100644
--- a/graph/path/yen_ksp_test.go
+++ b/graph/path/yen_ksp_test.go
@@ -171,6 +171,21 @@ var yenShortestPathTests = []struct {
 			{0, 1, 2, 5, 6},
 		},
 	},
+	{
+		name:  "clean_root", // This is the failing case in gonum/gonum#1778.
+		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
+		edges: []simple.WeightedEdge{
+			{F: simple.Node(1), T: simple.Node(2), W: 4},
+			{F: simple.Node(1), T: simple.Node(3), W: 1},
+			{F: simple.Node(2), T: simple.Node(3), W: 4},
+		},
+		query: simple.Edge{F: simple.Node(1), T: simple.Node(2)},
+		k:     3,
+		wantPaths: [][]int64{
+			{1, 2},
+			{1, 3, 2},
+		},
+	},
 }
 
 func bipartite(n int, weight, inc float64) []simple.WeightedEdge {