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  }