Avoid generating invalid test polygons

This commit is contained in:
Stefan Vodita 2023-11-03 23:52:23 +00:00
parent ba81826951
commit 0dab327299
3 changed files with 80 additions and 0 deletions

View File

@ -188,6 +188,8 @@ Other
* GITHUB#11023: Removing some dead code in CheckIndex. (Jakub Slowinski)
* GITHUB#12757: Stop generating test polygons with duplicate vertices when the radius is small. (Stefan Vodita)
======================== Lucene 9.9.0 =======================
API Changes

View File

@ -16,7 +16,9 @@
*/
package org.apache.lucene.document;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.Tessellator;
@ -286,6 +288,33 @@ public class TestXYShape extends LuceneTestCase {
IOUtils.close(w, reader, dir);
}
/** Checks if at least two of the given polygon's vertices are identical. */
private boolean hasDuplicateVertices(XYPolygon poly) {
Set<XYPoint> vertices = new HashSet<>();
// The first and last vertices are always equal, so we don't check the last.
for (int i = 0; i < poly.numPoints() - 1; i++) {
XYPoint vertex = new XYPoint(poly.getPolyX(i), poly.getPolyY(i));
if (vertices.contains(vertex)) {
return true;
}
vertices.add(vertex);
}
return false;
}
/**
* Tests that for small radii and large numbers of vertices, we produce valid regular polygons.
*/
public void testRegularPolygonsAreLargeEnough() {
final int maxGons = 100;
// The more vertices, the larger radius we need for the polygon not to collapse into a point.
double radius = random().nextDouble(0, ShapeTestUtil.smallestRadius(maxGons));
int gons = random().nextInt(3, maxGons);
XYPolygon regularNGon = ShapeTestUtil.createRegularPolygon(0, 0, radius, gons);
assertFalse(hasDuplicateVertices(regularNGon));
}
private static boolean areBoxDisjoint(XYRectangle r1, XYRectangle r2) {
return (r1.minX <= r2.minX && r1.minY <= r2.minY && r1.maxX >= r2.maxX && r1.maxY >= r2.maxY);
}

View File

@ -19,7 +19,9 @@ package org.apache.lucene.tests.geo;
import com.carrotsearch.randomizedtesting.RandomizedContext;
import com.carrotsearch.randomizedtesting.generators.BiasedNumbers;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.geo.XYCircle;
import org.apache.lucene.geo.XYEncodingUtils;
@ -154,12 +156,14 @@ public class ShapeTestUtil {
}
private static XYPolygon surpriseMePolygon(Random random) {
while (true) {
float centerX = nextFloat(random);
float centerY = nextFloat(random);
double radius = 0.1 + 20 * random.nextDouble();
double radiusDelta = random.nextDouble();
Set<XYPoint> vertices = new HashSet<>();
ArrayList<Float> xList = new ArrayList<>();
ArrayList<Float> yList = new ArrayList<>();
double angle = 0.0;
@ -183,6 +187,12 @@ public class ShapeTestUtil {
float x = (float) (centerX + len * Math.cos(Math.toRadians(angle)));
float y = (float) (centerY + len * Math.sin(Math.toRadians(angle)));
XYPoint vertex = new XYPoint(x, y);
if (vertices.contains(vertex)) {
// If we have already generated an identical vertex, ignore and try again.
continue;
}
vertices.add(vertex);
xList.add(x);
yList.add(y);
}
@ -197,10 +207,44 @@ public class ShapeTestUtil {
xArray[i] = xList.get(i);
yArray[i] = yList.get(i);
}
if (xArray.length < 4 || yArray.length < 4) {
// Not a valid polygon, try again.
continue;
}
return new XYPolygon(xArray, yArray);
}
}
/**
* Polygons can get so small that their sides are shorter than a float is able to represent. For a
* regular polygon, a smaller radius produces smaller sides, as does increasing the number of
* vertices.
*
* <p>This method checks that we can represent the regular polygon with the given radius and
* number of vertices.
*/
private static boolean regularPolygonFitsFloat(double radius, int gons) {
// https://en.wikipedia.org/wiki/Regular_polygon
double side = 2 * radius * Math.sin(Math.PI / gons);
// We need the difference between two vertices along at least one axis to be >= smallest
// positive float.
// To ensure that, we take the worst case, where the difference along the axes is equal.
// Effectively, the side in this case is the hypothenuse of an isosceles right triangle
// with catheti equal to the smallest positive float.
double threshold = Float.MIN_VALUE * Math.sqrt(2);
return side > threshold;
}
/**
* Find the radius of the smallest polygon with {@code gons} sides, whose vertices can be
* represented as floats.
*/
public static double smallestRadius(int gons) {
return (double) Float.MIN_VALUE / Math.sqrt(2) / Math.sin(Math.PI / gons);
}
/**
* Makes an n-gon, centered at the provided x/y, and each vertex approximately distanceMeters away
* from the center.
@ -210,6 +254,11 @@ public class ShapeTestUtil {
public static XYPolygon createRegularPolygon(
double centerX, double centerY, double radius, int gons) {
if (regularPolygonFitsFloat(radius, gons) == false) {
// The provided radius is too small, use the smallest radius that will work.
radius = smallestRadius(gons);
}
double maxX =
StrictMath.min(
StrictMath.abs(Float.MAX_VALUE - centerX), StrictMath.abs(-Float.MAX_VALUE - centerX));