github.com/gopherd/gonum@v0.0.4/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  	"math/rand"
    12  
    13  	"github.com/gopherd/gonum/graph"
    14  	"github.com/gopherd/gonum/graph/internal/ordered"
    15  	"github.com/gopherd/gonum/graph/internal/set"
    16  	"github.com/gopherd/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  }