• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 13980275562

20 Mar 2025 10:06PM UTC coverage: 58.6% (-10.2%) from 68.789%
13980275562

Pull #9623

github

web-flow
Merge b9b960345 into 09b674508
Pull Request #9623: Size msg test msg

0 of 1518 new or added lines in 42 files covered. (0.0%)

26603 existing lines in 443 files now uncovered.

96807 of 165200 relevant lines covered (58.6%)

1.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

3.66
/autopilot/betweenness_centrality.go
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

UNCOV
14
func (s *stack) push(v int) {
×
UNCOV
15
        s.stack = append(s.stack, v)
×
UNCOV
16
}
×
17

UNCOV
18
func (s *stack) top() int {
×
UNCOV
19
        return s.stack[len(s.stack)-1]
×
UNCOV
20
}
×
21

UNCOV
22
func (s *stack) pop() {
×
UNCOV
23
        s.stack = s.stack[:len(s.stack)-1]
×
UNCOV
24
}
×
25

UNCOV
26
func (s *stack) empty() bool {
×
UNCOV
27
        return len(s.stack) == 0
×
UNCOV
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

UNCOV
36
func (q *queue) push(v int) {
×
UNCOV
37
        q.queue = append(q.queue, v)
×
UNCOV
38
}
×
39

UNCOV
40
func (q *queue) front() int {
×
UNCOV
41
        return q.queue[0]
×
UNCOV
42
}
×
43

UNCOV
44
func (q *queue) pop() {
×
UNCOV
45
        q.queue = q.queue[1:]
×
UNCOV
46
}
×
47

UNCOV
48
func (q *queue) empty() bool {
×
UNCOV
49
        return len(q.queue) == 0
×
UNCOV
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 through 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) {
3✔
76
        // There should be at least one worker.
3✔
77
        if workers < 1 {
3✔
UNCOV
78
                return nil, fmt.Errorf("workers must be positive")
×
UNCOV
79
        }
×
80
        return &BetweennessCentrality{
3✔
81
                workers: workers,
3✔
82
        }, nil
3✔
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
UNCOV
96
func betweennessCentrality(g *SimpleGraph, s int, centrality []float64) {
×
UNCOV
97
        // pred[w] is the list of nodes that immediately precede w on a
×
UNCOV
98
        // shortest path from s to t for each node t.
×
UNCOV
99
        pred := make([][]int, len(g.Nodes))
×
UNCOV
100

×
UNCOV
101
        // sigma[t] is the number of shortest paths between nodes s and t
×
UNCOV
102
        // for each node t.
×
UNCOV
103
        sigma := make([]int, len(g.Nodes))
×
UNCOV
104
        sigma[s] = 1
×
UNCOV
105

×
UNCOV
106
        // dist[t] holds the distance between s and t for each node t.
×
UNCOV
107
        // We initialize this to -1 (meaning infinity) for each t != s.
×
UNCOV
108
        dist := make([]int, len(g.Nodes))
×
UNCOV
109
        for i := range dist {
×
UNCOV
110
                dist[i] = -1
×
UNCOV
111
        }
×
112

UNCOV
113
        dist[s] = 0
×
UNCOV
114

×
UNCOV
115
        var (
×
UNCOV
116
                st stack
×
UNCOV
117
                q  queue
×
UNCOV
118
        )
×
UNCOV
119
        q.push(s)
×
UNCOV
120

×
UNCOV
121
        // BFS to calculate the shortest paths (sigma and pred)
×
UNCOV
122
        // from s to t for each node t.
×
UNCOV
123
        for !q.empty() {
×
UNCOV
124
                v := q.front()
×
UNCOV
125
                q.pop()
×
UNCOV
126
                st.push(v)
×
UNCOV
127

×
UNCOV
128
                for _, w := range g.Adj[v] {
×
UNCOV
129
                        // If distance from s to w is infinity (-1)
×
UNCOV
130
                        // then set it and enqueue w.
×
UNCOV
131
                        if dist[w] < 0 {
×
UNCOV
132
                                dist[w] = dist[v] + 1
×
UNCOV
133
                                q.push(w)
×
UNCOV
134
                        }
×
135

136
                        // If w is on a shortest path the update
137
                        // sigma and add v to w's predecessor list.
UNCOV
138
                        if dist[w] == dist[v]+1 {
×
UNCOV
139
                                sigma[w] += sigma[v]
×
UNCOV
140
                                pred[w] = append(pred[w], v)
×
UNCOV
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.
UNCOV
149
        delta := make([]float64, len(g.Nodes))
×
UNCOV
150

×
UNCOV
151
        for !st.empty() {
×
UNCOV
152
                w := st.top()
×
UNCOV
153
                st.pop()
×
UNCOV
154

×
UNCOV
155
                // pred[w] is the list of nodes that immediately precede w on a
×
UNCOV
156
                // shortest path from s.
×
UNCOV
157
                for _, v := range pred[w] {
×
UNCOV
158
                        // Update delta using Brandes' equation.
×
UNCOV
159
                        delta[v] += (float64(sigma[v]) / float64(sigma[w])) * (1.0 + delta[w])
×
UNCOV
160
                }
×
161

UNCOV
162
                if w != s {
×
UNCOV
163
                        // As noted above centrality is simply the sum
×
UNCOV
164
                        // of delta[w] for each w != s.
×
UNCOV
165
                        centrality[w] += delta[w]
×
UNCOV
166
                }
×
167
        }
168
}
169

170
// Refresh recalculates and stores centrality values.
UNCOV
171
func (bc *BetweennessCentrality) Refresh(graph ChannelGraph) error {
×
UNCOV
172
        cache, err := NewSimpleGraph(graph)
×
UNCOV
173
        if err != nil {
×
174
                return err
×
175
        }
×
176

UNCOV
177
        var wg sync.WaitGroup
×
UNCOV
178
        work := make(chan int)
×
UNCOV
179
        partials := make(chan []float64, bc.workers)
×
UNCOV
180

×
UNCOV
181
        // Each worker will compute a partial result.
×
UNCOV
182
        // This partial result is a sum of centrality updates
×
UNCOV
183
        // on roughly N / workers nodes.
×
UNCOV
184
        worker := func() {
×
UNCOV
185
                defer wg.Done()
×
UNCOV
186
                partial := make([]float64, len(cache.Nodes))
×
UNCOV
187

×
UNCOV
188
                // Consume the next node, update centrality
×
UNCOV
189
                // parital to avoid unnecessary synchronization.
×
UNCOV
190
                for node := range work {
×
UNCOV
191
                        betweennessCentrality(cache, node, partial)
×
UNCOV
192
                }
×
UNCOV
193
                partials <- partial
×
194
        }
195

196
        // Now start the N workers.
UNCOV
197
        wg.Add(bc.workers)
×
UNCOV
198
        for i := 0; i < bc.workers; i++ {
×
UNCOV
199
                go worker()
×
UNCOV
200
        }
×
201

202
        // Distribute work amongst workers.
203
        // Should be fair when the graph is sufficiently large.
UNCOV
204
        for node := range cache.Nodes {
×
UNCOV
205
                work <- node
×
UNCOV
206
        }
×
207

UNCOV
208
        close(work)
×
UNCOV
209
        wg.Wait()
×
UNCOV
210
        close(partials)
×
UNCOV
211

×
UNCOV
212
        // Collect and sum partials for final result.
×
UNCOV
213
        centrality := make([]float64, len(cache.Nodes))
×
UNCOV
214
        for partial := range partials {
×
UNCOV
215
                for i := 0; i < len(partial); i++ {
×
UNCOV
216
                        centrality[i] += partial[i]
×
UNCOV
217
                }
×
218
        }
219

220
        // Get min/max to be able to normalize
221
        // centrality values between 0 and 1.
UNCOV
222
        bc.min = 0
×
UNCOV
223
        bc.max = 0
×
UNCOV
224
        if len(centrality) > 0 {
×
UNCOV
225
                for _, v := range centrality {
×
UNCOV
226
                        if v < bc.min {
×
227
                                bc.min = v
×
UNCOV
228
                        } else if v > bc.max {
×
UNCOV
229
                                bc.max = v
×
UNCOV
230
                        }
×
231
                }
232
        }
233

234
        // Divide by two as this is an undirected graph.
UNCOV
235
        bc.min /= 2.0
×
UNCOV
236
        bc.max /= 2.0
×
UNCOV
237

×
UNCOV
238
        bc.centrality = make(map[NodeID]float64)
×
UNCOV
239
        for u, value := range centrality {
×
UNCOV
240
                // Divide by two as this is an undirected graph.
×
UNCOV
241
                bc.centrality[cache.Nodes[u]] = value / 2.0
×
UNCOV
242
        }
×
243

UNCOV
244
        return nil
×
245
}
246

247
// GetMetric returns the current centrality values for each node indexed
248
// by node id.
UNCOV
249
func (bc *BetweennessCentrality) GetMetric(normalize bool) map[NodeID]float64 {
×
UNCOV
250
        // Normalization factor.
×
UNCOV
251
        var z float64
×
UNCOV
252
        if (bc.max - bc.min) > 0 {
×
UNCOV
253
                z = 1.0 / (bc.max - bc.min)
×
UNCOV
254
        }
×
255

UNCOV
256
        centrality := make(map[NodeID]float64)
×
UNCOV
257
        for k, v := range bc.centrality {
×
UNCOV
258
                if normalize {
×
UNCOV
259
                        v = (v - bc.min) * z
×
UNCOV
260
                }
×
UNCOV
261
                centrality[k] = v
×
262
        }
263

UNCOV
264
        return centrality
×
265
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc