// 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 }