Merge pull request #2304 from halseth/weigthed-choice-errnopositive

[autopilot] Don't fail on no more positive nodes
This commit is contained in:
Olaoluwa Osuntokun 2018-12-12 12:21:06 -08:00 committed by GitHub
commit d24a6d2a3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 80 additions and 67 deletions

@ -366,73 +366,6 @@ func mergeChanState(pendingChans map[NodeID]Channel,
return totalChans return totalChans
} }
// weightedChoice draws a random index from the map of channel candidates, with
// a probability propotional to their score.
func weightedChoice(s map[NodeID]*AttachmentDirective) (NodeID, error) {
// Calculate the sum of scores found in the map.
var sum float64
for _, v := range s {
sum += v.Score
}
if sum <= 0 {
return NodeID{}, fmt.Errorf("non-positive sum")
}
// Create a map of normalized scores such, that they sum to 1.0.
norm := make(map[NodeID]float64)
for k, v := range s {
norm[k] = v.Score / sum
}
// Pick a random number in the range [0.0, 1.0), and iterate the map
// until the number goes below 0. This means that each index is picked
// with a probablity equal to their normalized score.
//
// Example:
// Items with scores [1, 5, 2, 2]
// Normalized scores [0.1, 0.5, 0.2, 0.2]
// Imagine they each occupy a "range" equal to their normalized score
// in [0, 1.0]:
// [|-0.1-||-----0.5-----||--0.2--||--0.2--|]
// The following loop is now equivalent to "hitting" the intervals.
r := rand.Float64()
for k, v := range norm {
r -= v
if r <= 0 {
return k, nil
}
}
return NodeID{}, fmt.Errorf("no choice made")
}
// chooseN picks at random min[n, len(s)] nodes if from the
// AttachmentDirectives map, with a probability weighted by their score.
func chooseN(n int, s map[NodeID]*AttachmentDirective) (
map[NodeID]*AttachmentDirective, error) {
// Keep a map of nodes not yet choosen.
rem := make(map[NodeID]*AttachmentDirective)
for k, v := range s {
rem[k] = v
}
// Pick a weighted choice from the remaining nodes as long as there are
// nodes left, and we haven't already picked n.
chosen := make(map[NodeID]*AttachmentDirective)
for len(chosen) < n && len(rem) > 0 {
choice, err := weightedChoice(rem)
if err != nil {
return nil, err
}
chosen[choice] = rem[choice]
delete(rem, choice)
}
return chosen, nil
}
// controller implements the closed-loop control system of the Agent. The // controller implements the closed-loop control system of the Agent. The
// controller will make a decision w.r.t channel placement within the graph // controller will make a decision w.r.t channel placement within the graph
// based on: its current internal state of the set of active channels open, // based on: its current internal state of the set of active channels open,

80
autopilot/choice.go Normal file

@ -0,0 +1,80 @@
package autopilot
import (
"errors"
"fmt"
"math/rand"
)
// ErrNoPositive is returned from weightedChoice when there are no positive
// weights left to choose from.
var ErrNoPositive = errors.New("no positive weights left")
// weightedChoice draws a random index from the map of channel candidates, with
// a probability propotional to their score.
func weightedChoice(s map[NodeID]*AttachmentDirective) (NodeID, error) {
// Calculate the sum of scores found in the map.
var sum float64
for _, v := range s {
sum += v.Score
}
if sum <= 0 {
return NodeID{}, ErrNoPositive
}
// Create a map of normalized scores such, that they sum to 1.0.
norm := make(map[NodeID]float64)
for k, v := range s {
norm[k] = v.Score / sum
}
// Pick a random number in the range [0.0, 1.0), and iterate the map
// until the number goes below 0. This means that each index is picked
// with a probablity equal to their normalized score.
//
// Example:
// Items with scores [1, 5, 2, 2]
// Normalized scores [0.1, 0.5, 0.2, 0.2]
// Imagine they each occupy a "range" equal to their normalized score
// in [0, 1.0]:
// [|-0.1-||-----0.5-----||--0.2--||--0.2--|]
// The following loop is now equivalent to "hitting" the intervals.
r := rand.Float64()
for k, v := range norm {
r -= v
if r <= 0 {
return k, nil
}
}
return NodeID{}, fmt.Errorf("unable to make choice")
}
// chooseN picks at random min[n, len(s)] nodes if from the
// AttachmentDirectives map, with a probability weighted by their score.
func chooseN(n int, s map[NodeID]*AttachmentDirective) (
map[NodeID]*AttachmentDirective, error) {
// Keep a map of nodes not yet choosen.
rem := make(map[NodeID]*AttachmentDirective)
for k, v := range s {
rem[k] = v
}
// Pick a weighted choice from the remaining nodes as long as there are
// nodes left, and we haven't already picked n.
chosen := make(map[NodeID]*AttachmentDirective)
for len(chosen) < n && len(rem) > 0 {
choice, err := weightedChoice(rem)
if err == ErrNoPositive {
return chosen, nil
} else if err != nil {
return nil, err
}
chosen[choice] = rem[choice]
delete(rem, choice)
}
return chosen, nil
}