gonum.org/v1/gonum@v0.14.0/graph/path/shortest_test.go (about) 1 // Copyright ©2023 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 "fmt" 9 "math" 10 "reflect" 11 "testing" 12 13 "golang.org/x/exp/rand" 14 15 "gonum.org/v1/gonum/graph" 16 "gonum.org/v1/gonum/graph/graphs/gen" 17 "gonum.org/v1/gonum/graph/internal/ordered" 18 "gonum.org/v1/gonum/graph/simple" 19 ) 20 21 var shortestTests = []struct { 22 n, d int 23 p float64 24 seed uint64 25 }{ 26 {n: 100, d: 2, p: 0.5, seed: 1}, 27 {n: 200, d: 2, p: 0.5, seed: 1}, 28 {n: 100, d: 4, p: 0.25, seed: 1}, 29 {n: 200, d: 4, p: 0.25, seed: 1}, 30 {n: 100, d: 16, p: 0.1, seed: 1}, 31 {n: 200, d: 16, p: 0.1, seed: 1}, 32 } 33 34 func TestShortestAlts(t *testing.T) { 35 for _, test := range shortestTests { 36 t.Run(fmt.Sprintf("AllTo_%d×%d|%v", test.n, test.d, test.p), func(t *testing.T) { 37 g := simple.NewDirectedGraph() 38 gen.SmallWorldsBB(g, test.n, test.d, test.p, rand.New(rand.NewSource(test.seed))) 39 all := allShortest(DijkstraAllPaths(g)) 40 41 for uid := int64(0); uid < int64(test.n); uid++ { 42 p := DijkstraAllFrom(g.Node(uid), g) 43 for vid := int64(0); vid < int64(test.n); vid++ { 44 got, gotW := p.AllTo(vid) 45 want, wantW := all.AllBetween(uid, vid) 46 if gotW != wantW { 47 t.Errorf("mismatched weight: got:%f want:%f", gotW, wantW) 48 continue 49 } 50 51 var gotPaths [][]int64 52 if len(got) != 0 { 53 gotPaths = make([][]int64, len(got)) 54 } 55 for i, p := range got { 56 for _, v := range p { 57 gotPaths[i] = append(gotPaths[i], v.ID()) 58 } 59 } 60 ordered.BySliceValues(gotPaths) 61 var wantPaths [][]int64 62 if len(want) != 0 { 63 wantPaths = make([][]int64, len(want)) 64 } 65 for i, p := range want { 66 for _, v := range p { 67 wantPaths[i] = append(wantPaths[i], v.ID()) 68 } 69 } 70 ordered.BySliceValues(wantPaths) 71 if !reflect.DeepEqual(gotPaths, wantPaths) { 72 t.Errorf("unexpected shortest paths %d --> %d:\ngot: %v\nwant:%v", 73 uid, vid, gotPaths, wantPaths) 74 } 75 } 76 } 77 }) 78 } 79 } 80 81 func TestAllShortest(t *testing.T) { 82 for _, test := range shortestTests { 83 t.Run(fmt.Sprintf("AllBetween_%d×%d|%v", test.n, test.d, test.p), func(t *testing.T) { 84 g := simple.NewDirectedGraph() 85 gen.SmallWorldsBB(g, test.n, test.d, test.p, rand.New(rand.NewSource(test.seed))) 86 87 p := DijkstraAllPaths(g) 88 for uid := int64(0); uid < int64(test.n); uid++ { 89 for vid := int64(0); vid < int64(test.n); vid++ { 90 got, gotW := p.AllBetween(uid, vid) 91 want, wantW := allShortest(p).AllBetween(uid, vid) // Compare to naive. 92 if gotW != wantW { 93 t.Errorf("mismatched weight: got:%f want:%f", gotW, wantW) 94 continue 95 } 96 97 var gotPaths [][]int64 98 if len(got) != 0 { 99 gotPaths = make([][]int64, len(got)) 100 } 101 for i, p := range got { 102 for _, v := range p { 103 gotPaths[i] = append(gotPaths[i], v.ID()) 104 } 105 } 106 ordered.BySliceValues(gotPaths) 107 var wantPaths [][]int64 108 if len(want) != 0 { 109 wantPaths = make([][]int64, len(want)) 110 } 111 for i, p := range want { 112 for _, v := range p { 113 wantPaths[i] = append(wantPaths[i], v.ID()) 114 } 115 } 116 ordered.BySliceValues(wantPaths) 117 if !reflect.DeepEqual(gotPaths, wantPaths) { 118 t.Errorf("unexpected shortest paths %d --> %d:\ngot: %v\nwant:%v", 119 uid, vid, gotPaths, wantPaths) 120 } 121 } 122 } 123 }) 124 } 125 } 126 127 // allShortest implements an allocation-naive AllBetween. 128 type allShortest AllShortest 129 130 // at returns a slice of node indexes into p.nodes for nodes that are mid points 131 // between nodes indexed by from and to. 132 func (p allShortest) at(from, to int) (mid []int) { 133 return p.next[from+to*len(p.nodes)] 134 } 135 136 // AllBetween returns all shortest paths from u to v and the weight of the paths. Paths 137 // containing zero-weight cycles are not returned. If a negative cycle exists between 138 // u and v, paths is returned nil and weight is returned as -Inf. 139 func (p allShortest) AllBetween(uid, vid int64) (paths [][]graph.Node, weight float64) { 140 from, fromOK := p.indexOf[uid] 141 to, toOK := p.indexOf[vid] 142 if !fromOK || !toOK || len(p.at(from, to)) == 0 { 143 if uid == vid { 144 if !fromOK { 145 return [][]graph.Node{{node(uid)}}, 0 146 } 147 return [][]graph.Node{{p.nodes[from]}}, 0 148 } 149 return nil, math.Inf(1) 150 } 151 152 weight = p.dist.At(from, to) 153 if math.Float64bits(weight) == defacedBits { 154 return nil, math.Inf(-1) 155 } 156 157 var n graph.Node 158 if p.forward { 159 n = p.nodes[from] 160 } else { 161 n = p.nodes[to] 162 } 163 seen := make([]bool, len(p.nodes)) 164 paths = p.allBetween(from, to, seen, []graph.Node{n}, nil) 165 166 return paths, weight 167 } 168 169 // allBetween recursively constructs a slice of paths extending from the node 170 // indexed into p.nodes by from to the node indexed by to. len(seen) must match 171 // the number of nodes held by the receiver. The path parameter is the current 172 // working path and the results are written into paths. 173 func (p allShortest) allBetween(from, to int, seen []bool, path []graph.Node, paths [][]graph.Node) [][]graph.Node { 174 if p.forward { 175 seen[from] = true 176 } else { 177 seen[to] = true 178 } 179 if from == to { 180 if path == nil { 181 return paths 182 } 183 if !p.forward { 184 ordered.Reverse(path) 185 } 186 return append(paths, path) 187 } 188 first := true 189 for _, n := range p.at(from, to) { 190 if seen[n] { 191 continue 192 } 193 if first { 194 path = append([]graph.Node(nil), path...) 195 first = false 196 } 197 if p.forward { 198 from = n 199 } else { 200 to = n 201 } 202 path = path[:len(path):len(path)] 203 paths = p.allBetween(from, to, append([]bool(nil), seen...), append(path, p.nodes[n]), paths) 204 } 205 return paths 206 } 207 208 var shortestBenchmarks = []struct { 209 n, d int 210 p float64 211 seed uint64 212 }{ 213 {n: 100, d: 2, p: 0.5, seed: 1}, 214 {n: 1000, d: 2, p: 0.5, seed: 1}, 215 {n: 100, d: 4, p: 0.25, seed: 1}, 216 {n: 1000, d: 4, p: 0.25, seed: 1}, 217 {n: 100, d: 16, p: 0.1, seed: 1}, 218 {n: 1000, d: 16, p: 0.1, seed: 1}, 219 } 220 221 func BenchmarkShortestAlts(b *testing.B) { 222 for _, bench := range shortestBenchmarks { 223 g := simple.NewDirectedGraph() 224 gen.SmallWorldsBB(g, bench.n, bench.d, bench.p, rand.New(rand.NewSource(bench.seed))) 225 226 // Find the widest path set. 227 var ( 228 bestP ShortestAlts 229 bestVid int64 230 n int 231 ) 232 for uid := int64(0); uid < int64(bench.n); uid++ { 233 p := DijkstraAllFrom(g.Node(uid), g) 234 for vid := int64(0); vid < int64(bench.n); vid++ { 235 paths, _ := p.AllTo(vid) 236 if len(paths) > n { 237 n = len(paths) 238 bestP = p 239 bestVid = vid 240 } 241 } 242 } 243 244 b.Run(fmt.Sprintf("AllTo_%d×%d|%v(%d)", bench.n, bench.d, bench.p, n), func(b *testing.B) { 245 for i := 0; i < b.N; i++ { 246 paths, _ := bestP.AllTo(bestVid) 247 if len(paths) != n { 248 b.Errorf("unexpected number of paths: got:%d want:%d", len(paths), n) 249 } 250 } 251 }) 252 b.Run(fmt.Sprintf("AllToFunc_%d×%d|%v(%d)", bench.n, bench.d, bench.p, n), func(b *testing.B) { 253 for i := 0; i < b.N; i++ { 254 var paths int 255 bestP.AllToFunc(bestVid, func(_ []graph.Node) { paths++ }) 256 if paths != n { 257 b.Errorf("unexpected number of paths: got:%d want:%d", paths, n) 258 } 259 } 260 }) 261 } 262 } 263 264 func BenchmarkAllShortest(b *testing.B) { 265 for _, bench := range shortestBenchmarks { 266 g := simple.NewDirectedGraph() 267 gen.SmallWorldsBB(g, bench.n, bench.d, bench.p, rand.New(rand.NewSource(bench.seed))) 268 p := DijkstraAllPaths(g) 269 270 // Find the widest path set. 271 var ( 272 bestUid, bestVid int64 273 n int 274 ) 275 for uid := int64(0); uid < int64(bench.n); uid++ { 276 for vid := int64(0); vid < int64(bench.n); vid++ { 277 paths, _ := p.AllBetween(uid, vid) 278 if len(paths) > n { 279 n = len(paths) 280 bestUid = uid 281 bestVid = vid 282 } 283 } 284 } 285 286 b.Run(fmt.Sprintf("AllBetween_%d×%d|%v(%d)", bench.n, bench.d, bench.p, n), func(b *testing.B) { 287 for i := 0; i < b.N; i++ { 288 paths, _ := p.AllBetween(bestUid, bestVid) 289 if len(paths) != n { 290 b.Errorf("unexpected number of paths: got:%d want:%d", len(paths), n) 291 } 292 } 293 }) 294 b.Run(fmt.Sprintf("AllBetweenFunc_%d×%d|%v(%d)", bench.n, bench.d, bench.p, n), func(b *testing.B) { 295 for i := 0; i < b.N; i++ { 296 var paths int 297 p.AllBetweenFunc(bestUid, bestVid, func(_ []graph.Node) { paths++ }) 298 if paths != n { 299 b.Errorf("unexpected number of paths: got:%d want:%d", paths, n) 300 } 301 } 302 }) 303 } 304 }