func louvainUndirected(g graph.Undirected, resolution float64, src *rand.Rand) *ReducedUndirected {
	c := reduceUndirected(g, nil)
	rnd := rand.Intn
	if src != nil {
		rnd = src.Intn
	}
	for {
		l := newUndirectedLocalMover(c, c.communities, resolution)
		if l == nil {
			return c
		}
		if done := l.localMovingHeuristic(rnd); done {
			return c
		}
		c = reduceUndirected(c, l.communities)
	}
}
louvainUndirected returns the hierarchical modularization of g at the given
resolution using the Louvain algorithm. If src is nil, rand.Intn is used as
the random generator. louvainUndirected will panic if g has any edge with negative edge
weight.

graph.Undirect may be used as a shim to allow modularization of directed graphs.
package community
import (
	"math"
	"math/rand"
	"sort"
	"gonum.org/v1/gonum/graph"
	"gonum.org/v1/gonum/graph/internal/ordered"
	"gonum.org/v1/gonum/graph/internal/set"
)
func qUndirected(g graph.Undirected, communities [][]graph.Node, resolution float64) float64 {
	nodes := g.Nodes()
	weight := positiveWeightFuncFor(g)
	var m2 float64
	k := make(map[int64]float64, len(nodes))
	for _, u := range nodes {
		w := weight(u, u)
		for _, v := range g.From(u) {
			w += weight(u, v)
		}
		m2 += w
		k[u.ID()] = w
	}
	if communities == nil {
		var q float64
		for _, u := range nodes {
			kU := k[u.ID()]
			q += weight(u, u) - resolution*kU*kU/m2
		}
		return q / m2
	}
	var q float64
	for _, c := range communities {
		for i, u := range c {
			kU := k[u.ID()]
			q += weight(u, u) - resolution*kU*kU/m2
			for _, v := range c[i+1:] {
				q += 2 * (weight(u, v) - resolution*kU*k[v.ID()]/m2)
			}
		}
	}
	return q / m2
}