top_centrality.go 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. package autopilot
  2. import (
  3. "runtime"
  4. "github.com/btcsuite/btcd/btcutil"
  5. )
  6. // TopCentrality is a simple greedy technique to create connections to nodes
  7. // with the top betweenness centrality value. This algorithm is usually
  8. // referred to as TopK in the literature. The idea is that by opening channels
  9. // to nodes with top betweenness centrality we also increase our own betweenness
  10. // centrality (given we already have at least one channel, or create at least
  11. // two new channels).
  12. // A different and much better approach is instead of selecting nodes with top
  13. // centrality value, we extend the graph in a loop by inserting a new non
  14. // existing edge and recalculate the betweenness centrality of each node. This
  15. // technique is usually referred to as "greedy" algorithm and gives better
  16. // results than TopK but is considerably slower too.
  17. type TopCentrality struct {
  18. centralityMetric *BetweennessCentrality
  19. }
  20. // A compile time assertion to ensure TopCentrality meets the
  21. // AttachmentHeuristic interface.
  22. var _ AttachmentHeuristic = (*TopCentrality)(nil)
  23. // NewTopCentrality constructs and returns a new TopCentrality heuristic.
  24. func NewTopCentrality() *TopCentrality {
  25. metric, err := NewBetweennessCentralityMetric(
  26. runtime.NumCPU(),
  27. )
  28. if err != nil {
  29. panic(err)
  30. }
  31. return &TopCentrality{
  32. centralityMetric: metric,
  33. }
  34. }
  35. // Name returns the name of the heuristic.
  36. func (g *TopCentrality) Name() string {
  37. return "top_centrality"
  38. }
  39. // NodeScores will return a [0,1] normalized map of scores for the given nodes
  40. // except for the ones we already have channels with. The scores will simply
  41. // be the betweenness centrality values of the nodes.
  42. // As our current implementation of betweenness centrality is non-incremental,
  43. // NodeScores will recalculate the centrality values on every call, which is
  44. // slow for large graphs.
  45. func (g *TopCentrality) NodeScores(graph ChannelGraph, chans []LocalChannel,
  46. chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
  47. map[NodeID]*NodeScore, error) {
  48. // Calculate betweenness centrality for the whole graph.
  49. if err := g.centralityMetric.Refresh(graph); err != nil {
  50. return nil, err
  51. }
  52. normalize := true
  53. centrality := g.centralityMetric.GetMetric(normalize)
  54. // Create a map of the existing peers for faster filtering.
  55. existingPeers := make(map[NodeID]struct{})
  56. for _, c := range chans {
  57. existingPeers[c.Node] = struct{}{}
  58. }
  59. result := make(map[NodeID]*NodeScore, len(nodes))
  60. for nodeID := range nodes {
  61. // Skip nodes we already have channel with.
  62. if _, ok := existingPeers[nodeID]; ok {
  63. continue
  64. }
  65. // Skip passed nodes not in the graph. This could happen if
  66. // the graph changed before computing the centrality values as
  67. // the nodes we iterate are prefiltered by the autopilot agent.
  68. score, ok := centrality[nodeID]
  69. if !ok {
  70. continue
  71. }
  72. result[nodeID] = &NodeScore{
  73. NodeID: nodeID,
  74. Score: score,
  75. }
  76. }
  77. return result, nil
  78. }