Skip to content

Commit 5a90565

Browse files
S2 AuthorsTorreyHoffman
authored andcommitted
Project import generated by Copybara.
PiperOrigin-RevId: 538225776
1 parent c3115a0 commit 5a90565

90 files changed

Lines changed: 6348 additions & 682 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

benchmarks/src/com/google/common/geometry/BestEdgesBenchmarkHarness.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public enum FactoryType {
7474
}
7575

7676
/**
77-
* A container for an index, an closest or furthest edge query on that index, and a set of targets
77+
* A container for an index, a closest or furthest edge query on that index, and a set of targets
7878
* to test with.
7979
*/
8080
public abstract static class BestEdgeQueryWithTargets {

benchmarks/src/com/google/common/geometry/S2EdgeUtilBenchmark.java

Lines changed: 134 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@
1515
*/
1616
package com.google.common.geometry.benchmarks;
1717

18+
import static java.lang.Math.PI;
1819
import static java.util.concurrent.TimeUnit.NANOSECONDS;
1920
import static java.util.concurrent.TimeUnit.SECONDS;
2021

22+
import com.google.common.geometry.S1Angle;
23+
import com.google.common.geometry.S1ChordAngle;
2124
import com.google.common.geometry.S2EdgeUtil;
2225
import com.google.common.geometry.S2Point;
2326
import com.google.errorprone.annotations.CheckReturnValue;
2427
import java.io.IOException;
25-
import java.util.ArrayList;
26-
import java.util.List;
2728
import org.openjdk.jmh.annotations.Benchmark;
2829
import org.openjdk.jmh.annotations.BenchmarkMode;
2930
import org.openjdk.jmh.annotations.Level;
@@ -41,33 +42,55 @@
4142
public class S2EdgeUtilBenchmark {
4243
private S2EdgeUtilBenchmark() {}
4344

44-
/** Benchmarks the crossing functions on a list of randomly generated points. */
45+
/** Benchmarks the interpolation and crossing functions on a list of randomly generated points. */
4546
@State(Scope.Thread)
4647
@BenchmarkMode(Mode.AverageTime)
4748
@OutputTimeUnit(NANOSECONDS)
48-
@Warmup(iterations = 3, time = 15, timeUnit = SECONDS)
49-
@Measurement(iterations = 5, time = 15, timeUnit = SECONDS)
49+
@Warmup(iterations = 3, time = 10, timeUnit = SECONDS)
50+
@Measurement(iterations = 10, time = 10, timeUnit = SECONDS)
5051
public static class BenchmarkCrossingState extends S2BenchmarkBaseState {
51-
protected static final int NUM_POINTS = 400;
52+
// We want to avoid cache effects, so these arrays should be small enough to fit in L1 cache.
53+
// An S2Point is at most 40 bytes, so 250 will take about ~10 KiB.
54+
protected static final int NUM_POINTS = 250;
55+
protected static final int NUM_ANGLES = 120;
56+
57+
// Randomly selected values between 0 and 1.
58+
protected double[] fractions = new double[NUM_ANGLES];
59+
// Randomly selected angles between 0 and PI/4.
60+
protected S1Angle[] angles = new S1Angle[NUM_ANGLES];
61+
protected S1ChordAngle[] chordAngles = new S1ChordAngle[NUM_ANGLES];
62+
63+
// Randomly selected points.
64+
protected S2Point[] points = new S2Point[NUM_POINTS];
65+
// Precomputed S1Angle distances between successive points.
66+
protected S1Angle[] precomputedS1Angle = new S1Angle[NUM_POINTS];
5267

53-
protected List<S2Point> points;
5468
protected int pointIndex;
69+
protected int distanceIndex;
5570
protected S2Point a;
5671
protected S2Point b;
5772

5873
@Setup(Level.Trial)
5974
@Override
6075
public void setup() throws IOException {
6176
super.setup();
62-
// We want to avoid cache effects, so numPoints should be small enough so that the points can
63-
// be in L1 cache. The size of an S2Point is at most 40 bytes, so 400 will only take at most
64-
// ~16 KiB of 64 KiB of L1 cache.
65-
points = new ArrayList<>(NUM_POINTS);
6677
for (int i = 0; i < NUM_POINTS; ++i) {
67-
points.add(data.getRandomPoint());
78+
points[i] = data.getRandomPoint();
79+
}
80+
for (int i = 0; i < NUM_POINTS; ++i) {
81+
S2Point a = points[i];
82+
S2Point b = points[(i + 1) % NUM_POINTS];
83+
precomputedS1Angle[i] = new S1Angle(a, b);
6884
}
6985
pointIndex = 0;
7086

87+
for (int i = 0; i < NUM_ANGLES; ++i) {
88+
fractions[i] = data.uniform(0, 1);
89+
angles[i] = S1Angle.radians(data.uniform(0, PI / 4));
90+
chordAngles[i] = S1ChordAngle.fromS1Angle(angles[i]);
91+
}
92+
distanceIndex = 0;
93+
7194
// Approximately 1/4th of points will cross the edge 'ab'.
7295
a = data.getRandomPoint();
7396
b = S2Point.neg(a).add(new S2Point(0.1, 0.1, 0.1)).normalize();
@@ -80,17 +103,17 @@ public void setup() throws IOException {
80103
*
81104
* <p>For example, one test run found the following:
82105
* <ul>
83-
* <li> 37.787 +/- 0.499 ns/op for benchmarkOverhead.
106+
* <li> 37.787 +/- 0.499 ns/op for benchmarkFourPointOverhead.
84107
* <li>117.601 +/- 15.786 ns/op for edgeOrVertexCrossing, so true cost is 79 +/- 16 ns.
85108
* <li>113.949 +/- 1.507 ns/op for robustCrossing, so true cost is 76 +/- 2 ns.
86109
* </ul>
87110
*/
88111
@Benchmark
89-
public int benchmarkOverhead(Blackhole bh) {
90-
S2Point a = points.get((pointIndex + 0) % NUM_POINTS);
91-
S2Point b = points.get((pointIndex + 1) % NUM_POINTS);
92-
S2Point c = points.get((pointIndex + 2) % NUM_POINTS);
93-
S2Point d = points.get((pointIndex + 3) % NUM_POINTS);
112+
public int benchmarkFourPointOverhead(Blackhole bh) {
113+
S2Point a = points[(pointIndex + 0) % NUM_POINTS];
114+
S2Point b = points[(pointIndex + 1) % NUM_POINTS];
115+
S2Point c = points[(pointIndex + 2) % NUM_POINTS];
116+
S2Point d = points[(pointIndex + 3) % NUM_POINTS];
94117
pointIndex = (pointIndex + 1) % NUM_POINTS;
95118
bh.consume(a);
96119
bh.consume(b);
@@ -102,21 +125,21 @@ public int benchmarkOverhead(Blackhole bh) {
102125
/** Benchmarks a single call to edgeOrVertexCrossing() with random points. */
103126
@Benchmark
104127
public boolean edgeOrVertexCrossing() {
105-
S2Point a = points.get((pointIndex + 0) % NUM_POINTS);
106-
S2Point b = points.get((pointIndex + 1) % NUM_POINTS);
107-
S2Point c = points.get((pointIndex + 2) % NUM_POINTS);
108-
S2Point d = points.get((pointIndex + 3) % NUM_POINTS);
128+
S2Point a = points[(pointIndex + 0) % NUM_POINTS];
129+
S2Point b = points[(pointIndex + 1) % NUM_POINTS];
130+
S2Point c = points[(pointIndex + 2) % NUM_POINTS];
131+
S2Point d = points[(pointIndex + 3) % NUM_POINTS];
109132
pointIndex = (pointIndex + 1) % NUM_POINTS;
110133
return S2EdgeUtil.edgeOrVertexCrossing(a, b, c, d);
111134
}
112135

113136
/** Benchmarks a single call to robustCrossing() with random points. */
114137
@Benchmark
115138
public int robustCrossing() {
116-
S2Point a = points.get((pointIndex + 0) % NUM_POINTS);
117-
S2Point b = points.get((pointIndex + 1) % NUM_POINTS);
118-
S2Point c = points.get((pointIndex + 2) % NUM_POINTS);
119-
S2Point d = points.get((pointIndex + 3) % NUM_POINTS);
139+
S2Point a = points[(pointIndex + 0) % NUM_POINTS];
140+
S2Point b = points[(pointIndex + 1) % NUM_POINTS];
141+
S2Point c = points[(pointIndex + 2) % NUM_POINTS];
142+
S2Point d = points[(pointIndex + 3) % NUM_POINTS];
120143
pointIndex = (pointIndex + 1) % NUM_POINTS;
121144
return S2EdgeUtil.robustCrossing(a, b, c, d);
122145
}
@@ -128,11 +151,94 @@ public int robustCrossing() {
128151
*/
129152
@Benchmark
130153
public void edgeCrosser100RobustCrossings(Blackhole bh) {
131-
S2EdgeUtil.EdgeCrosser crosser = new S2EdgeUtil.EdgeCrosser(a, b, points.get(0));
154+
S2EdgeUtil.EdgeCrosser crosser = new S2EdgeUtil.EdgeCrosser(a, b, points[0]);
132155
for (int r = 100; r > 0; --r) {
133-
S2Point d = points.get(r % NUM_POINTS);
156+
S2Point d = points[r % NUM_POINTS];
134157
bh.consume(crosser.robustCrossing(d));
135158
}
136159
}
160+
161+
/**
162+
* Measures overhead due to iterating through and obtaining two S2Points with this benchmark
163+
* State. Subtract the time measured by this benchmark from the interpolation benchmarks below
164+
* to get a more accurate measure of their real cost.
165+
*/
166+
@Benchmark
167+
public int benchmarkTwoPointOverhead(Blackhole bh) {
168+
S2Point a = points[(pointIndex + 0) % NUM_POINTS];
169+
S2Point b = points[(pointIndex + 1) % NUM_POINTS];
170+
pointIndex = (pointIndex + 1) % NUM_POINTS;
171+
bh.consume(a);
172+
bh.consume(b);
173+
return pointIndex;
174+
}
175+
176+
/** Benchmarks a single call to interpolate() with random points and distances. */
177+
@Benchmark
178+
public S2Point interpolate() {
179+
S2Point a = points[(pointIndex + 0) % NUM_POINTS];
180+
S2Point b = points[(pointIndex + 1) % NUM_POINTS];
181+
double t = fractions[distanceIndex % NUM_ANGLES];
182+
pointIndex = (pointIndex + 1) % NUM_POINTS;
183+
distanceIndex = (distanceIndex + 1) % NUM_ANGLES;
184+
return S2EdgeUtil.interpolate(t, a, b);
185+
}
186+
187+
/**
188+
* Benchmarks {@link S2EdgeUtil#getPointOnLine(S2Point, S2Point, S1Angle)} with random
189+
* points and distances.
190+
*/
191+
@Benchmark
192+
public S2Point getPointOnLineS1Angle() {
193+
S2Point a = points[(pointIndex + 0) % NUM_POINTS];
194+
S2Point b = points[(pointIndex + 1) % NUM_POINTS];
195+
S1Angle angle = angles[distanceIndex % NUM_ANGLES];
196+
pointIndex = (pointIndex + 1) % NUM_POINTS;
197+
distanceIndex = (distanceIndex + 1) % NUM_ANGLES;
198+
return S2EdgeUtil.getPointOnLine(a, b, angle);
199+
}
200+
201+
/**
202+
* Benchmarks {@link S2EdgeUtil#getPointOnLine(S2Point, S2Point, S1Angle)} with random
203+
* points and distances.
204+
*/
205+
@Benchmark
206+
public S2Point getPointOnLineS1ChordAngle() {
207+
S2Point a = points[(pointIndex + 0) % NUM_POINTS];
208+
S2Point b = points[(pointIndex + 1) % NUM_POINTS];
209+
S1ChordAngle chordAngle = chordAngles[distanceIndex % NUM_ANGLES];
210+
pointIndex = (pointIndex + 1) % NUM_POINTS;
211+
distanceIndex = (distanceIndex + 1) % NUM_ANGLES;
212+
return S2EdgeUtil.getPointOnLine(a, b, chordAngle);
213+
}
214+
215+
/**
216+
* Benchmarks {@link S2EdgeUtil#interpolateAtDistance(S1Angle, S2Point, S2Point)} with random
217+
* points and distances.
218+
*/
219+
@Benchmark
220+
public S2Point interpolateAtDistance() {
221+
S2Point a = points[(pointIndex + 0) % NUM_POINTS];
222+
S2Point b = points[(pointIndex + 1) % NUM_POINTS];
223+
S1Angle angle = angles[distanceIndex % NUM_ANGLES];
224+
pointIndex = (pointIndex + 1) % NUM_POINTS;
225+
distanceIndex = (distanceIndex + 1) % NUM_ANGLES;
226+
return S2EdgeUtil.interpolateAtDistance(angle, a, b);
227+
}
228+
229+
/**
230+
* Benchmarks {@link S2EdgeUtil#interpolateAtDistance(S1Angle, S2Point, S2Point, S1Angle)} with
231+
* precomputed S1Angle distance AB for random points and distances.
232+
*/
233+
@Benchmark
234+
public S2Point interpolateAtDistanceWithPrecomputedS1AngleAB() {
235+
S2Point a = points[(pointIndex + 0) % NUM_POINTS];
236+
S2Point b = points[(pointIndex + 1) % NUM_POINTS];
237+
S1Angle ab = precomputedS1Angle[pointIndex % NUM_POINTS];
238+
S1Angle angle = angles[distanceIndex % NUM_ANGLES];
239+
pointIndex = (pointIndex + 1) % NUM_POINTS;
240+
distanceIndex = (distanceIndex + 1) % NUM_ANGLES;
241+
return S2EdgeUtil.interpolateAtDistance(angle, a, b, ab);
242+
}
137243
}
138244
}

benchmarks/src/com/google/common/geometry/S2LatLngRectBenchmark.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public S2LatLngRect addS2LatLngsX8() {
8787
/**
8888
* Measures the time to create an empty S2LatLngRect.Builder, add 8 S2LatLngs to it, first
8989
* converting each to an S2Point, and then build and return the resulting S2LatLngRect.
90-
* Note that converting S2Latlngs to S2Points before adding them to the builder is an bad idea.
90+
* Note that converting S2Latlngs to S2Points before adding them to the builder is a bad idea.
9191
* The addPoint(S2Point) call immediately converts them back to S2LatLngs, and these two
9292
* conversions make it about 8 times slower than adding S2LatLngs directly.
9393
*/

library/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
<packaging>jar</packaging>
3232

3333
<dependencies>
34+
<dependency>
35+
<groupId>com.google.errorprone</groupId>
36+
<artifactId>error_prone_annotations</artifactId>
37+
<version>${errorprone.version}</version>
38+
</dependency>
3439
<dependency>
3540
<groupId>com.google.guava</groupId>
3641
<artifactId>guava</artifactId>

library/src/com/google/common/geometry/BigPoint.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ final strictfp class BigPoint implements Comparable<BigPoint> {
3636
this.z = z;
3737
}
3838

39+
/** Returns the negative of this point. */
40+
public BigPoint neg() {
41+
return new BigPoint(x.negate(), y.negate(), z.negate());
42+
}
43+
3944
/** Returns an S2Point by rounding 'this' to double precision. */
4045
S2Point toS2Point() {
4146
return new S2Point(x.doubleValue(), y.doubleValue(), z.doubleValue());

library/src/com/google/common/geometry/LittleEndianInput.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,13 @@ public byte readByte() throws IOException {
3939
/**
4040
* Reads a fixed size of bytes from the input.
4141
*
42-
* @param size the number of bytes to read.
42+
* @param size the number of bytes to read. May be zero.
4343
* @throws IOException if past end of input or error in underlying stream
4444
*/
4545
public byte[] readBytes(final int size) throws IOException {
46+
if (size < 0) {
47+
throw new IOException("Attempt to read " + size + " bytes");
48+
}
4649
byte[] result = new byte[size];
4750
int numRead = input.read(result);
4851
if (numRead < size) {

library/src/com/google/common/geometry/Platform.java

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ final class Platform {
2828

2929
private Platform() {}
3030

31-
/** @see Math#IEEEremainder(double, double) */
31+
/**
32+
* @see Math#IEEEremainder(double, double)
33+
*/
3234
static double IEEEremainder(double f1, double f2) {
3335
return Math.IEEEremainder(f1, f2);
3436
}
3537

36-
/** @see Math#getExponent(double) */
38+
/**
39+
* @see Math#getExponent(double)
40+
*/
3741
static int getExponent(double d) {
3842
return Math.getExponent(d);
3943
}
@@ -116,19 +120,25 @@ public static long doubleHash(double value) {
116120
* techniques have been exhausted.
117121
*/
118122
public static int sign(S2Point a, S2Point b, S2Point c) {
119-
Real bycz = Real.mul(b.y, c.z);
120-
Real bzcy = Real.mul(b.z, c.y);
121-
Real bzcx = Real.mul(b.z, c.x);
122-
Real bxcz = Real.mul(b.x, c.z);
123-
Real bxcy = Real.mul(b.x, c.y);
124-
Real bycx = Real.mul(b.y, c.x);
125-
Real bcx = bycz.sub(bzcy);
126-
Real bcy = bzcx.sub(bxcz);
127-
Real bcz = bxcy.sub(bycx);
128-
Real x = bcx.mul(a.x);
129-
Real y = bcy.mul(a.y);
130-
Real z = bcz.mul(a.z);
131-
return x.add(y).add(z).signum();
123+
try {
124+
Real bycz = Real.strictMul(b.y, c.z);
125+
Real bzcy = Real.strictMul(b.z, c.y);
126+
Real bzcx = Real.strictMul(b.z, c.x);
127+
128+
Real bxcz = Real.strictMul(b.x, c.z);
129+
Real bxcy = Real.strictMul(b.x, c.y);
130+
Real bycx = Real.strictMul(b.y, c.x);
131+
132+
Real bcx = bycz.sub(bzcy);
133+
Real bcy = bzcx.sub(bxcz);
134+
Real bcz = bxcy.sub(bycx);
135+
Real x = bcx.strictMul(a.x);
136+
Real y = bcy.strictMul(a.y);
137+
Real z = bcz.strictMul(a.z);
138+
return x.add(y).add(z).signum();
139+
} catch (ArithmeticException unused) {
140+
return 0;
141+
}
132142
}
133143

134144
/**

library/src/com/google/common/geometry/PrimitiveArrays.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,8 @@ public int length() {
291291
/**
292292
* Decodes and returns this array as an {@code int[]}.
293293
*
294-
* <p>Throws an {@link IllegalArgumentException} if any value in this array is < {@link
295-
* Integer#MIN_VALUE} or > {@link Integer#MAX_VALUE}.
294+
* <p>Throws an {@link IllegalArgumentException} if any value in this array is less than {@link
295+
* Integer#MIN_VALUE} or greater than {@link Integer#MAX_VALUE}.
296296
*/
297297
default int[] toIntArray() {
298298
int[] result = new int[length()];
@@ -320,5 +320,12 @@ public static class Cursor {
320320
public long remaining() {
321321
return limit - position;
322322
}
323+
324+
/** Sets the cursor position to 'position' and returns this cursor. */
325+
public Cursor seek(long position) {
326+
Preconditions.checkArgument(position >= 0 && position < limit);
327+
this.position = position;
328+
return this;
329+
}
323330
}
324331
}

library/src/com/google/common/geometry/Projection.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ default S2Point unproject(R2Vector p) {
5555
/**
5656
* Returns the point obtained by interpolating the given fraction of the distance along the line
5757
* from A to B. Almost all projections should use the default implementation of this method, which
58-
* simply interpolates linearly in R2 space. Fractions < 0 or > 1 result in extrapolation instead.
58+
* simply interpolates linearly in R2 space. Fractions less than 0 or greater than 1 result in
59+
* extrapolation instead.
5960
*
6061
* <p>The only reason to override this method is if you want edges to be defined as something
6162
* other than straight lines in the 2D projected coordinate system. For example, using a

0 commit comments

Comments
 (0)