github.com/decred/dcrlnd@v0.7.6/autopilot/betweenness_centrality.go (about)

     1  package autopilot
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  )
     7  
     8  // stack is a simple int stack to help with readability of Brandes'
     9  // betweenness centrality implementation below.
    10  type stack struct {
    11  	stack []int
    12  }
    13  
    14  func (s *stack) push(v int) {
    15  	s.stack = append(s.stack, v)
    16  }
    17  
    18  func (s *stack) top() int {
    19  	return s.stack[len(s.stack)-1]
    20  }
    21  
    22  func (s *stack) pop() {
    23  	s.stack = s.stack[:len(s.stack)-1]
    24  }
    25  
    26  func (s *stack) empty() bool {
    27  	return len(s.stack) == 0
    28  }
    29  
    30  // queue is a simple int queue to help with readability of Brandes'
    31  // betweenness centrality implementation below.
    32  type queue struct {
    33  	queue []int
    34  }
    35  
    36  func (q *queue) push(v int) {
    37  	q.queue = append(q.queue, v)
    38  }
    39  
    40  func (q *queue) front() int {
    41  	return q.queue[0]
    42  }
    43  
    44  func (q *queue) pop() {
    45  	q.queue = q.queue[1:]
    46  }
    47  
    48  func (q *queue) empty() bool {
    49  	return len(q.queue) == 0
    50  }
    51  
    52  // BetweennessCentrality is a NodeMetric that calculates node betweenness
    53  // centrality using Brandes' algorithm. Betweenness centrality for each node
    54  // is the number of shortest paths passing trough that node, not counting
    55  // shortest paths starting or ending at that node. This is a useful metric
    56  // to measure control of individual nodes over the whole network.
    57  type BetweennessCentrality struct {
    58  	// workers number of goroutines are used to parallelize
    59  	// centrality calculation.
    60  	workers int
    61  
    62  	// centrality stores original (not normalized) centrality values for
    63  	// each node in the graph.
    64  	centrality map[NodeID]float64
    65  
    66  	// min is the minimum centrality in the graph.
    67  	min float64
    68  
    69  	// max is the maximum centrality in the graph.
    70  	max float64
    71  }
    72  
    73  // NewBetweennessCentralityMetric creates a new BetweennessCentrality instance.
    74  // Users can specify the number of workers to use for calculating centrality.
    75  func NewBetweennessCentralityMetric(workers int) (*BetweennessCentrality, error) {
    76  	// There should be at least one worker.
    77  	if workers < 1 {
    78  		return nil, fmt.Errorf("workers must be positive")
    79  	}
    80  	return &BetweennessCentrality{
    81  		workers: workers,
    82  	}, nil
    83  }
    84  
    85  // Name returns the name of the metric.
    86  func (bc *BetweennessCentrality) Name() string {
    87  	return "betweenness_centrality"
    88  }
    89  
    90  // betweennessCentrality is the core of Brandes' algorithm.
    91  // We first calculate the shortest paths from the start node s to all other
    92  // nodes with BFS, then update the betweenness centrality values by using
    93  // Brandes' dependency trick.
    94  // For detailed explanation please read:
    95  // https://www.cl.cam.ac.uk/teaching/1617/MLRD/handbook/brandes.html
    96  func betweennessCentrality(g *SimpleGraph, s int, centrality []float64) {
    97  	// pred[w] is the list of nodes that immediately precede w on a
    98  	// shortest path from s to t for each node t.
    99  	pred := make([][]int, len(g.Nodes))
   100  
   101  	// sigma[t] is the number of shortest paths between nodes s and t
   102  	// for each node t.
   103  	sigma := make([]int, len(g.Nodes))
   104  	sigma[s] = 1
   105  
   106  	// dist[t] holds the distance between s and t for each node t.
   107  	// We initialize this to -1 (meaning infinity) for each t != s.
   108  	dist := make([]int, len(g.Nodes))
   109  	for i := range dist {
   110  		dist[i] = -1
   111  	}
   112  
   113  	dist[s] = 0
   114  
   115  	var (
   116  		st stack
   117  		q  queue
   118  	)
   119  	q.push(s)
   120  
   121  	// BFS to calculate the shortest paths (sigma and pred)
   122  	// from s to t for each node t.
   123  	for !q.empty() {
   124  		v := q.front()
   125  		q.pop()
   126  		st.push(v)
   127  
   128  		for _, w := range g.Adj[v] {
   129  			// If distance from s to w is infinity (-1)
   130  			// then set it and enqueue w.
   131  			if dist[w] < 0 {
   132  				dist[w] = dist[v] + 1
   133  				q.push(w)
   134  			}
   135  
   136  			// If w is on a shortest path the update
   137  			// sigma and add v to w's predecessor list.
   138  			if dist[w] == dist[v]+1 {
   139  				sigma[w] += sigma[v]
   140  				pred[w] = append(pred[w], v)
   141  			}
   142  		}
   143  	}
   144  
   145  	// delta[v] is the ratio of the shortest paths between s and t that go
   146  	// through v and the total number of shortest paths between s and t.
   147  	// If we have delta then the betweenness centrality is simply the sum
   148  	// of delta[w] for each w != s.
   149  	delta := make([]float64, len(g.Nodes))
   150  
   151  	for !st.empty() {
   152  		w := st.top()
   153  		st.pop()
   154  
   155  		// pred[w] is the list of nodes that immediately precede w on a
   156  		// shortest path from s.
   157  		for _, v := range pred[w] {
   158  			// Update delta using Brandes' equation.
   159  			delta[v] += (float64(sigma[v]) / float64(sigma[w])) * (1.0 + delta[w])
   160  		}
   161  
   162  		if w != s {
   163  			// As noted above centrality is simply the sum
   164  			// of delta[w] for each w != s.
   165  			centrality[w] += delta[w]
   166  		}
   167  	}
   168  }
   169  
   170  // Refresh recaculates and stores centrality values.
   171  func (bc *BetweennessCentrality) Refresh(graph ChannelGraph) error {
   172  	cache, err := NewSimpleGraph(graph)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	var wg sync.WaitGroup
   178  	work := make(chan int)
   179  	partials := make(chan []float64, bc.workers)
   180  
   181  	// Each worker will compute a partial result.
   182  	// This partial result is a sum of centrality updates
   183  	// on roughly N / workers nodes.
   184  	worker := func() {
   185  		defer wg.Done()
   186  		partial := make([]float64, len(cache.Nodes))
   187  
   188  		// Consume the next node, update centrality
   189  		// parital to avoid unnecessary synchronizaton.
   190  		for node := range work {
   191  			betweennessCentrality(cache, node, partial)
   192  		}
   193  		partials <- partial
   194  	}
   195  
   196  	// Now start the N workers.
   197  	wg.Add(bc.workers)
   198  	for i := 0; i < bc.workers; i++ {
   199  		go worker()
   200  	}
   201  
   202  	// Distribute work amongst workers.
   203  	// Should be fair when the graph is sufficiently large.
   204  	for node := range cache.Nodes {
   205  		work <- node
   206  	}
   207  
   208  	close(work)
   209  	wg.Wait()
   210  	close(partials)
   211  
   212  	// Collect and sum partials for final result.
   213  	centrality := make([]float64, len(cache.Nodes))
   214  	for partial := range partials {
   215  		for i := 0; i < len(partial); i++ {
   216  			centrality[i] += partial[i]
   217  		}
   218  	}
   219  
   220  	// Get min/max to be able to normalize
   221  	// centrality values between 0 and 1.
   222  	bc.min = 0
   223  	bc.max = 0
   224  	if len(centrality) > 0 {
   225  		for _, v := range centrality {
   226  			if v < bc.min {
   227  				bc.min = v
   228  			} else if v > bc.max {
   229  				bc.max = v
   230  			}
   231  		}
   232  	}
   233  
   234  	// Divide by two as this is an undirected graph.
   235  	bc.min /= 2.0
   236  	bc.max /= 2.0
   237  
   238  	bc.centrality = make(map[NodeID]float64)
   239  	for u, value := range centrality {
   240  		// Divide by two as this is an undirected graph.
   241  		bc.centrality[cache.Nodes[u]] = value / 2.0
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  // GetMetric returns the current centrality values for each node indexed
   248  // by node id.
   249  func (bc *BetweennessCentrality) GetMetric(normalize bool) map[NodeID]float64 {
   250  	// Normalization factor.
   251  	var z float64
   252  	if (bc.max - bc.min) > 0 {
   253  		z = 1.0 / (bc.max - bc.min)
   254  	}
   255  
   256  	centrality := make(map[NodeID]float64)
   257  	for k, v := range bc.centrality {
   258  		if normalize {
   259  			v = (v - bc.min) * z
   260  		}
   261  		centrality[k] = v
   262  	}
   263  
   264  	return centrality
   265  }