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

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