forked from biased-unit/planout-golang
-
Notifications
You must be signed in to change notification settings - Fork 0
/
namespace.go
177 lines (149 loc) · 4.64 KB
/
namespace.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package planout
import (
"fmt"
"sort"
)
type Namespace interface {
AddExperiment(name string, code map[string]interface{}, segments int) error
RemoveExperiment(name string)
}
type SimpleNamespace struct {
Name string
PrimaryUnit string
NumSegments int
Inputs map[string]interface{}
segmentAllocations map[uint64]string
availableSegments []int
currentExperiments map[string]*Interpreter
defaultExperiment *Interpreter
selectedExperiment uint64
}
func NewSimpleNamespace(name string, numSegments int, primaryUnit string, inputs map[string]interface{}) SimpleNamespace {
avail := make([]int, 0, numSegments)
for i := 0; i < numSegments; i++ {
avail = append(avail, i)
}
noop := &Interpreter{
Name: name,
Salt: name,
Inputs: inputs,
Code: make(map[string]interface{}),
}
return SimpleNamespace{
Name: name,
PrimaryUnit: primaryUnit,
NumSegments: numSegments,
Inputs: inputs,
segmentAllocations: make(map[uint64]string),
availableSegments: avail,
currentExperiments: make(map[string]*Interpreter),
selectedExperiment: uint64(numSegments + 1),
defaultExperiment: noop,
}
}
func (n *SimpleNamespace) Run() *Interpreter {
interpreter := n.defaultExperiment
if name, ok := n.segmentAllocations[n.getSegment()]; ok {
interpreter = n.currentExperiments[name]
interpreter.Name = n.Name + "-" + interpreter.Name
interpreter.Salt = n.Name + "." + interpreter.Name
}
interpreter.Run()
return interpreter
}
func (n *SimpleNamespace) AddDefaultExperiment(defaultExperiment *Interpreter) {
n.defaultExperiment = defaultExperiment
}
func (n *SimpleNamespace) AddExperiment(name string, interpreter *Interpreter, segments int) error {
avail := len(n.availableSegments)
if avail < segments {
return fmt.Errorf("Not enough segments available %v to add the new experiment %v\n", avail, name)
}
if _, ok := n.currentExperiments[name]; ok {
return fmt.Errorf("There is already and experiment called %s\n", name)
}
n.allocateExperiment(name, segments)
n.currentExperiments[name] = interpreter
return nil
}
func (n *SimpleNamespace) RemoveExperiment(name string) error {
_, exists := n.currentExperiments[name]
if !exists {
return fmt.Errorf("Experiment %v does not exists in the namespace\n", name)
}
segmentsToFree := make([]int, 0, n.NumSegments)
for i := range n.segmentAllocations {
if n.segmentAllocations[i] == name {
segmentsToFree = append(segmentsToFree, int(i))
}
}
for i := range segmentsToFree {
n.availableSegments = append(n.availableSegments, segmentsToFree[i])
}
sort.Ints(n.availableSegments)
delete(n.currentExperiments, name)
return nil
}
func (n *SimpleNamespace) allocateExperiment(name string, segments int) {
// Sample(choices=available_segments, draws=segments, unit=name)
expt := &Interpreter{
Salt: n.Name,
Evaluated: false,
Inputs: n.Inputs,
Outputs: map[string]interface{}{},
Overrides: map[string]interface{}{},
}
// Compile Sample operator
var availableSegmentsAsInterface []interface{} = make([]interface{}, len(n.availableSegments))
for i, d := range n.availableSegments {
availableSegmentsAsInterface[i] = d
}
args := make(map[string]interface{})
args["choices"] = availableSegmentsAsInterface
args["unit"] = name
args["salt"] = n.Name
args["draws"] = segments
s := &sample{}
shuffle := s.execute(args, expt).([]interface{})
// Allocate sampled_segments to experiment
// Remove segment from available_segments
for i := range shuffle {
j := shuffle[i].(int)
n.segmentAllocations[uint64(j)] = name
n.availableSegments = deallocateSegments(n.availableSegments, j)
}
}
func (n *SimpleNamespace) getSegment() uint64 {
if n.selectedExperiment != uint64(n.NumSegments+1) {
return n.selectedExperiment
}
// generate random integer min=0, max=num_segments, unit=primary_unit
// RandomInteger(min=0, max=self.num_segments, unit=itemgetter(*self.primary_unit)(self.inputs))
expt := &Interpreter{
Salt: n.Name,
Evaluated: false,
Inputs: n.Inputs,
Outputs: map[string]interface{}{},
Overrides: map[string]interface{}{},
}
// Compile RandomInteger operator
args := make(map[string]interface{})
args["salt"] = n.Name
args["min"] = 0
args["max"] = n.NumSegments - 1
args["unit"] = n.Inputs[n.PrimaryUnit]
s := &randomInteger{}
n.selectedExperiment = s.execute(args, expt).(uint64)
return n.selectedExperiment
}
func deallocateSegments(allocated []int, segmentToRemove int) []int {
i := 0
n := len(allocated)
for i < n && allocated[i] != segmentToRemove {
i = i + 1
}
if i < n {
allocated = append(allocated[:i], allocated[i+1:]...)
}
return allocated
}