github.com/LdDl/ch@v1.7.8/bidirectional_ch_test.go (about) 1 package ch 2 3 import ( 4 "bufio" 5 "encoding/csv" 6 "fmt" 7 "io" 8 "math" 9 "math/rand" 10 "os" 11 "strconv" 12 "testing" 13 "time" 14 ) 15 16 func TestShortestPath(t *testing.T) { 17 g := Graph{} 18 err := graphFromCSV(&g, "./data/pgrouting_osm.csv") 19 if err != nil { 20 t.Error(err) 21 return 22 } 23 t.Log("Please wait until contraction hierarchy is prepared") 24 g.PrepareContractionHierarchies() 25 t.Log("TestShortestPath is starting...") 26 u := int64(69618) 27 v := int64(5924) 28 // 29 ans, path := g.ShortestPath(u, v) 30 if len(path) != 160 { 31 t.Errorf("Num of vertices in path should be 160, but got %d", len(path)) 32 return 33 } 34 correctCost := 19135.6581215226 35 if math.Abs(ans-correctCost) > eps { 36 t.Errorf("Cost of path should be %f, but got %f", correctCost, ans) 37 return 38 } 39 t.Log("TestShortestPath is Ok!") 40 } 41 42 func TestBothVanillaAndCH(t *testing.T) { 43 g := Graph{} 44 err := graphFromCSV(&g, "./data/pgrouting_osm.csv") 45 if err != nil { 46 t.Error(err) 47 return 48 } 49 t.Log("Please wait until contraction hierarchy is prepared") 50 g.PrepareContractionHierarchies() 51 t.Log("TestAndSHVanPath is starting...") 52 53 rand.Seed(time.Now().Unix()) 54 for i := 0; i < 10; i++ { 55 rndV := g.Vertices[rand.Intn(len(g.Vertices))].Label 56 rndU := g.Vertices[rand.Intn(len(g.Vertices))].Label 57 ansCH, pathCH := g.ShortestPath(rndV, rndU) 58 ansVanilla, pathVanilla := g.VanillaShortestPath(rndV, rndU) 59 if len(pathCH) != len(pathVanilla) { 60 t.Errorf("Num of vertices in path should be %d, but got %d", len(pathVanilla), len(pathCH)) 61 return 62 } 63 if math.Abs(ansCH-ansVanilla) > eps { 64 t.Errorf("Cost of path should be %f, but got %f", ansVanilla, ansCH) 65 return 66 } 67 } 68 t.Log("TestAndSHVanPath is Ok!") 69 } 70 71 func BenchmarkShortestPath(b *testing.B) { 72 b.Log("BenchmarkShortestPath is starting...") 73 rand.Seed(1337) 74 for k := 2.; k <= 8; k++ { 75 n := int(math.Pow(2, k)) 76 g, err := generateSyntheticGraph(n) 77 if err != nil { 78 b.Error(err) 79 return 80 } 81 b.ResetTimer() 82 b.Run(fmt.Sprintf("%s/%d/vertices-%d-edges-%d-shortcuts-%d", "CH shortest path", n, len(g.Vertices), g.GetEdgesNum(), g.GetShortcutsNum()), func(b *testing.B) { 83 for i := 0; i < b.N; i++ { 84 u := int64(rand.Intn(len(g.Vertices))) 85 v := int64(rand.Intn(len(g.Vertices))) 86 ans, path := g.ShortestPath(u, v) 87 _, _ = ans, path 88 } 89 }) 90 } 91 } 92 93 func BenchmarkStaticCaseShortestPath(b *testing.B) { 94 b.Log("BenchmarkStaticCaseShortestPath is starting...") 95 g := Graph{} 96 err := graphFromCSV(&g, "./data/pgrouting_osm.csv") 97 if err != nil { 98 b.Error(err) 99 return 100 } 101 b.ResetTimer() 102 g.PrepareContractionHierarchies() 103 b.Run(fmt.Sprintf("%s/vertices-%d-edges-%d-shortcuts-%d", "CH shortest path", len(g.Vertices), g.GetEdgesNum(), g.GetShortcutsNum()), func(b *testing.B) { 104 for i := 0; i < b.N; i++ { 105 u := int64(69618) 106 v := int64(5924) 107 ans, path := g.ShortestPath(u, v) 108 _, _ = ans, path 109 } 110 }) 111 } 112 113 // BenchmarkOneCaseShortestPath/CH_shortest_path/vertices-187853-edges-366113-shortcuts-394840-12 891 1412347 ns/op 3460158 B/op 1027 allocs/op 114 115 func BenchmarkPrepareContracts(b *testing.B) { 116 g := Graph{} 117 err := graphFromCSV(&g, "./data/pgrouting_osm.csv") 118 if err != nil { 119 b.Error(err) 120 return 121 } 122 b.ResetTimer() 123 g.PrepareContractionHierarchies() 124 } 125 126 func TestBadSpatialShortestPath(t *testing.T) { 127 rand.Seed(1337) 128 g := Graph{} 129 numVertices := 5 130 lastVertex := int64(numVertices + 1) 131 for i := 0; i < numVertices; i++ { 132 idx := int64(i) 133 g.CreateVertex(idx + 1) 134 g.CreateVertex(idx + 2) 135 g.AddEdge(idx+1, idx+2, rand.Float64()) 136 } 137 g.AddEdge(lastVertex, 1, rand.Float64()) 138 t.Log("Please wait until contraction hierarchy is prepared") 139 g.PrepareContractionHierarchies() 140 t.Log("TestBadSpatialShortestPath is starting...") 141 u := int64(1) 142 v := int64(5) 143 144 ans, path := g.ShortestPath(u, v) 145 if len(path) != 5 { 146 t.Errorf("Num of vertices in path should be 5, but got %d", len(path)) 147 return 148 } 149 correctCost := 2.348720 150 if math.Abs(ans-correctCost) > eps { 151 t.Errorf("Cost of path should be %f, but got %f", correctCost, ans) 152 return 153 } 154 t.Log("TestBadSpatialShortestPath is Ok!") 155 } 156 157 func TestLittleShortestPath(t *testing.T) { 158 g := Graph{} 159 g.CreateVertex(0) 160 g.CreateVertex(1) 161 g.CreateVertex(2) 162 g.CreateVertex(3) 163 g.CreateVertex(4) 164 g.CreateVertex(5) 165 g.CreateVertex(6) 166 g.CreateVertex(7) 167 g.CreateVertex(8) 168 g.CreateVertex(9) 169 g.AddEdge(0, 1, 6.0) 170 g.AddEdge(1, 0, 5.0) 171 g.AddEdge(1, 9, 3.0) 172 g.AddEdge(1, 2, 4.0) 173 g.AddEdge(2, 3, 2.0) 174 g.AddEdge(3, 2, 2.0) 175 g.AddEdge(3, 4, 2.0) 176 g.AddEdge(4, 3, 1.0) 177 g.AddEdge(0, 4, 0.5) 178 g.AddEdge(0, 4, 3.0) 179 g.AddEdge(9, 8, 2.0) 180 g.AddEdge(4, 8, 13.0) 181 g.AddEdge(8, 5, 6.5) 182 g.AddEdge(5, 4, 3.5) 183 g.AddEdge(7, 8, 1.0) 184 g.AddEdge(6, 7, 1.0) 185 g.AddEdge(5, 6, 2.0) 186 g.AddEdge(5, 6, 4.0) 187 188 g.PrepareContractionHierarchies() 189 t.Log("TestLittleShortestPath is starting...") 190 u := int64(0) 191 v := int64(7) 192 // 193 ans, path := g.ShortestPath(u, v) 194 if len(path) != 7 { 195 t.Errorf("Num of vertices in path should be 7, but got %d", len(path)) 196 } 197 198 correctCost := 20.5 199 if math.Abs(ans-correctCost) > eps { 200 t.Errorf("Cost of path should be %f, but got %f", correctCost, ans) 201 return 202 } 203 t.Log("TestLittleShortestPath is Ok!") 204 } 205 206 func TestVertexAlternatives(t *testing.T) { 207 // S-(1)-0-(1)-1-(1)-2 208 // | | | | 209 // (2) (1) (2) (2) 210 // | | | | 211 // 3-(1)-4-(1)-5-(1)-T 212 213 g := Graph{} 214 g.CreateVertex(0) 215 g.CreateVertex(1) 216 g.CreateVertex(2) 217 g.CreateVertex(3) 218 g.CreateVertex(4) 219 g.CreateVertex(5) 220 g.CreateVertex(6) 221 g.AddEdge(0, 1, 1.0) 222 g.AddEdge(0, 4, 1.0) 223 g.AddEdge(1, 2, 1.0) 224 g.AddEdge(1, 5, 2.0) 225 g.AddEdge(3, 4, 1.0) 226 g.AddEdge(4, 5, 1.0) 227 228 expectedPath := []int64{0, 4, 5} 229 230 g.PrepareContractionHierarchies() 231 t.Log("TestVertexAlternatives is starting...") 232 sources := []VertexAlternative{ 233 {Label: 0, AdditionalDistance: 1.0}, 234 {Label: 3, AdditionalDistance: 2.0}, 235 } 236 targets := []VertexAlternative{ 237 {Label: 2, AdditionalDistance: 2.0}, 238 {Label: 5, AdditionalDistance: 1.0}, 239 } 240 ans, path := g.ShortestPathWithAlternatives(sources, targets) 241 t.Log("ShortestPathWithAlternatives returned", ans, path) 242 if len(path) != len(expectedPath) { 243 t.Errorf("Num of vertices in path should be %d, but got %d", len(expectedPath), len(path)) 244 } 245 for i := range expectedPath { 246 if path[i] != expectedPath[i] { 247 t.Errorf("Path item %d should be %d, but got %d", i, expectedPath[i], path[i]) 248 } 249 } 250 correctCost := 4.0 251 if math.Abs(ans-correctCost) > eps { 252 t.Errorf("Cost of path should be %f, but got %f", correctCost, ans) 253 return 254 } 255 t.Log("TestVertexAlternatives is Ok!") 256 } 257 258 func TestVertexAlternativesConnected(t *testing.T) { 259 // S-(1)-0-----\ 260 // | | | 261 // (1) (1) (3) 262 // | | | 263 // \-----1-(1)-T 264 265 g := Graph{} 266 g.CreateVertex(0) 267 g.CreateVertex(1) 268 g.AddEdge(0, 1, 1.0) 269 270 expectedPath := []int64{1} 271 272 g.PrepareContractionHierarchies() 273 t.Log("TestVertexAlternativesConnected is starting...") 274 sources := []VertexAlternative{ 275 {Label: 0, AdditionalDistance: 1.0}, 276 {Label: 1, AdditionalDistance: 1.0}, 277 } 278 targets := []VertexAlternative{ 279 {Label: 0, AdditionalDistance: 3.0}, 280 {Label: 1, AdditionalDistance: 1.0}, 281 } 282 ans, path := g.ShortestPathWithAlternatives(sources, targets) 283 t.Log("ShortestPathWithAlternatives returned", ans, path) 284 if len(path) != len(expectedPath) { 285 t.Errorf("Num of vertices in path should be %d, but got %d", len(expectedPath), len(path)) 286 } 287 for i := range expectedPath { 288 if path[i] != expectedPath[i] { 289 t.Errorf("Path item %d should be %d, but got %d", i, expectedPath[i], path[i]) 290 } 291 } 292 correctCost := 2.0 293 if math.Abs(ans-correctCost) > eps { 294 t.Errorf("Cost of path should be %f, but got %f", correctCost, ans) 295 return 296 } 297 t.Log("TestVertexAlternativesConnected is Ok!") 298 } 299 300 func graphFromCSV(graph *Graph, fname string) error { 301 file, err := os.Open(fname) 302 if err != nil { 303 return err 304 } 305 defer file.Close() 306 reader := csv.NewReader(bufio.NewReader(file)) 307 308 reader.Comma = ';' 309 // reader.LazyQuotes = true 310 311 // Read header 312 _, err = reader.Read() 313 if err != nil { 314 return err 315 } 316 317 for { 318 record, err := reader.Read() 319 if err == io.EOF { 320 break 321 } 322 source, err := strconv.ParseInt(record[0], 10, 64) 323 if err != nil { 324 return err 325 } 326 target, err := strconv.ParseInt(record[1], 10, 64) 327 if err != nil { 328 return err 329 } 330 331 oneway := record[2] 332 weight, err := strconv.ParseFloat(record[3], 64) 333 if err != nil { 334 return err 335 } 336 337 err = graph.CreateVertex(source) 338 if err != nil { 339 return err 340 } 341 err = graph.CreateVertex(target) 342 if err != nil { 343 return err 344 } 345 346 err = graph.AddEdge(source, target, weight) 347 if err != nil { 348 return err 349 } 350 if oneway == "B" { 351 err = graph.AddEdge(target, source, weight) 352 if err != nil { 353 return err 354 } 355 } 356 } 357 return nil 358 } 359 360 func generateSyntheticGraph(verticesNum int) (*Graph, error) { 361 rand.Seed(1337) 362 graph := Graph{} 363 var i int 364 for i = 1; i < verticesNum; i++ { 365 source := int64(i - 1) 366 err := graph.CreateVertex(source) 367 if err != nil { 368 return nil, err 369 } 370 for j := 1; j < verticesNum; j++ { 371 if j == i { 372 continue 373 } 374 target := int64(j) 375 err := graph.CreateVertex(target) 376 if err != nil { 377 return nil, err 378 } 379 // weight := rand.Float64() 380 weight := 0.01 + rand.Float64()*(10-0.01) // Make more dispersion 381 err = graph.AddEdge(source, target, weight) 382 if err != nil { 383 return nil, err 384 } 385 addReverse := rand.Intn(2) 386 if addReverse != 0 { 387 // Add reverse edge imitating bidirectional=true 388 err = graph.AddEdge(target, source, weight) 389 if err != nil { 390 return nil, err 391 } 392 } 393 } 394 } 395 graph.PrepareContractionHierarchies() 396 return &graph, nil 397 }