gonum.org/v1/gonum@v0.14.0/graph/community/louvain_directed_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 community 6 7 import ( 8 "math" 9 "reflect" 10 "sort" 11 "testing" 12 13 "golang.org/x/exp/rand" 14 15 "gonum.org/v1/gonum/floats/scalar" 16 "gonum.org/v1/gonum/graph" 17 "gonum.org/v1/gonum/graph/internal/ordered" 18 "gonum.org/v1/gonum/graph/simple" 19 ) 20 21 type communityDirectedQTest struct { 22 name string 23 g []intset 24 structures []structure 25 26 wantLevels []level 27 } 28 29 var communityDirectedQTests = []communityDirectedQTest{ 30 { 31 name: "simple_directed", 32 g: simpleDirected, 33 // community structure and modularity calculated by C++ implementation: louvain igraph. 34 // Note that louvain igraph returns Q as an unscaled value. 35 structures: []structure{ 36 { 37 resolution: 1, 38 memberships: []intset{ 39 0: linksTo(0, 1), 40 1: linksTo(2, 3, 4), 41 }, 42 want: 0.5714285714285716 / 7, 43 tol: 1e-10, 44 }, 45 }, 46 wantLevels: []level{ 47 { 48 communities: [][]graph.Node{ 49 {simple.Node(0), simple.Node(1)}, 50 {simple.Node(2), simple.Node(3), simple.Node(4)}, 51 }, 52 q: 0.5714285714285716 / 7, 53 }, 54 { 55 communities: [][]graph.Node{ 56 {simple.Node(0)}, 57 {simple.Node(1)}, 58 {simple.Node(2)}, 59 {simple.Node(3)}, 60 {simple.Node(4)}, 61 }, 62 q: -1.2857142857142856 / 7, 63 }, 64 }, 65 }, 66 { 67 name: "zachary", 68 g: zachary, 69 // community structure and modularity calculated by C++ implementation: louvain igraph. 70 // Note that louvain igraph returns Q as an unscaled value. 71 structures: []structure{ 72 { 73 resolution: 1, 74 memberships: []intset{ 75 0: linksTo(0, 1, 2, 3, 7, 11, 12, 13, 17, 19, 21), 76 1: linksTo(4, 5, 6, 10, 16), 77 2: linksTo(8, 9, 14, 15, 18, 20, 22, 26, 29, 30, 32, 33), 78 3: linksTo(23, 24, 25, 27, 28, 31), 79 }, 80 want: 34.3417721519 / 79 /* 5->6 and 6->5 because of co-equal rank */, tol: 1e-4, 81 }, 82 }, 83 wantLevels: []level{ 84 { 85 q: 0.43470597660631316, 86 communities: [][]graph.Node{ 87 {simple.Node(0), simple.Node(1), simple.Node(2), simple.Node(3), simple.Node(7), simple.Node(11), simple.Node(12), simple.Node(13), simple.Node(17), simple.Node(19), simple.Node(21)}, 88 {simple.Node(4), simple.Node(5), simple.Node(6), simple.Node(10), simple.Node(16)}, 89 {simple.Node(8), simple.Node(9), simple.Node(14), simple.Node(15), simple.Node(18), simple.Node(20), simple.Node(22), simple.Node(26), simple.Node(29), simple.Node(30), simple.Node(32), simple.Node(33)}, 90 {simple.Node(23), simple.Node(24), simple.Node(25), simple.Node(27), simple.Node(28), simple.Node(31)}, 91 }, 92 }, 93 { 94 q: 0.3911232174331037, 95 communities: [][]graph.Node{ 96 {simple.Node(0), simple.Node(1), simple.Node(2), simple.Node(3), simple.Node(7), simple.Node(11), simple.Node(12), simple.Node(13), simple.Node(17), simple.Node(19), simple.Node(21)}, 97 {simple.Node(4), simple.Node(10)}, 98 {simple.Node(5), simple.Node(6), simple.Node(16)}, 99 {simple.Node(8), simple.Node(30)}, 100 {simple.Node(9), simple.Node(14), simple.Node(15), simple.Node(18), simple.Node(20), simple.Node(22), simple.Node(32), simple.Node(33)}, 101 {simple.Node(23), simple.Node(24), simple.Node(25), simple.Node(27), simple.Node(28), simple.Node(31)}, 102 {simple.Node(26), simple.Node(29)}, 103 }, 104 }, 105 { 106 q: -0.014580996635154624, 107 communities: [][]graph.Node{ 108 {simple.Node(0)}, 109 {simple.Node(1)}, 110 {simple.Node(2)}, 111 {simple.Node(3)}, 112 {simple.Node(4)}, 113 {simple.Node(5)}, 114 {simple.Node(6)}, 115 {simple.Node(7)}, 116 {simple.Node(8)}, 117 {simple.Node(9)}, 118 {simple.Node(10)}, 119 {simple.Node(11)}, 120 {simple.Node(12)}, 121 {simple.Node(13)}, 122 {simple.Node(14)}, 123 {simple.Node(15)}, 124 {simple.Node(16)}, 125 {simple.Node(17)}, 126 {simple.Node(18)}, 127 {simple.Node(19)}, 128 {simple.Node(20)}, 129 {simple.Node(21)}, 130 {simple.Node(22)}, 131 {simple.Node(23)}, 132 {simple.Node(24)}, 133 {simple.Node(25)}, 134 {simple.Node(26)}, 135 {simple.Node(27)}, 136 {simple.Node(28)}, 137 {simple.Node(29)}, 138 {simple.Node(30)}, 139 {simple.Node(31)}, 140 {simple.Node(32)}, 141 {simple.Node(33)}, 142 }, 143 }, 144 }, 145 }, 146 { 147 name: "blondel", 148 g: blondel, 149 // community structure and modularity calculated by C++ implementation: louvain igraph. 150 // Note that louvain igraph returns Q as an unscaled value. 151 structures: []structure{ 152 { 153 resolution: 1, 154 memberships: []intset{ 155 0: linksTo(0, 1, 2, 3, 4, 5, 6, 7), 156 1: linksTo(8, 9, 10, 11, 12, 13, 14, 15), 157 }, 158 want: 11.1428571429 / 28, tol: 1e-4, 159 }, 160 }, 161 wantLevels: []level{ 162 { 163 q: 0.3979591836734694, 164 communities: [][]graph.Node{ 165 {simple.Node(0), simple.Node(1), simple.Node(2), simple.Node(3), simple.Node(4), simple.Node(5), simple.Node(6), simple.Node(7)}, 166 {simple.Node(8), simple.Node(9), simple.Node(10), simple.Node(11), simple.Node(12), simple.Node(13), simple.Node(14), simple.Node(15)}, 167 }, 168 }, 169 { 170 q: 0.36862244897959184, 171 communities: [][]graph.Node{ 172 {simple.Node(0), simple.Node(1), simple.Node(2), simple.Node(4), simple.Node(5)}, 173 {simple.Node(3), simple.Node(6), simple.Node(7)}, 174 {simple.Node(8), simple.Node(9), simple.Node(10), simple.Node(11), simple.Node(12), simple.Node(13), simple.Node(14), simple.Node(15)}, 175 }, 176 }, 177 { 178 q: -0.022959183673469385, 179 communities: [][]graph.Node{ 180 {simple.Node(0)}, 181 {simple.Node(1)}, 182 {simple.Node(2)}, 183 {simple.Node(3)}, 184 {simple.Node(4)}, 185 {simple.Node(5)}, 186 {simple.Node(6)}, 187 {simple.Node(7)}, 188 {simple.Node(8)}, 189 {simple.Node(9)}, 190 {simple.Node(10)}, 191 {simple.Node(11)}, 192 {simple.Node(12)}, 193 {simple.Node(13)}, 194 {simple.Node(14)}, 195 {simple.Node(15)}, 196 }, 197 }, 198 }, 199 }, 200 } 201 202 func TestCommunityQDirected(t *testing.T) { 203 for _, test := range communityDirectedQTests { 204 g := simple.NewDirectedGraph() 205 for u, e := range test.g { 206 // Add nodes that are not defined by an edge. 207 if g.Node(int64(u)) == nil { 208 g.AddNode(simple.Node(u)) 209 } 210 for v := range e { 211 g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 212 } 213 } 214 215 testCommunityQDirected(t, test, g) 216 } 217 } 218 219 func TestCommunityQWeightedDirected(t *testing.T) { 220 for _, test := range communityDirectedQTests { 221 g := simple.NewWeightedDirectedGraph(0, 0) 222 for u, e := range test.g { 223 // Add nodes that are not defined by an edge. 224 if g.Node(int64(u)) == nil { 225 g.AddNode(simple.Node(u)) 226 } 227 for v := range e { 228 g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1}) 229 } 230 } 231 232 testCommunityQDirected(t, test, g) 233 } 234 } 235 236 func testCommunityQDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) { 237 for _, structure := range test.structures { 238 communities := make([][]graph.Node, len(structure.memberships)) 239 for i, c := range structure.memberships { 240 for n := range c { 241 communities[i] = append(communities[i], simple.Node(n)) 242 } 243 } 244 got := Q(g, communities, structure.resolution) 245 if !scalar.EqualWithinAbsOrRel(got, structure.want, structure.tol, structure.tol) && !math.IsNaN(structure.want) { 246 for _, c := range communities { 247 ordered.ByID(c) 248 } 249 t.Errorf("unexpected Q value for %q %v: got: %v want: %v", 250 test.name, communities, got, structure.want) 251 } 252 } 253 } 254 255 func TestCommunityDeltaQDirected(t *testing.T) { 256 for _, test := range communityDirectedQTests { 257 g := simple.NewDirectedGraph() 258 for u, e := range test.g { 259 // Add nodes that are not defined by an edge. 260 if g.Node(int64(u)) == nil { 261 g.AddNode(simple.Node(u)) 262 } 263 for v := range e { 264 g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 265 } 266 } 267 268 testCommunityDeltaQDirected(t, test, g) 269 } 270 } 271 272 func TestCommunityDeltaQWeightedDirected(t *testing.T) { 273 for _, test := range communityDirectedQTests { 274 g := simple.NewWeightedDirectedGraph(0, 0) 275 for u, e := range test.g { 276 // Add nodes that are not defined by an edge. 277 if g.Node(int64(u)) == nil { 278 g.AddNode(simple.Node(u)) 279 } 280 for v := range e { 281 g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1}) 282 } 283 } 284 285 testCommunityDeltaQDirected(t, test, g) 286 } 287 } 288 289 func testCommunityDeltaQDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) { 290 rnd := rand.New(rand.NewSource(1)).Intn 291 for _, structure := range test.structures { 292 communityOf := make(map[int64]int) 293 communities := make([][]graph.Node, len(structure.memberships)) 294 for i, c := range structure.memberships { 295 for n := range c { 296 n := int64(n) 297 communityOf[n] = i 298 communities[i] = append(communities[i], simple.Node(n)) 299 } 300 ordered.ByID(communities[i]) 301 } 302 303 before := Q(g, communities, structure.resolution) 304 305 l := newDirectedLocalMover(reduceDirected(g, nil), communities, structure.resolution) 306 if l == nil { 307 if !math.IsNaN(before) { 308 t.Errorf("unexpected nil localMover with non-NaN Q graph: Q=%.4v", before) 309 } 310 return 311 } 312 313 // This is done to avoid run-to-run 314 // variation due to map iteration order. 315 ordered.ByID(l.nodes) 316 317 l.shuffle(rnd) 318 319 for _, target := range l.nodes { 320 got, gotDst, gotSrc := l.deltaQ(target) 321 322 want, wantDst := math.Inf(-1), -1 323 migrated := make([][]graph.Node, len(structure.memberships)) 324 for i, c := range structure.memberships { 325 for n := range c { 326 n := int64(n) 327 if n == target.ID() { 328 continue 329 } 330 migrated[i] = append(migrated[i], simple.Node(n)) 331 } 332 ordered.ByID(migrated[i]) 333 } 334 335 for i, c := range structure.memberships { 336 if i == communityOf[target.ID()] { 337 continue 338 } 339 connected := false 340 for n := range c { 341 if g.HasEdgeBetween(int64(n), target.ID()) { 342 connected = true 343 break 344 } 345 } 346 if !connected { 347 continue 348 } 349 migrated[i] = append(migrated[i], target) 350 after := Q(g, migrated, structure.resolution) 351 migrated[i] = migrated[i][:len(migrated[i])-1] 352 if after-before > want { 353 want = after - before 354 wantDst = i 355 } 356 } 357 358 if !scalar.EqualWithinAbsOrRel(got, want, structure.tol, structure.tol) || gotDst != wantDst { 359 t.Errorf("unexpected result moving n=%d in c=%d of %s/%.4v: got: %.4v,%d want: %.4v,%d"+ 360 "\n\t%v\n\t%v", 361 target.ID(), communityOf[target.ID()], test.name, structure.resolution, got, gotDst, want, wantDst, 362 communities, migrated) 363 } 364 if gotSrc.community != communityOf[target.ID()] { 365 t.Errorf("unexpected source community index: got: %d want: %d", gotSrc, communityOf[target.ID()]) 366 } else if communities[gotSrc.community][gotSrc.node].ID() != target.ID() { 367 wantNodeIdx := -1 368 for i, n := range communities[gotSrc.community] { 369 if n.ID() == target.ID() { 370 wantNodeIdx = i 371 break 372 } 373 } 374 t.Errorf("unexpected source node index: got: %d want: %d", gotSrc.node, wantNodeIdx) 375 } 376 } 377 } 378 } 379 380 func TestReduceQConsistencyDirected(t *testing.T) { 381 for _, test := range communityDirectedQTests { 382 g := simple.NewDirectedGraph() 383 for u, e := range test.g { 384 // Add nodes that are not defined by an edge. 385 if g.Node(int64(u)) == nil { 386 g.AddNode(simple.Node(u)) 387 } 388 for v := range e { 389 g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 390 } 391 } 392 393 testReduceQConsistencyDirected(t, test, g) 394 } 395 } 396 397 func TestReduceQConsistencyWeightedDirected(t *testing.T) { 398 for _, test := range communityDirectedQTests { 399 g := simple.NewWeightedDirectedGraph(0, 0) 400 for u, e := range test.g { 401 // Add nodes that are not defined by an edge. 402 if g.Node(int64(u)) == nil { 403 g.AddNode(simple.Node(u)) 404 } 405 for v := range e { 406 g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1}) 407 } 408 } 409 410 testReduceQConsistencyDirected(t, test, g) 411 } 412 } 413 414 func testReduceQConsistencyDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) { 415 for _, structure := range test.structures { 416 if math.IsNaN(structure.want) { 417 return 418 } 419 420 communities := make([][]graph.Node, len(structure.memberships)) 421 for i, c := range structure.memberships { 422 for n := range c { 423 communities[i] = append(communities[i], simple.Node(n)) 424 } 425 ordered.ByID(communities[i]) 426 } 427 428 gQ := Q(g, communities, structure.resolution) 429 gQnull := Q(g, nil, 1) 430 431 cg0 := reduceDirected(g, nil) 432 cg0Qnull := Q(cg0, cg0.Structure(), 1) 433 if !scalar.EqualWithinAbsOrRel(gQnull, cg0Qnull, structure.tol, structure.tol) { 434 t.Errorf("disagreement between null Q from method: %v and function: %v", cg0Qnull, gQnull) 435 } 436 cg0Q := Q(cg0, communities, structure.resolution) 437 if !scalar.EqualWithinAbsOrRel(gQ, cg0Q, structure.tol, structure.tol) { 438 t.Errorf("unexpected Q result after initial reduction: got: %v want :%v", cg0Q, gQ) 439 } 440 441 cg1 := reduceDirected(cg0, communities) 442 cg1Q := Q(cg1, cg1.Structure(), structure.resolution) 443 if !scalar.EqualWithinAbsOrRel(gQ, cg1Q, structure.tol, structure.tol) { 444 t.Errorf("unexpected Q result after second reduction: got: %v want :%v", cg1Q, gQ) 445 } 446 } 447 } 448 449 type localDirectedMoveTest struct { 450 name string 451 g []intset 452 structures []moveStructures 453 } 454 455 var localDirectedMoveTests = []localDirectedMoveTest{ 456 { 457 name: "blondel", 458 g: blondel, 459 structures: []moveStructures{ 460 { 461 memberships: []intset{ 462 0: linksTo(0, 1, 2, 4, 5), 463 1: linksTo(3, 6, 7), 464 2: linksTo(8, 9, 10, 12, 14, 15), 465 3: linksTo(11, 13), 466 }, 467 targetNodes: []graph.Node{simple.Node(0)}, 468 resolution: 1, 469 tol: 1e-14, 470 }, 471 { 472 memberships: []intset{ 473 0: linksTo(0, 1, 2, 4, 5), 474 1: linksTo(3, 6, 7), 475 2: linksTo(8, 9, 10, 12, 14, 15), 476 3: linksTo(11, 13), 477 }, 478 targetNodes: []graph.Node{simple.Node(3)}, 479 resolution: 1, 480 tol: 1e-14, 481 }, 482 { 483 memberships: []intset{ 484 0: linksTo(0, 1, 2, 4, 5), 485 1: linksTo(3, 6, 7), 486 2: linksTo(8, 9, 10, 12, 14, 15), 487 3: linksTo(11, 13), 488 }, 489 // Case to demonstrate when A_aa != k_a^𝛼. 490 targetNodes: []graph.Node{simple.Node(3), simple.Node(2)}, 491 resolution: 1, 492 tol: 1e-14, 493 }, 494 }, 495 }, 496 } 497 498 func TestMoveLocalDirected(t *testing.T) { 499 for _, test := range localDirectedMoveTests { 500 g := simple.NewDirectedGraph() 501 for u, e := range test.g { 502 // Add nodes that are not defined by an edge. 503 if g.Node(int64(u)) == nil { 504 g.AddNode(simple.Node(u)) 505 } 506 for v := range e { 507 g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 508 } 509 } 510 511 testMoveLocalDirected(t, test, g) 512 } 513 } 514 515 func TestMoveLocalWeightedDirected(t *testing.T) { 516 for _, test := range localDirectedMoveTests { 517 g := simple.NewWeightedDirectedGraph(0, 0) 518 for u, e := range test.g { 519 // Add nodes that are not defined by an edge. 520 if g.Node(int64(u)) == nil { 521 g.AddNode(simple.Node(u)) 522 } 523 for v := range e { 524 g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1}) 525 } 526 } 527 528 testMoveLocalDirected(t, test, g) 529 } 530 } 531 532 func testMoveLocalDirected(t *testing.T, test localDirectedMoveTest, g graph.Directed) { 533 for _, structure := range test.structures { 534 communities := make([][]graph.Node, len(structure.memberships)) 535 for i, c := range structure.memberships { 536 for n := range c { 537 communities[i] = append(communities[i], simple.Node(n)) 538 } 539 ordered.ByID(communities[i]) 540 } 541 542 r := reduceDirected(reduceDirected(g, nil), communities) 543 544 l := newDirectedLocalMover(r, r.communities, structure.resolution) 545 for _, n := range structure.targetNodes { 546 dQ, dst, src := l.deltaQ(n) 547 if dQ > 0 { 548 before := Q(r, l.communities, structure.resolution) 549 l.move(dst, src) 550 after := Q(r, l.communities, structure.resolution) 551 want := after - before 552 if !scalar.EqualWithinAbsOrRel(dQ, want, structure.tol, structure.tol) { 553 t.Errorf("unexpected deltaQ for %q: got: %v want: %v", test.name, dQ, want) 554 } 555 } 556 } 557 } 558 } 559 560 func TestModularizeDirected(t *testing.T) { 561 for _, test := range communityDirectedQTests { 562 g := simple.NewDirectedGraph() 563 for u, e := range test.g { 564 // Add nodes that are not defined by an edge. 565 if g.Node(int64(u)) == nil { 566 g.AddNode(simple.Node(u)) 567 } 568 for v := range e { 569 g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) 570 } 571 } 572 573 testModularizeDirected(t, test, g) 574 } 575 } 576 577 func TestModularizeWeightedDirected(t *testing.T) { 578 for _, test := range communityDirectedQTests { 579 g := simple.NewWeightedDirectedGraph(0, 0) 580 for u, e := range test.g { 581 // Add nodes that are not defined by an edge. 582 if g.Node(int64(u)) == nil { 583 g.AddNode(simple.Node(u)) 584 } 585 for v := range e { 586 g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1}) 587 } 588 } 589 590 testModularizeDirected(t, test, g) 591 } 592 } 593 594 func testModularizeDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) { 595 const louvainIterations = 20 596 597 if test.structures[0].resolution != 1 { 598 panic("bad test: expect resolution=1") 599 } 600 want := make([][]graph.Node, len(test.structures[0].memberships)) 601 for i, c := range test.structures[0].memberships { 602 for n := range c { 603 want[i] = append(want[i], simple.Node(n)) 604 } 605 ordered.ByID(want[i]) 606 } 607 ordered.BySliceIDs(want) 608 609 var ( 610 got *ReducedDirected 611 bestQ = math.Inf(-1) 612 ) 613 // Modularize is randomised so we do this to 614 // ensure the level tests are consistent. 615 src := rand.New(rand.NewSource(1)) 616 for i := 0; i < louvainIterations; i++ { 617 r := Modularize(g, 1, src).(*ReducedDirected) 618 if q := Q(r, nil, 1); q > bestQ || math.IsNaN(q) { 619 bestQ = q 620 got = r 621 622 if math.IsNaN(q) { 623 // Don't try again for non-connected case. 624 break 625 } 626 } 627 628 var qs []float64 629 for p := r; p != nil; p = p.Expanded().(*ReducedDirected) { 630 qs = append(qs, Q(p, nil, 1)) 631 } 632 633 // Recovery of Q values is reversed. 634 if reverse(qs); !sort.Float64sAreSorted(qs) { 635 t.Errorf("Q values not monotonically increasing: %.5v", qs) 636 } 637 } 638 639 gotCommunities := got.Communities() 640 for _, c := range gotCommunities { 641 ordered.ByID(c) 642 } 643 ordered.BySliceIDs(gotCommunities) 644 if !reflect.DeepEqual(gotCommunities, want) { 645 t.Errorf("unexpected community membership for %s Q=%.4v:\n\tgot: %v\n\twant:%v", 646 test.name, bestQ, gotCommunities, want) 647 return 648 } 649 650 var levels []level 651 for p := got; p != nil; p = p.Expanded().(*ReducedDirected) { 652 var communities [][]graph.Node 653 if p.parent != nil { 654 communities = p.parent.Communities() 655 for _, c := range communities { 656 ordered.ByID(c) 657 } 658 ordered.BySliceIDs(communities) 659 } else { 660 communities = reduceDirected(g, nil).Communities() 661 } 662 q := Q(p, nil, 1) 663 if math.IsNaN(q) { 664 // Use an equalable flag value in place of NaN. 665 q = math.Inf(-1) 666 } 667 levels = append(levels, level{q: q, communities: communities}) 668 } 669 if !reflect.DeepEqual(levels, test.wantLevels) { 670 t.Errorf("unexpected level structure:\n\tgot: %v\n\twant:%v", levels, test.wantLevels) 671 } 672 } 673 674 func TestNonContiguousDirected(t *testing.T) { 675 g := simple.NewDirectedGraph() 676 for _, e := range []simple.Edge{ 677 {F: simple.Node(0), T: simple.Node(1)}, 678 {F: simple.Node(4), T: simple.Node(5)}, 679 } { 680 g.SetEdge(e) 681 } 682 683 func() { 684 defer func() { 685 r := recover() 686 if r != nil { 687 t.Error("unexpected panic with non-contiguous ID range") 688 } 689 }() 690 Modularize(g, 1, nil) 691 }() 692 } 693 694 func TestNonContiguousWeightedDirected(t *testing.T) { 695 g := simple.NewWeightedDirectedGraph(0, 0) 696 for _, e := range []simple.WeightedEdge{ 697 {F: simple.Node(0), T: simple.Node(1), W: 1}, 698 {F: simple.Node(4), T: simple.Node(5), W: 1}, 699 } { 700 g.SetWeightedEdge(e) 701 } 702 703 func() { 704 defer func() { 705 r := recover() 706 if r != nil { 707 t.Error("unexpected panic with non-contiguous ID range") 708 } 709 }() 710 Modularize(g, 1, nil) 711 }() 712 } 713 714 func BenchmarkLouvainDirected(b *testing.B) { 715 src := rand.New(rand.NewSource(1)) 716 for i := 0; i < b.N; i++ { 717 Modularize(dupGraphDirected, 1, src) 718 } 719 }