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