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] }