1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package s2
16
17 import (
18 "flag"
19
20 "fmt"
21 "math/rand"
22 "reflect"
23 "testing"
24
25 "github.com/golang/geo/s1"
26 )
27
28 var (
29
30
31
32
33
34
35
36
37
38 benchmarkEdgeQueryRange = flag.Int("benchmark_edge_query_range", 7,
39 "Set the upper limit on times to scale up the edge query "+
40 "edge counts in benchmark runs.")
41 )
42
43
44
45 func TestEdgeQueryMaxDistance(t *testing.T) {
46 index := makeShapeIndex("0:0 | 1:0 | 2:0 | 3:0 # #")
47 query := NewFurthestEdgeQuery(index, nil)
48 target := NewMaxDistanceToPointTarget(parsePoint("4:0"))
49 results := query.findEdges(target, NewFurthestEdgeQueryOptions().MaxResults(1).common)
50
51 if len(results) != 1 {
52 t.Errorf("len(results) = %v, want 1: %+v", len(results), results)
53 return
54 }
55
56 if results[0].shapeID != 0 {
57 t.Errorf("shapeID should be 0 got %v", results[0].shapeID)
58
59 }
60
61 if results[0].edgeID != 0 {
62 t.Errorf("edgeID should be 0, got %v", results[0].edgeID)
63
64 }
65
66 if got, want := results[0].Distance().Angle().Degrees(), 4.0; !float64Near(got, want, 1e-13) {
67 t.Errorf("results[0].Distance = %v, want ~%v", got, want)
68 }
69 }
70
71 func TestEdgeQuerySortAndUnique(t *testing.T) {
72 tests := []struct {
73 have []EdgeQueryResult
74 want []EdgeQueryResult
75 }{
76 {
77
78 have: []EdgeQueryResult{
79 {distance: minDistance(0.0790858), shapeID: 0, edgeID: 0},
80 },
81 want: []EdgeQueryResult{
82 {distance: minDistance(0.0790858), shapeID: 0, edgeID: 0},
83 },
84 },
85 {
86
87 have: []EdgeQueryResult{
88 {distance: minDistance(0.0639643), shapeID: 0, edgeID: 4},
89 {distance: minDistance(0.0639643), shapeID: 0, edgeID: 2},
90 },
91 want: []EdgeQueryResult{
92 {distance: minDistance(0.0639643), shapeID: 0, edgeID: 2},
93 {distance: minDistance(0.0639643), shapeID: 0, edgeID: 4},
94 },
95 },
96 {
97
98 have: []EdgeQueryResult{
99 {distance: minDistance(0.0639643), shapeID: 0, edgeID: 1},
100 {distance: minDistance(0.0508695), shapeID: 0, edgeID: 11},
101 {distance: minDistance(0.1181251), shapeID: 0, edgeID: 43},
102 },
103 want: []EdgeQueryResult{
104 {distance: minDistance(0.0508695), shapeID: 0, edgeID: 11},
105 {distance: minDistance(0.0639643), shapeID: 0, edgeID: 1},
106 {distance: minDistance(0.1181251), shapeID: 0, edgeID: 43},
107 },
108 },
109 {
110
111
112 have: []EdgeQueryResult{
113 {distance: minDistance(0.0639643), shapeID: 4, edgeID: 2},
114 {distance: minDistance(0.0639643), shapeID: 3, edgeID: 8},
115 {distance: minDistance(0.0508695), shapeID: 0, edgeID: 11},
116 {distance: minDistance(0.1181251), shapeID: 0, edgeID: 43},
117 },
118 want: []EdgeQueryResult{
119 {distance: minDistance(0.0508695), shapeID: 0, edgeID: 11},
120 {distance: minDistance(0.0639643), shapeID: 3, edgeID: 8},
121 {distance: minDistance(0.0639643), shapeID: 4, edgeID: 2},
122 {distance: minDistance(0.1181251), shapeID: 0, edgeID: 43},
123 },
124 },
125 {
126
127 have: []EdgeQueryResult{
128 {distance: minDistance(0.0790858), shapeID: 0, edgeID: 0},
129 {distance: minDistance(0.0639643), shapeID: 0, edgeID: 1},
130 {distance: minDistance(0.0639643), shapeID: 0, edgeID: 2},
131 {distance: minDistance(0.0677918), shapeID: 0, edgeID: 3},
132 {distance: minDistance(0.0542300), shapeID: 0, edgeID: 4},
133 {distance: minDistance(0.0455950), shapeID: 0, edgeID: 5},
134 {distance: minDistance(0.0423160), shapeID: 0, edgeID: 6},
135 {distance: minDistance(0.0320540), shapeID: 0, edgeID: 7},
136 {distance: minDistance(0.0320540), shapeID: 0, edgeID: 8},
137 {distance: minDistance(0.0404029), shapeID: 0, edgeID: 9},
138 {distance: minDistance(0.0405702), shapeID: 0, edgeID: 10},
139 {distance: minDistance(0.0508695), shapeID: 0, edgeID: 11},
140 {distance: minDistance(0.0627421), shapeID: 0, edgeID: 12},
141 {distance: minDistance(0.0539154), shapeID: 0, edgeID: 13},
142 {distance: minDistance(0.1181251), shapeID: 0, edgeID: 43},
143 {distance: minDistance(0.1061612), shapeID: 0, edgeID: 44},
144 {distance: minDistance(0.1061612), shapeID: 0, edgeID: 45},
145 {distance: minDistance(0.0957947), shapeID: 0, edgeID: 46},
146 },
147 want: []EdgeQueryResult{
148 {distance: minDistance(0.0320540), shapeID: 0, edgeID: 7},
149 {distance: minDistance(0.0320540), shapeID: 0, edgeID: 8},
150 {distance: minDistance(0.0404029), shapeID: 0, edgeID: 9},
151 {distance: minDistance(0.0405702), shapeID: 0, edgeID: 10},
152 {distance: minDistance(0.0423160), shapeID: 0, edgeID: 6},
153 {distance: minDistance(0.0455950), shapeID: 0, edgeID: 5},
154 {distance: minDistance(0.0508695), shapeID: 0, edgeID: 11},
155 {distance: minDistance(0.0539154), shapeID: 0, edgeID: 13},
156 {distance: minDistance(0.0542300), shapeID: 0, edgeID: 4},
157 {distance: minDistance(0.0627421), shapeID: 0, edgeID: 12},
158 {distance: minDistance(0.0639643), shapeID: 0, edgeID: 1},
159 {distance: minDistance(0.0639643), shapeID: 0, edgeID: 2},
160 {distance: minDistance(0.0677918), shapeID: 0, edgeID: 3},
161 {distance: minDistance(0.0790858), shapeID: 0, edgeID: 0},
162 {distance: minDistance(0.0957947), shapeID: 0, edgeID: 46},
163 {distance: minDistance(0.1061612), shapeID: 0, edgeID: 44},
164 {distance: minDistance(0.1061612), shapeID: 0, edgeID: 45},
165 {distance: minDistance(0.1181251), shapeID: 0, edgeID: 43},
166 },
167 },
168 }
169
170 for _, test := range tests {
171 have := append([]EdgeQueryResult{}, test.have...)
172 got := sortAndUniqueResults(have)
173 if !reflect.DeepEqual(got, test.want) {
174 t.Errorf("sortAndUniqueResults(%v) =\n %v, \nwant %v", test.have, got, test.want)
175 }
176 }
177 }
178
179
180
181 type shapeIndexGeneratorFunc func(c Cap, numEdges int, index *ShapeIndex)
182
183
184
185
186
187
188
189 func loopShapeIndexGenerator(c Cap, numEdges int, index *ShapeIndex) {
190 index.Add(RegularLoop(c.Center(), c.Radius(), numEdges))
191 }
192
193
194
195 func fractalLoopShapeIndexGenerator(c Cap, numEdges int, index *ShapeIndex) {
196 fractal := newFractal()
197 fractal.setLevelForApproxMaxEdges(numEdges)
198 index.Add(fractal.makeLoop(randomFrameAtPoint(c.Center()), c.Radius()))
199 }
200
201
202
203 func pointCloudShapeIndexGenerator(c Cap, numPoints int, index *ShapeIndex) {
204 var points PointVector
205 for i := 0; i < numPoints; i++ {
206 points = append(points, samplePointFromCap(c))
207 }
208 index.Add(&points)
209 }
210
211 type queryTargetType int
212
213 const (
214 queryTypePoint queryTargetType = iota
215 queryTypeEdge
216 queryTypeCell
217 queryTypeIndex
218 )
219
220 const edgeQueryTestNumIndexes = 50
221 const edgeQueryTestNumEdges = 100
222 const edgeQueryTestNumQueries = 200
223
224
225 var testCapRadius = kmToAngle(10)
226
227
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338 func benchmarkEdgeQueryFindClosest(b *testing.B, bmOpts *edgeQueryBenchmarkOptions) {
339 index := NewShapeIndex()
340 opts := NewClosestEdgeQueryOptions().MaxResults(1).IncludeInteriors(bmOpts.includeInteriors)
341
342 radius := kmToAngle(bmOpts.radiusKm.Radians())
343 if bmOpts.maxDistanceFraction > 0 {
344 opts.DistanceLimit(s1.ChordAngleFromAngle(s1.Angle(bmOpts.maxDistanceFraction) * radius))
345 }
346 if bmOpts.maxErrorFraction > 0 {
347 opts.MaxError(s1.ChordAngleFromAngle(s1.Angle(bmOpts.maxErrorFraction) * radius))
348 }
349
350 opts.UseBruteForce(*benchmarkBruteForce)
351 query := NewClosestEdgeQuery(index, opts)
352
353 var targets []distanceTarget
354
355
356
357 bmOpts.numIndexEdges = 3
358 for n := 1; n <= *benchmarkEdgeQueryRange; n++ {
359 bmOpts.numIndexEdges *= 4
360 b.Run(fmt.Sprintf("%d", bmOpts.numIndexEdges),
361 func(b *testing.B) {
362
363
364
365 targets, _ = generateEdgeQueryWithTargets(bmOpts, query, index)
366 for i := 0; i < b.N; i++ {
367
368
369
370
371
372
373
374
375 query.FindEdges(targets[i%len(targets)])
376 }
377 })
378 }
379 }
380
381
382
383 type edgeQueryBenchmarkOptions struct {
384 iters int
385 indexGenerator shapeIndexGeneratorFunc
386 numIndexEdges int
387 includeInteriors bool
388 targetType queryTargetType
389 numTargetEdges int
390 chooseTargetFromIndex bool
391 radiusKm s1.Angle
392 maxDistanceFraction float64
393 maxErrorFraction float64
394 targetRadiusFraction float64
395 centerSeparationFraction float64
396 randomSeed int64
397 }
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429 func generateEdgeQueryWithTargets(opts *edgeQueryBenchmarkOptions, query *EdgeQuery, queryIndex *ShapeIndex) (targets []distanceTarget, targetIndexes []*ShapeIndex) {
430
431
432 const maxTargetsPerIndex = 100
433
434
435 rand.Seed(opts.randomSeed)
436 opts.randomSeed++
437 indexCap := CapFromCenterAngle(randomPoint(), opts.radiusKm)
438
439 query.Reset()
440 queryIndex.Reset()
441 opts.indexGenerator(indexCap, opts.numIndexEdges, queryIndex)
442
443 targets = make([]distanceTarget, 0)
444 targetIndexes = make([]*ShapeIndex, 0)
445
446 numTargets := maxTargetsPerIndex
447 if opts.targetType == queryTypeIndex {
448
449 numTargets = minInt(numTargets, 500000/opts.numTargetEdges)
450 }
451
452 for i := 0; i < numTargets; i++ {
453 targetDist := fractionToRadius(opts.centerSeparationFraction, opts.radiusKm.Radians())
454 targetCap := CapFromCenterAngle(
455 sampleBoundaryFromCap(CapFromCenterAngle(indexCap.Center(), targetDist)),
456 fractionToRadius(opts.targetRadiusFraction, opts.radiusKm.Radians()))
457
458 switch opts.targetType {
459 case queryTypePoint:
460 var pt Point
461 if opts.chooseTargetFromIndex {
462 pt = sampleEdgeFromIndex(queryIndex).V0
463 } else {
464 pt = targetCap.Center()
465 }
466 targets = append(targets, NewMinDistanceToPointTarget(pt))
467
468 case queryTypeEdge:
469 var edge Edge
470 if opts.chooseTargetFromIndex {
471 edge = sampleEdgeFromIndex(queryIndex)
472 } else {
473 edge.V0 = sampleBoundaryFromCap(targetCap)
474 edge.V1 = sampleBoundaryFromCap(targetCap)
475 }
476 targets = append(targets, NewMinDistanceToEdgeTarget(edge))
477 case queryTypeCell:
478 var cellID CellID
479 if opts.chooseTargetFromIndex {
480 cellID = sampleCellFromIndex(queryIndex)
481 } else {
482 cellID = cellIDFromPoint(targetCap.Center()).Parent(
483 MaxDiagMetric.ClosestLevel(targetCap.Radius().Radians()))
484 }
485 targets = append(targets, NewMinDistanceToCellTarget(CellFromCellID(cellID)))
486 case queryTypeIndex:
487 targetIndex := NewShapeIndex()
488 if opts.chooseTargetFromIndex {
489 var shape edgeVectorShape
490 for i := 0; i < opts.numTargetEdges; i++ {
491 edge := sampleEdgeFromIndex(queryIndex)
492 shape.Add(edge.V0, edge.V1)
493 }
494 targetIndex.Add(&shape)
495 } else {
496 opts.indexGenerator(targetCap, opts.numTargetEdges, targetIndex)
497 }
498 target := NewMinDistanceToShapeIndexTarget(targetIndex)
499 target.setIncludeInteriors(opts.includeInteriors)
500 targets = append(targets, target)
501 targetIndexes = append(targetIndexes, targetIndex)
502 default:
503 panic(fmt.Sprintf("unknown query target type %v", opts.targetType))
504 }
505 }
506
507 return targets, targetIndexes
508 }
509
510 func sampleBoundaryFromCap(c Cap) Point {
511 return InterpolateAtDistance(c.Radius(), c.Center(), randomPoint())
512 }
513
514 func sampleEdgeFromIndex(index *ShapeIndex) Edge {
515 e := randomUniformInt(index.NumEdges())
516
517 for _, shape := range index.shapes {
518 if e < shape.NumEdges() {
519 return shape.Edge(e)
520 }
521 e -= shape.NumEdges()
522 }
523
524 panic("index with no edges")
525 }
526
527 func sampleCellFromIndex(index *ShapeIndex) CellID {
528 iter := index.Iterator()
529 for i := randomUniformInt(len(index.cells)); i >= 0; i-- {
530 iter.Next()
531 continue
532 }
533 return iter.CellID()
534 }
535
536 func fractionToRadius(fraction, radiusKm float64) s1.Angle {
537 if fraction < 0 {
538 fraction = -randomFloat64() * fraction
539 }
540 return s1.Angle(fraction) * kmToAngle(radiusKm)
541 }
542
View as plain text