github.com/gopherd/gonum@v0.0.4/graph/path/yen_ksp_test.go (about) 1 // Copyright ©2018 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package path 6 7 import ( 8 "math" 9 "reflect" 10 "sort" 11 "testing" 12 13 "github.com/gopherd/gonum/graph" 14 "github.com/gopherd/gonum/graph/internal/ordered" 15 "github.com/gopherd/gonum/graph/simple" 16 ) 17 18 var yenShortestPathTests = []struct { 19 name string 20 graph func() graph.WeightedEdgeAdder 21 edges []simple.WeightedEdge 22 23 query simple.Edge 24 k int 25 wantPaths [][]int64 26 27 relaxed bool 28 }{ 29 { 30 // https://en.wikipedia.org/w/index.php?title=Yen%27s_algorithm&oldid=841018784#Example 31 name: "wikipedia example", 32 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 33 edges: []simple.WeightedEdge{ 34 {F: simple.Node('C'), T: simple.Node('D'), W: 3}, 35 {F: simple.Node('C'), T: simple.Node('E'), W: 2}, 36 {F: simple.Node('E'), T: simple.Node('D'), W: 1}, 37 {F: simple.Node('D'), T: simple.Node('F'), W: 4}, 38 {F: simple.Node('E'), T: simple.Node('F'), W: 2}, 39 {F: simple.Node('E'), T: simple.Node('G'), W: 3}, 40 {F: simple.Node('F'), T: simple.Node('G'), W: 2}, 41 {F: simple.Node('F'), T: simple.Node('H'), W: 1}, 42 {F: simple.Node('G'), T: simple.Node('H'), W: 2}, 43 }, 44 query: simple.Edge{F: simple.Node('C'), T: simple.Node('H')}, 45 k: 3, 46 wantPaths: [][]int64{ 47 {'C', 'E', 'F', 'H'}, 48 {'C', 'E', 'G', 'H'}, 49 {'C', 'D', 'F', 'H'}, 50 }, 51 }, 52 { 53 name: "1 edge graph", 54 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 55 edges: []simple.WeightedEdge{ 56 {F: simple.Node(0), T: simple.Node(1), W: 3}, 57 }, 58 query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, 59 k: 10, 60 wantPaths: [][]int64{ 61 {0, 1}, 62 }, 63 }, 64 { 65 name: "empty graph", 66 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 67 edges: []simple.WeightedEdge{}, 68 query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, 69 k: 1, 70 wantPaths: nil, 71 }, 72 { 73 name: "n-star graph", 74 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 75 edges: []simple.WeightedEdge{ 76 {F: simple.Node(0), T: simple.Node(1), W: 3}, 77 {F: simple.Node(0), T: simple.Node(2), W: 3}, 78 {F: simple.Node(0), T: simple.Node(3), W: 3}, 79 }, 80 query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, 81 k: 1, 82 wantPaths: [][]int64{ 83 {0, 1}, 84 }, 85 }, 86 { 87 name: "bipartite small", 88 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 89 edges: bipartite(5, 3, 0), 90 query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)}, 91 k: 10, 92 wantPaths: [][]int64{ 93 {-1, 2, 1}, 94 {-1, 3, 1}, 95 {-1, 4, 1}, 96 {-1, 5, 1}, 97 {-1, 6, 1}, 98 }, 99 }, 100 { 101 name: "bipartite parity", 102 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 103 edges: bipartite(5, 3, 0), 104 query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)}, 105 k: 5, 106 wantPaths: [][]int64{ 107 {-1, 2, 1}, 108 {-1, 3, 1}, 109 {-1, 4, 1}, 110 {-1, 5, 1}, 111 {-1, 6, 1}, 112 }, 113 }, 114 { 115 name: "bipartite large", 116 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 117 edges: bipartite(10, 3, 0), 118 query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)}, 119 k: 5, 120 relaxed: true, 121 }, 122 { 123 name: "bipartite inc", 124 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 125 edges: bipartite(5, 10, 1), 126 query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)}, 127 k: 5, 128 wantPaths: [][]int64{ 129 {-1, 2, 1}, 130 {-1, 3, 1}, 131 {-1, 4, 1}, 132 {-1, 5, 1}, 133 {-1, 6, 1}, 134 }, 135 }, 136 { 137 name: "bipartite dec", 138 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 139 edges: bipartite(5, 10, -1), 140 query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)}, 141 k: 5, 142 wantPaths: [][]int64{ 143 {-1, 6, 1}, 144 {-1, 5, 1}, 145 {-1, 4, 1}, 146 {-1, 3, 1}, 147 {-1, 2, 1}, 148 }, 149 }, 150 { 151 name: "waterfall", // This is the failing case in gonum/gonum#1700. 152 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 153 edges: []simple.WeightedEdge{ 154 {F: simple.Node(0), T: simple.Node(1), W: 1}, 155 {F: simple.Node(1), T: simple.Node(2), W: 1}, 156 {F: simple.Node(1), T: simple.Node(3), W: 1}, 157 {F: simple.Node(1), T: simple.Node(5), W: 1}, 158 {F: simple.Node(2), T: simple.Node(4), W: 1}, 159 {F: simple.Node(2), T: simple.Node(5), W: 1}, 160 {F: simple.Node(3), T: simple.Node(6), W: 1}, 161 {F: simple.Node(4), T: simple.Node(6), W: 1}, 162 {F: simple.Node(5), T: simple.Node(6), W: 1}, 163 {F: simple.Node(5), T: simple.Node(6), W: 1}, 164 }, 165 query: simple.Edge{F: simple.Node(0), T: simple.Node(6)}, 166 k: 4, 167 wantPaths: [][]int64{ 168 {0, 1, 3, 6}, 169 {0, 1, 5, 6}, 170 {0, 1, 2, 4, 6}, 171 {0, 1, 2, 5, 6}, 172 }, 173 }, 174 } 175 176 func bipartite(n int, weight, inc float64) []simple.WeightedEdge { 177 var edges []simple.WeightedEdge 178 for i := 2; i < n+2; i++ { 179 edges = append(edges, 180 simple.WeightedEdge{F: simple.Node(-1), T: simple.Node(i), W: weight}, 181 simple.WeightedEdge{F: simple.Node(i), T: simple.Node(1), W: weight}, 182 ) 183 weight += inc 184 } 185 return edges 186 } 187 188 func pathIDs(paths [][]graph.Node) [][]int64 { 189 if paths == nil { 190 return nil 191 } 192 ids := make([][]int64, len(paths)) 193 for i, p := range paths { 194 if p == nil { 195 continue 196 } 197 ids[i] = make([]int64, len(p)) 198 for j, n := range p { 199 ids[i][j] = n.ID() 200 } 201 } 202 return ids 203 } 204 205 func TestYenKSP(t *testing.T) { 206 t.Parallel() 207 for _, test := range yenShortestPathTests { 208 g := test.graph() 209 for _, e := range test.edges { 210 g.SetWeightedEdge(e) 211 } 212 213 got := YenKShortestPaths(g.(graph.Graph), test.k, test.query.From(), test.query.To()) 214 gotIDs := pathIDs(got) 215 216 paths := make(byPathWeight, len(gotIDs)) 217 for i, p := range got { 218 paths[i] = yenShortest{path: p, weight: pathWeight(p, g.(graph.Weighted))} 219 } 220 if !sort.IsSorted(paths) { 221 t.Errorf("unexpected result for %q: got:%+v", test.name, paths) 222 } 223 if test.relaxed { 224 continue 225 } 226 227 if len(gotIDs) != 0 { 228 first := 0 229 last := pathWeight(got[0], g.(graph.Weighted)) 230 for i := 1; i < len(got); i++ { 231 w := pathWeight(got[i], g.(graph.Weighted)) 232 if w == last { 233 continue 234 } 235 ordered.BySliceValues(gotIDs[first:i]) 236 first = i 237 last = w 238 } 239 ordered.BySliceValues(gotIDs[first:]) 240 } 241 242 if !reflect.DeepEqual(test.wantPaths, gotIDs) { 243 t.Errorf("unexpected result for %q:\ngot: %v\nwant:%v", test.name, gotIDs, test.wantPaths) 244 } 245 } 246 } 247 248 func pathWeight(path []graph.Node, g graph.Weighted) float64 { 249 switch len(path) { 250 case 0: 251 return math.NaN() 252 case 1: 253 return 0 254 default: 255 var w float64 256 for i, u := range path[:len(path)-1] { 257 _w, _ := g.Weight(u.ID(), path[i+1].ID()) 258 w += _w 259 } 260 return w 261 } 262 }