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