gonum.org/v1/gonum@v0.14.0/graph/path/dijkstra_test.go (about)

     1  // Copyright ©2015 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  	"testing"
    11  
    12  	"gonum.org/v1/gonum/graph"
    13  	"gonum.org/v1/gonum/graph/internal/ordered"
    14  	"gonum.org/v1/gonum/graph/path/internal/testgraphs"
    15  	"gonum.org/v1/gonum/graph/simple"
    16  	"gonum.org/v1/gonum/graph/traverse"
    17  )
    18  
    19  func TestDijkstraFrom(t *testing.T) {
    20  	t.Parallel()
    21  	for _, test := range testgraphs.ShortestPathTests {
    22  		g := test.Graph()
    23  		for _, e := range test.Edges {
    24  			g.SetWeightedEdge(e)
    25  		}
    26  
    27  		for _, tg := range []struct {
    28  			typ string
    29  			g   traverse.Graph
    30  		}{
    31  			{"complete", g.(graph.Graph)},
    32  			{"incremental", incremental{g.(graph.Weighted)}},
    33  		} {
    34  			var (
    35  				pt Shortest
    36  
    37  				panicked bool
    38  			)
    39  			func() {
    40  				defer func() {
    41  					panicked = recover() != nil
    42  				}()
    43  				pt = DijkstraFrom(test.Query.From(), tg.g)
    44  			}()
    45  			if panicked || test.HasNegativeWeight {
    46  				if !test.HasNegativeWeight {
    47  					t.Errorf("%q %s: unexpected panic", test.Name, tg.typ)
    48  				}
    49  				if !panicked {
    50  					t.Errorf("%q %s: expected panic for negative edge weight", test.Name, tg.typ)
    51  				}
    52  				continue
    53  			}
    54  
    55  			if pt.From().ID() != test.Query.From().ID() {
    56  				t.Fatalf("%q %s: unexpected from node ID: got:%d want:%d", test.Name, tg.typ, pt.From().ID(), test.Query.From().ID())
    57  			}
    58  
    59  			p, weight := pt.To(test.Query.To().ID())
    60  			if weight != test.Weight {
    61  				t.Errorf("%q %s: unexpected weight from To: got:%f want:%f",
    62  					test.Name, tg.typ, weight, test.Weight)
    63  			}
    64  			if weight := pt.WeightTo(test.Query.To().ID()); weight != test.Weight {
    65  				t.Errorf("%q %s: unexpected weight from Weight: got:%f want:%f",
    66  					test.Name, tg.typ, weight, test.Weight)
    67  			}
    68  
    69  			var got []int64
    70  			for _, n := range p {
    71  				got = append(got, n.ID())
    72  			}
    73  			ok := len(got) == 0 && len(test.WantPaths) == 0
    74  			for _, sp := range test.WantPaths {
    75  				if reflect.DeepEqual(got, sp) {
    76  					ok = true
    77  					break
    78  				}
    79  			}
    80  			if !ok {
    81  				t.Errorf("%q %s: unexpected shortest path:\ngot: %v\nwant from:%v",
    82  					test.Name, tg.typ, p, test.WantPaths)
    83  			}
    84  
    85  			np, weight := pt.To(test.NoPathFor.To().ID())
    86  			if pt.From().ID() == test.NoPathFor.From().ID() && (np != nil || !math.IsInf(weight, 1)) {
    87  				t.Errorf("%q %s: unexpected path:\ngot: path=%v weight=%f\nwant:path=<nil> weight=+Inf",
    88  					test.Name, tg.typ, np, weight)
    89  			}
    90  		}
    91  	}
    92  }
    93  
    94  func TestDijkstraAllFrom(t *testing.T) {
    95  	t.Parallel()
    96  	for _, test := range testgraphs.ShortestPathTests {
    97  		g := test.Graph()
    98  		for _, e := range test.Edges {
    99  			g.SetWeightedEdge(e)
   100  		}
   101  
   102  		for _, tg := range []struct {
   103  			typ string
   104  			g   traverse.Graph
   105  		}{
   106  			{"complete", g.(graph.Graph)},
   107  			{"incremental", incremental{g.(graph.Weighted)}},
   108  		} {
   109  			var (
   110  				pt ShortestAlts
   111  
   112  				panicked bool
   113  			)
   114  			func() {
   115  				defer func() {
   116  					panicked = recover() != nil
   117  				}()
   118  				pt = DijkstraAllFrom(test.Query.From(), tg.g)
   119  			}()
   120  			if panicked || test.HasNegativeWeight {
   121  				if !test.HasNegativeWeight {
   122  					t.Errorf("%q %s: unexpected panic", test.Name, tg.typ)
   123  				}
   124  				if !panicked {
   125  					t.Errorf("%q %s: expected panic for negative edge weight", test.Name, tg.typ)
   126  				}
   127  				continue
   128  			}
   129  
   130  			if pt.From().ID() != test.Query.From().ID() {
   131  				t.Fatalf("%q %s: unexpected from node ID: got:%d want:%d", test.Name, tg.typ, pt.From().ID(), test.Query.From().ID())
   132  			}
   133  
   134  			// Test single path results.
   135  			p, weight, unique := pt.To(test.Query.To().ID())
   136  			if weight != test.Weight {
   137  				t.Errorf("%q %s: unexpected weight from To: got:%f want:%f",
   138  					test.Name, tg.typ, weight, test.Weight)
   139  			}
   140  			if weight := pt.WeightTo(test.Query.To().ID()); weight != test.Weight {
   141  				t.Errorf("%q %s: unexpected weight from Weight: got:%f want:%f",
   142  					test.Name, tg.typ, weight, test.Weight)
   143  			}
   144  
   145  			var gotPath []int64
   146  			for _, n := range p {
   147  				gotPath = append(gotPath, n.ID())
   148  			}
   149  			ok := len(gotPath) == 0 && len(test.WantPaths) == 0
   150  			for _, sp := range test.WantPaths {
   151  				if reflect.DeepEqual(gotPath, sp) {
   152  					ok = true
   153  					break
   154  				}
   155  			}
   156  			if !ok {
   157  				t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v",
   158  					test.Name, p, test.WantPaths)
   159  			}
   160  			if unique != test.HasUniquePath {
   161  				t.Errorf("%q: unexpected uniqueness from To: got:%t want:%t (%d paths)",
   162  					test.Name, unique, test.HasUniquePath, len(test.WantPaths))
   163  			}
   164  
   165  			// Test multiple path results.
   166  			paths, weight := pt.AllTo(test.Query.To().ID())
   167  			if weight != test.Weight {
   168  				t.Errorf("%q: unexpected weight from AllTo: got:%f want:%f",
   169  					test.Name, weight, test.Weight)
   170  			}
   171  			if weight := pt.WeightTo(test.Query.To().ID()); weight != test.Weight {
   172  				t.Errorf("%q: unexpected weight from Weight: got:%f want:%f",
   173  					test.Name, weight, test.Weight)
   174  			}
   175  
   176  			var gotPaths [][]int64
   177  			if len(paths) != 0 {
   178  				gotPaths = make([][]int64, len(paths))
   179  			}
   180  			for i, p := range paths {
   181  				for _, v := range p {
   182  					gotPaths[i] = append(gotPaths[i], v.ID())
   183  				}
   184  			}
   185  			ordered.BySliceValues(gotPaths)
   186  			if !reflect.DeepEqual(gotPaths, test.WantPaths) {
   187  				t.Errorf("testing %q: unexpected shortest paths:\ngot: %v\nwant:%v",
   188  					test.Name, gotPaths, test.WantPaths)
   189  			}
   190  
   191  			paths = paths[:0]
   192  			pt.AllToFunc(test.Query.To().ID(), func(path []graph.Node) {
   193  				paths = append(paths, append([]graph.Node(nil), path...))
   194  			})
   195  			if weight != test.Weight {
   196  				t.Errorf("%q %s: unexpected weight from AllTo: got:%f want:%f",
   197  					test.Name, tg.typ, weight, test.Weight)
   198  			}
   199  			if weight := pt.WeightTo(test.Query.To().ID()); !math.IsInf(test.Weight, -1) && weight != test.Weight {
   200  				t.Errorf("%q %s: unexpected weight from Weight: got:%f want:%f",
   201  					test.Name, tg.typ, weight, test.Weight)
   202  			}
   203  
   204  			gotPaths = nil
   205  			if len(paths) != 0 {
   206  				gotPaths = make([][]int64, len(paths))
   207  			}
   208  			for i, p := range paths {
   209  				for _, v := range p {
   210  					gotPaths[i] = append(gotPaths[i], v.ID())
   211  				}
   212  			}
   213  			ordered.BySliceValues(gotPaths)
   214  			if !reflect.DeepEqual(gotPaths, test.WantPaths) {
   215  				t.Errorf("testing %q %s: unexpected shortest paths:\ngot: %v\nwant:%v",
   216  					test.Name, tg.typ, gotPaths, test.WantPaths)
   217  			}
   218  
   219  			// Test absent paths.
   220  			np, weight, unique := pt.To(test.NoPathFor.To().ID())
   221  			if pt.From().ID() == test.NoPathFor.From().ID() && !(np == nil && math.IsInf(weight, 1) && !unique) {
   222  				t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path=<nil> weight=+Inf unique=false",
   223  					test.Name, np, weight, unique)
   224  			}
   225  		}
   226  	}
   227  }
   228  
   229  type weightedTraverseGraph interface {
   230  	traverse.Graph
   231  	Weighted
   232  }
   233  
   234  type incremental struct {
   235  	weightedTraverseGraph
   236  }
   237  
   238  func TestDijkstraAllPaths(t *testing.T) {
   239  	t.Parallel()
   240  	for _, test := range testgraphs.ShortestPathTests {
   241  		g := test.Graph()
   242  		for _, e := range test.Edges {
   243  			g.SetWeightedEdge(e)
   244  		}
   245  
   246  		var (
   247  			pt AllShortest
   248  
   249  			panicked bool
   250  		)
   251  		func() {
   252  			defer func() {
   253  				panicked = recover() != nil
   254  			}()
   255  			pt = DijkstraAllPaths(g.(graph.Graph))
   256  		}()
   257  		if panicked || test.HasNegativeWeight {
   258  			if !test.HasNegativeWeight {
   259  				t.Errorf("%q: unexpected panic", test.Name)
   260  			}
   261  			if !panicked {
   262  				t.Errorf("%q: expected panic for negative edge weight", test.Name)
   263  			}
   264  			continue
   265  		}
   266  
   267  		// Check all random paths returned are OK.
   268  		for i := 0; i < 10; i++ {
   269  			p, weight, unique := pt.Between(test.Query.From().ID(), test.Query.To().ID())
   270  			if weight != test.Weight {
   271  				t.Errorf("%q: unexpected weight from Between: got:%f want:%f",
   272  					test.Name, weight, test.Weight)
   273  			}
   274  			if weight := pt.Weight(test.Query.From().ID(), test.Query.To().ID()); weight != test.Weight {
   275  				t.Errorf("%q: unexpected weight from Weight: got:%f want:%f",
   276  					test.Name, weight, test.Weight)
   277  			}
   278  			if unique != test.HasUniquePath {
   279  				t.Errorf("%q: unexpected number of paths: got: unique=%t want: unique=%t",
   280  					test.Name, unique, test.HasUniquePath)
   281  			}
   282  
   283  			var got []int64
   284  			for _, n := range p {
   285  				got = append(got, n.ID())
   286  			}
   287  			ok := len(got) == 0 && len(test.WantPaths) == 0
   288  			for _, sp := range test.WantPaths {
   289  				if reflect.DeepEqual(got, sp) {
   290  					ok = true
   291  					break
   292  				}
   293  			}
   294  			if !ok {
   295  				t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v",
   296  					test.Name, p, test.WantPaths)
   297  			}
   298  		}
   299  
   300  		np, weight, unique := pt.Between(test.NoPathFor.From().ID(), test.NoPathFor.To().ID())
   301  		if np != nil || !math.IsInf(weight, 1) || unique {
   302  			t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path=<nil> weight=+Inf unique=false",
   303  				test.Name, np, weight, unique)
   304  		}
   305  
   306  		paths, weight := pt.AllBetween(test.Query.From().ID(), test.Query.To().ID())
   307  		if weight != test.Weight {
   308  			t.Errorf("%q: unexpected weight from Between: got:%f want:%f",
   309  				test.Name, weight, test.Weight)
   310  		}
   311  
   312  		var got [][]int64
   313  		if len(paths) != 0 {
   314  			got = make([][]int64, len(paths))
   315  		}
   316  		for i, p := range paths {
   317  			for _, v := range p {
   318  				got[i] = append(got[i], v.ID())
   319  			}
   320  		}
   321  		ordered.BySliceValues(got)
   322  		if !reflect.DeepEqual(got, test.WantPaths) {
   323  			t.Errorf("testing %q: unexpected shortest paths:\ngot: %v\nwant:%v",
   324  				test.Name, got, test.WantPaths)
   325  		}
   326  
   327  		paths = paths[:0]
   328  		pt.AllBetweenFunc(test.Query.From().ID(), test.Query.To().ID(), func(path []graph.Node) {
   329  			paths = append(paths, append([]graph.Node(nil), path...))
   330  		})
   331  		got = nil
   332  		if len(paths) != 0 {
   333  			got = make([][]int64, len(paths))
   334  		}
   335  		for i, p := range paths {
   336  			for _, v := range p {
   337  				got[i] = append(got[i], v.ID())
   338  			}
   339  		}
   340  		ordered.BySliceValues(got)
   341  		if !reflect.DeepEqual(got, test.WantPaths) {
   342  			t.Errorf("testing %q: unexpected shortest paths:\ngot: %v\nwant:%v",
   343  				test.Name, got, test.WantPaths)
   344  		}
   345  
   346  		nps, weight := pt.AllBetween(test.NoPathFor.From().ID(), test.NoPathFor.To().ID())
   347  		if nps != nil || !math.IsInf(weight, 1) {
   348  			t.Errorf("%q: unexpected path:\ngot: paths=%v weight=%f\nwant:path=<nil> weight=+Inf",
   349  				test.Name, nps, weight)
   350  		}
   351  	}
   352  }
   353  
   354  func TestAllShortestAbsentNode(t *testing.T) {
   355  	t.Parallel()
   356  	g := simple.NewUndirectedGraph()
   357  	g.SetEdge(simple.Edge{F: simple.Node(1), T: simple.Node(2)})
   358  	paths := DijkstraAllPaths(g)
   359  	// Confirm we have a good paths tree.
   360  	if _, cost := paths.AllBetween(1, 2); cost != 1 {
   361  		t.Errorf("unexpected cost between existing nodes: got:%v want:1", cost)
   362  	}
   363  
   364  	gotPath, cost, unique := paths.Between(0, 0)
   365  	if cost != 0 {
   366  		t.Errorf("unexpected cost from absent node to itself: got:%v want:0", cost)
   367  	}
   368  	if !unique {
   369  		t.Error("unexpected non-unique path from absent node to itself")
   370  	}
   371  	wantPath := []graph.Node{node(0)}
   372  	if !reflect.DeepEqual(gotPath, wantPath) {
   373  		t.Errorf("unexpected path from absent node to itself: got:%#v want:%#v", gotPath, wantPath)
   374  	}
   375  
   376  	gotPaths, cost := paths.AllBetween(0, 0)
   377  	if cost != 0 {
   378  		t.Errorf("unexpected cost from absent node to itself: got:%v want:0", cost)
   379  	}
   380  	wantPaths := [][]graph.Node{{node(0)}}
   381  	if !reflect.DeepEqual(gotPaths, wantPaths) {
   382  		t.Errorf("unexpected paths from absent node to itself: got:%#v want:%#v", gotPaths, wantPaths)
   383  	}
   384  }