github.com/gopherd/gonum@v0.0.4/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  	"github.com/gopherd/gonum/graph"
    14  	"github.com/gopherd/gonum/graph/internal/ordered"
    15  	"github.com/gopherd/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  	wantPaths [][]int64
    26  
    27  	relaxed bool
    28  }{
    29  	{
    30  		// https://en.wikipedia.org/w/index.php?title=Yen%27s_algorithm&oldid=841018784#Example
    31  		name:  "wikipedia example",
    32  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
    33  		edges: []simple.WeightedEdge{
    34  			{F: simple.Node('C'), T: simple.Node('D'), W: 3},
    35  			{F: simple.Node('C'), T: simple.Node('E'), W: 2},
    36  			{F: simple.Node('E'), T: simple.Node('D'), W: 1},
    37  			{F: simple.Node('D'), T: simple.Node('F'), W: 4},
    38  			{F: simple.Node('E'), T: simple.Node('F'), W: 2},
    39  			{F: simple.Node('E'), T: simple.Node('G'), W: 3},
    40  			{F: simple.Node('F'), T: simple.Node('G'), W: 2},
    41  			{F: simple.Node('F'), T: simple.Node('H'), W: 1},
    42  			{F: simple.Node('G'), T: simple.Node('H'), W: 2},
    43  		},
    44  		query: simple.Edge{F: simple.Node('C'), T: simple.Node('H')},
    45  		k:     3,
    46  		wantPaths: [][]int64{
    47  			{'C', 'E', 'F', 'H'},
    48  			{'C', 'E', 'G', 'H'},
    49  			{'C', 'D', 'F', 'H'},
    50  		},
    51  	},
    52  	{
    53  		name:  "1 edge graph",
    54  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
    55  		edges: []simple.WeightedEdge{
    56  			{F: simple.Node(0), T: simple.Node(1), W: 3},
    57  		},
    58  		query: simple.Edge{F: simple.Node(0), T: simple.Node(1)},
    59  		k:     10,
    60  		wantPaths: [][]int64{
    61  			{0, 1},
    62  		},
    63  	},
    64  	{
    65  		name:      "empty graph",
    66  		graph:     func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
    67  		edges:     []simple.WeightedEdge{},
    68  		query:     simple.Edge{F: simple.Node(0), T: simple.Node(1)},
    69  		k:         1,
    70  		wantPaths: nil,
    71  	},
    72  	{
    73  		name:  "n-star graph",
    74  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
    75  		edges: []simple.WeightedEdge{
    76  			{F: simple.Node(0), T: simple.Node(1), W: 3},
    77  			{F: simple.Node(0), T: simple.Node(2), W: 3},
    78  			{F: simple.Node(0), T: simple.Node(3), W: 3},
    79  		},
    80  		query: simple.Edge{F: simple.Node(0), T: simple.Node(1)},
    81  		k:     1,
    82  		wantPaths: [][]int64{
    83  			{0, 1},
    84  		},
    85  	},
    86  	{
    87  		name:  "bipartite small",
    88  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
    89  		edges: bipartite(5, 3, 0),
    90  		query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)},
    91  		k:     10,
    92  		wantPaths: [][]int64{
    93  			{-1, 2, 1},
    94  			{-1, 3, 1},
    95  			{-1, 4, 1},
    96  			{-1, 5, 1},
    97  			{-1, 6, 1},
    98  		},
    99  	},
   100  	{
   101  		name:  "bipartite parity",
   102  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   103  		edges: bipartite(5, 3, 0),
   104  		query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)},
   105  		k:     5,
   106  		wantPaths: [][]int64{
   107  			{-1, 2, 1},
   108  			{-1, 3, 1},
   109  			{-1, 4, 1},
   110  			{-1, 5, 1},
   111  			{-1, 6, 1},
   112  		},
   113  	},
   114  	{
   115  		name:    "bipartite large",
   116  		graph:   func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   117  		edges:   bipartite(10, 3, 0),
   118  		query:   simple.Edge{F: simple.Node(-1), T: simple.Node(1)},
   119  		k:       5,
   120  		relaxed: true,
   121  	},
   122  	{
   123  		name:  "bipartite inc",
   124  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   125  		edges: bipartite(5, 10, 1),
   126  		query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)},
   127  		k:     5,
   128  		wantPaths: [][]int64{
   129  			{-1, 2, 1},
   130  			{-1, 3, 1},
   131  			{-1, 4, 1},
   132  			{-1, 5, 1},
   133  			{-1, 6, 1},
   134  		},
   135  	},
   136  	{
   137  		name:  "bipartite dec",
   138  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   139  		edges: bipartite(5, 10, -1),
   140  		query: simple.Edge{F: simple.Node(-1), T: simple.Node(1)},
   141  		k:     5,
   142  		wantPaths: [][]int64{
   143  			{-1, 6, 1},
   144  			{-1, 5, 1},
   145  			{-1, 4, 1},
   146  			{-1, 3, 1},
   147  			{-1, 2, 1},
   148  		},
   149  	},
   150  	{
   151  		name:  "waterfall", // This is the failing case in gonum/gonum#1700.
   152  		graph: func() graph.WeightedEdgeAdder { return simple.NewWeightedDirectedGraph(0, math.Inf(1)) },
   153  		edges: []simple.WeightedEdge{
   154  			{F: simple.Node(0), T: simple.Node(1), W: 1},
   155  			{F: simple.Node(1), T: simple.Node(2), W: 1},
   156  			{F: simple.Node(1), T: simple.Node(3), W: 1},
   157  			{F: simple.Node(1), T: simple.Node(5), W: 1},
   158  			{F: simple.Node(2), T: simple.Node(4), W: 1},
   159  			{F: simple.Node(2), T: simple.Node(5), W: 1},
   160  			{F: simple.Node(3), T: simple.Node(6), W: 1},
   161  			{F: simple.Node(4), T: simple.Node(6), W: 1},
   162  			{F: simple.Node(5), T: simple.Node(6), W: 1},
   163  			{F: simple.Node(5), T: simple.Node(6), W: 1},
   164  		},
   165  		query: simple.Edge{F: simple.Node(0), T: simple.Node(6)},
   166  		k:     4,
   167  		wantPaths: [][]int64{
   168  			{0, 1, 3, 6},
   169  			{0, 1, 5, 6},
   170  			{0, 1, 2, 4, 6},
   171  			{0, 1, 2, 5, 6},
   172  		},
   173  	},
   174  }
   175  
   176  func bipartite(n int, weight, inc float64) []simple.WeightedEdge {
   177  	var edges []simple.WeightedEdge
   178  	for i := 2; i < n+2; i++ {
   179  		edges = append(edges,
   180  			simple.WeightedEdge{F: simple.Node(-1), T: simple.Node(i), W: weight},
   181  			simple.WeightedEdge{F: simple.Node(i), T: simple.Node(1), W: weight},
   182  		)
   183  		weight += inc
   184  	}
   185  	return edges
   186  }
   187  
   188  func pathIDs(paths [][]graph.Node) [][]int64 {
   189  	if paths == nil {
   190  		return nil
   191  	}
   192  	ids := make([][]int64, len(paths))
   193  	for i, p := range paths {
   194  		if p == nil {
   195  			continue
   196  		}
   197  		ids[i] = make([]int64, len(p))
   198  		for j, n := range p {
   199  			ids[i][j] = n.ID()
   200  		}
   201  	}
   202  	return ids
   203  }
   204  
   205  func TestYenKSP(t *testing.T) {
   206  	t.Parallel()
   207  	for _, test := range yenShortestPathTests {
   208  		g := test.graph()
   209  		for _, e := range test.edges {
   210  			g.SetWeightedEdge(e)
   211  		}
   212  
   213  		got := YenKShortestPaths(g.(graph.Graph), test.k, test.query.From(), test.query.To())
   214  		gotIDs := pathIDs(got)
   215  
   216  		paths := make(byPathWeight, len(gotIDs))
   217  		for i, p := range got {
   218  			paths[i] = yenShortest{path: p, weight: pathWeight(p, g.(graph.Weighted))}
   219  		}
   220  		if !sort.IsSorted(paths) {
   221  			t.Errorf("unexpected result for %q: got:%+v", test.name, paths)
   222  		}
   223  		if test.relaxed {
   224  			continue
   225  		}
   226  
   227  		if len(gotIDs) != 0 {
   228  			first := 0
   229  			last := pathWeight(got[0], g.(graph.Weighted))
   230  			for i := 1; i < len(got); i++ {
   231  				w := pathWeight(got[i], g.(graph.Weighted))
   232  				if w == last {
   233  					continue
   234  				}
   235  				ordered.BySliceValues(gotIDs[first:i])
   236  				first = i
   237  				last = w
   238  			}
   239  			ordered.BySliceValues(gotIDs[first:])
   240  		}
   241  
   242  		if !reflect.DeepEqual(test.wantPaths, gotIDs) {
   243  			t.Errorf("unexpected result for %q:\ngot: %v\nwant:%v", test.name, gotIDs, test.wantPaths)
   244  		}
   245  	}
   246  }
   247  
   248  func pathWeight(path []graph.Node, g graph.Weighted) float64 {
   249  	switch len(path) {
   250  	case 0:
   251  		return math.NaN()
   252  	case 1:
   253  		return 0
   254  	default:
   255  		var w float64
   256  		for i, u := range path[:len(path)-1] {
   257  			_w, _ := g.Weight(u.ID(), path[i+1].ID())
   258  			w += _w
   259  		}
   260  		return w
   261  	}
   262  }