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  }