gonum.org/v1/gonum@v0.14.0/graph/network/betweenness_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 network
     6  
     7  import (
     8  	"fmt"
     9  	"math"
    10  	"sort"
    11  	"testing"
    12  
    13  	"gonum.org/v1/gonum/floats/scalar"
    14  	"gonum.org/v1/gonum/graph/path"
    15  	"gonum.org/v1/gonum/graph/simple"
    16  )
    17  
    18  var betweennessTests = []struct {
    19  	g []set
    20  
    21  	wantTol   float64
    22  	want      map[int64]float64
    23  	wantEdges map[[2]int64]float64
    24  }{
    25  	{
    26  		// Example graph from http://en.wikipedia.org/wiki/File:PageRanks-Example.svg 16:17, 8 July 2009
    27  		g: []set{
    28  			A: nil,
    29  			B: linksTo(C),
    30  			C: linksTo(B),
    31  			D: linksTo(A, B),
    32  			E: linksTo(D, B, F),
    33  			F: linksTo(B, E),
    34  			G: linksTo(B, E),
    35  			H: linksTo(B, E),
    36  			I: linksTo(B, E),
    37  			J: linksTo(E),
    38  			K: linksTo(E),
    39  		},
    40  
    41  		wantTol: 1e-1,
    42  		want: map[int64]float64{
    43  			B: 32,
    44  			D: 18,
    45  			E: 48,
    46  		},
    47  		wantEdges: map[[2]int64]float64{
    48  			{A, D}: 20,
    49  			{B, C}: 20,
    50  			{B, D}: 16,
    51  			{B, E}: 12,
    52  			{B, F}: 9,
    53  			{B, G}: 9,
    54  			{B, H}: 9,
    55  			{B, I}: 9,
    56  			{D, E}: 20,
    57  			{E, F}: 11,
    58  			{E, G}: 11,
    59  			{E, H}: 11,
    60  			{E, I}: 11,
    61  			{E, J}: 20,
    62  			{E, K}: 20,
    63  		},
    64  	},
    65  	{
    66  		// Example graph from http://en.wikipedia.org/w/index.php?title=PageRank&oldid=659286279#Power_Method
    67  		g: []set{
    68  			A: linksTo(B, C),
    69  			B: linksTo(D),
    70  			C: linksTo(D, E),
    71  			D: linksTo(E),
    72  			E: linksTo(A),
    73  		},
    74  
    75  		wantTol: 1e-3,
    76  		want: map[int64]float64{
    77  			A: 2,
    78  			B: 0.6667,
    79  			C: 0.6667,
    80  			D: 2,
    81  			E: 0.6667,
    82  		},
    83  		wantEdges: map[[2]int64]float64{
    84  			{A, B}: 2 + 2/3. + 4/2.,
    85  			{A, C}: 2 + 2/3. + 2/2.,
    86  			{A, E}: 2 + 2/3. + 2/2.,
    87  			{B, D}: 2 + 2/3. + 4/2.,
    88  			{C, D}: 2 + 2/3. + 2/2.,
    89  			{C, E}: 2,
    90  			{D, E}: 2 + 2/3. + 2/2.,
    91  		},
    92  	},
    93  	{
    94  		g: []set{
    95  			A: linksTo(B),
    96  			B: linksTo(C),
    97  			C: nil,
    98  		},
    99  
   100  		wantTol: 1e-3,
   101  		want: map[int64]float64{
   102  			B: 2,
   103  		},
   104  		wantEdges: map[[2]int64]float64{
   105  			{A, B}: 4,
   106  			{B, C}: 4,
   107  		},
   108  	},
   109  	{
   110  		g: []set{
   111  			A: linksTo(B),
   112  			B: linksTo(C),
   113  			C: linksTo(D),
   114  			D: linksTo(E),
   115  			E: nil,
   116  		},
   117  
   118  		wantTol: 1e-3,
   119  		want: map[int64]float64{
   120  			B: 6,
   121  			C: 8,
   122  			D: 6,
   123  		},
   124  		wantEdges: map[[2]int64]float64{
   125  			{A, B}: 8,
   126  			{B, C}: 12,
   127  			{C, D}: 12,
   128  			{D, E}: 8,
   129  		},
   130  	},
   131  	{
   132  		g: []set{
   133  			A: linksTo(C),
   134  			B: linksTo(C),
   135  			C: nil,
   136  			D: linksTo(C),
   137  			E: linksTo(C),
   138  		},
   139  
   140  		wantTol: 1e-3,
   141  		want: map[int64]float64{
   142  			C: 12,
   143  		},
   144  		wantEdges: map[[2]int64]float64{
   145  			{A, C}: 8,
   146  			{B, C}: 8,
   147  			{C, D}: 8,
   148  			{C, E}: 8,
   149  		},
   150  	},
   151  	{
   152  		g: []set{
   153  			A: linksTo(B, C, D, E),
   154  			B: linksTo(C, D, E),
   155  			C: linksTo(D, E),
   156  			D: linksTo(E),
   157  			E: nil,
   158  		},
   159  
   160  		wantTol: 1e-3,
   161  		want:    map[int64]float64{},
   162  		wantEdges: map[[2]int64]float64{
   163  			{A, B}: 2,
   164  			{A, C}: 2,
   165  			{A, D}: 2,
   166  			{A, E}: 2,
   167  			{B, C}: 2,
   168  			{B, D}: 2,
   169  			{B, E}: 2,
   170  			{C, D}: 2,
   171  			{C, E}: 2,
   172  			{D, E}: 2,
   173  		},
   174  	},
   175  }
   176  
   177  func TestBetweenness(t *testing.T) {
   178  	for i, test := range betweennessTests {
   179  		g := simple.NewUndirectedGraph()
   180  		for u, e := range test.g {
   181  			// Add nodes that are not defined by an edge.
   182  			if g.Node(int64(u)) == nil {
   183  				g.AddNode(simple.Node(u))
   184  			}
   185  			for v := range e {
   186  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
   187  			}
   188  		}
   189  		got := Betweenness(g)
   190  		prec := 1 - int(math.Log10(test.wantTol))
   191  		for n := range test.g {
   192  			wantN, gotOK := got[int64(n)]
   193  			gotN, wantOK := test.want[int64(n)]
   194  			if gotOK != wantOK {
   195  				t.Errorf("unexpected betweenness result for test %d, node %c", i, n+'A')
   196  			}
   197  			if !scalar.EqualWithinAbsOrRel(gotN, wantN, test.wantTol, test.wantTol) {
   198  				t.Errorf("unexpected betweenness result for test %d:\ngot: %v\nwant:%v",
   199  					i, orderedFloats(got, prec), orderedFloats(test.want, prec))
   200  				break
   201  			}
   202  		}
   203  	}
   204  }
   205  
   206  func TestEdgeBetweenness(t *testing.T) {
   207  	for i, test := range betweennessTests {
   208  		g := simple.NewUndirectedGraph()
   209  		for u, e := range test.g {
   210  			// Add nodes that are not defined by an edge.
   211  			if g.Node(int64(u)) == nil {
   212  				g.AddNode(simple.Node(u))
   213  			}
   214  			for v := range e {
   215  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
   216  			}
   217  		}
   218  		got := EdgeBetweenness(g)
   219  		prec := 1 - int(math.Log10(test.wantTol))
   220  	outer:
   221  		for u := range test.g {
   222  			for v := range test.g {
   223  				wantQ, gotOK := got[[2]int64{int64(u), int64(v)}]
   224  				gotQ, wantOK := test.wantEdges[[2]int64{int64(u), int64(v)}]
   225  				if gotOK != wantOK {
   226  					t.Errorf("unexpected betweenness result for test %d, edge (%c,%c)", i, u+'A', v+'A')
   227  				}
   228  				if !scalar.EqualWithinAbsOrRel(gotQ, wantQ, test.wantTol, test.wantTol) {
   229  					t.Errorf("unexpected betweenness result for test %d:\ngot: %v\nwant:%v",
   230  						i, orderedPairFloats(got, prec), orderedPairFloats(test.wantEdges, prec))
   231  					break outer
   232  				}
   233  			}
   234  		}
   235  	}
   236  }
   237  
   238  func TestBetweennessWeighted(t *testing.T) {
   239  	for i, test := range betweennessTests {
   240  		g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
   241  		for u, e := range test.g {
   242  			// Add nodes that are not defined by an edge.
   243  			if g.Node(int64(u)) == nil {
   244  				g.AddNode(simple.Node(u))
   245  			}
   246  			for v := range e {
   247  				g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1})
   248  			}
   249  		}
   250  
   251  		p, ok := path.FloydWarshall(g)
   252  		if !ok {
   253  			t.Errorf("unexpected negative cycle in test %d", i)
   254  			continue
   255  		}
   256  
   257  		got := BetweennessWeighted(g, p)
   258  		prec := 1 - int(math.Log10(test.wantTol))
   259  		for n := range test.g {
   260  			gotN, gotOK := got[int64(n)]
   261  			wantN, wantOK := test.want[int64(n)]
   262  			if gotOK != wantOK {
   263  				t.Errorf("unexpected betweenness existence for test %d, node %c", i, n+'A')
   264  			}
   265  			if !scalar.EqualWithinAbsOrRel(gotN, wantN, test.wantTol, test.wantTol) {
   266  				t.Errorf("unexpected betweenness result for test %d:\ngot: %v\nwant:%v",
   267  					i, orderedFloats(got, prec), orderedFloats(test.want, prec))
   268  				break
   269  			}
   270  		}
   271  	}
   272  }
   273  
   274  func TestEdgeBetweennessWeighted(t *testing.T) {
   275  	for i, test := range betweennessTests {
   276  		g := simple.NewWeightedUndirectedGraph(0, math.Inf(1))
   277  		for u, e := range test.g {
   278  			// Add nodes that are not defined by an edge.
   279  			if g.Node(int64(u)) == nil {
   280  				g.AddNode(simple.Node(u))
   281  			}
   282  			for v := range e {
   283  				g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1})
   284  			}
   285  		}
   286  
   287  		p, ok := path.FloydWarshall(g)
   288  		if !ok {
   289  			t.Errorf("unexpected negative cycle in test %d", i)
   290  			continue
   291  		}
   292  
   293  		got := EdgeBetweennessWeighted(g, p)
   294  		prec := 1 - int(math.Log10(test.wantTol))
   295  	outer:
   296  		for u := range test.g {
   297  			for v := range test.g {
   298  				wantQ, gotOK := got[[2]int64{int64(u), int64(v)}]
   299  				gotQ, wantOK := test.wantEdges[[2]int64{int64(u), int64(v)}]
   300  				if gotOK != wantOK {
   301  					t.Errorf("unexpected betweenness result for test %d, edge (%c,%c)", i, u+'A', v+'A')
   302  				}
   303  				if !scalar.EqualWithinAbsOrRel(gotQ, wantQ, test.wantTol, test.wantTol) {
   304  					t.Errorf("unexpected betweenness result for test %d:\ngot: %v\nwant:%v",
   305  						i, orderedPairFloats(got, prec), orderedPairFloats(test.wantEdges, prec))
   306  					break outer
   307  				}
   308  			}
   309  		}
   310  	}
   311  }
   312  
   313  func orderedPairFloats(w map[[2]int64]float64, prec int) []pairKeyFloatVal {
   314  	o := make(orderedPairFloatsMap, 0, len(w))
   315  	for k, v := range w {
   316  		o = append(o, pairKeyFloatVal{prec: prec, key: k, val: v})
   317  	}
   318  	sort.Sort(o)
   319  	return o
   320  }
   321  
   322  type pairKeyFloatVal struct {
   323  	prec int
   324  	key  [2]int64
   325  	val  float64
   326  }
   327  
   328  func (kv pairKeyFloatVal) String() string {
   329  	return fmt.Sprintf("(%c,%c):%.*f", kv.key[0]+'A', kv.key[1]+'A', kv.prec, kv.val)
   330  }
   331  
   332  type orderedPairFloatsMap []pairKeyFloatVal
   333  
   334  func (o orderedPairFloatsMap) Len() int { return len(o) }
   335  func (o orderedPairFloatsMap) Less(i, j int) bool {
   336  	return o[i].key[0] < o[j].key[0] || (o[i].key[0] == o[j].key[0] && o[i].key[1] < o[j].key[1])
   337  }
   338  func (o orderedPairFloatsMap) Swap(i, j int) { o[i], o[j] = o[j], o[i] }