github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/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/jingcheng-WU/gonum/graph" 14 "github.com/jingcheng-WU/gonum/graph/internal/ordered" 15 "github.com/jingcheng-WU/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 152 func bipartite(n int, weight, inc float64) []simple.WeightedEdge { 153 var edges []simple.WeightedEdge 154 for i := 2; i < n+2; i++ { 155 edges = append(edges, 156 simple.WeightedEdge{F: simple.Node(-1), T: simple.Node(i), W: weight}, 157 simple.WeightedEdge{F: simple.Node(i), T: simple.Node(1), W: weight}, 158 ) 159 weight += inc 160 } 161 return edges 162 } 163 164 func pathIDs(paths [][]graph.Node) [][]int64 { 165 if paths == nil { 166 return nil 167 } 168 ids := make([][]int64, len(paths)) 169 for i, p := range paths { 170 if p == nil { 171 continue 172 } 173 ids[i] = make([]int64, len(p)) 174 for j, n := range p { 175 ids[i][j] = n.ID() 176 } 177 } 178 return ids 179 } 180 181 func TestYenKSP(t *testing.T) { 182 t.Parallel() 183 for _, test := range yenShortestPathTests { 184 g := test.graph() 185 for _, e := range test.edges { 186 g.SetWeightedEdge(e) 187 } 188 189 got := YenKShortestPaths(g.(graph.Graph), test.k, test.query.From(), test.query.To()) 190 gotIDs := pathIDs(got) 191 192 paths := make(byPathWeight, len(gotIDs)) 193 for i, p := range got { 194 paths[i] = yenShortest{path: p, weight: pathWeight(p, g.(graph.Weighted))} 195 } 196 if !sort.IsSorted(paths) { 197 t.Errorf("unexpected result for %q: got:%+v", test.name, paths) 198 } 199 if test.relaxed { 200 continue 201 } 202 203 if len(gotIDs) != 0 { 204 first := 0 205 last := pathWeight(got[0], g.(graph.Weighted)) 206 for i := 1; i < len(got); i++ { 207 w := pathWeight(got[i], g.(graph.Weighted)) 208 if w == last { 209 continue 210 } 211 sort.Sort(ordered.BySliceValues(gotIDs[first:i])) 212 first = i 213 last = w 214 } 215 sort.Sort(ordered.BySliceValues(gotIDs[first:])) 216 } 217 218 if !reflect.DeepEqual(test.wantPaths, gotIDs) { 219 t.Errorf("unexpected result for %q:\ngot: %v\nwant:%v", test.name, gotIDs, test.wantPaths) 220 } 221 } 222 } 223 224 func pathWeight(path []graph.Node, g graph.Weighted) float64 { 225 switch len(path) { 226 case 0: 227 return math.NaN() 228 case 1: 229 return 0 230 default: 231 var w float64 232 for i, u := range path[:len(path)-1] { 233 _w, _ := g.Weight(u.ID(), path[i+1].ID()) 234 w += _w 235 } 236 return w 237 } 238 }