mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-10-02 18:24:42 +02:00
autopilot: betweenness centrality using Brandes algo on simplifed graph
This commit adds betweenness centrality to the available node metrics. Betweenness centrality is a per node centrality measure which for an arbitrary node v equals to the sum of shortest paths going trough v divided by the number of all shortest paths for for each vertex pair k, s where k != s != v.
This commit is contained in:
156
autopilot/betweenness_centrality_test.go
Normal file
156
autopilot/betweenness_centrality_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package autopilot
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// Tests that empty graph results in empty centrality result.
|
||||
func TestBetweennessCentralityEmptyGraph(t *testing.T) {
|
||||
centralityMetric := NewBetweennessCentralityMetric()
|
||||
|
||||
for _, chanGraph := range chanGraphs {
|
||||
graph, cleanup, err := chanGraph.genFunc()
|
||||
success := t.Run(chanGraph.name, func(t1 *testing.T) {
|
||||
if err != nil {
|
||||
t1.Fatalf("unable to create graph: %v", err)
|
||||
}
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
if err := centralityMetric.Refresh(graph); err != nil {
|
||||
t.Fatalf("unexpected failure during metric refresh: %v", err)
|
||||
}
|
||||
|
||||
centrality := centralityMetric.GetMetric(false)
|
||||
if len(centrality) > 0 {
|
||||
t.Fatalf("expected empty metric, got: %v", len(centrality))
|
||||
}
|
||||
|
||||
centrality = centralityMetric.GetMetric(true)
|
||||
if len(centrality) > 0 {
|
||||
t.Fatalf("expected empty metric, got: %v", len(centrality))
|
||||
}
|
||||
|
||||
})
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testGraphDesc is a helper type to describe a test graph.
|
||||
type testGraphDesc struct {
|
||||
nodes int
|
||||
edges map[int][]int
|
||||
}
|
||||
|
||||
// buildTestGraph builds a test graph from a passed graph desriptor.
|
||||
func buildTestGraph(t *testing.T,
|
||||
graph testGraph, desc testGraphDesc) map[int]*btcec.PublicKey {
|
||||
|
||||
nodes := make(map[int]*btcec.PublicKey)
|
||||
|
||||
for i := 0; i < desc.nodes; i++ {
|
||||
key, err := graph.addRandNode()
|
||||
if err != nil {
|
||||
t.Fatalf("cannot create random node")
|
||||
}
|
||||
|
||||
nodes[i] = key
|
||||
}
|
||||
|
||||
const chanCapacity = btcutil.SatoshiPerBitcoin
|
||||
for u, neighbors := range desc.edges {
|
||||
for _, v := range neighbors {
|
||||
_, _, err := graph.addRandChannel(nodes[u], nodes[v], chanCapacity)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error while adding random channel: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
// Test betweenness centrality calculating using an example graph.
|
||||
func TestBetweennessCentralityWithNonEmptyGraph(t *testing.T) {
|
||||
graphDesc := testGraphDesc{
|
||||
nodes: 9,
|
||||
edges: map[int][]int{
|
||||
0: {1, 2, 3},
|
||||
1: {2},
|
||||
2: {3},
|
||||
3: {4, 5},
|
||||
4: {5, 6, 7},
|
||||
5: {6, 7},
|
||||
6: {7, 8},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
normalize bool
|
||||
centrality []float64
|
||||
}{
|
||||
{
|
||||
normalize: true,
|
||||
centrality: []float64{
|
||||
0.2, 0.0, 0.2, 1.0, 0.4, 0.4, 7.0 / 15.0, 0.0, 0.0,
|
||||
},
|
||||
},
|
||||
{
|
||||
normalize: false,
|
||||
centrality: []float64{
|
||||
3.0, 0.0, 3.0, 15.0, 6.0, 6.0, 7.0, 0.0, 0.0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, chanGraph := range chanGraphs {
|
||||
graph, cleanup, err := chanGraph.genFunc()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create graph: %v", err)
|
||||
}
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
success := t.Run(chanGraph.name, func(t1 *testing.T) {
|
||||
centralityMetric := NewBetweennessCentralityMetric()
|
||||
graphNodes := buildTestGraph(t1, graph, graphDesc)
|
||||
|
||||
if err := centralityMetric.Refresh(graph); err != nil {
|
||||
t1.Fatalf("error while calculating betweeness centrality")
|
||||
}
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
centrality := centralityMetric.GetMetric(test.normalize)
|
||||
|
||||
if len(centrality) != graphDesc.nodes {
|
||||
t.Fatalf("expected %v values, got: %v",
|
||||
graphDesc.nodes, len(centrality))
|
||||
}
|
||||
|
||||
for node, nodeCentrality := range test.centrality {
|
||||
nodeID := NewNodeID(graphNodes[node])
|
||||
calculatedCentrality, ok := centrality[nodeID]
|
||||
if !ok {
|
||||
t1.Fatalf("no result for node: %x (%v)", nodeID, node)
|
||||
}
|
||||
|
||||
if nodeCentrality != calculatedCentrality {
|
||||
t1.Errorf("centrality for node: %v should be %v, got: %v",
|
||||
node, test.centrality[node], calculatedCentrality)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user