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

lightningnetwork / lnd / 13566028875

27 Feb 2025 12:09PM UTC coverage: 49.396% (-9.4%) from 58.748%
13566028875

Pull #9555

github

ellemouton
graph/db: populate the graph cache in Start instead of during construction

In this commit, we move the graph cache population logic out of the
ChannelGraph constructor and into its Start method instead.
Pull Request #9555: graph: extract cache from CRUD [6]

34 of 54 new or added lines in 4 files covered. (62.96%)

27464 existing lines in 436 files now uncovered.

101095 of 204664 relevant lines covered (49.4%)

1.54 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