blob: 6fb033c0cd3de1d9560911d6c2413d8a3992b943 [file] [log] [blame]
William Kurkianea869482019-04-09 15:16:11 -04001package coordinate
2
3import (
4 "fmt"
5 "math"
6 "math/rand"
7 "time"
8)
9
10// GenerateClients returns a slice with nodes number of clients, all with the
11// given config.
12func GenerateClients(nodes int, config *Config) ([]*Client, error) {
13 clients := make([]*Client, nodes)
14 for i, _ := range clients {
15 client, err := NewClient(config)
16 if err != nil {
17 return nil, err
18 }
19
20 clients[i] = client
21 }
22 return clients, nil
23}
24
25// GenerateLine returns a truth matrix as if all the nodes are in a straight linke
26// with the given spacing between them.
27func GenerateLine(nodes int, spacing time.Duration) [][]time.Duration {
28 truth := make([][]time.Duration, nodes)
29 for i := range truth {
30 truth[i] = make([]time.Duration, nodes)
31 }
32
33 for i := 0; i < nodes; i++ {
34 for j := i + 1; j < nodes; j++ {
35 rtt := time.Duration(j-i) * spacing
36 truth[i][j], truth[j][i] = rtt, rtt
37 }
38 }
39 return truth
40}
41
42// GenerateGrid returns a truth matrix as if all the nodes are in a two dimensional
43// grid with the given spacing between them.
44func GenerateGrid(nodes int, spacing time.Duration) [][]time.Duration {
45 truth := make([][]time.Duration, nodes)
46 for i := range truth {
47 truth[i] = make([]time.Duration, nodes)
48 }
49
50 n := int(math.Sqrt(float64(nodes)))
51 for i := 0; i < nodes; i++ {
52 for j := i + 1; j < nodes; j++ {
53 x1, y1 := float64(i%n), float64(i/n)
54 x2, y2 := float64(j%n), float64(j/n)
55 dx, dy := x2-x1, y2-y1
56 dist := math.Sqrt(dx*dx + dy*dy)
57 rtt := time.Duration(dist * float64(spacing))
58 truth[i][j], truth[j][i] = rtt, rtt
59 }
60 }
61 return truth
62}
63
64// GenerateSplit returns a truth matrix as if half the nodes are close together in
65// one location and half the nodes are close together in another. The lan factor
66// is used to separate the nodes locally and the wan factor represents the split
67// between the two sides.
68func GenerateSplit(nodes int, lan time.Duration, wan time.Duration) [][]time.Duration {
69 truth := make([][]time.Duration, nodes)
70 for i := range truth {
71 truth[i] = make([]time.Duration, nodes)
72 }
73
74 split := nodes / 2
75 for i := 0; i < nodes; i++ {
76 for j := i + 1; j < nodes; j++ {
77 rtt := lan
78 if (i <= split && j > split) || (i > split && j <= split) {
79 rtt += wan
80 }
81 truth[i][j], truth[j][i] = rtt, rtt
82 }
83 }
84 return truth
85}
86
87// GenerateCircle returns a truth matrix for a set of nodes, evenly distributed
88// around a circle with the given radius. The first node is at the "center" of the
89// circle because it's equidistant from all the other nodes, but we place it at
90// double the radius, so it should show up above all the other nodes in height.
91func GenerateCircle(nodes int, radius time.Duration) [][]time.Duration {
92 truth := make([][]time.Duration, nodes)
93 for i := range truth {
94 truth[i] = make([]time.Duration, nodes)
95 }
96
97 for i := 0; i < nodes; i++ {
98 for j := i + 1; j < nodes; j++ {
99 var rtt time.Duration
100 if i == 0 {
101 rtt = 2 * radius
102 } else {
103 t1 := 2.0 * math.Pi * float64(i) / float64(nodes)
104 x1, y1 := math.Cos(t1), math.Sin(t1)
105 t2 := 2.0 * math.Pi * float64(j) / float64(nodes)
106 x2, y2 := math.Cos(t2), math.Sin(t2)
107 dx, dy := x2-x1, y2-y1
108 dist := math.Sqrt(dx*dx + dy*dy)
109 rtt = time.Duration(dist * float64(radius))
110 }
111 truth[i][j], truth[j][i] = rtt, rtt
112 }
113 }
114 return truth
115}
116
117// GenerateRandom returns a truth matrix for a set of nodes with normally
118// distributed delays, with the given mean and deviation. The RNG is re-seeded
119// so you always get the same matrix for a given size.
120func GenerateRandom(nodes int, mean time.Duration, deviation time.Duration) [][]time.Duration {
121 rand.Seed(1)
122
123 truth := make([][]time.Duration, nodes)
124 for i := range truth {
125 truth[i] = make([]time.Duration, nodes)
126 }
127
128 for i := 0; i < nodes; i++ {
129 for j := i + 1; j < nodes; j++ {
130 rttSeconds := rand.NormFloat64()*deviation.Seconds() + mean.Seconds()
131 rtt := time.Duration(rttSeconds * secondsToNanoseconds)
132 truth[i][j], truth[j][i] = rtt, rtt
133 }
134 }
135 return truth
136}
137
138// Simulate runs the given number of cycles using the given list of clients and
139// truth matrix. On each cycle, each client will pick a random node and observe
140// the truth RTT, updating its coordinate estimate. The RNG is re-seeded for
141// each simulation run to get deterministic results (for this algorithm and the
142// underlying algorithm which will use random numbers for position vectors when
143// starting out with everything at the origin).
144func Simulate(clients []*Client, truth [][]time.Duration, cycles int) {
145 rand.Seed(1)
146
147 nodes := len(clients)
148 for cycle := 0; cycle < cycles; cycle++ {
149 for i, _ := range clients {
150 if j := rand.Intn(nodes); j != i {
151 c := clients[j].GetCoordinate()
152 rtt := truth[i][j]
153 node := fmt.Sprintf("node_%d", j)
154 clients[i].Update(node, c, rtt)
155 }
156 }
157 }
158}
159
160// Stats is returned from the Evaluate function with a summary of the algorithm
161// performance.
162type Stats struct {
163 ErrorMax float64
164 ErrorAvg float64
165}
166
167// Evaluate uses the coordinates of the given clients to calculate estimated
168// distances and compares them with the given truth matrix, returning summary
169// stats.
170func Evaluate(clients []*Client, truth [][]time.Duration) (stats Stats) {
171 nodes := len(clients)
172 count := 0
173 for i := 0; i < nodes; i++ {
174 for j := i + 1; j < nodes; j++ {
175 est := clients[i].DistanceTo(clients[j].GetCoordinate()).Seconds()
176 actual := truth[i][j].Seconds()
177 error := math.Abs(est-actual) / actual
178 stats.ErrorMax = math.Max(stats.ErrorMax, error)
179 stats.ErrorAvg += error
180 count += 1
181 }
182 }
183
184 stats.ErrorAvg /= float64(count)
185 fmt.Printf("Error avg=%9.6f max=%9.6f\n", stats.ErrorAvg, stats.ErrorMax)
186 return
187}