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

     1  package autopilot
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/decred/dcrd/dcrutil/v4"
     7  )
     8  
     9  // WeightedHeuristic is a tuple that associates a weight to an
    10  // AttachmentHeuristic. This is used to determining a node's final score when
    11  // querying several heuristics for scores.
    12  type WeightedHeuristic struct {
    13  	// Weight is this AttachmentHeuristic's relative weight factor. It
    14  	// should be between 0.0 and 1.0.
    15  	Weight float64
    16  
    17  	AttachmentHeuristic
    18  }
    19  
    20  // WeightedCombAttachment is an implementation of the AttachmentHeuristic
    21  // interface that combines the scores given by several sub-heuristics into one.
    22  type WeightedCombAttachment struct {
    23  	heuristics []*WeightedHeuristic
    24  }
    25  
    26  // NewWeightedCombAttachment creates a new instance of a WeightedCombAttachment.
    27  func NewWeightedCombAttachment(h ...*WeightedHeuristic) (
    28  	*WeightedCombAttachment, error) {
    29  
    30  	// The sum of weights given to the sub-heuristics must sum to exactly
    31  	// 1.0.
    32  	var sum float64
    33  	for _, w := range h {
    34  		sum += w.Weight
    35  	}
    36  
    37  	if sum != 1.0 {
    38  		return nil, fmt.Errorf("weights MUST sum to 1.0 (was %v)", sum)
    39  	}
    40  
    41  	return &WeightedCombAttachment{
    42  		heuristics: h,
    43  	}, nil
    44  }
    45  
    46  // A compile time assertion to ensure WeightedCombAttachment meets the
    47  // AttachmentHeuristic and ScoreSettable interfaces.
    48  var _ AttachmentHeuristic = (*WeightedCombAttachment)(nil)
    49  var _ ScoreSettable = (*WeightedCombAttachment)(nil)
    50  
    51  // Name returns the name of this heuristic.
    52  //
    53  // NOTE: This is a part of the AttachmentHeuristic interface.
    54  func (c *WeightedCombAttachment) Name() string {
    55  	return "weightedcomb"
    56  }
    57  
    58  // NodeScores is a method that given the current channel graph, current set of
    59  // local channels and funds available, scores the given nodes according to the
    60  // preference of opening a channel with them. The returned channel candidates
    61  // maps the NodeID to an attachment directive containing a score and a channel
    62  // size.
    63  //
    64  // The scores is determined by quering the set of sub-heuristics, then
    65  // combining these scores into a final score according to the active
    66  // configuration.
    67  //
    68  // The returned scores will be in the range [0, 1.0], where 0 indicates no
    69  // improvement in connectivity if a channel is opened to this node, while 1.0
    70  // is the maximum possible improvement in connectivity.
    71  //
    72  // NOTE: This is a part of the AttachmentHeuristic interface.
    73  func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
    74  	chanSize dcrutil.Amount, nodes map[NodeID]struct{}) (
    75  	map[NodeID]*NodeScore, error) {
    76  
    77  	// We now query each heuristic to determine the score they give to the
    78  	// nodes for the given channel size.
    79  	var subScores []map[NodeID]*NodeScore
    80  	for _, h := range c.heuristics {
    81  		log.Tracef("Getting scores from sub heuristic %v", h.Name())
    82  
    83  		s, err := h.NodeScores(
    84  			g, chans, chanSize, nodes,
    85  		)
    86  		if err != nil {
    87  			return nil, fmt.Errorf("unable to get sub score: %v",
    88  				err)
    89  		}
    90  
    91  		subScores = append(subScores, s)
    92  	}
    93  
    94  	// We combine the scores given by the sub-heuristics by using the
    95  	// heruistics' given weight factor.
    96  	scores := make(map[NodeID]*NodeScore)
    97  	for nID := range nodes {
    98  		score := &NodeScore{
    99  			NodeID: nID,
   100  		}
   101  
   102  		// Each sub-heuristic should have scored the node, if not it is
   103  		// implicitly given a zero score by that heuristic.
   104  		for i, h := range c.heuristics {
   105  			sub, ok := subScores[i][nID]
   106  			if !ok {
   107  				log.Tracef("No score given to node %x by sub "+
   108  					"heuristic %v", nID[:], h.Name())
   109  				continue
   110  			}
   111  			// Use the heuristic's weight factor to determine of
   112  			// how much weight we should give to this particular
   113  			// score.
   114  			subScore := h.Weight * sub.Score
   115  			log.Tracef("Giving node %x a sub score of %v "+
   116  				"(%v * %v) from sub heuristic %v", nID[:],
   117  				subScore, h.Weight, sub.Score, h.Name())
   118  
   119  			score.Score += subScore
   120  		}
   121  
   122  		log.Tracef("Node %x got final combined score %v", nID[:],
   123  			score.Score)
   124  
   125  		switch {
   126  		// Instead of adding a node with score 0 to the returned set,
   127  		// we just skip it.
   128  		case score.Score == 0:
   129  			continue
   130  
   131  		// Sanity check the new score.
   132  		case score.Score < 0 || score.Score > 1.0:
   133  			return nil, fmt.Errorf("invalid node score from "+
   134  				"combination: %v", score.Score)
   135  		}
   136  
   137  		scores[nID] = score
   138  	}
   139  
   140  	return scores, nil
   141  }
   142  
   143  // SetNodeScores is used to set the internal map from NodeIDs to scores. The
   144  // passed scores must be in the range [0, 1.0]. The fist parameter is the name
   145  // of the targeted heuristic, to allow recursively target specific
   146  // sub-heuristics. The returned boolean indicates whether the targeted
   147  // heuristic was found.
   148  //
   149  // Since this heuristic doesn't keep any internal scores, it will recursively
   150  // apply the scores to its sub-heuristics.
   151  //
   152  // NOTE: This is a part of the ScoreSettable interface.
   153  func (c *WeightedCombAttachment) SetNodeScores(targetHeuristic string,
   154  	newScores map[NodeID]float64) (bool, error) {
   155  
   156  	found := false
   157  	for _, h := range c.heuristics {
   158  		// It must be ScoreSettable to be available for external
   159  		// scores.
   160  		s, ok := h.AttachmentHeuristic.(ScoreSettable)
   161  		if !ok {
   162  			continue
   163  		}
   164  
   165  		// Heuristic supports scoring, attempt to set them.
   166  		applied, err := s.SetNodeScores(targetHeuristic, newScores)
   167  		if err != nil {
   168  			return false, err
   169  		}
   170  		found = found || applied
   171  	}
   172  
   173  	return found, nil
   174  }