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