WelzlEncloser.java

  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.  *      https://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. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.geometry.enclosing;

  22. import java.util.ArrayList;
  23. import java.util.List;

  24. import org.hipparchus.exception.MathRuntimeException;
  25. import org.hipparchus.geometry.Point;
  26. import org.hipparchus.geometry.Space;

  27. /** Class implementing Emo Welzl algorithm to find the smallest enclosing ball in linear time.
  28.  * <p>
  29.  * The class implements the algorithm described in paper <a
  30.  * href="http://www.inf.ethz.ch/personal/emo/PublFiles/SmallEnclDisk_LNCS555_91.pdf">Smallest
  31.  * Enclosing Disks (Balls and Ellipsoids)</a> by Emo Welzl, Lecture Notes in Computer Science
  32.  * 555 (1991) 359-370. The pivoting improvement published in the paper <a
  33.  * href="http://www.inf.ethz.ch/personal/gaertner/texts/own_work/esa99_final.pdf">Fast and
  34.  * Robust Smallest Enclosing Balls</a>, by Bernd Gärtner and further modified in
  35.  * paper <a href="http://www.idt.mdh.se/kurser/ct3340/ht12/MINICONFERENCE/FinalPapers/ircse12_submission_30.pdf">
  36.  * Efficient Computation of Smallest Enclosing Balls in Three Dimensions</a> by Linus Källberg
  37.  * to avoid performing local copies of data have been included.
  38.  * </p>
  39.  * @param <S> Space type.
  40.  * @param <P> Point type.
  41.  */
  42. public class WelzlEncloser<S extends Space, P extends Point<S, P>> implements Encloser<S, P> {

  43.     /** Tolerance below which points are consider to be identical. */
  44.     private final double tolerance;

  45.     /** Generator for balls on support. */
  46.     private final SupportBallGenerator<S, P> generator;

  47.     /** Simple constructor.
  48.      * @param tolerance below which points are consider to be identical
  49.      * @param generator generator for balls on support
  50.      */
  51.     public WelzlEncloser(final double tolerance, final SupportBallGenerator<S, P> generator) {
  52.         this.tolerance = tolerance;
  53.         this.generator = generator;
  54.     }

  55.     /** {@inheritDoc} */
  56.     @Override
  57.     public EnclosingBall<S, P> enclose(final Iterable<P> points) {

  58.         if (points == null || !points.iterator().hasNext()) {
  59.             // return an empty ball
  60.             return generator.ballOnSupport(new ArrayList<>());
  61.         }

  62.         // Emo Welzl algorithm with Bernd Gärtner and Linus Källberg improvements
  63.         return pivotingBall(points);

  64.     }

  65.     /** Compute enclosing ball using Gärtner's pivoting heuristic.
  66.      * @param points points to be enclosed
  67.      * @return enclosing ball
  68.      */
  69.     private EnclosingBall<S, P> pivotingBall(final Iterable<P> points) {

  70.         final P first = points.iterator().next();
  71.         final List<P> extreme = new ArrayList<>(first.getSpace().getDimension() + 1);
  72.         final List<P> support = new ArrayList<>(first.getSpace().getDimension() + 1);

  73.         // start with only first point selected as a candidate support
  74.         extreme.add(first);
  75.         EnclosingBall<S, P> ball = moveToFrontBall(extreme, extreme.size(), support);

  76.         while (true) {

  77.             // select the point farthest to current ball
  78.             final P farthest = selectFarthest(points, ball);

  79.             if (ball.contains(farthest, tolerance)) {
  80.                 // we have found a ball containing all points
  81.                 return ball;
  82.             }

  83.             // recurse search, restricted to the small subset containing support and farthest point
  84.             support.clear();
  85.             support.add(farthest);
  86.             EnclosingBall<S, P> savedBall = ball;
  87.             ball = moveToFrontBall(extreme, extreme.size(), support);
  88.             if (ball.getRadius() < savedBall.getRadius()) {
  89.                 // this should never happen
  90.                 throw MathRuntimeException.createInternalError();
  91.             }

  92.             // it was an interesting point, move it to the front
  93.             // according to Gärtner's heuristic
  94.             extreme.add(0, farthest);

  95.             // prune the least interesting points
  96.             extreme.subList(ball.getSupportSize(), extreme.size()).clear();


  97.         }
  98.     }

  99.     /** Compute enclosing ball using Welzl's move to front heuristic.
  100.      * @param extreme subset of extreme points
  101.      * @param nbExtreme number of extreme points to consider
  102.      * @param support points that must belong to the ball support
  103.      * @return enclosing ball, for the extreme subset only
  104.      */
  105.     private EnclosingBall<S, P> moveToFrontBall(final List<P> extreme, final int nbExtreme,
  106.                                                 final List<P> support) {

  107.         // create a new ball on the prescribed support
  108.         EnclosingBall<S, P> ball = generator.ballOnSupport(support);

  109.         if (ball.getSupportSize() <= ball.getCenter().getSpace().getDimension()) {

  110.             for (int i = 0; i < nbExtreme; ++i) {
  111.                 final P pi = extreme.get(i);
  112.                 if (!ball.contains(pi, tolerance)) {

  113.                     // we have found an outside point,
  114.                     // enlarge the ball by adding it to the support
  115.                     support.add(pi);
  116.                     ball = moveToFrontBall(extreme, i, support);
  117.                     support.remove(support.size() - 1);

  118.                     // it was an interesting point, move it to the front
  119.                     // according to Welzl's heuristic
  120.                     for (int j = i; j > 0; --j) {
  121.                         extreme.set(j, extreme.get(j - 1));
  122.                     }
  123.                     extreme.set(0, pi);

  124.                 }
  125.             }

  126.         }

  127.         return ball;

  128.     }

  129.     /** Select the point farthest to the current ball.
  130.      * @param points points to be enclosed
  131.      * @param ball current ball
  132.      * @return farthest point
  133.      */
  134.     public P selectFarthest(final Iterable<P> points, final EnclosingBall<S, P> ball) {

  135.         final P center = ball.getCenter();
  136.         P farthest   = null;
  137.         double dMax  = -1.0;

  138.         for (final P point : points) {
  139.             final double d = point.distance(center);
  140.             if (d > dMax) {
  141.                 farthest = point;
  142.                 dMax     = d;
  143.             }
  144.         }

  145.         return farthest;

  146.     }

  147. }