View Javadoc
1   /*
2    * Copyright (C) 2016 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.numerical.polynomials.estimators;
17  
18  import com.irurueta.numerical.LockedException;
19  import com.irurueta.numerical.NotReadyException;
20  import com.irurueta.numerical.polynomials.Polynomial;
21  import com.irurueta.numerical.robust.PROMedSRobustEstimator;
22  import com.irurueta.numerical.robust.PROMedSRobustEstimatorListener;
23  import com.irurueta.numerical.robust.RobustEstimator;
24  import com.irurueta.numerical.robust.RobustEstimatorException;
25  import com.irurueta.numerical.robust.RobustEstimatorMethod;
26  
27  import java.util.ArrayList;
28  import java.util.List;
29  
30  /**
31   * Finds the best polynomial using PROMedS algorithm.
32   */
33  public class PROMedSPolynomialRobustEstimator extends PolynomialRobustEstimator {
34  
35      /**
36       * Default value to be used for stop threshold. Stop threshold can be used
37       * to keep the algorithm iterating in case that best estimated threshold
38       * using median of residuals is not small enough. Once a solution is found
39       * that generates a threshold below this value, the algorithm will stop.
40       * The stop threshold can be used to prevent the LMedS algorithm iterating
41       * too many times in cases where samples have a very similar accuracy.
42       * For instance, in cases where proportion of outliers is very small (close
43       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
44       * iterate for a long time trying to find the best solution when indeed
45       * there is no need to do that if a reasonable threshold has already been
46       * reached.
47       * Because of this behaviour the stop threshold can be set to a value much
48       * lower than the one typically used in RANSAC, and yet the algorithm could
49       * still produce even smaller thresholds in estimated results
50       */
51      public static final double DEFAULT_STOP_THRESHOLD = 1e-6;
52  
53      /**
54       * Minimum allowed stop threshold value
55       */
56      public static final double MIN_STOP_THRESHOLD = 0.0;
57  
58      /**
59       * Threshold to be used to keep the algorithm iterating in case that best
60       * estimated threshold using median of residuals is not small enough. Once
61       * a solution is found that generates a threshold below this value, the
62       * algorithm will stop.
63       * The stop threshold can be used to prevent the LMedS algorithm iterating
64       * too many times in cases where samples have a very similar accuracy.
65       * For instance, in cases where proportion of outliers is very small (close
66       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
67       * iterate for a long time trying to find the best solution when indeed
68       * there is no need to do that if a reasonable threshold has already been
69       * reached.
70       * Because of this behaviour the stop threshold can be set to a value much
71       * lower than the one typically used in RANSAC, and yet the algorithm could
72       * still produce even smaller thresholds in estimated results
73       */
74      private double stopThreshold;
75  
76      /**
77       * Quality scores corresponding to each provided polynomial evaluation.
78       * The larger the score value the better the quality of the sample.
79       */
80      private double[] qualityScores;
81  
82      /**
83       * Constructor.
84       */
85      public PROMedSPolynomialRobustEstimator() {
86          super();
87          stopThreshold = DEFAULT_STOP_THRESHOLD;
88      }
89  
90      /**
91       * Constructor.
92       *
93       * @param degree degree of polynomial to be estimated.
94       * @throws IllegalArgumentException if provided degree is less than 1.
95       */
96      public PROMedSPolynomialRobustEstimator(final int degree) {
97          super(degree);
98          stopThreshold = DEFAULT_STOP_THRESHOLD;
99      }
100 
101     /**
102      * Constructor.
103      *
104      * @param evaluations collection of polynomial evaluations.
105      * @throws IllegalArgumentException if provided number of evaluations is
106      *                                  less than the required minimum.
107      */
108     public PROMedSPolynomialRobustEstimator(final List<PolynomialEvaluation> evaluations) {
109         super(evaluations);
110         stopThreshold = DEFAULT_STOP_THRESHOLD;
111     }
112 
113     /**
114      * Constructor.
115      *
116      * @param listener listener to be notified of events such as when estimation
117      *                 starts, ends or its progress significantly changes.
118      */
119     public PROMedSPolynomialRobustEstimator(final PolynomialRobustEstimatorListener listener) {
120         super(listener);
121         stopThreshold = DEFAULT_STOP_THRESHOLD;
122     }
123 
124     /**
125      * Constructor.
126      *
127      * @param degree      degree of polynomial to be estimated.
128      * @param evaluations collection of polynomial evaluations.
129      * @throws IllegalArgumentException if provided degree is less than 1 or if
130      *                                  provided number of evaluations is less than the required minimum for
131      *                                  provided degree.
132      */
133     public PROMedSPolynomialRobustEstimator(final int degree, final List<PolynomialEvaluation> evaluations) {
134         super(degree, evaluations);
135         stopThreshold = DEFAULT_STOP_THRESHOLD;
136     }
137 
138     /**
139      * Constructor.
140      *
141      * @param degree   degree of polynomial to be estimated.
142      * @param listener listener to be notified of events such as when estimation
143      *                 starts, ends or its progress significantly changes.
144      * @throws IllegalArgumentException if provided degree is less than 1.
145      */
146     public PROMedSPolynomialRobustEstimator(final int degree, final PolynomialRobustEstimatorListener listener) {
147         super(degree, listener);
148         stopThreshold = DEFAULT_STOP_THRESHOLD;
149     }
150 
151     /**
152      * Constructor.
153      *
154      * @param evaluations collection of polynomial evaluations.
155      * @param listener    listener to be notified of events such as when estimation
156      *                    starts, ends or its progress significantly changes.
157      * @throws IllegalArgumentException if provided number of evaluations is
158      *                                  less than the required minimum.
159      */
160     public PROMedSPolynomialRobustEstimator(
161             final List<PolynomialEvaluation> evaluations, final PolynomialRobustEstimatorListener listener) {
162         super(evaluations, listener);
163         stopThreshold = DEFAULT_STOP_THRESHOLD;
164     }
165 
166     /**
167      * Constructor.
168      *
169      * @param degree      degree of polynomial to be estimated.
170      * @param evaluations collection of polynomial evaluations.
171      * @param listener    listener to be notified of events such as when estimation
172      *                    starts, ends or its progress significantly changes.
173      * @throws IllegalArgumentException if provided degree is less than 1 or if
174      *                                  provided number of evaluations is less than the required minimum for
175      *                                  provided degree.
176      */
177     public PROMedSPolynomialRobustEstimator(
178             final int degree, final List<PolynomialEvaluation> evaluations,
179             final PolynomialRobustEstimatorListener listener) {
180         super(degree, evaluations, listener);
181         stopThreshold = DEFAULT_STOP_THRESHOLD;
182     }
183 
184     /**
185      * Returns threshold to be used to keep the algorithm iterating in case that
186      * best estimated threshold using median of residuals is not small enough.
187      * Once a solution is found that generates a threshold below this value, the
188      * algorithm will stop.
189      * The stop threshold can be used to prevent the LMedS algorithm iterating
190      * too many times in cases where samples have a very similar accuracy.
191      * For instance, in cases where proportion of outliers is very small (close
192      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
193      * iterate for a long time trying to find the best solution when indeed
194      * there is no need to do that if a reasonable threshold has already been
195      * reached.
196      * Because of this behaviour the stop threshold can be set to a value much
197      * lower than the one typically used in RANSAC, and yet the algorithm could
198      * still produce even smaller thresholds in estimated results
199      *
200      * @return stop threshold to stop the algorithm prematurely when a certain
201      * accuracy has been reached
202      */
203     public double getStopThreshold() {
204         return stopThreshold;
205     }
206 
207     /**
208      * Sets threshold to be used to keep the algorithm iterating in case that
209      * best estimated threshold using median of residuals is not small enough.
210      * Once a solution is found that generates a threshold below this value, the
211      * algorithm will stop.
212      * The stop threshold can be used to prevent the LMedS algorithm iterating
213      * too many times in cases where samples have a very similar accuracy.
214      * For instance, in cases where proportion of outliers is very small (close
215      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
216      * iterate for a long time trying to find the best solution when indeed
217      * there is no need to do that if a reasonable threshold has already been
218      * reached.
219      * Because of this behaviour the stop threshold can be set to a value much
220      * lower than the one typically used in RANSAC, and yet the algorithm could
221      * still produce even smaller thresholds in estimated results
222      *
223      * @param stopThreshold stop threshold to stop the algorithm prematurely
224      *                      when a certain accuracy has been reached
225      * @throws IllegalArgumentException if provided value is zero or negative
226      * @throws LockedException          if robust estimator is locked because an
227      *                                  estimation is already in progress
228      */
229     public void setStopThreshold(final double stopThreshold) throws LockedException {
230         if (isLocked()) {
231             throw new LockedException();
232         }
233         if (stopThreshold <= MIN_STOP_THRESHOLD) {
234             throw new IllegalArgumentException();
235         }
236 
237         this.stopThreshold = stopThreshold;
238     }
239 
240     /**
241      * Returns quality scores corresponding to each provided point.
242      * The larger the score value the better the quality of the sampled point
243      *
244      * @return quality scores corresponding to each point
245      */
246     @Override
247     public double[] getQualityScores() {
248         return qualityScores;
249     }
250 
251     /**
252      * Sets quality scores corresponding to each provided point.
253      * The larger the score value the better the quality of the sampled point.
254      *
255      * @param qualityScores quality scores corresponding to each point.
256      * @throws LockedException          if robust estimator is locked because an
257      *                                  estimation is already in progress.
258      * @throws IllegalArgumentException if provided quality scores length is
259      *                                  smaller than required minimum size.
260      */
261     @Override
262     public void setQualityScores(final double[] qualityScores) throws LockedException {
263         if (isLocked()) {
264             throw new LockedException();
265         }
266         internalSetQualityScores(qualityScores);
267     }
268 
269     /**
270      * Indicates if estimator is ready to start the polynomial estimation.
271      * This is true when input data (i.e. polynomial evaluations and quality
272      * scores) are provided and enough data is available.
273      *
274      * @return true if estimator is ready, false otherwise
275      */
276     @Override
277     public boolean isReady() {
278         return super.isReady() && qualityScores != null && qualityScores.length == evaluations.size();
279     }
280 
281     /**
282      * Estimates polynomial.
283      *
284      * @return estimated polynomial.
285      * @throws LockedException          if robust estimator is locked because an
286      *                                  estimation is already in progress.
287      * @throws NotReadyException        if provided input data is not enough to start
288      *                                  the estimation.
289      * @throws RobustEstimatorException if estimation fails for any other reason
290      *                                  (i.e. numerical instability, no solution available, etc).
291      */
292     @Override
293     public Polynomial estimate() throws LockedException, NotReadyException, RobustEstimatorException {
294         if (isLocked()) {
295             throw new LockedException();
296         }
297         if (!isReady()) {
298             throw new NotReadyException();
299         }
300 
301         final PROMedSRobustEstimator<Polynomial> innerEstimator = new PROMedSRobustEstimator<>(
302                 new PROMedSRobustEstimatorListener<>() {
303 
304                     // subset of evaluations picked on each iteration
305                     private final List<PolynomialEvaluation> subsetEvaluations = new ArrayList<>();
306 
307                     @Override
308                     public double getThreshold() {
309                         return stopThreshold;
310                     }
311 
312                     @Override
313                     public int getTotalSamples() {
314                         return evaluations.size();
315                     }
316 
317                     @Override
318                     public int getSubsetSize() {
319                         return polynomialEstimator.getMinNumberOfEvaluations();
320                     }
321 
322                     @SuppressWarnings("DuplicatedCode")
323                     @Override
324                     public void estimatePreliminarSolutions(
325                             final int[] samplesIndices, final List<Polynomial> solutions) {
326                         subsetEvaluations.clear();
327                         for (var samplesIndex : samplesIndices) {
328                             subsetEvaluations.add(evaluations.get(samplesIndex));
329                         }
330 
331                         try {
332                             polynomialEstimator.setLMSESolutionAllowed(false);
333                             polynomialEstimator.setEvaluations(subsetEvaluations);
334 
335                             final var polynomial = polynomialEstimator.estimate();
336                             solutions.add(polynomial);
337                         } catch (final Exception e) {
338                             // if anything fails, no solution is added
339                         }
340                     }
341 
342                     @Override
343                     public double computeResidual(final Polynomial currentEstimation, int i) {
344                         final var eval = evaluations.get(i);
345                         return getDistance(eval, currentEstimation);
346                     }
347 
348                     @Override
349                     public boolean isReady() {
350                         return PROMedSPolynomialRobustEstimator.this.isReady();
351                     }
352 
353                     @Override
354                     public void onEstimateStart(final RobustEstimator<Polynomial> estimator) {
355                         if (listener != null) {
356                             listener.onEstimateStart(PROMedSPolynomialRobustEstimator.this);
357                         }
358                     }
359 
360                     @Override
361                     public void onEstimateEnd(final RobustEstimator<Polynomial> estimator) {
362                         if (listener != null) {
363                             listener.onEstimateEnd(PROMedSPolynomialRobustEstimator.this);
364                         }
365                     }
366 
367                     @Override
368                     public void onEstimateNextIteration(
369                             final RobustEstimator<Polynomial> estimator, final int iteration) {
370                         if (listener != null) {
371                             listener.onEstimateNextIteration(PROMedSPolynomialRobustEstimator.this, iteration);
372                         }
373                     }
374 
375                     @Override
376                     public void onEstimateProgressChange(
377                             final RobustEstimator<Polynomial> estimator, final float progress) {
378                         if (listener != null) {
379                             listener.onEstimateProgressChange(PROMedSPolynomialRobustEstimator.this, progress);
380                         }
381                     }
382 
383                     @Override
384                     public double[] getQualityScores() {
385                         return qualityScores;
386                     }
387                 });
388 
389         try {
390             locked = true;
391             innerEstimator.setConfidence(confidence);
392             innerEstimator.setMaxIterations(maxIterations);
393             innerEstimator.setProgressDelta(progressDelta);
394             return innerEstimator.estimate();
395         } finally {
396             locked = false;
397         }
398     }
399 
400     /**
401      * Returns method being used for robust estimation.
402      *
403      * @return method being used for robust estimation.
404      */
405     @Override
406     public RobustEstimatorMethod getMethod() {
407         return RobustEstimatorMethod.PROMEDS;
408     }
409 
410     /**
411      * Sets quality scores corresponding to each provided polynomial evaluation.
412      * This method is used internally and does not check whether instance is
413      * locked or not
414      *
415      * @param qualityScores quality scores to be set
416      * @throws IllegalArgumentException if provided quality scores length is
417      *                                  smaller than required minimum size.
418      */
419     private void internalSetQualityScores(final double[] qualityScores) {
420         if (qualityScores.length < polynomialEstimator.getMinNumberOfEvaluations()) {
421             throw new IllegalArgumentException();
422         }
423 
424         this.qualityScores = qualityScores;
425     }
426 }