func (l *directedMultiplexLocalMover) localMovingHeuristic(rnd func(int) int) (done bool) {
	for {
		l.shuffle(rnd)
		for _, n := range l.nodes {
			dQ, dst, src := l.deltaQ(n)
			if dQ <= deltaQtol {
				continue
			}
			l.move(dst, src)
		}
		if !l.moved {
			return !l.changed
		}
	}
}
localMovingHeuristic performs the Louvain local moving heuristic until
no further moves can be made. It returns a boolean indicating that the
directedMultiplexLocalMover has not made any improvement to the community
structure and so the Louvain algorithm is done.
package community
import (
	"fmt"
	"math"
	"golang.org/x/exp/rand"
	"gonum.org/v1/gonum/graph"
	"gonum.org/v1/gonum/graph/internal/ordered"
	"gonum.org/v1/gonum/graph/internal/set"
	"gonum.org/v1/gonum/graph/iterator"
)
type DirectedMultiplex interface {
	Multiplex
	Layer(l int) graph.Directed
}
func qDirectedMultiplex(g DirectedMultiplex, communities [][]graph.Node, weights, resolutions []float64) []float64 {
	q := make([]float64, g.Depth())
	nodes := graph.NodesOf(g.Nodes())
	layerWeight := 1.0
	layerResolution := 1.0
	if len(resolutions) == 1 {
		layerResolution = resolutions[0]
	}
	for l := 0; l < g.Depth(); l++ {
		layer := g.Layer(l)
		if weights != nil {
			layerWeight = weights[l]
		}
		if layerWeight == 0 {
			continue
		}
		if len(resolutions) > 1 {
			layerResolution = resolutions[l]
		}
		var weight func(xid, yid int64) float64
		if layerWeight < 0 {
			weight = negativeWeightFuncFor(layer)
		} else {
			weight = positiveWeightFuncFor(layer)
		}
		var m float64
		k := make(map[int64]directedWeights, len(nodes))
		for _, n := range nodes {
			var wOut float64
			u := n
			uid := u.ID()
			to := layer.From(uid)
			for to.Next() {
				wOut += weight(uid, to.Node().ID())
			}
			var wIn float64
			v := n
			vid := v.ID()
			from := layer.To(vid)
			for from.Next() {
				wIn += weight(from.Node().ID(), vid)
			}
			id := n.ID()
			w := weight(id, id)
			m += w + wOut  
			k[n.ID()] = directedWeights{out: w + wOut, in: w + wIn}
		}
		if communities == nil {
			var qLayer float64
			for _, u := range nodes {
				uid := u.ID()
				kU := k[uid]
				qLayer += weight(uid, uid) - layerResolution*kU.out*kU.in/m
			}
			q[l] = layerWeight * qLayer
			continue
		}
		var qLayer float64
		for _, c := range communities {
			for _, u := range c {
				uid := u.ID()
				kU := k[uid]
				for _, v := range c {
					vid := v.ID()
					kV := k[vid]
					qLayer += weight(uid, vid) - layerResolution*kU.out*kV.in/m
				}
			}
		}
		q[l] = layerWeight * qLayer
	}
	return q
}
type DirectedLayers []graph.Directed
func NewDirectedLayers(layers ...graph.Directed) (DirectedLayers, error) {
	if len(layers) == 0 {
		return nil, nil
	}
	base := make(set.Int64s)
	nodes := layers[0].Nodes()
	for nodes.Next() {
		base.Add(nodes.Node().ID())
	}
	for i, l := range layers[1:] {
		next := make(set.Int64s)
		nodes := l.Nodes()
		for nodes.Next() {
			next.Add(nodes.Node().ID())
		}
		if !set.Int64sEqual(base, next) {
			return nil, fmt.Errorf("community: layer ID mismatch between layers: %d", i+1)
		}
	}
	return layers, nil
}
func (g DirectedLayers) Nodes() graph.Nodes {
	if len(g) == 0 {
		return nil
	}
	return g[0].Nodes()
}
func (g DirectedLayers) Depth() int { return len(g) }
func (g DirectedLayers) Layer(l int) graph.Directed { return g[l] }
func louvainDirectedMultiplex(g DirectedMultiplex, weights, resolutions []float64, all bool, src rand.Source) *ReducedDirectedMultiplex {
	if weights != nil && len(weights) != g.Depth() {
		panic("community: weights vector length mismatch")
	}
	if resolutions != nil && len(resolutions) != 1 && len(resolutions) != g.Depth() {
		panic("community: resolutions vector length mismatch")
	}
	c := reduceDirectedMultiplex(g, nil, weights)
	rnd := rand.Intn
	if src != nil {
		rnd = rand.New(src).Intn
	}
	for {
		l := newDirectedMultiplexLocalMover(c, c.communities, weights, resolutions, all)
		if l == nil {
			return c
		}
		if done := l.localMovingHeuristic(rnd); done {
			return c
		}
		c = reduceDirectedMultiplex(c, l.communities, weights)
	}
}
type ReducedDirectedMultiplex struct {
	nodes  []multiplexCommunity
	layers []directedEdges
	communities [][]graph.Node
	parent *ReducedDirectedMultiplex
}
var (
	_ DirectedMultiplex      = (*ReducedDirectedMultiplex)(nil)
	_ graph.WeightedDirected = (*directedLayerHandle)(nil)
)
func (g *ReducedDirectedMultiplex) Nodes() graph.Nodes {
	nodes := make([]graph.Node, len(g.nodes))
	for i := range g.nodes {
		nodes[i] = node(i)
	}
	return iterator.NewOrderedNodes(nodes)
}
func (g *ReducedDirectedMultiplex) Depth() int { return len(g.layers) }
func (g *ReducedDirectedMultiplex) Layer(l int) graph.Directed {
	return directedLayerHandle{multiplex: g, layer: l}
}
func (g *ReducedDirectedMultiplex) Communities() [][]graph.Node {
	communities := make([][]graph.Node, len(g.communities))
	if g.parent == nil {
		for i, members := range g.communities {
			comm := make([]graph.Node, len(members))
			for j, n := range members {
				nodes := g.nodes[n.ID()].nodes
				if len(nodes) != 1 {
					panic("community: unexpected number of nodes in base graph community")
				}
				comm[j] = nodes[0]
			}
			communities[i] = comm
		}
		return communities
	}
	sub := g.parent.Communities()
	for i, members := range g.communities {
		var comm []graph.Node
		for _, n := range members {
			comm = append(comm, sub[n.ID()]...)
		}
		communities[i] = comm
	}
	return communities
}
func (g *ReducedDirectedMultiplex) Structure() [][]graph.Node {
	return g.communities
}
func (g *ReducedDirectedMultiplex) Expanded() ReducedMultiplex {
	return g.parent
}
func reduceDirectedMultiplex(g DirectedMultiplex, communities [][]graph.Node, weights []float64) *ReducedDirectedMultiplex {
	if communities == nil {
		if r, ok := g.(*ReducedDirectedMultiplex); ok {
			return r
		}
		nodes := graph.NodesOf(g.Nodes())
		ordered.ByID(nodes)
		communities = make([][]graph.Node, len(nodes))
		for i := range nodes {
			communities[i] = []graph.Node{node(i)}
		}
		r := ReducedDirectedMultiplex{
			nodes:       make([]multiplexCommunity, len(nodes)),
			layers:      make([]directedEdges, g.Depth()),
			communities: communities,
		}
		communityOf := make(map[int64]int, len(nodes))
		for i, n := range nodes {
			r.nodes[i] = multiplexCommunity{id: i, nodes: []graph.Node{n}, weights: make([]float64, depth(weights))}
			communityOf[n.ID()] = i
		}
		for i := range r.layers {
			r.layers[i] = directedEdges{
				edgesFrom: make([][]int, len(nodes)),
				edgesTo:   make([][]int, len(nodes)),
				weights:   make(map[[2]int]float64),
			}
		}
		w := 1.0
		for l := 0; l < g.Depth(); l++ {
			layer := g.Layer(l)
			if weights != nil {
				w = weights[l]
			}
			if w == 0 {
				continue
			}
			var sign float64
			var weight func(xid, yid int64) float64
			if w < 0 {
				sign, weight = -1, negativeWeightFuncFor(layer)
			} else {
				sign, weight = 1, positiveWeightFuncFor(layer)
			}
			for _, n := range nodes {
				id := communityOf[n.ID()]
				var out []int
				u := n
				uid := u.ID()
				to := layer.From(uid)
				for to.Next() {
					vid := to.Node().ID()
					vcid := communityOf[vid]
					if vcid != id {
						out = append(out, vcid)
					}
					r.layers[l].weights[[2]int{id, vcid}] = sign * weight(uid, vid)
				}
				r.layers[l].edgesFrom[id] = out
				var in []int
				v := n
				vid := v.ID()
				from := layer.To(vid)
				for from.Next() {
					uid := from.Node().ID()
					ucid := communityOf[uid]
					if ucid != id {
						in = append(in, ucid)
					}
					r.layers[l].weights[[2]int{ucid, id}] = sign * weight(uid, vid)
				}
				r.layers[l].edgesTo[id] = in
			}
		}
		return &r
	}
	var commNodes int
	for i := 0; i < len(communities); {
		comm := communities[i]
		if len(comm) == 0 {
			communities[i] = communities[len(communities)-1]
			communities[len(communities)-1] = nil
			communities = communities[:len(communities)-1]
		} else {
			commNodes += len(comm)
			i++
		}
	}
	r := ReducedDirectedMultiplex{
		nodes:  make([]multiplexCommunity, len(communities)),
		layers: make([]directedEdges, g.Depth()),
	}
	communityOf := make(map[int64]int, commNodes)
	for i, comm := range communities {
		r.nodes[i] = multiplexCommunity{id: i, nodes: comm, weights: make([]float64, depth(weights))}
		for _, n := range comm {
			communityOf[n.ID()] = i
		}
	}
	for i := range r.layers {
		r.layers[i] = directedEdges{
			edgesFrom: make([][]int, len(communities)),
			edgesTo:   make([][]int, len(communities)),
			weights:   make(map[[2]int]float64),
		}
	}
	r.communities = make([][]graph.Node, len(communities))
	for i := range r.communities {
		r.communities[i] = []graph.Node{node(i)}
	}
	if g, ok := g.(*ReducedDirectedMultiplex); ok {
		g.communities = communities
		r.parent = g
	}
	w := 1.0
	for l := 0; l < g.Depth(); l++ {
		layer := g.Layer(l)
		if weights != nil {
			w = weights[l]
		}
		if w == 0 {
			continue
		}
		var sign float64
		var weight func(xid, yid int64) float64
		if w < 0 {
			sign, weight = -1, negativeWeightFuncFor(layer)
		} else {
			sign, weight = 1, positiveWeightFuncFor(layer)
		}
		for id, comm := range communities {
			var out, in []int
			for _, n := range comm {
				u := n
				uid := u.ID()
				for _, v := range comm {
					r.nodes[id].weights[l] += sign * weight(uid, v.ID())
				}
				to := layer.From(uid)
				for to.Next() {
					vid := to.Node().ID()
					vcid := communityOf[vid]
					found := false
					for _, e := range out {
						if e == vcid {
							found = true
							break
						}
					}
					if !found && vcid != id {
						out = append(out, vcid)
					}
					r.layers[l].weights[[2]int{id, vcid}] += sign * weight(uid, vid) / 2
				}
				v := n
				vid := v.ID()
				from := layer.To(vid)
				for from.Next() {
					uid := from.Node().ID()
					ucid := communityOf[uid]
					found := false
					for _, e := range in {
						if e == ucid {
							found = true
							break
						}
					}
					if !found && ucid != id {
						in = append(in, ucid)
					}
					r.layers[l].weights[[2]int{ucid, id}] += sign * weight(uid, vid) / 2
				}
			}
			r.layers[l].edgesFrom[id] = out
			r.layers[l].edgesTo[id] = in
		}
	}
	return &r
}
type directedLayerHandle struct {
	multiplex *ReducedDirectedMultiplex
	layer int
}
func (g directedLayerHandle) Node(id int64) graph.Node {
	if g.has(id) {
		return g.multiplex.nodes[id]
	}
	return nil
}
func (g directedLayerHandle) has(id int64) bool {
	return 0 <= id && id < int64(len(g.multiplex.nodes))
}
func (g directedLayerHandle) Nodes() graph.Nodes {
	nodes := make([]graph.Node, len(g.multiplex.nodes))
	for i := range g.multiplex.nodes {
		nodes[i] = node(i)
	}
	return iterator.NewOrderedNodes(nodes)
}
func (g directedLayerHandle) From(uid int64) graph.Nodes {
	out := g.multiplex.layers[g.layer].edgesFrom[uid]
	nodes := make([]graph.Node, len(out))
	for i, vid := range out {
		nodes[i] = g.multiplex.nodes[vid]
	}
	return iterator.NewOrderedNodes(nodes)
}
func (g directedLayerHandle) To(vid int64) graph.Nodes {
	in := g.multiplex.layers[g.layer].edgesTo[vid]
	nodes := make([]graph.Node, len(in))
	for i, uid := range in {
		nodes[i] = g.multiplex.nodes[uid]
	}
	return iterator.NewOrderedNodes(nodes)
}
func (g directedLayerHandle) HasEdgeBetween(xid, yid int64) bool {
	if xid == yid {
		return false
	}
	if xid == yid || !isValidID(xid) || !isValidID(yid) {
		return false
	}
	_, ok := g.multiplex.layers[g.layer].weights[[2]int{int(xid), int(yid)}]
	if ok {
		return true
	}
	_, ok = g.multiplex.layers[g.layer].weights[[2]int{int(yid), int(xid)}]
	return ok
}
func (g directedLayerHandle) HasEdgeFromTo(uid, vid int64) bool {
	if uid == vid || !isValidID(uid) || !isValidID(vid) {
		return false
	}
	_, ok := g.multiplex.layers[g.layer].weights[[2]int{int(uid), int(vid)}]
	return ok
}
func (g directedLayerHandle) Edge(uid, vid int64) graph.Edge {
	return g.WeightedEdge(uid, vid)
}
func (g directedLayerHandle) WeightedEdge(uid, vid int64) graph.WeightedEdge {
	if uid == vid || !isValidID(uid) || !isValidID(vid) {
		return nil
	}
	w, ok := g.multiplex.layers[g.layer].weights[[2]int{int(uid), int(vid)}]
	if !ok {
		return nil
	}
	return multiplexEdge{from: g.multiplex.nodes[uid], to: g.multiplex.nodes[vid], weight: w}
}
func (g directedLayerHandle) Weight(xid, yid int64) (w float64, ok bool) {
	if !isValidID(xid) || !isValidID(yid) {
		return 0, false
	}
	if xid == yid {
		return g.multiplex.nodes[xid].weights[g.layer], true
	}
	w, ok = g.multiplex.layers[g.layer].weights[[2]int{int(xid), int(yid)}]
	return w, ok
}
type directedMultiplexLocalMover struct {
	g *ReducedDirectedMultiplex
	nodes []graph.Node
	edgeWeightsOf [][]directedWeights
	m []float64
	weight []func(xid, yid int64) float64
	communities [][]graph.Node
	memberships []int
	resolutions []float64
	weights []float64
	searchAll bool
	moved bool
	changed bool
}
func newDirectedMultiplexLocalMover(g *ReducedDirectedMultiplex, communities [][]graph.Node, weights, resolutions []float64, all bool) *directedMultiplexLocalMover {
	nodes := graph.NodesOf(g.Nodes())
	l := directedMultiplexLocalMover{
		g:             g,
		nodes:         nodes,
		edgeWeightsOf: make([][]directedWeights, g.Depth()),
		m:             make([]float64, g.Depth()),
		communities:   communities,
		memberships:   make([]int, len(nodes)),
		resolutions:   resolutions,
		weights:       weights,
		weight:        make([]func(xid, yid int64) float64, g.Depth()),
	}
	var zero int
	for i := 0; i < g.Depth(); i++ {
		l.edgeWeightsOf[i] = make([]directedWeights, len(nodes))
		var weight func(xid, yid int64) float64
		if weights != nil {
			if weights[i] == 0 {
				zero++
				continue
			}
			if weights[i] < 0 {
				weight = negativeWeightFuncFor(g.Layer(i))
				l.searchAll = all
			} else {
				weight = positiveWeightFuncFor(g.Layer(i))
			}
		} else {
			weight = positiveWeightFuncFor(g.Layer(i))
		}
		l.weight[i] = weight
		layer := g.Layer(i)
		for _, n := range l.nodes {
			u := n
			uid := u.ID()
			var wOut float64
			to := layer.From(uid)
			for to.Next() {
				wOut += weight(uid, to.Node().ID())
			}
			v := n
			vid := v.ID()
			var wIn float64
			from := layer.To(vid)
			for from.Next() {
				wIn += weight(from.Node().ID(), vid)
			}
			id := n.ID()
			w := weight(id, id)
			l.edgeWeightsOf[i][uid] = directedWeights{out: w + wOut, in: w + wIn}
			l.m[i] += w + wOut
		}
		if l.m[i] == 0 {
			zero++
		}
	}
	if zero == g.Depth() {
		return nil
	}
	for i, c := range communities {
		for _, n := range c {
			l.memberships[n.ID()] = i
		}
	}
	return &l
}