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  }