From f1e8c8d5b567efe953dbc1c6f312cdf847defeaa Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 10 Dec 2018 11:23:18 +0100 Subject: [PATCH 1/2] autopilot/agent: move choice algorithms to new file choice.go --- autopilot/agent.go | 67 ----------------------------------------- autopilot/choice.go | 73 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 67 deletions(-) create mode 100644 autopilot/choice.go diff --git a/autopilot/agent.go b/autopilot/agent.go index be6480c4..8475bb7c 100644 --- a/autopilot/agent.go +++ b/autopilot/agent.go @@ -366,73 +366,6 @@ func mergeChanState(pendingChans map[NodeID]Channel, 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 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, diff --git a/autopilot/choice.go b/autopilot/choice.go new file mode 100644 index 00000000..0e7fd5d6 --- /dev/null +++ b/autopilot/choice.go @@ -0,0 +1,73 @@ +package autopilot + +import ( + "fmt" + "math/rand" +) + +// 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 +} From 40db2dd5a52c857ca6aaec4eb40ca2f7a23cce67 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 10 Dec 2018 11:23:19 +0100 Subject: [PATCH 2/2] autopilot/choice: return ErrNoPositive in case no choice can be made --- autopilot/choice.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/autopilot/choice.go b/autopilot/choice.go index 0e7fd5d6..6e4ec9c0 100644 --- a/autopilot/choice.go +++ b/autopilot/choice.go @@ -1,10 +1,15 @@ 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) { @@ -15,7 +20,7 @@ func weightedChoice(s map[NodeID]*AttachmentDirective) (NodeID, error) { } if sum <= 0 { - return NodeID{}, fmt.Errorf("non-positive sum") + return NodeID{}, ErrNoPositive } // Create a map of normalized scores such, that they sum to 1.0. @@ -42,7 +47,7 @@ func weightedChoice(s map[NodeID]*AttachmentDirective) (NodeID, error) { return k, nil } } - return NodeID{}, fmt.Errorf("no choice made") + return NodeID{}, fmt.Errorf("unable to make choice") } // chooseN picks at random min[n, len(s)] nodes if from the @@ -61,7 +66,9 @@ func chooseN(n int, s map[NodeID]*AttachmentDirective) ( chosen := make(map[NodeID]*AttachmentDirective) for len(chosen) < n && len(rem) > 0 { choice, err := weightedChoice(rem) - if err != nil { + if err == ErrNoPositive { + return chosen, nil + } else if err != nil { return nil, err }