gonum.org/v1/gonum@v0.14.0/graph/community/louvain_directed_multiplex.go (about)

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