WelzlEncloser.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /*
- * This is not the original file distributed by the Apache Software Foundation
- * It has been modified by the Hipparchus project
- */
- package org.hipparchus.geometry.enclosing;
- import java.util.ArrayList;
- import java.util.List;
- import org.hipparchus.exception.MathRuntimeException;
- import org.hipparchus.geometry.Point;
- import org.hipparchus.geometry.Space;
- /** Class implementing Emo Welzl algorithm to find the smallest enclosing ball in linear time.
- * <p>
- * The class implements the algorithm described in paper <a
- * href="http://www.inf.ethz.ch/personal/emo/PublFiles/SmallEnclDisk_LNCS555_91.pdf">Smallest
- * Enclosing Disks (Balls and Ellipsoids)</a> by Emo Welzl, Lecture Notes in Computer Science
- * 555 (1991) 359-370. The pivoting improvement published in the paper <a
- * href="http://www.inf.ethz.ch/personal/gaertner/texts/own_work/esa99_final.pdf">Fast and
- * Robust Smallest Enclosing Balls</a>, by Bernd Gärtner and further modified in
- * paper <a href="http://www.idt.mdh.se/kurser/ct3340/ht12/MINICONFERENCE/FinalPapers/ircse12_submission_30.pdf">
- * Efficient Computation of Smallest Enclosing Balls in Three Dimensions</a> by Linus Källberg
- * to avoid performing local copies of data have been included.
- * </p>
- * @param <S> Space type.
- * @param <P> Point type.
- */
- public class WelzlEncloser<S extends Space, P extends Point<S, P>> implements Encloser<S, P> {
- /** Tolerance below which points are consider to be identical. */
- private final double tolerance;
- /** Generator for balls on support. */
- private final SupportBallGenerator<S, P> generator;
- /** Simple constructor.
- * @param tolerance below which points are consider to be identical
- * @param generator generator for balls on support
- */
- public WelzlEncloser(final double tolerance, final SupportBallGenerator<S, P> generator) {
- this.tolerance = tolerance;
- this.generator = generator;
- }
- /** {@inheritDoc} */
- @Override
- public EnclosingBall<S, P> enclose(final Iterable<P> points) {
- if (points == null || !points.iterator().hasNext()) {
- // return an empty ball
- return generator.ballOnSupport(new ArrayList<>());
- }
- // Emo Welzl algorithm with Bernd Gärtner and Linus Källberg improvements
- return pivotingBall(points);
- }
- /** Compute enclosing ball using Gärtner's pivoting heuristic.
- * @param points points to be enclosed
- * @return enclosing ball
- */
- private EnclosingBall<S, P> pivotingBall(final Iterable<P> points) {
- final P first = points.iterator().next();
- final List<P> extreme = new ArrayList<>(first.getSpace().getDimension() + 1);
- final List<P> support = new ArrayList<>(first.getSpace().getDimension() + 1);
- // start with only first point selected as a candidate support
- extreme.add(first);
- EnclosingBall<S, P> ball = moveToFrontBall(extreme, extreme.size(), support);
- while (true) {
- // select the point farthest to current ball
- final P farthest = selectFarthest(points, ball);
- if (ball.contains(farthest, tolerance)) {
- // we have found a ball containing all points
- return ball;
- }
- // recurse search, restricted to the small subset containing support and farthest point
- support.clear();
- support.add(farthest);
- EnclosingBall<S, P> savedBall = ball;
- ball = moveToFrontBall(extreme, extreme.size(), support);
- if (ball.getRadius() < savedBall.getRadius()) {
- // this should never happen
- throw MathRuntimeException.createInternalError();
- }
- // it was an interesting point, move it to the front
- // according to Gärtner's heuristic
- extreme.add(0, farthest);
- // prune the least interesting points
- extreme.subList(ball.getSupportSize(), extreme.size()).clear();
- }
- }
- /** Compute enclosing ball using Welzl's move to front heuristic.
- * @param extreme subset of extreme points
- * @param nbExtreme number of extreme points to consider
- * @param support points that must belong to the ball support
- * @return enclosing ball, for the extreme subset only
- */
- private EnclosingBall<S, P> moveToFrontBall(final List<P> extreme, final int nbExtreme,
- final List<P> support) {
- // create a new ball on the prescribed support
- EnclosingBall<S, P> ball = generator.ballOnSupport(support);
- if (ball.getSupportSize() <= ball.getCenter().getSpace().getDimension()) {
- for (int i = 0; i < nbExtreme; ++i) {
- final P pi = extreme.get(i);
- if (!ball.contains(pi, tolerance)) {
- // we have found an outside point,
- // enlarge the ball by adding it to the support
- support.add(pi);
- ball = moveToFrontBall(extreme, i, support);
- support.remove(support.size() - 1);
- // it was an interesting point, move it to the front
- // according to Welzl's heuristic
- for (int j = i; j > 0; --j) {
- extreme.set(j, extreme.get(j - 1));
- }
- extreme.set(0, pi);
- }
- }
- }
- return ball;
- }
- /** Select the point farthest to the current ball.
- * @param points points to be enclosed
- * @param ball current ball
- * @return farthest point
- */
- public P selectFarthest(final Iterable<P> points, final EnclosingBall<S, P> ball) {
- final P center = ball.getCenter();
- P farthest = null;
- double dMax = -1.0;
- for (final P point : points) {
- final double d = point.distance(center);
- if (d > dMax) {
- farthest = point;
- dMax = d;
- }
- }
- return farthest;
- }
- }