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