gonum.org/v1/gonum@v0.14.0/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  	"gonum.org/v1/gonum/graph"
    14  	"gonum.org/v1/gonum/graph/internal/ordered"
    15  	"gonum.org/v1/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  	cost      float64
    26  	wantPaths [][]int64
    27  
    28  	relaxed bool
    29  }{
    30  	{
    31  		// https://en.wikipedia.org/w/index.php?title=Yen%27s_algorithm&oldid=841018784#Example
    32  		name:  "wikipedia example",
    33  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
    34  		edges: []simple.WeightedEdge{
    35  			{F: simple.Node('C'), T: simple.Node('D'), W: 3},
    36  			{F: simple.Node('C'), T: simple.Node('E'), W: 2},
    37  			{F: simple.Node('E'), T: simple.Node('D'), W: 1},
    38  			{F: simple.Node('D'), T: simple.Node('F'), W: 4},
    39  			{F: simple.Node('E'), T: simple.Node('F'), W: 2},
    40  			{F: simple.Node('E'), T: simple.Node('G'), W: 3},
    41  			{F: simple.Node('F'), T: simple.Node('G'), W: 2},
    42  			{F: simple.Node('F'), T: simple.Node('H'), W: 1},
    43  			{F: simple.Node('G'), T: simple.Node('H'), W: 2},
    44  		},
    45  		query: simple.Edge{F: simple.Node('C'), T: simple.Node('H')},
    46  		k:     3,
    47  		cost:  math.Inf(1),
    48  		wantPaths: [][]int64{
    49  			{'C', 'E', 'F', 'H'},
    50  			{'C', 'E', 'G', 'H'},
    51  			{'C', 'D', 'F', 'H'},
    52  		},
    53  	},
    54  	{
    55  		name:  "1 edge graph",
    56  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
    57  		edges: []simple.WeightedEdge{
    58  			{F: simple.Node(0), T: simple.Node(1), W: 3},
    59  		},
    60  		query: simple.Edge{F: simple.Node(0), T: simple.Node(1)},
    61  		k:     10,
    62  		cost:  math.Inf(1),
    63  		wantPaths: [][]int64{
    64  			{0, 1},
    65  		},
    66  	},
    67  	{
    68  		name:      "empty graph",
    69  		graph:     func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
    70  		edges:     []simple.WeightedEdge{},
    71  		query:     simple.Edge{F: simple.Node(0), T: simple.Node(1)},
    72  		k:         1,
    73  		wantPaths: nil,
    74  	},
    75  	{
    76  		name:  "n-star graph",
    77  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
    78  		edges: []simple.WeightedEdge{
    79  			{F: simple.Node(0), T: simple.Node(1), W: 3},
    80  			{F: simple.Node(0), T: simple.Node(2), W: 3},
    81  			{F: simple.Node(0), T: simple.Node(3), W: 3},
    82  		},
    83  		query: simple.Edge{F: simple.Node(0), T: simple.Node(1)},
    84  		k:     1,
    85  		cost:  math.Inf(1),
    86  		wantPaths: [][]int64{
    87  			{0, 1},
    88  		},
    89  	},
    90  	{
    91  		name:  "bipartite small",
    92  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
    93  		edges: bipartite(5, 3, 0),
    94  		query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)},
    95  		k:     10,
    96  		cost:  math.Inf(1),
    97  		wantPaths: [][]int64{
    98  			{-1, 2, 1},
    99  			{-1, 3, 1},
   100  			{-1, 4, 1},
   101  			{-1, 5, 1},
   102  			{-1, 6, 1},
   103  		},
   104  	},
   105  	{
   106  		name:  "bipartite parity",
   107  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   108  		edges: bipartite(5, 3, 0),
   109  		query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)},
   110  		k:     5,
   111  		cost:  math.Inf(1),
   112  		wantPaths: [][]int64{
   113  			{-1, 2, 1},
   114  			{-1, 3, 1},
   115  			{-1, 4, 1},
   116  			{-1, 5, 1},
   117  			{-1, 6, 1},
   118  		},
   119  	},
   120  	{
   121  		name:    "bipartite large",
   122  		graph:   func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   123  		edges:   bipartite(10, 3, 0),
   124  		query:   simple.Edge{F: simple.Node(-1), T: simple.Node(1)},
   125  		k:       5,
   126  		cost:    math.Inf(1),
   127  		relaxed: true,
   128  	},
   129  	{
   130  		name:  "bipartite inc",
   131  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   132  		edges: bipartite(5, 10, 1),
   133  		query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)},
   134  		k:     5,
   135  		cost:  math.Inf(1),
   136  		wantPaths: [][]int64{
   137  			{-1, 2, 1},
   138  			{-1, 3, 1},
   139  			{-1, 4, 1},
   140  			{-1, 5, 1},
   141  			{-1, 6, 1},
   142  		},
   143  	},
   144  	{
   145  		name:  "bipartite dec",
   146  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   147  		edges: bipartite(5, 10, -1),
   148  		query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)},
   149  		k:     5,
   150  		cost:  math.Inf(1),
   151  		wantPaths: [][]int64{
   152  			{-1, 6, 1},
   153  			{-1, 5, 1},
   154  			{-1, 4, 1},
   155  			{-1, 3, 1},
   156  			{-1, 2, 1},
   157  		},
   158  	},
   159  	{
   160  		name:  "waterfall", // This is the failing case in gonum/gonum#1700.
   161  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   162  		edges: []simple.WeightedEdge{
   163  			{F: simple.Node(0), T: simple.Node(1), W: 1},
   164  			{F: simple.Node(1), T: simple.Node(2), W: 1},
   165  			{F: simple.Node(1), T: simple.Node(3), W: 1},
   166  			{F: simple.Node(1), T: simple.Node(5), W: 1},
   167  			{F: simple.Node(2), T: simple.Node(4), W: 1},
   168  			{F: simple.Node(2), T: simple.Node(5), W: 1},
   169  			{F: simple.Node(3), T: simple.Node(6), W: 1},
   170  			{F: simple.Node(4), T: simple.Node(6), W: 1},
   171  			{F: simple.Node(5), T: simple.Node(6), W: 1},
   172  			{F: simple.Node(5), T: simple.Node(6), W: 1},
   173  		},
   174  		query: simple.Edge{F: simple.Node(0), T: simple.Node(6)},
   175  		k:     4,
   176  		cost:  math.Inf(1),
   177  		wantPaths: [][]int64{
   178  			{0, 1, 3, 6},
   179  			{0, 1, 5, 6},
   180  			{0, 1, 2, 4, 6},
   181  			{0, 1, 2, 5, 6},
   182  		},
   183  	},
   184  	{
   185  		name:  "waterfall_3",
   186  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   187  		edges: []simple.WeightedEdge{
   188  			{F: simple.Node(0), T: simple.Node(1), W: 1},
   189  			{F: simple.Node(1), T: simple.Node(2), W: 1},
   190  			{F: simple.Node(1), T: simple.Node(3), W: 1},
   191  			{F: simple.Node(1), T: simple.Node(5), W: 1},
   192  			{F: simple.Node(2), T: simple.Node(4), W: 1},
   193  			{F: simple.Node(2), T: simple.Node(5), W: 1},
   194  			{F: simple.Node(3), T: simple.Node(6), W: 1},
   195  			{F: simple.Node(4), T: simple.Node(6), W: 1},
   196  			{F: simple.Node(5), T: simple.Node(6), W: 1},
   197  			{F: simple.Node(5), T: simple.Node(6), W: 1},
   198  		},
   199  		query: simple.Edge{F: simple.Node(0), T: simple.Node(6)},
   200  		k:     -1,
   201  		cost:  0, // Find all paths equivalent to shortest.
   202  		wantPaths: [][]int64{
   203  			{0, 1, 3, 6},
   204  			{0, 1, 5, 6},
   205  		},
   206  	},
   207  	{
   208  		name:  "clean_root", // This is the failing case in gonum/gonum#1778.
   209  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
   210  		edges: []simple.WeightedEdge{
   211  			{F: simple.Node(1), T: simple.Node(2), W: 4},
   212  			{F: simple.Node(1), T: simple.Node(3), W: 1},
   213  			{F: simple.Node(2), T: simple.Node(3), W: 4},
   214  		},
   215  		query: simple.Edge{F: simple.Node(1), T: simple.Node(2)},
   216  		k:     3,
   217  		cost:  math.Inf(1),
   218  		wantPaths: [][]int64{
   219  			{1, 2},
   220  			{1, 3, 2},
   221  		},
   222  	},
   223  	{
   224  		name:  "clean_root_4",
   225  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
   226  		edges: []simple.WeightedEdge{
   227  			{F: simple.Node(1), T: simple.Node(2), W: 4},
   228  			{F: simple.Node(1), T: simple.Node(3), W: 1},
   229  			{F: simple.Node(2), T: simple.Node(3), W: 4},
   230  		},
   231  		query: simple.Edge{F: simple.Node(1), T: simple.Node(2)},
   232  		k:     -1,
   233  		cost:  0,
   234  		wantPaths: [][]int64{
   235  			{1, 2},
   236  		},
   237  	},
   238  	{
   239  		name:  "clean_root_1060",
   240  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
   241  		edges: []simple.WeightedEdge{
   242  			{F: simple.Node(1), T: simple.Node(2), W: 450},
   243  			{F: simple.Node(3), T: simple.Node(4), W: 450},
   244  			{F: simple.Node(3), T: simple.Node(1), W: 20},
   245  			{F: simple.Node(5), T: simple.Node(2), W: 450},
   246  			{F: simple.Node(2), T: simple.Node(4), W: 20},
   247  			{F: simple.Node(4), T: simple.Node(6), W: 610},
   248  			{F: simple.Node(5), T: simple.Node(7), W: 20},
   249  			{F: simple.Node(2), T: simple.Node(8), W: 610},
   250  			{F: simple.Node(7), T: simple.Node(3), W: 40},
   251  			{F: simple.Node(1), T: simple.Node(5), W: 40},
   252  		},
   253  		query: simple.Edge{F: simple.Node(8), T: simple.Node(5)},
   254  		k:     -1,
   255  		cost:  0,
   256  		wantPaths: [][]int64{
   257  			{8, 2, 5},
   258  		},
   259  	},
   260  	{
   261  		name:  "clean_root_1100",
   262  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
   263  		edges: []simple.WeightedEdge{
   264  			{F: simple.Node(1), T: simple.Node(2), W: 450},
   265  			{F: simple.Node(3), T: simple.Node(4), W: 450},
   266  			{F: simple.Node(3), T: simple.Node(1), W: 20},
   267  			{F: simple.Node(5), T: simple.Node(2), W: 450},
   268  			{F: simple.Node(2), T: simple.Node(4), W: 20},
   269  			{F: simple.Node(4), T: simple.Node(6), W: 610},
   270  			{F: simple.Node(5), T: simple.Node(7), W: 20},
   271  			{F: simple.Node(2), T: simple.Node(8), W: 610},
   272  			{F: simple.Node(7), T: simple.Node(3), W: 40},
   273  			{F: simple.Node(1), T: simple.Node(5), W: 40},
   274  		},
   275  		query: simple.Edge{F: simple.Node(8), T: simple.Node(5)},
   276  		k:     -1,
   277  		cost:  40,
   278  		wantPaths: [][]int64{
   279  			{8, 2, 5},
   280  			{8, 2, 1, 5},
   281  		},
   282  	},
   283  	{
   284  		name:  "clean_root_1140",
   285  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedUndirectedGraph(0, math.Inf(1)) },
   286  		edges: []simple.WeightedEdge{
   287  			{F: simple.Node(1), T: simple.Node(2), W: 450},
   288  			{F: simple.Node(3), T: simple.Node(4), W: 450},
   289  			{F: simple.Node(3), T: simple.Node(1), W: 20},
   290  			{F: simple.Node(5), T: simple.Node(2), W: 450},
   291  			{F: simple.Node(2), T: simple.Node(4), W: 20},
   292  			{F: simple.Node(4), T: simple.Node(6), W: 610},
   293  			{F: simple.Node(5), T: simple.Node(7), W: 20},
   294  			{F: simple.Node(2), T: simple.Node(8), W: 610},
   295  			{F: simple.Node(7), T: simple.Node(3), W: 40},
   296  			{F: simple.Node(1), T: simple.Node(5), W: 40},
   297  		},
   298  		query: simple.Edge{F: simple.Node(8), T: simple.Node(5)},
   299  		k:     -1,
   300  		cost:  80,
   301  		wantPaths: [][]int64{
   302  			{8, 2, 5},
   303  			{8, 2, 1, 5},
   304  			{8, 2, 1, 3, 7, 5},
   305  			{8, 2, 4, 3, 1, 5},
   306  			{8, 2, 4, 3, 7, 5},
   307  		},
   308  	},
   309  }
   310  
   311  func bipartite(n int, weight, inc float64) []simple.WeightedEdge {
   312  	var edges []simple.WeightedEdge
   313  	for i := 2; i < n+2; i++ {
   314  		edges = append(edges,
   315  			simple.WeightedEdge{F: simple.Node(-1), T: simple.Node(i), W: weight},
   316  			simple.WeightedEdge{F: simple.Node(i), T: simple.Node(1), W: weight},
   317  		)
   318  		weight += inc
   319  	}
   320  	return edges
   321  }
   322  
   323  func pathIDs(paths [][]graph.Node) [][]int64 {
   324  	if paths == nil {
   325  		return nil
   326  	}
   327  	ids := make([][]int64, len(paths))
   328  	for i, p := range paths {
   329  		if p == nil {
   330  			continue
   331  		}
   332  		ids[i] = make([]int64, len(p))
   333  		for j, n := range p {
   334  			ids[i][j] = n.ID()
   335  		}
   336  	}
   337  	return ids
   338  }
   339  
   340  func TestYenKSP(t *testing.T) {
   341  	t.Parallel()
   342  	for _, test := range yenShortestPathTests {
   343  		g := test.graph()
   344  		for _, e := range test.edges {
   345  			g.SetWeightedEdge(e)
   346  		}
   347  
   348  		got := YenKShortestPaths(g.(graph.Graph), test.k, test.cost, test.query.From(), test.query.To())
   349  		gotIDs := pathIDs(got)
   350  
   351  		paths := make(byPathWeight, len(gotIDs))
   352  		for i, p := range got {
   353  			paths[i] = yenShortest{path: p, weight: pathWeight(p, g.(graph.Weighted))}
   354  		}
   355  		if !sort.IsSorted(paths) {
   356  			t.Errorf("unexpected result for %q: got:%+v", test.name, paths)
   357  		}
   358  		if test.relaxed {
   359  			continue
   360  		}
   361  
   362  		if len(gotIDs) != 0 {
   363  			first := 0
   364  			last := pathWeight(got[0], g.(graph.Weighted))
   365  			for i := 1; i < len(got); i++ {
   366  				w := pathWeight(got[i], g.(graph.Weighted))
   367  				if w == last {
   368  					continue
   369  				}
   370  				ordered.BySliceValues(gotIDs[first:i])
   371  				first = i
   372  				last = w
   373  			}
   374  			ordered.BySliceValues(gotIDs[first:])
   375  		}
   376  
   377  		if !reflect.DeepEqual(test.wantPaths, gotIDs) {
   378  			t.Errorf("unexpected result for %q:\ngot: %v\nwant:%v", test.name, gotIDs, test.wantPaths)
   379  		}
   380  	}
   381  }
   382  
   383  func pathWeight(path []graph.Node, g graph.Weighted) float64 {
   384  	switch len(path) {
   385  	case 0:
   386  		return math.NaN()
   387  	case 1:
   388  		return 0
   389  	default:
   390  		var w float64
   391  		for i, u := range path[:len(path)-1] {
   392  			_w, _ := g.Weight(u.ID(), path[i+1].ID())
   393  			w += _w
   394  		}
   395  		return w
   396  	}
   397  }