/*
* Smallest enclosing circle - Test suite (Java)
*
* Copyright (c) 2017 Project Nayuki
* https://www.nayuki.io/page/smallest-enclosing-circle
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program (see COPYING.txt and COPYING.LESSER.txt).
* If not, see .
*/
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.junit.Test;
public final class SmallestEnclosingCircleTest {
/*---- Test suite functions ----*/
@Test public void testMatchingNaiveAlgorithm() {
final int TRIALS = 1000;
for (int i = 0; i < TRIALS; i++) {
List points = makeRandomPoints(rand.nextInt(30) + 1);
Circle reference = smallestEnclosingCircleNaive(points);
Circle actual = SmallestEnclosingCircle.makeCircle(points);
assertEquals(reference.c.x, actual.c.x, EPSILON);
assertEquals(reference.c.y, actual.c.y, EPSILON);
assertEquals(reference.r , actual.r , EPSILON);
}
}
@Test public void testTranslation() {
final int TRIALS = 100;
final int CHECKS = 10;
for (int i = 0; i < TRIALS; i++) {
List points = makeRandomPoints(rand.nextInt(300) + 1);
Circle reference = SmallestEnclosingCircle.makeCircle(points);
for (int j = 0; j < CHECKS; j++) {
double dx = rand.nextGaussian();
double dy = rand.nextGaussian();
List newPoints = new ArrayList<>();
for (Point p : points)
newPoints.add(new Point(p.x + dx, p.y + dy));
Circle translated = SmallestEnclosingCircle.makeCircle(newPoints);
assertEquals(reference.c.x + dx, translated.c.x, EPSILON);
assertEquals(reference.c.y + dy, translated.c.y, EPSILON);
assertEquals(reference.r , translated.r , EPSILON);
}
}
}
@Test public void testScaling() {
final int TRIALS = 100;
final int CHECKS = 10;
for (int i = 0; i < TRIALS; i++) {
List points = makeRandomPoints(rand.nextInt(300) + 1);
Circle reference = SmallestEnclosingCircle.makeCircle(points);
for (int j = 0; j < CHECKS; j++) {
double scale = rand.nextGaussian();
List newPoints = new ArrayList<>();
for (Point p : points)
newPoints.add(new Point(p.x * scale, p.y * scale));
Circle scaled = SmallestEnclosingCircle.makeCircle(newPoints);
assertEquals(reference.c.x * scale, scaled.c.x, EPSILON);
assertEquals(reference.c.y * scale, scaled.c.y, EPSILON);
assertEquals(reference.r * Math.abs(scale), scaled.r, EPSILON);
}
}
}
/*---- Helper functions ----*/
private static List makeRandomPoints(int n) {
List result = new ArrayList<>();
if (rand.nextDouble() < 0.2) { // Discrete lattice (to have a chance of duplicated points)
for (int i = 0; i < n; i++)
result.add(new Point(rand.nextInt(10), rand.nextInt(10)));
} else { // Gaussian distribution
for (int i = 0; i < n; i++)
result.add(new Point(rand.nextGaussian(), rand.nextGaussian()));
}
return result;
}
// Returns the smallest enclosing circle in O(n^4) time using the naive algorithm.
private static Circle smallestEnclosingCircleNaive(List points) {
// Degenerate cases
if (points.size() == 0)
return null;
else if (points.size() == 1)
return new Circle(points.get(0), 0);
// Try all unique pairs
Circle result = null;
for (int i = 0; i < points.size(); i++) {
for (int j = i + 1; j < points.size(); j++) {
Circle c = SmallestEnclosingCircle.makeDiameter(points.get(i), points.get(j));
if ((result == null || c.r < result.r) && c.contains(points))
result = c;
}
}
if (result != null)
return result; // This optimization is not mathematically proven
// Try all unique triples
for (int i = 0; i < points.size(); i++) {
for (int j = i + 1; j < points.size(); j++) {
for (int k = j + 1; k < points.size(); k++) {
Circle c = SmallestEnclosingCircle.makeCircumcircle(points.get(i), points.get(j), points.get(k));
if (c != null && (result == null || c.r < result.r) && c.contains(points))
result = c;
}
}
}
if (result == null)
throw new AssertionError();
return result;
}
private static final double EPSILON = 1e-12;
private static final Random rand = new Random();
}