func (g *ReducedUndirected) Has(n graph.Node) bool {
	id := n.ID()
	return 0 <= id || id < int64(len(g.nodes))
}
Has returns whether the node exists within the graph.
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
}
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)
	}
}
type ReducedUndirected struct {
	nodes []community
	undirectedEdges
	communities [][]graph.Node
	parent *ReducedUndirected
}
var (
	reducedUndirected = (*ReducedUndirected)(nil)
	_ graph.WeightedUndirected = reducedUndirected
	_ ReducedGraph             = reducedUndirected
)
func (g *ReducedUndirected) 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 *ReducedUndirected) Structure() [][]graph.Node {
	return g.communities
}
func (g *ReducedUndirected) Expanded() ReducedGraph {
	return g.parent
}
func reduceUndirected(g graph.Undirected, communities [][]graph.Node) *ReducedUndirected {
	if communities == nil {
		if r, ok := g.(*ReducedUndirected); ok {
			return r
		}
		nodes := g.Nodes()
		sort.Sort(ordered.ByID(nodes))
		communities = make([][]graph.Node, len(nodes))
		for i := range nodes {
			communities[i] = []graph.Node{node(i)}
		}
		weight := positiveWeightFuncFor(g)
		r := ReducedUndirected{
			nodes: make([]community, len(nodes)),
			undirectedEdges: undirectedEdges{
				edges:   make([][]int, len(nodes)),
				weights: make(map[[2]int]float64),
			},
			communities: communities,
		}
		communityOf := make(map[int64]int, len(nodes))
		for i, n := range nodes {
			r.nodes[i] = community{id: i, nodes: []graph.Node{n}}
			communityOf[n.ID()] = i
		}
		for _, u := range nodes {
			var out []int
			uid := communityOf[u.ID()]
			for _, v := range g.From(u) {
				vid := communityOf[v.ID()]
				if vid != uid {
					out = append(out, vid)
				}
				if uid < vid {
					r.weights[[2]int{uid, vid}] = weight(u, v)
				}
			}
			r.edges[uid] = out
		}
		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 := ReducedUndirected{
		nodes: make([]community, len(communities)),
		undirectedEdges: undirectedEdges{
			edges:   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.(*ReducedUndirected); ok {
		g.communities = communities
		r.parent = g
	}
	weight := positiveWeightFuncFor(g)
	communityOf := make(map[int64]int, commNodes)
	for i, comm := range communities {
		r.nodes[i] = community{id: i, nodes: comm}
		for _, n := range comm {
			communityOf[n.ID()] = i
		}
	}
	for uid, comm := range communities {
		var out []int
		for i, u := range comm {
			r.nodes[uid].weight += weight(u, u)
			for _, v := range comm[i+1:] {
				r.nodes[uid].weight += 2 * weight(u, v)
			}
			for _, v := range g.From(u) {
				vid := communityOf[v.ID()]
				found := false
				for _, e := range out {
					if e == vid {
						found = true
						break
					}
				}
				if !found && vid != uid {
					out = append(out, vid)
				}
				if uid < vid {
					r.weights[[2]int{uid, vid}] += weight(u, v)
				}
			}
		}
		r.edges[uid] = out
	}
	return &r
}