Skip to content

Commit a5e5e9a

Browse files
committed
Move sloppySin into SloppyMath from GeoUtils (apache#14516)
1 parent b2fbcd2 commit a5e5e9a

File tree

7 files changed

+231
-29
lines changed

7 files changed

+231
-29
lines changed

lucene/CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ Other
5454

5555
* GITHUB#7145: Adding basic unit tests for SpanWithinQuery. (Jakub Slowinski)
5656

57+
* GITHUB#14516: Move sloppySin into SloppyMath from GeoUtils (Ankit Jain)
58+
59+
5760
======================= Lucene 10.2.1 =======================
5861

5962
Bug Fixes
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.lucene.benchmark.jmh;
18+
19+
import static java.lang.Math.PI;
20+
import static java.lang.Math.max;
21+
import static java.lang.Math.min;
22+
import static org.apache.lucene.geo.GeoUtils.EARTH_MEAN_RADIUS_METERS;
23+
import static org.apache.lucene.geo.GeoUtils.MAX_LAT_RADIANS;
24+
import static org.apache.lucene.geo.GeoUtils.MAX_LON_RADIANS;
25+
import static org.apache.lucene.geo.GeoUtils.MIN_LAT_RADIANS;
26+
import static org.apache.lucene.geo.GeoUtils.MIN_LON_RADIANS;
27+
import static org.apache.lucene.geo.GeoUtils.checkLatitude;
28+
import static org.apache.lucene.geo.GeoUtils.checkLongitude;
29+
import static org.apache.lucene.util.SloppyMath.asin;
30+
import static org.apache.lucene.util.SloppyMath.cos;
31+
32+
import java.util.concurrent.TimeUnit;
33+
import org.apache.lucene.geo.Rectangle;
34+
import org.openjdk.jmh.annotations.*;
35+
36+
@State(Scope.Thread)
37+
@BenchmarkMode(Mode.Throughput)
38+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
39+
@Fork(value = 1, warmups = 1)
40+
@Warmup(iterations = 1, time = 1)
41+
@Measurement(iterations = 3, time = 2)
42+
public class RectangleBenchmark {
43+
44+
@State(Scope.Benchmark)
45+
public static class ExecutionPlan {
46+
// Test cases representing different geographical scenarios
47+
@Param({
48+
"0.0", // Equator
49+
"45.0", // Mid-latitude
50+
"89.0", // Near pole
51+
"-45.0", // Southern hemisphere
52+
"-89.0" // Near south pole
53+
})
54+
private double latitude;
55+
56+
@Param({
57+
"0.0", // Prime meridian
58+
"179.0", // Near dateline
59+
"-179.0", // Near dateline other side
60+
"90.0", // Mid-longitude
61+
"-90.0" // Mid-longitude west
62+
})
63+
private double longitude;
64+
65+
@Param({
66+
"100", // Small radius (100m)
67+
"1000", // 1km
68+
"10000", // 10km
69+
"100000", // 100km
70+
"1000000" // 1000km
71+
})
72+
private double radiusMeters;
73+
}
74+
75+
@Benchmark
76+
public Rectangle benchmarkFromPointDistanceSloppySin(ExecutionPlan plan) {
77+
return Rectangle.fromPointDistance(plan.latitude, plan.longitude, plan.radiusMeters);
78+
}
79+
80+
@Benchmark
81+
public Rectangle benchmarkFromPointDistanceStandardSin(ExecutionPlan plan) {
82+
return fromPointDistanceStandardSin(plan.latitude, plan.longitude, plan.radiusMeters);
83+
}
84+
85+
private static Rectangle fromPointDistanceStandardSin(
86+
final double centerLat, final double centerLon, final double radiusMeters) {
87+
checkLatitude(centerLat);
88+
checkLongitude(centerLon);
89+
final double radLat = Math.toRadians(centerLat);
90+
final double radLon = Math.toRadians(centerLon);
91+
// LUCENE-7143
92+
double radDistance = (radiusMeters + 7E-2) / EARTH_MEAN_RADIUS_METERS;
93+
double minLat = radLat - radDistance;
94+
double maxLat = radLat + radDistance;
95+
double minLon;
96+
double maxLon;
97+
98+
if (minLat > MIN_LAT_RADIANS && maxLat < MAX_LAT_RADIANS) {
99+
double deltaLon = asin(Math.sin(radDistance) / cos(radLat));
100+
minLon = radLon - deltaLon;
101+
if (minLon < MIN_LON_RADIANS) {
102+
minLon += 2d * PI;
103+
}
104+
maxLon = radLon + deltaLon;
105+
if (maxLon > MAX_LON_RADIANS) {
106+
maxLon -= 2d * PI;
107+
}
108+
} else {
109+
// a pole is within the distance
110+
minLat = max(minLat, MIN_LAT_RADIANS);
111+
maxLat = min(maxLat, MAX_LAT_RADIANS);
112+
minLon = MIN_LON_RADIANS;
113+
maxLon = MAX_LON_RADIANS;
114+
}
115+
116+
return new Rectangle(
117+
Math.toDegrees(minLat),
118+
Math.toDegrees(maxLat),
119+
Math.toDegrees(minLon),
120+
Math.toDegrees(maxLon));
121+
}
122+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.lucene.benchmark.jmh;
18+
19+
import java.util.concurrent.TimeUnit;
20+
import org.apache.lucene.util.SloppyMath;
21+
import org.openjdk.jmh.annotations.*;
22+
23+
@State(Scope.Thread)
24+
@BenchmarkMode(Mode.Throughput)
25+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
26+
@Fork(value = 1, warmups = 1)
27+
@Warmup(iterations = 1, time = 1)
28+
@Measurement(iterations = 3, time = 2)
29+
public class SloppySinBenchmark {
30+
@Benchmark
31+
public double standardSin(ExecutionPlan plan) {
32+
return Math.sin(plan.value);
33+
}
34+
35+
@Benchmark
36+
public double sloppySin(ExecutionPlan plan) {
37+
return SloppyMath.sin(plan.value);
38+
}
39+
40+
@State(Scope.Benchmark)
41+
public static class ExecutionPlan {
42+
43+
// Test with different input ranges
44+
@Param({"0.1", "0.5", "1.0", "2.0", "3.14"})
45+
public double value;
46+
}
47+
}

lucene/core/src/java/org/apache/lucene/geo/GeoUtils.java

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717
package org.apache.lucene.geo;
1818

19-
import static org.apache.lucene.util.SloppyMath.cos;
2019
import static org.apache.lucene.util.SloppyMath.haversinMeters;
2120

2221
import org.apache.lucene.index.PointValues;
@@ -87,30 +86,6 @@ public static void checkLongitude(double longitude) {
8786
}
8887
}
8988

90-
// some sloppyish stuff, do we really need this to be done in a sloppy way?
91-
// unless it is performance sensitive, we should try to remove.
92-
private static final double PIO2 = Math.PI / 2D;
93-
94-
/**
95-
* Returns the trigonometric sine of an angle converted as a cos operation.
96-
*
97-
* <p>Note that this is not quite right... e.g. sin(0) != 0
98-
*
99-
* <p>Special cases:
100-
*
101-
* <ul>
102-
* <li>If the argument is {@code NaN} or an infinity, then the result is {@code NaN}.
103-
* </ul>
104-
*
105-
* @param a an angle, in radians.
106-
* @return the sine of the argument.
107-
* @see Math#sin(double)
108-
*/
109-
// TODO: deprecate/remove this? at least its no longer public.
110-
public static double sloppySin(double a) {
111-
return cos(a - PIO2);
112-
}
113-
11489
/**
11590
* binary search to find the exact sortKey needed to match the specified radius any sort key lte
11691
* this is a query match.

lucene/core/src/java/org/apache/lucene/geo/Rectangle.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
import static org.apache.lucene.geo.GeoUtils.MIN_LON_RADIANS;
2929
import static org.apache.lucene.geo.GeoUtils.checkLatitude;
3030
import static org.apache.lucene.geo.GeoUtils.checkLongitude;
31-
import static org.apache.lucene.geo.GeoUtils.sloppySin;
3231
import static org.apache.lucene.util.SloppyMath.asin;
3332
import static org.apache.lucene.util.SloppyMath.cos;
33+
import static org.apache.lucene.util.SloppyMath.sin;
3434

3535
/** Represents a lat/lon rectangle. */
3636
public class Rectangle extends LatLonGeometry {
@@ -121,7 +121,7 @@ public static Rectangle fromPointDistance(
121121
double maxLon;
122122

123123
if (minLat > MIN_LAT_RADIANS && maxLat < MAX_LAT_RADIANS) {
124-
double deltaLon = asin(sloppySin(radDistance) / cos(radLat));
124+
double deltaLon = asin(sin(radDistance) / cos(radLat));
125125
minLon = radLon - deltaLon;
126126
if (minLon < MIN_LON_RADIANS) {
127127
minLon += 2d * PI;

lucene/core/src/java/org/apache/lucene/util/SloppyMath.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,32 @@ public static double asin(double a) {
178178
}
179179
}
180180

181+
// some sloppyish stuff, do we really need this to be done in a sloppy way?
182+
// unless it is performance sensitive, we should try to remove.
183+
// This is performance sensitive, check SloppySinBenchmark and RectangleBenchmark
184+
private static final double PIO2 = Math.PI / 2D;
185+
186+
/**
187+
* Returns the trigonometric sine of an angle converted as a cos operation.
188+
*
189+
* <p>Error is around 1E-12.
190+
*
191+
* <p>Note that this is not quite right... e.g. sin(0) != 0
192+
*
193+
* <p>Special cases:
194+
*
195+
* <ul>
196+
* <li>If the argument is {@code NaN} or an infinity, then the result is {@code NaN}.
197+
* </ul>
198+
*
199+
* @param a an angle, in radians.
200+
* @return the sine of the argument.
201+
* @see Math#sin(double)
202+
*/
203+
public static double sin(double a) {
204+
return cos(a - PIO2);
205+
}
206+
181207
// Earth's mean radius, in meters and kilometers; see
182208
// http://earth-info.nga.mil/GandG/publications/tr8350.2/wgs84fin.pdf
183209
private static final double TO_METERS = 6_371_008.7714D; // equatorial radius

lucene/core/src/test/org/apache/lucene/util/TestSloppyMath.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.apache.lucene.util.SloppyMath.cos;
2121
import static org.apache.lucene.util.SloppyMath.haversinMeters;
2222
import static org.apache.lucene.util.SloppyMath.haversinSortKey;
23+
import static org.apache.lucene.util.SloppyMath.sin;
2324

2425
import java.util.Random;
2526
import org.apache.lucene.tests.geo.GeoTestUtil;
@@ -28,6 +29,8 @@
2829
public class TestSloppyMath extends LuceneTestCase {
2930
// accuracy for cos()
3031
private static final double COS_DELTA = 1E-15;
32+
// accuracy for sin()
33+
private static final double SIN_DELTA = 1E-12;
3134
// accuracy for asin()
3235
private static final double ASIN_DELTA = 1E-7;
3336
// accuracy for haversinMeters()
@@ -50,8 +53,8 @@ public void testCos() {
5053
assertEquals(StrictMath.cos(Math.PI / 6), cos(Math.PI / 6), COS_DELTA);
5154
assertEquals(StrictMath.cos(-Math.PI / 6), cos(-Math.PI / 6), COS_DELTA);
5255

53-
// testing purely random longs is inefficent, as for stupid parameters we just
54-
// pass thru to Math.cos() instead of doing some huperduper arg reduction
56+
// testing purely random longs is inefficient, as for stupid parameters we just
57+
// pass through to Math.cos() instead of doing some superduper arg reduction
5558
for (int i = 0; i < 10000; i++) {
5659
double d = random().nextDouble() * SloppyMath.SIN_COS_MAX_VALUE_FOR_INT_MODULO;
5760
if (random().nextBoolean()) {
@@ -61,6 +64,32 @@ public void testCos() {
6164
}
6265
}
6366

67+
public void testSin() {
68+
assertTrue(Double.isNaN(sin(Double.NaN)));
69+
assertTrue(Double.isNaN(sin(Double.NEGATIVE_INFINITY)));
70+
assertTrue(Double.isNaN(sin(Double.POSITIVE_INFINITY)));
71+
assertEquals(StrictMath.sin(1), sin(1), SIN_DELTA);
72+
assertEquals(StrictMath.sin(0), sin(0), SIN_DELTA);
73+
assertEquals(StrictMath.sin(Math.PI / 2), sin(Math.PI / 2), SIN_DELTA);
74+
assertEquals(StrictMath.sin(-Math.PI / 2), sin(-Math.PI / 2), SIN_DELTA);
75+
assertEquals(StrictMath.sin(Math.PI / 4), sin(Math.PI / 4), SIN_DELTA);
76+
assertEquals(StrictMath.sin(-Math.PI / 4), sin(-Math.PI / 4), SIN_DELTA);
77+
assertEquals(StrictMath.sin(Math.PI * 2 / 3), sin(Math.PI * 2 / 3), SIN_DELTA);
78+
assertEquals(StrictMath.sin(-Math.PI * 2 / 3), sin(-Math.PI * 2 / 3), SIN_DELTA);
79+
assertEquals(StrictMath.sin(Math.PI / 6), sin(Math.PI / 6), SIN_DELTA);
80+
assertEquals(StrictMath.sin(-Math.PI / 6), sin(-Math.PI / 6), SIN_DELTA);
81+
82+
// testing purely random longs is inefficient, as for stupid parameters we just
83+
// pass through to Math.sin() instead of doing some superduper arg reduction
84+
for (int i = 0; i < 10000; i++) {
85+
double d = random().nextDouble() * SloppyMath.SIN_COS_MAX_VALUE_FOR_INT_MODULO;
86+
if (random().nextBoolean()) {
87+
d = -d;
88+
}
89+
assertEquals(StrictMath.sin(d), sin(d), SIN_DELTA);
90+
}
91+
}
92+
6493
public void testAsin() {
6594
assertTrue(Double.isNaN(asin(Double.NaN)));
6695
assertTrue(Double.isNaN(asin(2)));

0 commit comments

Comments
 (0)