gonum.org/v1/gonum@v0.14.0/graph/community/louvain_undirected_multiplex.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 "fmt" 9 "math" 10 11 "golang.org/x/exp/rand" 12 13 "gonum.org/v1/gonum/graph" 14 "gonum.org/v1/gonum/graph/internal/ordered" 15 "gonum.org/v1/gonum/graph/internal/set" 16 "gonum.org/v1/gonum/graph/iterator" 17 ) 18 19 // UndirectedMultiplex is an undirected multiplex graph. 20 type UndirectedMultiplex interface { 21 Multiplex 22 23 // Layer returns the lth layer of the 24 // multiplex graph. 25 Layer(l int) graph.Undirected 26 } 27 28 // qUndirectedMultiplex returns the modularity Q score of the multiplex graph layers 29 // subdivided into the given communities at the given resolutions and weights. Q is 30 // returned as the vector of weighted Q scores for each layer of the multiplex graph. 31 // If communities is nil, the unclustered modularity score is returned. 32 // If weights is nil layers are equally weighted, otherwise the length of 33 // weights must equal the number of layers. If resolutions is nil, a resolution 34 // of 1.0 is used for all layers, otherwise either a single element slice may be used 35 // to specify a global resolution, or the length of resolutions must equal the number 36 // of layers. The resolution parameter is γ as defined in Reichardt and Bornholdt 37 // doi:10.1103/PhysRevE.74.016110. 38 // qUndirectedMultiplex will panic if the graph has any layer weight-scaled edge with 39 // negative edge weight. 40 // 41 // Q_{layer} = w_{layer} \sum_{ij} [ A_{layer}*_{ij} - (\gamma_{layer} k_i k_j)/2m ] \delta(c_i,c_j) 42 // 43 // Note that Q values for multiplex graphs are not scaled by the total layer edge weight. 44 // 45 // graph.Undirect may be used as a shim to allow calculation of Q for 46 // directed graphs. 47 func qUndirectedMultiplex(g UndirectedMultiplex, communities [][]graph.Node, weights, resolutions []float64) []float64 { 48 q := make([]float64, g.Depth()) 49 nodes := graph.NodesOf(g.Nodes()) 50 layerWeight := 1.0 51 layerResolution := 1.0 52 if len(resolutions) == 1 { 53 layerResolution = resolutions[0] 54 } 55 for l := 0; l < g.Depth(); l++ { 56 layer := g.Layer(l) 57 58 if weights != nil { 59 layerWeight = weights[l] 60 } 61 if layerWeight == 0 { 62 continue 63 } 64 65 if len(resolutions) > 1 { 66 layerResolution = resolutions[l] 67 } 68 69 var weight func(xid, yid int64) float64 70 if layerWeight < 0 { 71 weight = negativeWeightFuncFor(layer) 72 } else { 73 weight = positiveWeightFuncFor(layer) 74 } 75 76 // Calculate the total edge weight of the layer 77 // and the table of penetrating edge weight sums. 78 var m2 float64 79 k := make(map[int64]float64, len(nodes)) 80 for _, u := range nodes { 81 uid := u.ID() 82 w := weight(uid, uid) 83 to := layer.From(uid) 84 for to.Next() { 85 w += weight(uid, to.Node().ID()) 86 } 87 m2 += w 88 k[uid] = w 89 } 90 91 if communities == nil { 92 var qLayer float64 93 for _, u := range nodes { 94 uid := u.ID() 95 kU := k[uid] 96 qLayer += weight(uid, uid) - layerResolution*kU*kU/m2 97 } 98 q[l] = layerWeight * qLayer 99 continue 100 } 101 102 // Iterate over the communities, calculating 103 // the non-self edge weights for the upper 104 // triangle and adjust the diagonal. 105 var qLayer float64 106 for _, c := range communities { 107 for i, u := range c { 108 uid := u.ID() 109 kU := k[uid] 110 qLayer += weight(uid, uid) - layerResolution*kU*kU/m2 111 for _, v := range c[i+1:] { 112 vid := v.ID() 113 qLayer += 2 * (weight(uid, vid) - layerResolution*kU*k[vid]/m2) 114 } 115 } 116 } 117 q[l] = layerWeight * qLayer 118 } 119 120 return q 121 } 122 123 // UndirectedLayers implements UndirectedMultiplex. 124 type UndirectedLayers []graph.Undirected 125 126 // NewUndirectedLayers returns an UndirectedLayers using the provided layers 127 // ensuring there is a match between IDs for each layer. 128 func NewUndirectedLayers(layers ...graph.Undirected) (UndirectedLayers, error) { 129 if len(layers) == 0 { 130 return nil, nil 131 } 132 base := make(set.Int64s) 133 nodes := layers[0].Nodes() 134 for nodes.Next() { 135 base.Add(nodes.Node().ID()) 136 } 137 for i, l := range layers[1:] { 138 next := make(set.Int64s) 139 nodes := l.Nodes() 140 for nodes.Next() { 141 next.Add(nodes.Node().ID()) 142 } 143 if !set.Int64sEqual(next, base) { 144 return nil, fmt.Errorf("community: layer ID mismatch between layers: %d", i+1) 145 } 146 } 147 return layers, nil 148 } 149 150 // Nodes returns the nodes of the receiver. 151 func (g UndirectedLayers) Nodes() graph.Nodes { 152 if len(g) == 0 { 153 return nil 154 } 155 return g[0].Nodes() 156 } 157 158 // Depth returns the depth of the multiplex graph. 159 func (g UndirectedLayers) Depth() int { return len(g) } 160 161 // Layer returns the lth layer of the multiplex graph. 162 func (g UndirectedLayers) Layer(l int) graph.Undirected { return g[l] } 163 164 // louvainUndirectedMultiplex returns the hierarchical modularization of g at the given resolution 165 // using the Louvain algorithm. If all is true and g has negatively weighted layers, all 166 // communities will be searched during the modularization. If src is nil, rand.Intn is 167 // used as the random generator. louvainUndirectedMultiplex will panic if g has any edge with 168 // edge weight that does not sign-match the layer weight. 169 // 170 // graph.Undirect may be used as a shim to allow modularization of directed graphs. 171 func louvainUndirectedMultiplex(g UndirectedMultiplex, weights, resolutions []float64, all bool, src rand.Source) *ReducedUndirectedMultiplex { 172 if weights != nil && len(weights) != g.Depth() { 173 panic("community: weights vector length mismatch") 174 } 175 if resolutions != nil && len(resolutions) != 1 && len(resolutions) != g.Depth() { 176 panic("community: resolutions vector length mismatch") 177 } 178 179 // See louvain.tex for a detailed description 180 // of the algorithm used here. 181 182 c := reduceUndirectedMultiplex(g, nil, weights) 183 rnd := rand.Intn 184 if src != nil { 185 rnd = rand.New(src).Intn 186 } 187 for { 188 l := newUndirectedMultiplexLocalMover(c, c.communities, weights, resolutions, all) 189 if l == nil { 190 return c 191 } 192 if done := l.localMovingHeuristic(rnd); done { 193 return c 194 } 195 c = reduceUndirectedMultiplex(c, l.communities, weights) 196 } 197 } 198 199 // ReducedUndirectedMultiplex is an undirected graph of communities derived from a 200 // parent graph by reduction. 201 type ReducedUndirectedMultiplex struct { 202 // nodes is the set of nodes held 203 // by the graph. In a ReducedUndirectedMultiplex 204 // the node ID is the index into 205 // nodes. 206 nodes []multiplexCommunity 207 layers []undirectedEdges 208 209 // communities is the community 210 // structure of the graph. 211 communities [][]graph.Node 212 213 parent *ReducedUndirectedMultiplex 214 } 215 216 var ( 217 _ UndirectedMultiplex = (*ReducedUndirectedMultiplex)(nil) 218 _ graph.WeightedUndirected = (*undirectedLayerHandle)(nil) 219 ) 220 221 // Nodes returns all the nodes in the graph. 222 func (g *ReducedUndirectedMultiplex) Nodes() graph.Nodes { 223 nodes := make([]graph.Node, len(g.nodes)) 224 for i := range g.nodes { 225 nodes[i] = node(i) 226 } 227 return iterator.NewOrderedNodes(nodes) 228 } 229 230 // Depth returns the number of layers in the multiplex graph. 231 func (g *ReducedUndirectedMultiplex) Depth() int { return len(g.layers) } 232 233 // Layer returns the lth layer of the multiplex graph. 234 func (g *ReducedUndirectedMultiplex) Layer(l int) graph.Undirected { 235 return undirectedLayerHandle{multiplex: g, layer: l} 236 } 237 238 // Communities returns the community memberships of the nodes in the 239 // graph used to generate the reduced graph. 240 func (g *ReducedUndirectedMultiplex) Communities() [][]graph.Node { 241 communities := make([][]graph.Node, len(g.communities)) 242 if g.parent == nil { 243 for i, members := range g.communities { 244 comm := make([]graph.Node, len(members)) 245 for j, n := range members { 246 nodes := g.nodes[n.ID()].nodes 247 if len(nodes) != 1 { 248 panic("community: unexpected number of nodes in base graph community") 249 } 250 comm[j] = nodes[0] 251 } 252 communities[i] = comm 253 } 254 return communities 255 } 256 sub := g.parent.Communities() 257 for i, members := range g.communities { 258 var comm []graph.Node 259 for _, n := range members { 260 comm = append(comm, sub[n.ID()]...) 261 } 262 communities[i] = comm 263 } 264 return communities 265 } 266 267 // Structure returns the community structure of the current level of 268 // the module clustering. The first index of the returned value 269 // corresponds to the index of the nodes in the next higher level if 270 // it exists. The returned value should not be mutated. 271 func (g *ReducedUndirectedMultiplex) Structure() [][]graph.Node { 272 return g.communities 273 } 274 275 // Expanded returns the next lower level of the module clustering or nil 276 // if at the lowest level. 277 func (g *ReducedUndirectedMultiplex) Expanded() ReducedMultiplex { 278 return g.parent 279 } 280 281 // reduceUndirectedMultiplex returns a reduced graph constructed from g divided 282 // into the given communities. The communities value is mutated 283 // by the call to reduceUndirectedMultiplex. If communities is nil and g is a 284 // ReducedUndirectedMultiplex, it is returned unaltered. 285 func reduceUndirectedMultiplex(g UndirectedMultiplex, communities [][]graph.Node, weights []float64) *ReducedUndirectedMultiplex { 286 if communities == nil { 287 if r, ok := g.(*ReducedUndirectedMultiplex); ok { 288 return r 289 } 290 291 nodes := graph.NodesOf(g.Nodes()) 292 // TODO(kortschak) This sort is necessary really only 293 // for testing. In practice we would not be using the 294 // community provided by the user for a Q calculation. 295 // Probably we should use a function to map the 296 // communities in the test sets to the remapped order. 297 ordered.ByID(nodes) 298 communities = make([][]graph.Node, len(nodes)) 299 for i := range nodes { 300 communities[i] = []graph.Node{node(i)} 301 } 302 303 r := ReducedUndirectedMultiplex{ 304 nodes: make([]multiplexCommunity, len(nodes)), 305 layers: make([]undirectedEdges, g.Depth()), 306 communities: communities, 307 } 308 communityOf := make(map[int64]int, len(nodes)) 309 for i, n := range nodes { 310 r.nodes[i] = multiplexCommunity{id: i, nodes: []graph.Node{n}, weights: make([]float64, depth(weights))} 311 communityOf[n.ID()] = i 312 } 313 for i := range r.layers { 314 r.layers[i] = undirectedEdges{ 315 edges: make([][]int, len(nodes)), 316 weights: make(map[[2]int]float64), 317 } 318 } 319 w := 1.0 320 for l := 0; l < g.Depth(); l++ { 321 layer := g.Layer(l) 322 if weights != nil { 323 w = weights[l] 324 } 325 if w == 0 { 326 continue 327 } 328 var sign float64 329 var weight func(xid, yid int64) float64 330 if w < 0 { 331 sign, weight = -1, negativeWeightFuncFor(layer) 332 } else { 333 sign, weight = 1, positiveWeightFuncFor(layer) 334 } 335 for _, u := range nodes { 336 var out []int 337 uid := u.ID() 338 ucid := communityOf[uid] 339 to := layer.From(uid) 340 for to.Next() { 341 vid := to.Node().ID() 342 vcid := communityOf[vid] 343 if vcid != ucid { 344 out = append(out, vcid) 345 } 346 if ucid < vcid { 347 // Only store the weight once. 348 r.layers[l].weights[[2]int{ucid, vcid}] = sign * weight(uid, vid) 349 } 350 } 351 r.layers[l].edges[ucid] = out 352 } 353 } 354 return &r 355 } 356 357 // Remove zero length communities destructively. 358 var commNodes int 359 for i := 0; i < len(communities); { 360 comm := communities[i] 361 if len(comm) == 0 { 362 communities[i] = communities[len(communities)-1] 363 communities[len(communities)-1] = nil 364 communities = communities[:len(communities)-1] 365 } else { 366 commNodes += len(comm) 367 i++ 368 } 369 } 370 371 r := ReducedUndirectedMultiplex{ 372 nodes: make([]multiplexCommunity, len(communities)), 373 layers: make([]undirectedEdges, g.Depth()), 374 } 375 communityOf := make(map[int64]int, commNodes) 376 for i, comm := range communities { 377 r.nodes[i] = multiplexCommunity{id: i, nodes: comm, weights: make([]float64, depth(weights))} 378 for _, n := range comm { 379 communityOf[n.ID()] = i 380 } 381 } 382 for i := range r.layers { 383 r.layers[i] = undirectedEdges{ 384 edges: make([][]int, len(communities)), 385 weights: make(map[[2]int]float64), 386 } 387 } 388 r.communities = make([][]graph.Node, len(communities)) 389 for i := range r.communities { 390 r.communities[i] = []graph.Node{node(i)} 391 } 392 if g, ok := g.(*ReducedUndirectedMultiplex); ok { 393 // Make sure we retain the truncated 394 // community structure. 395 g.communities = communities 396 r.parent = g 397 } 398 w := 1.0 399 for l := 0; l < g.Depth(); l++ { 400 layer := g.Layer(l) 401 if weights != nil { 402 w = weights[l] 403 } 404 if w == 0 { 405 continue 406 } 407 var sign float64 408 var weight func(xid, yid int64) float64 409 if w < 0 { 410 sign, weight = -1, negativeWeightFuncFor(layer) 411 } else { 412 sign, weight = 1, positiveWeightFuncFor(layer) 413 } 414 for ucid, comm := range communities { 415 var out []int 416 for i, u := range comm { 417 uid := u.ID() 418 r.nodes[ucid].weights[l] += sign * weight(uid, uid) 419 for _, v := range comm[i+1:] { 420 r.nodes[ucid].weights[l] += 2 * sign * weight(uid, v.ID()) 421 } 422 to := layer.From(uid) 423 for to.Next() { 424 vid := to.Node().ID() 425 vcid := communityOf[vid] 426 found := false 427 for _, e := range out { 428 if e == vcid { 429 found = true 430 break 431 } 432 } 433 if !found && vcid != ucid { 434 out = append(out, vcid) 435 } 436 if ucid < vcid { 437 // Only store the weight once. 438 r.layers[l].weights[[2]int{ucid, vcid}] += sign * weight(uid, vid) 439 } 440 } 441 } 442 r.layers[l].edges[ucid] = out 443 } 444 } 445 return &r 446 } 447 448 // undirectedLayerHandle is a handle to a multiplex graph layer. 449 type undirectedLayerHandle struct { 450 // multiplex is the complete 451 // multiplex graph. 452 multiplex *ReducedUndirectedMultiplex 453 454 // layer is an index into the 455 // multiplex for the current 456 // layer. 457 layer int 458 } 459 460 // Node returns the node with the given ID if it exists in the graph, 461 // and nil otherwise. 462 func (g undirectedLayerHandle) Node(id int64) graph.Node { 463 if g.has(id) { 464 return g.multiplex.nodes[id] 465 } 466 return nil 467 } 468 469 // has returns whether the node exists within the graph. 470 func (g undirectedLayerHandle) has(id int64) bool { 471 return 0 <= id && id < int64(len(g.multiplex.nodes)) 472 } 473 474 // Nodes returns all the nodes in the graph. 475 func (g undirectedLayerHandle) Nodes() graph.Nodes { 476 nodes := make([]graph.Node, len(g.multiplex.nodes)) 477 for i := range g.multiplex.nodes { 478 nodes[i] = node(i) 479 } 480 return iterator.NewOrderedNodes(nodes) 481 } 482 483 // From returns all nodes in g that can be reached directly from u. 484 func (g undirectedLayerHandle) From(uid int64) graph.Nodes { 485 out := g.multiplex.layers[g.layer].edges[uid] 486 nodes := make([]graph.Node, len(out)) 487 for i, vid := range out { 488 nodes[i] = g.multiplex.nodes[vid] 489 } 490 return iterator.NewOrderedNodes(nodes) 491 } 492 493 // HasEdgeBetween returns whether an edge exists between nodes x and y. 494 func (g undirectedLayerHandle) HasEdgeBetween(xid, yid int64) bool { 495 if xid == yid || !isValidID(xid) || !isValidID(yid) { 496 return false 497 } 498 if xid > yid { 499 xid, yid = yid, xid 500 } 501 _, ok := g.multiplex.layers[g.layer].weights[[2]int{int(xid), int(yid)}] 502 return ok 503 } 504 505 // Edge returns the edge from u to v if such an edge exists and nil otherwise. 506 // The node v must be directly reachable from u as defined by the From method. 507 func (g undirectedLayerHandle) Edge(uid, vid int64) graph.Edge { 508 return g.WeightedEdgeBetween(uid, vid) 509 } 510 511 // WeightedEdge returns the weighted edge from u to v if such an edge exists and nil otherwise. 512 // The node v must be directly reachable from u as defined by the From method. 513 func (g undirectedLayerHandle) WeightedEdge(uid, vid int64) graph.WeightedEdge { 514 return g.WeightedEdgeBetween(uid, vid) 515 } 516 517 // EdgeBetween returns the edge between nodes x and y. 518 func (g undirectedLayerHandle) EdgeBetween(xid, yid int64) graph.Edge { 519 return g.WeightedEdgeBetween(xid, yid) 520 } 521 522 // WeightedEdgeBetween returns the weighted edge between nodes x and y. 523 func (g undirectedLayerHandle) WeightedEdgeBetween(xid, yid int64) graph.WeightedEdge { 524 if xid == yid || !isValidID(xid) || !isValidID(yid) { 525 return nil 526 } 527 if yid < xid { 528 xid, yid = yid, xid 529 } 530 w, ok := g.multiplex.layers[g.layer].weights[[2]int{int(xid), int(yid)}] 531 if !ok { 532 return nil 533 } 534 return multiplexEdge{from: g.multiplex.nodes[xid], to: g.multiplex.nodes[yid], weight: w} 535 } 536 537 // Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge. 538 // If x and y are the same node the internal node weight is returned. If there is no joining 539 // edge between the two nodes the weight value returned is zero. Weight returns true if an edge 540 // exists between x and y or if x and y have the same ID, false otherwise. 541 func (g undirectedLayerHandle) Weight(xid, yid int64) (w float64, ok bool) { 542 if !isValidID(xid) || !isValidID(yid) { 543 return 0, false 544 } 545 if xid == yid { 546 return g.multiplex.nodes[xid].weights[g.layer], true 547 } 548 if xid > yid { 549 xid, yid = yid, xid 550 } 551 w, ok = g.multiplex.layers[g.layer].weights[[2]int{int(xid), int(yid)}] 552 return w, ok 553 } 554 555 // undirectedMultiplexLocalMover is a step in graph modularity optimization. 556 type undirectedMultiplexLocalMover struct { 557 g *ReducedUndirectedMultiplex 558 559 // nodes is the set of working nodes. 560 nodes []graph.Node 561 // edgeWeightOf is the weighted degree 562 // of each node indexed by ID. 563 edgeWeightOf [][]float64 564 565 // m2 is the total sum of 566 // edge weights in g. 567 m2 []float64 568 569 // weight is the weight function 570 // provided by g or a function 571 // that returns the Weight value 572 // of the non-nil edge between x 573 // and y. 574 weight []func(xid, yid int64) float64 575 576 // communities is the current 577 // division of g. 578 communities [][]graph.Node 579 // memberships is a mapping between 580 // node ID and community membership. 581 memberships []int 582 583 // resolution is the Reichardt and 584 // Bornholdt γ parameter as defined 585 // in doi:10.1103/PhysRevE.74.016110. 586 resolutions []float64 587 588 // weights is the layer weights for 589 // the modularisation. 590 weights []float64 591 592 // searchAll specifies whether the local 593 // mover should consider non-connected 594 // communities during the local moving 595 // heuristic. 596 searchAll bool 597 598 // moved indicates that a call to 599 // move has been made since the last 600 // call to shuffle. 601 moved bool 602 603 // changed indicates that a move 604 // has been made since the creation 605 // of the local mover. 606 changed bool 607 } 608 609 // newUndirectedMultiplexLocalMover returns a new undirectedMultiplexLocalMover initialized with 610 // the graph g, a set of communities and a modularity resolution parameter. The 611 // node IDs of g must be contiguous in [0,n) where n is the number of nodes. 612 // If g has a zero edge weight sum, nil is returned. 613 func newUndirectedMultiplexLocalMover(g *ReducedUndirectedMultiplex, communities [][]graph.Node, weights, resolutions []float64, all bool) *undirectedMultiplexLocalMover { 614 nodes := graph.NodesOf(g.Nodes()) 615 l := undirectedMultiplexLocalMover{ 616 g: g, 617 nodes: nodes, 618 edgeWeightOf: make([][]float64, g.Depth()), 619 m2: make([]float64, g.Depth()), 620 communities: communities, 621 memberships: make([]int, len(nodes)), 622 resolutions: resolutions, 623 weights: weights, 624 weight: make([]func(xid, yid int64) float64, g.Depth()), 625 } 626 627 // Calculate the total edge weight of the graph 628 // and degree weights for each node. 629 var zero int 630 for i := 0; i < g.Depth(); i++ { 631 l.edgeWeightOf[i] = make([]float64, len(nodes)) 632 var weight func(xid, yid int64) float64 633 634 if weights != nil { 635 if weights[i] == 0 { 636 zero++ 637 continue 638 } 639 if weights[i] < 0 { 640 weight = negativeWeightFuncFor(g.Layer(i)) 641 l.searchAll = all 642 } else { 643 weight = positiveWeightFuncFor(g.Layer(i)) 644 } 645 } else { 646 weight = positiveWeightFuncFor(g.Layer(i)) 647 } 648 649 l.weight[i] = weight 650 layer := g.Layer(i) 651 for _, u := range l.nodes { 652 uid := u.ID() 653 w := weight(uid, uid) 654 to := layer.From(uid) 655 for to.Next() { 656 w += weight(uid, to.Node().ID()) 657 } 658 l.edgeWeightOf[i][uid] = w 659 l.m2[i] += w 660 } 661 if l.m2[i] == 0 { 662 zero++ 663 } 664 } 665 if zero == g.Depth() { 666 return nil 667 } 668 669 // Assign membership mappings. 670 for i, c := range communities { 671 for _, u := range c { 672 l.memberships[u.ID()] = i 673 } 674 } 675 676 return &l 677 } 678 679 // localMovingHeuristic performs the Louvain local moving heuristic until 680 // no further moves can be made. It returns a boolean indicating that the 681 // undirectedMultiplexLocalMover has not made any improvement to the community 682 // structure and so the Louvain algorithm is done. 683 func (l *undirectedMultiplexLocalMover) localMovingHeuristic(rnd func(int) int) (done bool) { 684 for { 685 l.shuffle(rnd) 686 for _, n := range l.nodes { 687 dQ, dst, src := l.deltaQ(n) 688 if dQ <= deltaQtol { 689 continue 690 } 691 l.move(dst, src) 692 } 693 if !l.moved { 694 return !l.changed 695 } 696 } 697 } 698 699 // shuffle performs a Fisher-Yates shuffle on the nodes held by the 700 // undirectedMultiplexLocalMover using the random source rnd which should return 701 // an integer in the range [0,n). 702 func (l *undirectedMultiplexLocalMover) shuffle(rnd func(n int) int) { 703 l.moved = false 704 for i := range l.nodes[:len(l.nodes)-1] { 705 j := i + rnd(len(l.nodes)-i) 706 l.nodes[i], l.nodes[j] = l.nodes[j], l.nodes[i] 707 } 708 } 709 710 // move moves the node at src to the community at dst. 711 func (l *undirectedMultiplexLocalMover) move(dst int, src commIdx) { 712 l.moved = true 713 l.changed = true 714 715 srcComm := l.communities[src.community] 716 n := srcComm[src.node] 717 718 l.memberships[n.ID()] = dst 719 720 l.communities[dst] = append(l.communities[dst], n) 721 srcComm[src.node], srcComm[len(srcComm)-1] = srcComm[len(srcComm)-1], nil 722 l.communities[src.community] = srcComm[:len(srcComm)-1] 723 } 724 725 // deltaQ returns the highest gain in modularity attainable by moving 726 // n from its current community to another connected community and 727 // the index of the chosen destination. The index into the 728 // undirectedMultiplexLocalMover's communities field is returned in src if n 729 // is in communities. 730 func (l *undirectedMultiplexLocalMover) deltaQ(n graph.Node) (deltaQ float64, dst int, src commIdx) { 731 id := n.ID() 732 733 var iterator minTaker 734 if l.searchAll { 735 iterator = &dense{n: len(l.communities)} 736 } else { 737 // Find communities connected to n. 738 connected := make(set.Ints) 739 // The following for loop is equivalent to: 740 // 741 // for i := 0; i < l.g.Depth(); i++ { 742 // for _, v := range l.g.Layer(i).From(n) { 743 // connected.Add(l.memberships[v.ID()]) 744 // } 745 // } 746 // 747 // This is done to avoid an allocation for 748 // each layer. 749 for _, layer := range l.g.layers { 750 for _, vid := range layer.edges[id] { 751 connected.Add(l.memberships[vid]) 752 } 753 } 754 // Insert the node's own community. 755 connected.Add(l.memberships[id]) 756 iterator = newSlice(connected) 757 } 758 759 // Calculate the highest modularity gain 760 // from moving into another community and 761 // keep the index of that community. 762 var dQremove float64 763 dQadd, dst, src := math.Inf(-1), -1, commIdx{-1, -1} 764 var i int 765 for iterator.TakeMin(&i) { 766 c := l.communities[i] 767 var removal bool 768 var _dQadd float64 769 for layer := 0; layer < l.g.Depth(); layer++ { 770 m2 := l.m2[layer] 771 if m2 == 0 { 772 // Do not consider layers with zero sum edge weight. 773 continue 774 } 775 w := 1.0 776 if l.weights != nil { 777 w = l.weights[layer] 778 } 779 if w == 0 { 780 // Do not consider layers with zero weighting. 781 continue 782 } 783 784 var k_aC, sigma_totC float64 // C is a substitution for ^𝛼 or ^𝛽. 785 removal = false 786 for j, u := range c { 787 uid := u.ID() 788 if uid == id { 789 // Only mark and check src community on the first layer. 790 if layer == 0 { 791 if src.community != -1 { 792 panic("community: multiple sources") 793 } 794 src = commIdx{i, j} 795 } 796 removal = true 797 } 798 799 k_aC += l.weight[layer](id, uid) 800 // sigma_totC could be kept for each community 801 // and updated for moves, changing the calculation 802 // of sigma_totC here from O(n_c) to O(1), but 803 // in practice the time savings do not appear 804 // to be compelling and do not make up for the 805 // increase in code complexity and space required. 806 sigma_totC += l.edgeWeightOf[layer][uid] 807 } 808 809 a_aa := l.weight[layer](id, id) 810 k_a := l.edgeWeightOf[layer][id] 811 gamma := 1.0 812 if l.resolutions != nil { 813 if len(l.resolutions) == 1 { 814 gamma = l.resolutions[0] 815 } else { 816 gamma = l.resolutions[layer] 817 } 818 } 819 820 // See louvain.tex for a derivation of these equations. 821 // The weighting term, w, is described in V Traag, 822 // "Algorithms and dynamical models for communities and 823 // reputation in social networks", chapter 5. 824 // http://www.traag.net/wp/wp-content/papercite-data/pdf/traag_algorithms_2013.pdf 825 switch { 826 case removal: 827 // The community c was the current community, 828 // so calculate the change due to removal. 829 dQremove += w * (k_aC /*^𝛼*/ - a_aa - gamma*k_a*(sigma_totC /*^𝛼*/ -k_a)/m2) 830 831 default: 832 // Otherwise calculate the change due to an addition 833 // to c. 834 _dQadd += w * (k_aC /*^𝛽*/ - gamma*k_a*sigma_totC /*^𝛽*/ /m2) 835 } 836 } 837 if !removal && _dQadd > dQadd { 838 dQadd = _dQadd 839 dst = i 840 } 841 } 842 843 return 2 * (dQadd - dQremove), dst, src 844 }