gonum.org/v1/gonum@v0.14.0/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 "gonum.org/v1/gonum/graph" 14 "gonum.org/v1/gonum/graph/internal/ordered" 15 "gonum.org/v1/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 cost float64 26 wantPaths [][]int64 27 28 relaxed bool 29 }{ 30 { 31 // https://en.wikipedia.org/w/index.php?title=Yen%27s_algorithm&oldid=841018784#Example 32 name: "wikipedia example", 33 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 34 edges: []simple.WeightedEdge{ 35 {F: simple.Node('C'), T: simple.Node('D'), W: 3}, 36 {F: simple.Node('C'), T: simple.Node('E'), W: 2}, 37 {F: simple.Node('E'), T: simple.Node('D'), W: 1}, 38 {F: simple.Node('D'), T: simple.Node('F'), W: 4}, 39 {F: simple.Node('E'), T: simple.Node('F'), W: 2}, 40 {F: simple.Node('E'), T: simple.Node('G'), W: 3}, 41 {F: simple.Node('F'), T: simple.Node('G'), W: 2}, 42 {F: simple.Node('F'), T: simple.Node('H'), W: 1}, 43 {F: simple.Node('G'), T: simple.Node('H'), W: 2}, 44 }, 45 query: simple.Edge{F: simple.Node('C'), T: simple.Node('H')}, 46 k: 3, 47 cost: math.Inf(1), 48 wantPaths: [][]int64{ 49 {'C', 'E', 'F', 'H'}, 50 {'C', 'E', 'G', 'H'}, 51 {'C', 'D', 'F', 'H'}, 52 }, 53 }, 54 { 55 name: "1 edge graph", 56 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 57 edges: []simple.WeightedEdge{ 58 {F: simple.Node(0), T: simple.Node(1), W: 3}, 59 }, 60 query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, 61 k: 10, 62 cost: math.Inf(1), 63 wantPaths: [][]int64{ 64 {0, 1}, 65 }, 66 }, 67 { 68 name: "empty graph", 69 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 70 edges: []simple.WeightedEdge{}, 71 query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, 72 k: 1, 73 wantPaths: nil, 74 }, 75 { 76 name: "n-star graph", 77 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 78 edges: []simple.WeightedEdge{ 79 {F: simple.Node(0), T: simple.Node(1), W: 3}, 80 {F: simple.Node(0), T: simple.Node(2), W: 3}, 81 {F: simple.Node(0), T: simple.Node(3), W: 3}, 82 }, 83 query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, 84 k: 1, 85 cost: math.Inf(1), 86 wantPaths: [][]int64{ 87 {0, 1}, 88 }, 89 }, 90 { 91 name: "bipartite small", 92 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 93 edges: bipartite(5, 3, 0), 94 query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)}, 95 k: 10, 96 cost: math.Inf(1), 97 wantPaths: [][]int64{ 98 {-1, 2, 1}, 99 {-1, 3, 1}, 100 {-1, 4, 1}, 101 {-1, 5, 1}, 102 {-1, 6, 1}, 103 }, 104 }, 105 { 106 name: "bipartite parity", 107 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 108 edges: bipartite(5, 3, 0), 109 query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)}, 110 k: 5, 111 cost: math.Inf(1), 112 wantPaths: [][]int64{ 113 {-1, 2, 1}, 114 {-1, 3, 1}, 115 {-1, 4, 1}, 116 {-1, 5, 1}, 117 {-1, 6, 1}, 118 }, 119 }, 120 { 121 name: "bipartite large", 122 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 123 edges: bipartite(10, 3, 0), 124 query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)}, 125 k: 5, 126 cost: math.Inf(1), 127 relaxed: true, 128 }, 129 { 130 name: "bipartite inc", 131 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 132 edges: bipartite(5, 10, 1), 133 query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)}, 134 k: 5, 135 cost: math.Inf(1), 136 wantPaths: [][]int64{ 137 {-1, 2, 1}, 138 {-1, 3, 1}, 139 {-1, 4, 1}, 140 {-1, 5, 1}, 141 {-1, 6, 1}, 142 }, 143 }, 144 { 145 name: "bipartite dec", 146 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 147 edges: bipartite(5, 10, -1), 148 query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)}, 149 k: 5, 150 cost: math.Inf(1), 151 wantPaths: [][]int64{ 152 {-1, 6, 1}, 153 {-1, 5, 1}, 154 {-1, 4, 1}, 155 {-1, 3, 1}, 156 {-1, 2, 1}, 157 }, 158 }, 159 { 160 name: "waterfall", // This is the failing case in gonum/gonum#1700. 161 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 162 edges: []simple.WeightedEdge{ 163 {F: simple.Node(0), T: simple.Node(1), W: 1}, 164 {F: simple.Node(1), T: simple.Node(2), W: 1}, 165 {F: simple.Node(1), T: simple.Node(3), W: 1}, 166 {F: simple.Node(1), T: simple.Node(5), W: 1}, 167 {F: simple.Node(2), T: simple.Node(4), W: 1}, 168 {F: simple.Node(2), T: simple.Node(5), W: 1}, 169 {F: simple.Node(3), T: simple.Node(6), W: 1}, 170 {F: simple.Node(4), T: simple.Node(6), W: 1}, 171 {F: simple.Node(5), T: simple.Node(6), W: 1}, 172 {F: simple.Node(5), T: simple.Node(6), W: 1}, 173 }, 174 query: simple.Edge{F: simple.Node(0), T: simple.Node(6)}, 175 k: 4, 176 cost: math.Inf(1), 177 wantPaths: [][]int64{ 178 {0, 1, 3, 6}, 179 {0, 1, 5, 6}, 180 {0, 1, 2, 4, 6}, 181 {0, 1, 2, 5, 6}, 182 }, 183 }, 184 { 185 name: "waterfall_3", 186 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) }, 187 edges: []simple.WeightedEdge{ 188 {F: simple.Node(0), T: simple.Node(1), W: 1}, 189 {F: simple.Node(1), T: simple.Node(2), W: 1}, 190 {F: simple.Node(1), T: simple.Node(3), W: 1}, 191 {F: simple.Node(1), T: simple.Node(5), W: 1}, 192 {F: simple.Node(2), T: simple.Node(4), W: 1}, 193 {F: simple.Node(2), T: simple.Node(5), W: 1}, 194 {F: simple.Node(3), T: simple.Node(6), W: 1}, 195 {F: simple.Node(4), T: simple.Node(6), W: 1}, 196 {F: simple.Node(5), T: simple.Node(6), W: 1}, 197 {F: simple.Node(5), T: simple.Node(6), W: 1}, 198 }, 199 query: simple.Edge{F: simple.Node(0), T: simple.Node(6)}, 200 k: -1, 201 cost: 0, // Find all paths equivalent to shortest. 202 wantPaths: [][]int64{ 203 {0, 1, 3, 6}, 204 {0, 1, 5, 6}, 205 }, 206 }, 207 { 208 name: "clean_root", // This is the failing case in gonum/gonum#1778. 209 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 210 edges: []simple.WeightedEdge{ 211 {F: simple.Node(1), T: simple.Node(2), W: 4}, 212 {F: simple.Node(1), T: simple.Node(3), W: 1}, 213 {F: simple.Node(2), T: simple.Node(3), W: 4}, 214 }, 215 query: simple.Edge{F: simple.Node(1), T: simple.Node(2)}, 216 k: 3, 217 cost: math.Inf(1), 218 wantPaths: [][]int64{ 219 {1, 2}, 220 {1, 3, 2}, 221 }, 222 }, 223 { 224 name: "clean_root_4", 225 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 226 edges: []simple.WeightedEdge{ 227 {F: simple.Node(1), T: simple.Node(2), W: 4}, 228 {F: simple.Node(1), T: simple.Node(3), W: 1}, 229 {F: simple.Node(2), T: simple.Node(3), W: 4}, 230 }, 231 query: simple.Edge{F: simple.Node(1), T: simple.Node(2)}, 232 k: -1, 233 cost: 0, 234 wantPaths: [][]int64{ 235 {1, 2}, 236 }, 237 }, 238 { 239 name: "clean_root_1060", 240 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 241 edges: []simple.WeightedEdge{ 242 {F: simple.Node(1), T: simple.Node(2), W: 450}, 243 {F: simple.Node(3), T: simple.Node(4), W: 450}, 244 {F: simple.Node(3), T: simple.Node(1), W: 20}, 245 {F: simple.Node(5), T: simple.Node(2), W: 450}, 246 {F: simple.Node(2), T: simple.Node(4), W: 20}, 247 {F: simple.Node(4), T: simple.Node(6), W: 610}, 248 {F: simple.Node(5), T: simple.Node(7), W: 20}, 249 {F: simple.Node(2), T: simple.Node(8), W: 610}, 250 {F: simple.Node(7), T: simple.Node(3), W: 40}, 251 {F: simple.Node(1), T: simple.Node(5), W: 40}, 252 }, 253 query: simple.Edge{F: simple.Node(8), T: simple.Node(5)}, 254 k: -1, 255 cost: 0, 256 wantPaths: [][]int64{ 257 {8, 2, 5}, 258 }, 259 }, 260 { 261 name: "clean_root_1100", 262 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 263 edges: []simple.WeightedEdge{ 264 {F: simple.Node(1), T: simple.Node(2), W: 450}, 265 {F: simple.Node(3), T: simple.Node(4), W: 450}, 266 {F: simple.Node(3), T: simple.Node(1), W: 20}, 267 {F: simple.Node(5), T: simple.Node(2), W: 450}, 268 {F: simple.Node(2), T: simple.Node(4), W: 20}, 269 {F: simple.Node(4), T: simple.Node(6), W: 610}, 270 {F: simple.Node(5), T: simple.Node(7), W: 20}, 271 {F: simple.Node(2), T: simple.Node(8), W: 610}, 272 {F: simple.Node(7), T: simple.Node(3), W: 40}, 273 {F: simple.Node(1), T: simple.Node(5), W: 40}, 274 }, 275 query: simple.Edge{F: simple.Node(8), T: simple.Node(5)}, 276 k: -1, 277 cost: 40, 278 wantPaths: [][]int64{ 279 {8, 2, 5}, 280 {8, 2, 1, 5}, 281 }, 282 }, 283 { 284 name: "clean_root_1140", 285 graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) }, 286 edges: []simple.WeightedEdge{ 287 {F: simple.Node(1), T: simple.Node(2), W: 450}, 288 {F: simple.Node(3), T: simple.Node(4), W: 450}, 289 {F: simple.Node(3), T: simple.Node(1), W: 20}, 290 {F: simple.Node(5), T: simple.Node(2), W: 450}, 291 {F: simple.Node(2), T: simple.Node(4), W: 20}, 292 {F: simple.Node(4), T: simple.Node(6), W: 610}, 293 {F: simple.Node(5), T: simple.Node(7), W: 20}, 294 {F: simple.Node(2), T: simple.Node(8), W: 610}, 295 {F: simple.Node(7), T: simple.Node(3), W: 40}, 296 {F: simple.Node(1), T: simple.Node(5), W: 40}, 297 }, 298 query: simple.Edge{F: simple.Node(8), T: simple.Node(5)}, 299 k: -1, 300 cost: 80, 301 wantPaths: [][]int64{ 302 {8, 2, 5}, 303 {8, 2, 1, 5}, 304 {8, 2, 1, 3, 7, 5}, 305 {8, 2, 4, 3, 1, 5}, 306 {8, 2, 4, 3, 7, 5}, 307 }, 308 }, 309 } 310 311 func bipartite(n int, weight, inc float64) []simple.WeightedEdge { 312 var edges []simple.WeightedEdge 313 for i := 2; i < n+2; i++ { 314 edges = append(edges, 315 simple.WeightedEdge{F: simple.Node(-1), T: simple.Node(i), W: weight}, 316 simple.WeightedEdge{F: simple.Node(i), T: simple.Node(1), W: weight}, 317 ) 318 weight += inc 319 } 320 return edges 321 } 322 323 func pathIDs(paths [][]graph.Node) [][]int64 { 324 if paths == nil { 325 return nil 326 } 327 ids := make([][]int64, len(paths)) 328 for i, p := range paths { 329 if p == nil { 330 continue 331 } 332 ids[i] = make([]int64, len(p)) 333 for j, n := range p { 334 ids[i][j] = n.ID() 335 } 336 } 337 return ids 338 } 339 340 func TestYenKSP(t *testing.T) { 341 t.Parallel() 342 for _, test := range yenShortestPathTests { 343 g := test.graph() 344 for _, e := range test.edges { 345 g.SetWeightedEdge(e) 346 } 347 348 got := YenKShortestPaths(g.(graph.Graph), test.k, test.cost, test.query.From(), test.query.To()) 349 gotIDs := pathIDs(got) 350 351 paths := make(byPathWeight, len(gotIDs)) 352 for i, p := range got { 353 paths[i] = yenShortest{path: p, weight: pathWeight(p, g.(graph.Weighted))} 354 } 355 if !sort.IsSorted(paths) { 356 t.Errorf("unexpected result for %q: got:%+v", test.name, paths) 357 } 358 if test.relaxed { 359 continue 360 } 361 362 if len(gotIDs) != 0 { 363 first := 0 364 last := pathWeight(got[0], g.(graph.Weighted)) 365 for i := 1; i < len(got); i++ { 366 w := pathWeight(got[i], g.(graph.Weighted)) 367 if w == last { 368 continue 369 } 370 ordered.BySliceValues(gotIDs[first:i]) 371 first = i 372 last = w 373 } 374 ordered.BySliceValues(gotIDs[first:]) 375 } 376 377 if !reflect.DeepEqual(test.wantPaths, gotIDs) { 378 t.Errorf("unexpected result for %q:\ngot: %v\nwant:%v", test.name, gotIDs, test.wantPaths) 379 } 380 } 381 } 382 383 func pathWeight(path []graph.Node, g graph.Weighted) float64 { 384 switch len(path) { 385 case 0: 386 return math.NaN() 387 case 1: 388 return 0 389 default: 390 var w float64 391 for i, u := range path[:len(path)-1] { 392 _w, _ := g.Weight(u.ID(), path[i+1].ID()) 393 w += _w 394 } 395 return w 396 } 397 }