blob: af57b081411ffc6d8fa15f47ef10fec6087f3f17 [file] [log] [blame]
// Copyright ©2017 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package community
import (
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/internal/set"
"gonum.org/v1/gonum/graph/simple"
"gonum.org/v1/gonum/graph/topo"
"gonum.org/v1/gonum/graph/traverse"
)
// KCliqueCommunities returns the k-clique communties of the undirected graph g for
// k greater than zero. The returned communities are identified by linkage via k-clique
// adjacency, where adjacency is defined as having k-1 common nodes. KCliqueCommunities
// returns a single component including the full set of nodes of g when k is 1,
// and the classical connected components of g when k is 2. Note that k-clique
// communities may contain common nodes from g.
//
// k-clique communities are described in Palla et al. doi:10.1038/nature03607.
func KCliqueCommunities(k int, g graph.Undirected) [][]graph.Node {
if k < 1 {
panic("community: invalid k for k-clique communities")
}
switch k {
case 1:
return [][]graph.Node{graph.NodesOf(g.Nodes())}
case 2:
return topo.ConnectedComponents(g)
default:
cg := simple.NewUndirectedGraph()
topo.CliqueGraph(cg, g)
cc := kConnectedComponents(k, cg)
// Extract the nodes in g from cg,
// removing duplicates and separating
// cliques smaller than k into separate
// single nodes.
var kcc [][]graph.Node
single := make(set.Nodes)
inCommunity := make(set.Nodes)
for _, c := range cc {
nodes := make(set.Nodes, len(c))
for _, cn := range c {
for _, n := range cn.(topo.Clique).Nodes() {
nodes.Add(n)
}
}
if len(nodes) < k {
for _, n := range nodes {
single.Add(n)
}
continue
}
var kc []graph.Node
for _, n := range nodes {
inCommunity.Add(n)
kc = append(kc, n)
}
kcc = append(kcc, kc)
}
for _, n := range single {
if !inCommunity.Has(n) {
kcc = append(kcc, []graph.Node{n})
}
}
return kcc
}
}
// kConnectedComponents returns the connected components of topo.Clique nodes that
// are joined by k-1 underlying shared nodes in the graph that created the clique
// graph cg.
func kConnectedComponents(k int, cg graph.Undirected) [][]graph.Node {
var (
c []graph.Node
cc [][]graph.Node
)
during := func(n graph.Node) {
c = append(c, n)
}
after := func() {
cc = append(cc, []graph.Node(nil))
cc[len(cc)-1] = append(cc[len(cc)-1], c...)
c = c[:0]
}
w := traverse.DepthFirst{
EdgeFilter: func(e graph.Edge) bool {
return len(e.(topo.CliqueGraphEdge).Nodes()) >= k-1
},
}
w.WalkAll(cg, nil, after, during)
return cc
}