github.com/gopherd/gonum@v0.0.4/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  	"github.com/gopherd/gonum/graph"
    13  	"github.com/gopherd/gonum/graph/internal/ordered"
    14  	"github.com/gopherd/gonum/graph/path/internal/testgraphs"
    15  	"github.com/gopherd/gonum/graph/simple"
    16  	"github.com/gopherd/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  			// Test absent paths.
   192  			np, weight, unique := pt.To(test.NoPathFor.To().ID())
   193  			if pt.From().ID() == test.NoPathFor.From().ID() && !(np == nil && math.IsInf(weight, 1) && !unique) {
   194  				t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path=<nil> weight=+Inf unique=false",
   195  					test.Name, np, weight, unique)
   196  			}
   197  		}
   198  	}
   199  }
   200  
   201  type weightedTraverseGraph interface {
   202  	traverse.Graph
   203  	Weighted
   204  }
   205  
   206  type incremental struct {
   207  	weightedTraverseGraph
   208  }
   209  
   210  func TestDijkstraAllPaths(t *testing.T) {
   211  	t.Parallel()
   212  	for _, test := range testgraphs.ShortestPathTests {
   213  		g := test.Graph()
   214  		for _, e := range test.Edges {
   215  			g.SetWeightedEdge(e)
   216  		}
   217  
   218  		var (
   219  			pt AllShortest
   220  
   221  			panicked bool
   222  		)
   223  		func() {
   224  			defer func() {
   225  				panicked = recover() != nil
   226  			}()
   227  			pt = DijkstraAllPaths(g.(graph.Graph))
   228  		}()
   229  		if panicked || test.HasNegativeWeight {
   230  			if !test.HasNegativeWeight {
   231  				t.Errorf("%q: unexpected panic", test.Name)
   232  			}
   233  			if !panicked {
   234  				t.Errorf("%q: expected panic for negative edge weight", test.Name)
   235  			}
   236  			continue
   237  		}
   238  
   239  		// Check all random paths returned are OK.
   240  		for i := 0; i < 10; i++ {
   241  			p, weight, unique := pt.Between(test.Query.From().ID(), test.Query.To().ID())
   242  			if weight != test.Weight {
   243  				t.Errorf("%q: unexpected weight from Between: got:%f want:%f",
   244  					test.Name, weight, test.Weight)
   245  			}
   246  			if weight := pt.Weight(test.Query.From().ID(), test.Query.To().ID()); weight != test.Weight {
   247  				t.Errorf("%q: unexpected weight from Weight: got:%f want:%f",
   248  					test.Name, weight, test.Weight)
   249  			}
   250  			if unique != test.HasUniquePath {
   251  				t.Errorf("%q: unexpected number of paths: got: unique=%t want: unique=%t",
   252  					test.Name, unique, test.HasUniquePath)
   253  			}
   254  
   255  			var got []int64
   256  			for _, n := range p {
   257  				got = append(got, n.ID())
   258  			}
   259  			ok := len(got) == 0 && len(test.WantPaths) == 0
   260  			for _, sp := range test.WantPaths {
   261  				if reflect.DeepEqual(got, sp) {
   262  					ok = true
   263  					break
   264  				}
   265  			}
   266  			if !ok {
   267  				t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v",
   268  					test.Name, p, test.WantPaths)
   269  			}
   270  		}
   271  
   272  		np, weight, unique := pt.Between(test.NoPathFor.From().ID(), test.NoPathFor.To().ID())
   273  		if np != nil || !math.IsInf(weight, 1) || unique {
   274  			t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path=<nil> weight=+Inf unique=false",
   275  				test.Name, np, weight, unique)
   276  		}
   277  
   278  		paths, weight := pt.AllBetween(test.Query.From().ID(), test.Query.To().ID())
   279  		if weight != test.Weight {
   280  			t.Errorf("%q: unexpected weight from Between: got:%f want:%f",
   281  				test.Name, weight, test.Weight)
   282  		}
   283  
   284  		var got [][]int64
   285  		if len(paths) != 0 {
   286  			got = make([][]int64, len(paths))
   287  		}
   288  		for i, p := range paths {
   289  			for _, v := range p {
   290  				got[i] = append(got[i], v.ID())
   291  			}
   292  		}
   293  		ordered.BySliceValues(got)
   294  		if !reflect.DeepEqual(got, test.WantPaths) {
   295  			t.Errorf("testing %q: unexpected shortest paths:\ngot: %v\nwant:%v",
   296  				test.Name, got, test.WantPaths)
   297  		}
   298  
   299  		nps, weight := pt.AllBetween(test.NoPathFor.From().ID(), test.NoPathFor.To().ID())
   300  		if nps != nil || !math.IsInf(weight, 1) {
   301  			t.Errorf("%q: unexpected path:\ngot: paths=%v weight=%f\nwant:path=<nil> weight=+Inf",
   302  				test.Name, nps, weight)
   303  		}
   304  	}
   305  }
   306  
   307  func TestAllShortestAbsentNode(t *testing.T) {
   308  	t.Parallel()
   309  	g := simple.NewUndirectedGraph()
   310  	g.SetEdge(simple.Edge{F: simple.Node(1), T: simple.Node(2)})
   311  	paths := DijkstraAllPaths(g)
   312  	// Confirm we have a good paths tree.
   313  	if _, cost := paths.AllBetween(1, 2); cost != 1 {
   314  		t.Errorf("unexpected cost between existing nodes: got:%v want:1", cost)
   315  	}
   316  
   317  	gotPath, cost, unique := paths.Between(0, 0)
   318  	if cost != 0 {
   319  		t.Errorf("unexpected cost from absent node to itself: got:%v want:0", cost)
   320  	}
   321  	if !unique {
   322  		t.Error("unexpected non-unique path from absent node to itself")
   323  	}
   324  	wantPath := []graph.Node{node(0)}
   325  	if !reflect.DeepEqual(gotPath, wantPath) {
   326  		t.Errorf("unexpected path from absent node to itself: got:%#v want:%#v", gotPath, wantPath)
   327  	}
   328  
   329  	gotPaths, cost := paths.AllBetween(0, 0)
   330  	if cost != 0 {
   331  		t.Errorf("unexpected cost from absent node to itself: got:%v want:0", cost)
   332  	}
   333  	wantPaths := [][]graph.Node{{node(0)}}
   334  	if !reflect.DeepEqual(gotPaths, wantPaths) {
   335  		t.Errorf("unexpected paths from absent node to itself: got:%#v want:%#v", gotPaths, wantPaths)
   336  	}
   337  }