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  }