mirror of
https://github.com/gonum/gonum.git
synced 2025-10-06 07:37:03 +08:00
259 lines
7.2 KiB
Go
259 lines
7.2 KiB
Go
// Copyright ©2015 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 network
|
|
|
|
import (
|
|
"math"
|
|
|
|
"gonum.org/v1/gonum/graph"
|
|
"gonum.org/v1/gonum/graph/internal/linear"
|
|
"gonum.org/v1/gonum/graph/path"
|
|
)
|
|
|
|
// Betweenness returns the non-zero betweenness centrality for nodes in the unweighted graph g.
|
|
//
|
|
// C_B(v) = \sum_{s ≠ v ≠ t ∈ V} (\sigma_{st}(v) / \sigma_{st})
|
|
//
|
|
// where \sigma_{st} and \sigma_{st}(v) are the number of shortest paths from s to t,
|
|
// and the subset of those paths containing v respectively.
|
|
func Betweenness(g graph.Graph) map[int64]float64 {
|
|
// Brandes' algorithm for finding betweenness centrality for nodes in
|
|
// and unweighted graph:
|
|
//
|
|
// http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf
|
|
|
|
// TODO(kortschak): Consider using the parallel algorithm when
|
|
// GOMAXPROCS != 1.
|
|
//
|
|
// http://htor.inf.ethz.ch/publications/img/edmonds-hoefler-lumsdaine-bc.pdf
|
|
|
|
// Also note special case for sparse networks:
|
|
// http://wwwold.iit.cnr.it/staff/marco.pellegrini/papiri/asonam-final.pdf
|
|
|
|
cb := make(map[int64]float64)
|
|
brandes(g, func(s graph.Node, stack linear.NodeStack, p map[int64][]graph.Node, delta, sigma map[int64]float64) {
|
|
for stack.Len() != 0 {
|
|
w := stack.Pop()
|
|
for _, v := range p[w.ID()] {
|
|
delta[v.ID()] += sigma[v.ID()] / sigma[w.ID()] * (1 + delta[w.ID()])
|
|
}
|
|
if w.ID() != s.ID() {
|
|
if d := delta[w.ID()]; d != 0 {
|
|
cb[w.ID()] += d
|
|
}
|
|
}
|
|
}
|
|
})
|
|
return cb
|
|
}
|
|
|
|
// EdgeBetweenness returns the non-zero betweenness centrality for edges in the
|
|
// unweighted graph g. For an edge e the centrality C_B is computed as
|
|
//
|
|
// C_B(e) = \sum_{s ≠ t ∈ V} (\sigma_{st}(e) / \sigma_{st}),
|
|
//
|
|
// where \sigma_{st} and \sigma_{st}(e) are the number of shortest paths from s
|
|
// to t, and the subset of those paths containing e, respectively.
|
|
//
|
|
// If g is undirected, edges are retained such that u.ID < v.ID where u and v are
|
|
// the nodes of e.
|
|
func EdgeBetweenness(g graph.Graph) map[[2]int64]float64 {
|
|
// Modified from Brandes' original algorithm as described in Algorithm 7
|
|
// with the exception that node betweenness is not calculated:
|
|
//
|
|
// http://algo.uni-konstanz.de/publications/b-vspbc-08.pdf
|
|
|
|
_, isUndirected := g.(graph.Undirected)
|
|
cb := make(map[[2]int64]float64)
|
|
brandes(g, func(s graph.Node, stack linear.NodeStack, p map[int64][]graph.Node, delta, sigma map[int64]float64) {
|
|
for stack.Len() != 0 {
|
|
w := stack.Pop()
|
|
for _, v := range p[w.ID()] {
|
|
c := sigma[v.ID()] / sigma[w.ID()] * (1 + delta[w.ID()])
|
|
vid := v.ID()
|
|
wid := w.ID()
|
|
if isUndirected && wid < vid {
|
|
vid, wid = wid, vid
|
|
}
|
|
cb[[2]int64{vid, wid}] += c
|
|
delta[v.ID()] += c
|
|
}
|
|
}
|
|
})
|
|
return cb
|
|
}
|
|
|
|
// brandes is the common code for Betweenness and EdgeBetweenness. It corresponds
|
|
// to algorithm 1 in http://algo.uni-konstanz.de/publications/b-vspbc-08.pdf with
|
|
// the accumulation loop provided by the accumulate closure.
|
|
func brandes(g graph.Graph, accumulate func(s graph.Node, stack linear.NodeStack, p map[int64][]graph.Node, delta, sigma map[int64]float64)) {
|
|
var (
|
|
nodes = graph.NodesOf(g.Nodes())
|
|
stack linear.NodeStack
|
|
p = make(map[int64][]graph.Node, len(nodes))
|
|
sigma = make(map[int64]float64, len(nodes))
|
|
d = make(map[int64]int, len(nodes))
|
|
delta = make(map[int64]float64, len(nodes))
|
|
queue linear.NodeQueue
|
|
)
|
|
for _, s := range nodes {
|
|
stack = stack[:0]
|
|
|
|
for _, w := range nodes {
|
|
p[w.ID()] = p[w.ID()][:0]
|
|
}
|
|
|
|
for _, t := range nodes {
|
|
sigma[t.ID()] = 0
|
|
d[t.ID()] = -1
|
|
}
|
|
sigma[s.ID()] = 1
|
|
d[s.ID()] = 0
|
|
|
|
queue.Enqueue(s)
|
|
for queue.Len() != 0 {
|
|
v := queue.Dequeue()
|
|
vid := v.ID()
|
|
stack.Push(v)
|
|
to := g.From(vid)
|
|
for to.Next() {
|
|
w := to.Node()
|
|
wid := w.ID()
|
|
// w found for the first time?
|
|
if d[wid] < 0 {
|
|
queue.Enqueue(w)
|
|
d[wid] = d[vid] + 1
|
|
}
|
|
// shortest path to w via v?
|
|
if d[wid] == d[vid]+1 {
|
|
sigma[wid] += sigma[vid]
|
|
p[wid] = append(p[wid], v)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, v := range nodes {
|
|
delta[v.ID()] = 0
|
|
}
|
|
|
|
// S returns vertices in order of non-increasing distance from s
|
|
accumulate(s, stack, p, delta, sigma)
|
|
}
|
|
}
|
|
|
|
// BetweennessWeighted returns the non-zero betweenness centrality for nodes in the weighted
|
|
// graph g used to construct the given shortest paths.
|
|
//
|
|
// C_B(v) = \sum_{s ≠ v ≠ t ∈ V} (\sigma_{st}(v) / \sigma_{st})
|
|
//
|
|
// where \sigma_{st} and \sigma_{st}(v) are the number of shortest paths from s to t,
|
|
// and the subset of those paths containing v respectively.
|
|
func BetweennessWeighted(g graph.Weighted, p path.AllShortest) map[int64]float64 {
|
|
cb := make(map[int64]float64)
|
|
|
|
nodes := graph.NodesOf(g.Nodes())
|
|
for i, s := range nodes {
|
|
sid := s.ID()
|
|
for j, t := range nodes {
|
|
if i == j {
|
|
continue
|
|
}
|
|
tid := t.ID()
|
|
d := p.Weight(sid, tid)
|
|
if math.IsInf(d, 0) {
|
|
continue
|
|
}
|
|
|
|
// If we have a unique path, don't do the
|
|
// extra work needed to get all paths.
|
|
path, _, unique := p.Between(sid, tid)
|
|
if unique {
|
|
for _, v := range path[1 : len(path)-1] {
|
|
// For undirected graphs we double count
|
|
// passage though nodes. This is consistent
|
|
// with Brandes' algorithm's behaviour.
|
|
cb[v.ID()]++
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Otherwise iterate over all paths.
|
|
paths, _ := p.AllBetween(sid, tid)
|
|
stFrac := 1 / float64(len(paths))
|
|
for _, path := range paths {
|
|
for _, v := range path[1 : len(path)-1] {
|
|
cb[v.ID()] += stFrac
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return cb
|
|
}
|
|
|
|
// EdgeBetweennessWeighted returns the non-zero betweenness centrality for edges in
|
|
// the weighted graph g. For an edge e the centrality C_B is computed as
|
|
//
|
|
// C_B(e) = \sum_{s ≠ t ∈ V} (\sigma_{st}(e) / \sigma_{st}),
|
|
//
|
|
// where \sigma_{st} and \sigma_{st}(e) are the number of shortest paths from s
|
|
// to t, and the subset of those paths containing e, respectively.
|
|
//
|
|
// If g is undirected, edges are retained such that u.ID < v.ID where u and v are
|
|
// the nodes of e.
|
|
func EdgeBetweennessWeighted(g graph.Weighted, p path.AllShortest) map[[2]int64]float64 {
|
|
cb := make(map[[2]int64]float64)
|
|
|
|
_, isUndirected := g.(graph.Undirected)
|
|
nodes := graph.NodesOf(g.Nodes())
|
|
for i, s := range nodes {
|
|
sid := s.ID()
|
|
for j, t := range nodes {
|
|
if i == j {
|
|
continue
|
|
}
|
|
tid := t.ID()
|
|
d := p.Weight(sid, tid)
|
|
if math.IsInf(d, 0) {
|
|
continue
|
|
}
|
|
|
|
// If we have a unique path, don't do the
|
|
// extra work needed to get all paths.
|
|
path, _, unique := p.Between(sid, tid)
|
|
if unique {
|
|
for k, v := range path[1:] {
|
|
// For undirected graphs we double count
|
|
// passage though edges. This is consistent
|
|
// with Brandes' algorithm's behaviour.
|
|
uid := path[k].ID()
|
|
vid := v.ID()
|
|
if isUndirected && vid < uid {
|
|
uid, vid = vid, uid
|
|
}
|
|
cb[[2]int64{uid, vid}]++
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Otherwise iterate over all paths.
|
|
paths, _ := p.AllBetween(sid, tid)
|
|
stFrac := 1 / float64(len(paths))
|
|
for _, path := range paths {
|
|
for k, v := range path[1:] {
|
|
uid := path[k].ID()
|
|
vid := v.ID()
|
|
if isUndirected && vid < uid {
|
|
uid, vid = vid, uid
|
|
}
|
|
cb[[2]int64{uid, vid}] += stFrac
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return cb
|
|
}
|