gonum.org/v1/gonum@v0.14.0/graph/network/betweenness.go (about) 1 // Copyright ©2015 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package network 6 7 import ( 8 "math" 9 10 "gonum.org/v1/gonum/graph" 11 "gonum.org/v1/gonum/graph/internal/linear" 12 "gonum.org/v1/gonum/graph/path" 13 ) 14 15 // Betweenness returns the non-zero betweenness centrality for nodes in the unweighted graph g. 16 // 17 // C_B(v) = \sum_{s ≠ v ≠ t ∈ V} (\sigma_{st}(v) / \sigma_{st}) 18 // 19 // where \sigma_{st} and \sigma_{st}(v) are the number of shortest paths from s to t, 20 // and the subset of those paths containing v respectively. 21 func Betweenness(g graph.Graph) map[int64]float64 { 22 // Brandes' algorithm for finding betweenness centrality for nodes in 23 // and unweighted graph: 24 // 25 // http://www.inf.uni-konstanz.de/algo/publications/b-fabc-01.pdf 26 27 // TODO(kortschak): Consider using the parallel algorithm when 28 // GOMAXPROCS != 1. 29 // 30 // http://htor.inf.ethz.ch/publications/img/edmonds-hoefler-lumsdaine-bc.pdf 31 32 // Also note special case for sparse networks: 33 // http://wwwold.iit.cnr.it/staff/marco.pellegrini/papiri/asonam-final.pdf 34 35 cb := make(map[int64]float64) 36 brandes(g, func(s graph.Node, stack linear.NodeStack, p map[int64][]graph.Node, delta, sigma map[int64]float64) { 37 for stack.Len() != 0 { 38 w := stack.Pop() 39 for _, v := range p[w.ID()] { 40 delta[v.ID()] += sigma[v.ID()] / sigma[w.ID()] * (1 + delta[w.ID()]) 41 } 42 if w.ID() != s.ID() { 43 if d := delta[w.ID()]; d != 0 { 44 cb[w.ID()] += d 45 } 46 } 47 } 48 }) 49 return cb 50 } 51 52 // EdgeBetweenness returns the non-zero betweenness centrality for edges in the 53 // unweighted graph g. For an edge e the centrality C_B is computed as 54 // 55 // C_B(e) = \sum_{s ≠ t ∈ V} (\sigma_{st}(e) / \sigma_{st}), 56 // 57 // where \sigma_{st} and \sigma_{st}(e) are the number of shortest paths from s 58 // to t, and the subset of those paths containing e, respectively. 59 // 60 // If g is undirected, edges are retained such that u.ID < v.ID where u and v are 61 // the nodes of e. 62 func EdgeBetweenness(g graph.Graph) map[[2]int64]float64 { 63 // Modified from Brandes' original algorithm as described in Algorithm 7 64 // with the exception that node betweenness is not calculated: 65 // 66 // http://algo.uni-konstanz.de/publications/b-vspbc-08.pdf 67 68 _, isUndirected := g.(graph.Undirected) 69 cb := make(map[[2]int64]float64) 70 brandes(g, func(s graph.Node, stack linear.NodeStack, p map[int64][]graph.Node, delta, sigma map[int64]float64) { 71 for stack.Len() != 0 { 72 w := stack.Pop() 73 for _, v := range p[w.ID()] { 74 c := sigma[v.ID()] / sigma[w.ID()] * (1 + delta[w.ID()]) 75 vid := v.ID() 76 wid := w.ID() 77 if isUndirected && wid < vid { 78 vid, wid = wid, vid 79 } 80 cb[[2]int64{vid, wid}] += c 81 delta[v.ID()] += c 82 } 83 } 84 }) 85 return cb 86 } 87 88 // brandes is the common code for Betweenness and EdgeBetweenness. It corresponds 89 // to algorithm 1 in http://algo.uni-konstanz.de/publications/b-vspbc-08.pdf with 90 // the accumulation loop provided by the accumulate closure. 91 func brandes(g graph.Graph, accumulate func(s graph.Node, stack linear.NodeStack, p map[int64][]graph.Node, delta, sigma map[int64]float64)) { 92 var ( 93 nodes = graph.NodesOf(g.Nodes()) 94 stack linear.NodeStack 95 p = make(map[int64][]graph.Node, len(nodes)) 96 sigma = make(map[int64]float64, len(nodes)) 97 d = make(map[int64]int, len(nodes)) 98 delta = make(map[int64]float64, len(nodes)) 99 queue linear.NodeQueue 100 ) 101 for _, s := range nodes { 102 stack = stack[:0] 103 104 for _, w := range nodes { 105 p[w.ID()] = p[w.ID()][:0] 106 } 107 108 for _, t := range nodes { 109 sigma[t.ID()] = 0 110 d[t.ID()] = -1 111 } 112 sigma[s.ID()] = 1 113 d[s.ID()] = 0 114 115 queue.Enqueue(s) 116 for queue.Len() != 0 { 117 v := queue.Dequeue() 118 vid := v.ID() 119 stack.Push(v) 120 to := g.From(vid) 121 for to.Next() { 122 w := to.Node() 123 wid := w.ID() 124 // w found for the first time? 125 if d[wid] < 0 { 126 queue.Enqueue(w) 127 d[wid] = d[vid] + 1 128 } 129 // shortest path to w via v? 130 if d[wid] == d[vid]+1 { 131 sigma[wid] += sigma[vid] 132 p[wid] = append(p[wid], v) 133 } 134 } 135 } 136 137 for _, v := range nodes { 138 delta[v.ID()] = 0 139 } 140 141 // S returns vertices in order of non-increasing distance from s 142 accumulate(s, stack, p, delta, sigma) 143 } 144 } 145 146 // BetweennessWeighted returns the non-zero betweenness centrality for nodes in the weighted 147 // graph g used to construct the given shortest paths. 148 // 149 // C_B(v) = \sum_{s ≠ v ≠ t ∈ V} (\sigma_{st}(v) / \sigma_{st}) 150 // 151 // where \sigma_{st} and \sigma_{st}(v) are the number of shortest paths from s to t, 152 // and the subset of those paths containing v respectively. 153 func BetweennessWeighted(g graph.Weighted, p path.AllShortest) map[int64]float64 { 154 cb := make(map[int64]float64) 155 156 nodes := graph.NodesOf(g.Nodes()) 157 for i, s := range nodes { 158 sid := s.ID() 159 for j, t := range nodes { 160 if i == j { 161 continue 162 } 163 tid := t.ID() 164 d := p.Weight(sid, tid) 165 if math.IsInf(d, 0) { 166 continue 167 } 168 169 // If we have a unique path, don't do the 170 // extra work needed to get all paths. 171 path, _, unique := p.Between(sid, tid) 172 if unique { 173 for _, v := range path[1 : len(path)-1] { 174 // For undirected graphs we double count 175 // passage though nodes. This is consistent 176 // with Brandes' algorithm's behaviour. 177 cb[v.ID()]++ 178 } 179 continue 180 } 181 182 // Otherwise iterate over all paths. 183 paths, _ := p.AllBetween(sid, tid) 184 stFrac := 1 / float64(len(paths)) 185 for _, path := range paths { 186 for _, v := range path[1 : len(path)-1] { 187 cb[v.ID()] += stFrac 188 } 189 } 190 } 191 } 192 193 return cb 194 } 195 196 // EdgeBetweennessWeighted returns the non-zero betweenness centrality for edges in 197 // the weighted graph g. For an edge e the centrality C_B is computed as 198 // 199 // C_B(e) = \sum_{s ≠ t ∈ V} (\sigma_{st}(e) / \sigma_{st}), 200 // 201 // where \sigma_{st} and \sigma_{st}(e) are the number of shortest paths from s 202 // to t, and the subset of those paths containing e, respectively. 203 // 204 // If g is undirected, edges are retained such that u.ID < v.ID where u and v are 205 // the nodes of e. 206 func EdgeBetweennessWeighted(g graph.Weighted, p path.AllShortest) map[[2]int64]float64 { 207 cb := make(map[[2]int64]float64) 208 209 _, isUndirected := g.(graph.Undirected) 210 nodes := graph.NodesOf(g.Nodes()) 211 for i, s := range nodes { 212 sid := s.ID() 213 for j, t := range nodes { 214 if i == j { 215 continue 216 } 217 tid := t.ID() 218 d := p.Weight(sid, tid) 219 if math.IsInf(d, 0) { 220 continue 221 } 222 223 // If we have a unique path, don't do the 224 // extra work needed to get all paths. 225 path, _, unique := p.Between(sid, tid) 226 if unique { 227 for k, v := range path[1:] { 228 // For undirected graphs we double count 229 // passage though edges. This is consistent 230 // with Brandes' algorithm's behaviour. 231 uid := path[k].ID() 232 vid := v.ID() 233 if isUndirected && vid < uid { 234 uid, vid = vid, uid 235 } 236 cb[[2]int64{uid, vid}]++ 237 } 238 continue 239 } 240 241 // Otherwise iterate over all paths. 242 paths, _ := p.AllBetween(sid, tid) 243 stFrac := 1 / float64(len(paths)) 244 for _, path := range paths { 245 for k, v := range path[1:] { 246 uid := path[k].ID() 247 vid := v.ID() 248 if isUndirected && vid < uid { 249 uid, vid = vid, uid 250 } 251 cb[[2]int64{uid, vid}] += stFrac 252 } 253 } 254 } 255 } 256 257 return cb 258 }