/* * Smallest enclosing circle - Test suite (C#) * * 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 . */ using System; using System.Collections.Generic; public sealed class SmallestEnclosingCircleTest { public static void Main(string[] args) { TestMatchingNaiveAlgorithm(); TestTranslation(); TestScaling(); } /*---- Test suite functions ----*/ private static void TestMatchingNaiveAlgorithm() { int TRIALS = 1000; for (int i = 0; i < TRIALS; i++) { IList points = MakeRandomPoints(rand.Next(30) + 1); Circle reference = SmallestEnclosingCircleNaive(points); Circle actual = SmallestEnclosingCircle.MakeCircle(points); AssertApproxEqual(reference.c.x, actual.c.x, EPSILON); AssertApproxEqual(reference.c.y, actual.c.y, EPSILON); AssertApproxEqual(reference.r , actual.r , EPSILON); } } private static void TestTranslation() { int TRIALS = 100; int CHECKS = 10; for (int i = 0; i < TRIALS; i++) { IList points = MakeRandomPoints(rand.Next(300) + 1); Circle reference = SmallestEnclosingCircle.MakeCircle(points); for (int j = 0; j < CHECKS; j++) { double dx = NextGaussian(); double dy = NextGaussian(); IList newPoints = new List(); foreach (Point p in points) newPoints.Add(new Point(p.x + dx, p.y + dy)); Circle translated = SmallestEnclosingCircle.MakeCircle(newPoints); AssertApproxEqual(reference.c.x + dx, translated.c.x, EPSILON); AssertApproxEqual(reference.c.y + dy, translated.c.y, EPSILON); AssertApproxEqual(reference.r , translated.r , EPSILON); } } } private static void TestScaling() { int TRIALS = 100; int CHECKS = 10; for (int i = 0; i < TRIALS; i++) { IList points = MakeRandomPoints(rand.Next(300) + 1); Circle reference = SmallestEnclosingCircle.MakeCircle(points); for (int j = 0; j < CHECKS; j++) { double scale = NextGaussian(); IList newPoints = new List(); foreach (Point p in points) newPoints.Add(new Point(p.x * scale, p.y * scale)); Circle scaled = SmallestEnclosingCircle.MakeCircle(newPoints); AssertApproxEqual(reference.c.x * scale, scaled.c.x, EPSILON); AssertApproxEqual(reference.c.y * scale, scaled.c.y, EPSILON); AssertApproxEqual(reference.r * Math.Abs(scale), scaled.r, EPSILON); } } } /*---- Helper functions ----*/ private static IList MakeRandomPoints(int n) { IList result = new List(); 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.Next(10), rand.Next(10))); } else { // Gaussian distribution for (int i = 0; i < n; i++) result.Add(new Point(NextGaussian(), NextGaussian())); } return result; } // Returns the smallest enclosing circle in O(n^4) time using the naive algorithm. private static Circle SmallestEnclosingCircleNaive(IList points) { // Degenerate cases if (points.Count == 0) return new Circle(new Point(0, 0), -1); else if (points.Count == 1) return new Circle(points[0], 0); // Try all unique pairs Circle result = new Circle(new Point(0, 0), -1); for (int i = 0; i < points.Count; i++) { for (int j = i + 1; j < points.Count; j++) { Circle c = SmallestEnclosingCircle.MakeDiameter(points[i], points[j]); if ((result.r < 0 || c.r < result.r) && c.Contains(points)) result = c; } } if (result.r >= 0) return result; // This optimization is not mathematically proven // Try all unique triples for (int i = 0; i < points.Count; i++) { for (int j = i + 1; j < points.Count; j++) { for (int k = j + 1; k < points.Count; k++) { Circle c = SmallestEnclosingCircle.MakeCircumcircle(points[i], points[j], points[k]); if (c.r >= 0 && (result.r < 0 || c.r < result.r) && c.Contains(points)) result = c; } } } if (result.r < 0) throw new SystemException("Assertion error"); return result; } private static double NextGaussian() { return Math.Sqrt(-2 * Math.Log(rand.NextDouble())) * Math.Cos(rand.NextDouble() * Math.PI * 2); } private static void AssertApproxEqual(double expect, double actual, double epsilon) { if (Math.Abs(expect - actual) > epsilon) throw new SystemException("Value mismatch"); } private const double EPSILON = 1e-12; private static Random rand = new Random(); }