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.RobustEstimatorException;
22  import com.irurueta.numerical.robust.RobustEstimatorMethod;
23  
24  import java.util.List;
25  
26  /**
27   * This is an abstract class for algorithms to robustly find the best
28   * Polynomial for provided collection of evaluations.
29   * Implementations of this class should be able to detect and discard outliers
30   * in order to find the best solution.
31   */
32  @SuppressWarnings("Duplicates")
33  public abstract class PolynomialRobustEstimator {
34  
35      /**
36       * Default robust estimator method when none is provided.
37       * In general for Polynomial estimation is best to use PROSAC or RANSAC
38       * than any other method, as it provides more robust methods.
39       */
40      public static final RobustEstimatorMethod DEFAULT_ROBUST_METHOD = RobustEstimatorMethod.PROSAC;
41  
42      /**
43       * Default amount of progress variation before notifying a change in
44       * estimation progress. By default, this is set to 5%.
45       */
46      public static final float DEFAULT_PROGRESS_DELTA = 0.05f;
47  
48      /**
49       * Minimum allowed value for progress delta.
50       */
51      public static final float MIN_PROGRESS_DELTA = 0.0f;
52  
53      /**
54       * Maximum allowed value for progress delta.
55       */
56      public static final float MAX_PROGRESS_DELTA = 1.0f;
57  
58      /**
59       * Constant defining default confidence of the estimated result, which is
60       * 99%. This means that with a probability of 99% estimation will be
61       * accurate because chosen sub-samples will be inliers.
62       */
63      public static final double DEFAULT_CONFIDENCE = 0.99;
64  
65      /**
66       * Default maximum allowed number of iterations.
67       */
68      public static final int DEFAULT_MAX_ITERATIONS = 5000;
69  
70      /**
71       * Minimum allowed confidence value.
72       */
73      public static final double MIN_CONFIDENCE = 0.0;
74  
75      /**
76       * Maximum allowed confidence value.
77       */
78      public static final double MAX_CONFIDENCE = 1.0;
79  
80      /**
81       * Minimum allowed number of iterations.
82       */
83      public static final int MIN_ITERATIONS = 1;
84  
85      /**
86       * Flag indicating whether geometric distance is used by default or not
87       * to find outliers.
88       */
89      public static final boolean DEFAULT_USE_GEOMETRIC_DISTANCE = false;
90  
91      /**
92       * Collection of polynomial evaluations and their corresponding point of
93       * evaluation used to determine a polynomial of required degree.
94       */
95      protected List<PolynomialEvaluation> evaluations;
96  
97      /**
98       * Internal non robust estimator of polynomial estimator.
99       */
100     protected final LMSEPolynomialEstimator polynomialEstimator;
101 
102     /**
103      * Listener to be notified of events such as when estimation starts, ends or
104      * its progress significantly changes.
105      */
106     protected PolynomialRobustEstimatorListener listener;
107 
108     /**
109      * Indicates if this estimator is locked because an estimation is being
110      * computed.
111      */
112     protected boolean locked;
113 
114     /**
115      * Amount of progress variation before notifying a progress change during
116      * estimation.
117      */
118     protected float progressDelta;
119 
120     /**
121      * Amount of confidence expressed as a value between 0.0 and 1.0 (which is
122      * equivalent to 100%). The amount of confidence indicates the probability
123      * that the estimated result is correct. Usually this value will be close
124      * to 1.0, but not exactly 1.0.
125      */
126     protected double confidence;
127 
128     /**
129      * Maximum allowed number of iterations. When the maximum number of
130      * iterations is exceeded, result will not be available, however an
131      * approximate result will be available for retrieval.
132      */
133     protected int maxIterations;
134 
135     /**
136      * Indicates whether geometric distance will be used to find outliers or
137      * algebraic distance will be used instead.
138      */
139     protected boolean useGeometricDistance;
140 
141     /**
142      * Constructor.
143      */
144     protected PolynomialRobustEstimator() {
145         progressDelta = DEFAULT_PROGRESS_DELTA;
146         confidence = DEFAULT_CONFIDENCE;
147         maxIterations = DEFAULT_MAX_ITERATIONS;
148         useGeometricDistance = DEFAULT_USE_GEOMETRIC_DISTANCE;
149         polynomialEstimator = new LMSEPolynomialEstimator();
150     }
151 
152     /**
153      * Constructor.
154      *
155      * @param degree degree of polynomial to be estimated.
156      * @throws IllegalArgumentException if provided degree is less than 1.
157      */
158     protected PolynomialRobustEstimator(final int degree) {
159         progressDelta = DEFAULT_PROGRESS_DELTA;
160         confidence = DEFAULT_CONFIDENCE;
161         maxIterations = DEFAULT_MAX_ITERATIONS;
162         useGeometricDistance = DEFAULT_USE_GEOMETRIC_DISTANCE;
163         polynomialEstimator = new LMSEPolynomialEstimator(degree);
164     }
165 
166     /**
167      * Constructor.
168      *
169      * @param evaluations collection of polynomial evaluations.
170      * @throws IllegalArgumentException if provided number of evaluations is
171      *                                  less than the required minimum.
172      */
173     protected PolynomialRobustEstimator(final List<PolynomialEvaluation> evaluations) {
174         this();
175         internalSetEvaluations(evaluations);
176     }
177 
178     /**
179      * Constructor.
180      *
181      * @param listener listener to be notified of events such as when estimation
182      *                 starts, ends or its progress significantly changes.
183      */
184     protected PolynomialRobustEstimator(final PolynomialRobustEstimatorListener listener) {
185         this();
186         this.listener = listener;
187     }
188 
189     /**
190      * Constructor.
191      *
192      * @param degree      degree of polynomial to be estimated.
193      * @param evaluations collection of polynomial evaluations.
194      * @throws IllegalArgumentException if provided degree is less than 1 or if
195      *                                  provided number of evaluations is less than the required minimum for
196      *                                  provided degree.
197      */
198     protected PolynomialRobustEstimator(
199             final int degree, final List<PolynomialEvaluation> evaluations) {
200         this(degree);
201         internalSetEvaluations(evaluations);
202     }
203 
204     /**
205      * Constructor.
206      *
207      * @param degree   degree of polynomial to be estimated.
208      * @param listener listener to be notified of events such as when estimation
209      *                 starts, ends or its progress significantly changes.
210      * @throws IllegalArgumentException if provided degree is less than 1.
211      */
212     protected PolynomialRobustEstimator(
213             final int degree, final PolynomialRobustEstimatorListener listener) {
214         this(degree);
215         this.listener = listener;
216     }
217 
218     /**
219      * Constructor.
220      *
221      * @param evaluations collection of polynomial evaluations.
222      * @param listener    listener to be notified of events such as when estimation
223      *                    starts, ends or its progress significantly changes.
224      * @throws IllegalArgumentException if provided number of evaluations is
225      *                                  less than the required minimum.
226      */
227     protected PolynomialRobustEstimator(
228             final List<PolynomialEvaluation> evaluations, final PolynomialRobustEstimatorListener listener) {
229         this(evaluations);
230         this.listener = listener;
231     }
232 
233     /**
234      * Constructor.
235      *
236      * @param degree      degree of polynomial to be estimated.
237      * @param evaluations collection of polynomial evaluations.
238      * @param listener    listener to be notified of events.
239      * @throws IllegalArgumentException if provided degree is less than 1 or if
240      *                                  provided number of evaluations is less than the required minimum for
241      *                                  provided degree.
242      */
243     protected PolynomialRobustEstimator(
244             final int degree, final List<PolynomialEvaluation> evaluations,
245             final PolynomialRobustEstimatorListener listener) {
246         this(degree, evaluations);
247         this.listener = listener;
248     }
249 
250     /**
251      * Gets collection of polynomial evaluations and their corresponding point
252      * of evaluation used to determine a polynomial of required degree.
253      *
254      * @return collection of polynomial evaluations.
255      */
256     public List<PolynomialEvaluation> getEvaluations() {
257         return evaluations;
258     }
259 
260     /**
261      * Sets collection of polynomial evaluations and their corresponding point
262      * of evaluation used to determine a polynomial of required degree.
263      *
264      * @param evaluations collection of polynomial evaluations.
265      * @throws LockedException          if estimator is locked.
266      * @throws IllegalArgumentException if provided list of evaluations does
267      *                                  not contain enough evaluations to estimate the polynomial using current
268      *                                  settings.
269      */
270     public void setEvaluations(final List<PolynomialEvaluation> evaluations) throws LockedException {
271         if (isLocked()) {
272             throw new LockedException();
273         }
274         internalSetEvaluations(evaluations);
275     }
276 
277     /**
278      * Gets minimum number of evaluations required to estimate a polynomial of
279      * the specified degree.
280      *
281      * @return number of required evaluations.
282      */
283     public int getMinNumberOfEvaluations() {
284         return polynomialEstimator.getMinNumberOfEvaluations();
285     }
286 
287     /**
288      * Gets listener to be notified of events such as when estimation starts,
289      * ends or its progress significantly changes.
290      *
291      * @return listener to be notified of events.
292      */
293     public PolynomialRobustEstimatorListener getListener() {
294         return listener;
295     }
296 
297     /**
298      * Sets listener to be notified of events such as when estimation starts,
299      * ends or its progress significantly changes.
300      *
301      * @param listener listener to be notified of events.
302      */
303     public void setListener(final PolynomialRobustEstimatorListener listener) {
304         this.listener = listener;
305     }
306 
307     /**
308      * Indicates if this estimator is locked because an estimation is being
309      * computed.
310      *
311      * @return true if this estimator is locked, false otherwise.
312      */
313     public boolean isLocked() {
314         return locked;
315     }
316 
317     /**
318      * Returns amount of progress variation before notifying a progress change
319      * during estimation.
320      *
321      * @return amount of progress variation before notifying a progress change
322      * during estimation.
323      */
324     public float getProgressDelta() {
325         return progressDelta;
326     }
327 
328     /**
329      * Sets amount of progress variation before notifying a progress change
330      * during estimation.
331      *
332      * @param progressDelta amount of progress variation before notifying a
333      *                      progress change during estimation.
334      * @throws IllegalArgumentException if progress delta is less than zero or
335      *                                  greater than 1.
336      * @throws LockedException          if this estimator is locked because an estimation
337      *                                  is being computed.
338      */
339     public void setProgressDelta(final float progressDelta) throws LockedException {
340         if (isLocked()) {
341             throw new LockedException();
342         }
343         if (progressDelta < MIN_PROGRESS_DELTA || progressDelta > MAX_PROGRESS_DELTA) {
344             throw new IllegalArgumentException();
345         }
346         this.progressDelta = progressDelta;
347     }
348 
349     /**
350      * Returns amount of confidence expressed as a value between 0.0 and 1.0
351      * (which is equivalent to 100%). The amount of confidence indicates the
352      * probability that the estimated result is correct. Usually this value will
353      * be close to 1.0, but not exactly 1.0.
354      *
355      * @return amount of confidence as a value between 0.0 and 1.0.
356      */
357     public double getConfidence() {
358         return confidence;
359     }
360 
361     /**
362      * Sets amount of confidence expressed as a value between 0.0 and 1.0 (which
363      * is equivalent to 100%). The amount of confidence indicates the
364      * probability that the estimated result is correct. Usually this value will
365      * be close to 1.0 but not exactly 1.0.
366      *
367      * @param confidence confidence to be set as a value between 0.0 and 1.0.
368      * @throws IllegalArgumentException if provided value is not between 0.0 and
369      *                                  1.0.
370      * @throws LockedException          if this estimator is locked because an estimator
371      *                                  is being computed.
372      */
373     public void setConfidence(final double confidence) throws LockedException {
374         if (isLocked()) {
375             throw new LockedException();
376         }
377         if (confidence < MIN_CONFIDENCE || confidence > MAX_CONFIDENCE) {
378             throw new IllegalArgumentException();
379         }
380         this.confidence = confidence;
381     }
382 
383     /**
384      * Returns maximum allowed number of iterations. If maximum allowed number
385      * of iterations is achieved without converging to a result when calling
386      * estimate(), a RobustEstimatorException will be raised.
387      *
388      * @return maximum allowed number of iterations.
389      */
390     public int getMaxIterations() {
391         return maxIterations;
392     }
393 
394     /**
395      * Sets maximum allowed number of iterations. When the maximum number of
396      * iterations is exceeded, result will not be available, however an
397      * approximate result will be available for retrieval.
398      *
399      * @param maxIterations maximum allowed number of iterations to be set.
400      * @throws IllegalArgumentException if provided value is less than 1.
401      * @throws LockedException          if this estimator is locked because an estimation
402      *                                  is being computed.
403      */
404     public void setMaxIterations(final int maxIterations) throws LockedException {
405         if (isLocked()) {
406             throw new LockedException();
407         }
408         if (maxIterations < MIN_ITERATIONS) {
409             throw new IllegalArgumentException();
410         }
411         this.maxIterations = maxIterations;
412     }
413 
414     /**
415      * Indicates whether geometric distance will be used to find outliers or
416      * algebraic distance will be used instead.
417      *
418      * @return true if geometric distance is used, false otherwise.
419      */
420     public boolean isGeometricDistanceUsed() {
421         return useGeometricDistance;
422     }
423 
424     /**
425      * Specifies whether geometric distance will be used to find outliers or
426      * algebraic distance will be used instead.
427      *
428      * @param geometricDistanceUsed true if geometric distance is used, false
429      *                              otherwise.
430      * @throws LockedException if this estimator is locked.
431      */
432     public void setGeometricDistanceUsed(final boolean geometricDistanceUsed) throws LockedException {
433         if (isLocked()) {
434             throw new LockedException();
435         }
436         useGeometricDistance = geometricDistanceUsed;
437     }
438 
439     /**
440      * Gets degree of polynomial to be estimated.
441      *
442      * @return degree of polynomial to be estimated.
443      */
444     public int getDegree() {
445         return polynomialEstimator.getDegree();
446     }
447 
448     /**
449      * Sets degree of polynomial to be estimated.
450      *
451      * @param degree degree of polynomial to be estimated.
452      * @throws IllegalArgumentException if provided degree is less than 1.
453      * @throws LockedException          if this instance is locked.
454      */
455     public void setDegree(final int degree) throws LockedException {
456         if (isLocked()) {
457             throw new LockedException();
458         }
459         polynomialEstimator.setDegree(degree);
460     }
461 
462     /**
463      * Determines whether estimation is ready to start with the given data and
464      * required degree of polynomial to be estimated.
465      *
466      * @return true if estimator is ready, false otherwise.
467      */
468     public boolean isReady() {
469         final var nParams = polynomialEstimator.getDegree() + 1;
470         if (evaluations == null || evaluations.size() < nParams) {
471             return false;
472         }
473 
474         // also ensure that at least a direct or integral evaluation exists
475         var count = 0;
476         for (final var eval : evaluations) {
477             if (eval.getType() == PolynomialEvaluationType.DIRECT_EVALUATION
478                     || eval.getType() == PolynomialEvaluationType.INTEGRAL_EVALUATION
479                     || eval.getType() == PolynomialEvaluationType.INTEGRAL_INTERVAL) {
480                 count++;
481             }
482         }
483 
484         return count >= 1 && evaluations.size() >= nParams;
485     }
486 
487     /**
488      * Returns quality scores corresponding to each polynomial evaluation.
489      * The larger the score value the better the quality of the evaluation.
490      * This implementation always returns null.
491      * Subclasses using quality scores must implement proper behaviour.
492      *
493      * @return quality scores corresponding to each evaluation.
494      */
495     public double[] getQualityScores() {
496         // quality scores ignored
497         return null;
498     }
499 
500     /**
501      * Sets quality scores corresponding to each polynomial evaluation.
502      * The larger the score value the better the quality of the evaluation.
503      * This implementation makes no action.
504      * Subclasses using quality scores must implement proper behaviour.
505      *
506      * @param qualityScores quality scores corresponding to each evaluation.
507      * @throws LockedException          if robust estimator is locked because an
508      *                                  estimation is already in progress.
509      * @throws IllegalArgumentException if provided quality scores length is
510      *                                  smaller than minimum required number of evaluations.
511      */
512     public void setQualityScores(final double[] qualityScores) throws LockedException {
513         // quality scores ignored
514     }
515 
516     /**
517      * Estimates polynomial.
518      *
519      * @return estimated polynomial.
520      * @throws LockedException          if robust estimator is locked because an
521      *                                  estimation is already in progress.
522      * @throws NotReadyException        if provided input data is not enough to start
523      *                                  the estimation.
524      * @throws RobustEstimatorException if estimation fails for any other reason
525      *                                  (i.e. numerical instability, no solution available, etc).
526      */
527     public abstract Polynomial estimate() throws LockedException, NotReadyException, RobustEstimatorException;
528 
529     /**
530      * Returns method being used for robust estimation.
531      *
532      * @return method being used for robust estimation.
533      */
534     public abstract RobustEstimatorMethod getMethod();
535 
536     /**
537      * Creates a robust polynomial estimator using default method.
538      *
539      * @return an instance of a robust polynomial estimator.
540      */
541     public static PolynomialRobustEstimator create() {
542         return create(DEFAULT_ROBUST_METHOD);
543     }
544 
545     /**
546      * Creates a robust polynomial estimator using provided degree and default
547      * method.
548      *
549      * @param degree degree of polynomial to be estimated.
550      * @return an instance of a robust polynomial estimator.
551      * @throws IllegalArgumentException if provided degree is less than 1.
552      */
553     public static PolynomialRobustEstimator create(final int degree) {
554         return create(degree, DEFAULT_ROBUST_METHOD);
555     }
556 
557     /**
558      * Creates a robust polynomial estimator using provided evaluations and
559      * default method.
560      *
561      * @param evaluations collection of polynomial evaluations.
562      * @return an instance of a robust polynomial estimator.
563      */
564     public static PolynomialRobustEstimator create(final List<PolynomialEvaluation> evaluations) {
565         return create(evaluations, DEFAULT_ROBUST_METHOD);
566     }
567 
568     /**
569      * Creates a robust polynomial estimator using provided listener and default
570      * method.
571      *
572      * @param listener listener to be notified of events such as when estimation
573      *                 starts, ends or its progress significantly changes.
574      * @return an instance of a robust polynomial estimator.
575      */
576     public static PolynomialRobustEstimator create(final PolynomialRobustEstimatorListener listener) {
577         return create(listener, DEFAULT_ROBUST_METHOD);
578     }
579 
580     /**
581      * Creates a robust polynomial estimator using provided degree, evaluations
582      * and default method.
583      *
584      * @param degree      degree of polynomial to be estimated.
585      * @param evaluations collection of polynomial evaluations.
586      * @return an instance of a robust polynomial estimator.
587      * @throws IllegalArgumentException if provided degree is less than 1.
588      */
589     public static PolynomialRobustEstimator create(final int degree, final List<PolynomialEvaluation> evaluations) {
590         return create(degree, evaluations, DEFAULT_ROBUST_METHOD);
591     }
592 
593     /**
594      * Creates a robust polynomial estimator using provided degree, listener
595      * and default method.
596      *
597      * @param degree   degree of polynomial to be estimated.
598      * @param listener listener to be notified of events such as when estimation
599      *                 starts, ends or its progress significantly changes.
600      * @return an instance of a robust polynomial estimator.
601      * @throws IllegalArgumentException if provided degree is less than 1.
602      */
603     public static PolynomialRobustEstimator create(final int degree, final PolynomialRobustEstimatorListener listener) {
604         return create(degree, listener, DEFAULT_ROBUST_METHOD);
605     }
606 
607     /**
608      * Creates a robust polynomial estimator using provided evaluations,
609      * listener and default method.
610      *
611      * @param evaluations collection of polynomial evaluations.
612      * @param listener    listener to be notified of events such as when estimation
613      *                    starts, ends or its progress significantly changes.
614      * @return an instance of a robust polynomial estimator.
615      */
616     public static PolynomialRobustEstimator create(
617             final List<PolynomialEvaluation> evaluations, final PolynomialRobustEstimatorListener listener) {
618         return create(evaluations, listener, DEFAULT_ROBUST_METHOD);
619     }
620 
621     /**
622      * Creates a robust polynomial estimator using provided degree, evaluations,
623      * listener and default method.
624      *
625      * @param degree      degree of polynomial to be estimated.
626      * @param evaluations collection of polynomial evaluations.
627      * @param listener    listener to be notified of events such as when estimation
628      *                    starts, ends or its progress significantly changes.
629      * @return an instance of a robust polynomial estimator.
630      * @throws IllegalArgumentException if provided degree is less than 1.
631      */
632     public static PolynomialRobustEstimator create(
633             final int degree, final List<PolynomialEvaluation> evaluations,
634             final PolynomialRobustEstimatorListener listener) {
635         return create(degree, evaluations, listener, DEFAULT_ROBUST_METHOD);
636     }
637 
638     /**
639      * Creates a robust polynomial estimator using provided method.
640      *
641      * @param method method of a robust polynomial estimator.
642      * @return an instance of a robust polynomial estimator.
643      */
644     public static PolynomialRobustEstimator create(final RobustEstimatorMethod method) {
645         return switch (method) {
646             case RANSAC -> new RANSACPolynomialRobustEstimator();
647             case LMEDS -> new LMedSPolynomialRobustEstimator();
648             case MSAC -> new MSACPolynomialRobustEstimator();
649             case PROMEDS -> new PROMedSPolynomialRobustEstimator();
650             default -> new PROSACPolynomialRobustEstimator();
651         };
652     }
653 
654     /**
655      * Creates a robust polynomial estimator using provided degree and method.
656      *
657      * @param degree degree of polynomial to be estimated.
658      * @param method method of a robust polynomial estimator.
659      * @return an instance of a robust polynomial estimator.
660      * @throws IllegalArgumentException if provided degree is less than 1.
661      */
662     public static PolynomialRobustEstimator create(final int degree, final RobustEstimatorMethod method) {
663         return switch (method) {
664             case RANSAC -> new RANSACPolynomialRobustEstimator(degree);
665             case LMEDS -> new LMedSPolynomialRobustEstimator(degree);
666             case MSAC -> new MSACPolynomialRobustEstimator(degree);
667             case PROMEDS -> new PROMedSPolynomialRobustEstimator(degree);
668             default -> new PROSACPolynomialRobustEstimator(degree);
669         };
670     }
671 
672     /**
673      * Creates a robust polynomial estimator using provided evaluations and
674      * method.
675      *
676      * @param evaluations collection of polynomial evaluations.
677      * @param method      method of a robust polynomial estimator.
678      * @return an instance of a robust polynomial estimator.
679      */
680     public static PolynomialRobustEstimator create(
681             final List<PolynomialEvaluation> evaluations, final RobustEstimatorMethod method) {
682         return switch (method) {
683             case RANSAC -> new RANSACPolynomialRobustEstimator(evaluations);
684             case LMEDS -> new LMedSPolynomialRobustEstimator(evaluations);
685             case MSAC -> new MSACPolynomialRobustEstimator(evaluations);
686             case PROMEDS -> new PROMedSPolynomialRobustEstimator(evaluations);
687             default -> new PROSACPolynomialRobustEstimator(evaluations);
688         };
689     }
690 
691     /**
692      * Creates a robust polynomial estimator using provided listener and method.
693      *
694      * @param listener listener to be notified of events such as when estimation
695      *                 starts, ends or its progress significantly changes.
696      * @param method   method of a robust polynomial estimator.
697      * @return an instance of a robust polynomial estimator.
698      */
699     public static PolynomialRobustEstimator create(
700             final PolynomialRobustEstimatorListener listener, final RobustEstimatorMethod method) {
701         return switch (method) {
702             case RANSAC -> new RANSACPolynomialRobustEstimator(listener);
703             case LMEDS -> new LMedSPolynomialRobustEstimator(listener);
704             case MSAC -> new MSACPolynomialRobustEstimator(listener);
705             case PROMEDS -> new PROMedSPolynomialRobustEstimator(listener);
706             default -> new PROSACPolynomialRobustEstimator(listener);
707         };
708     }
709 
710     /**
711      * Creates a robust polynomial estimator using provided degree, evaluations
712      * and method.
713      *
714      * @param degree      degree of polynomial to be estimated.
715      * @param evaluations collection of polynomial evaluations.
716      * @param method      method of a robust polynomial estimator.
717      * @return an instance of a robust polynomial estimator.
718      * @throws IllegalArgumentException if provided degree is less than 1.
719      */
720     public static PolynomialRobustEstimator create(
721             final int degree, final List<PolynomialEvaluation> evaluations, final RobustEstimatorMethod method) {
722         return switch (method) {
723             case RANSAC -> new RANSACPolynomialRobustEstimator(degree, evaluations);
724             case LMEDS -> new LMedSPolynomialRobustEstimator(degree, evaluations);
725             case MSAC -> new MSACPolynomialRobustEstimator(degree, evaluations);
726             case PROMEDS -> new PROMedSPolynomialRobustEstimator(degree, evaluations);
727             default -> new PROSACPolynomialRobustEstimator(degree, evaluations);
728         };
729     }
730 
731     /**
732      * Creates a robust polynomial estimator using provided degree, listener
733      * and method.
734      *
735      * @param degree   degree of polynomial to be estimated.
736      * @param listener listener to be notified of events such as when estimation
737      *                 starts, ends or its progress significantly changes.
738      * @param method   method of a robust polynomial estimator.
739      * @return an instance of a robust polynomial estimator.
740      * @throws IllegalArgumentException if provided degree is less than 1.
741      */
742     public static PolynomialRobustEstimator create(
743             final int degree, final PolynomialRobustEstimatorListener listener, final RobustEstimatorMethod method) {
744         return switch (method) {
745             case RANSAC -> new RANSACPolynomialRobustEstimator(degree, listener);
746             case LMEDS -> new LMedSPolynomialRobustEstimator(degree, listener);
747             case MSAC -> new MSACPolynomialRobustEstimator(degree, listener);
748             case PROMEDS -> new PROMedSPolynomialRobustEstimator(degree, listener);
749             default -> new PROSACPolynomialRobustEstimator(degree, listener);
750         };
751     }
752 
753     /**
754      * Creates a robust polynomial estimator using provided evaluations,
755      * listener and method.
756      *
757      * @param evaluations collection of polynomial evaluations.
758      * @param listener    listener to be notified of events such as when estimation
759      *                    starts, ends or its progress significantly changes.
760      * @param method      method of a robust polynomial estimator.
761      * @return an instance of a robust polynomial estimator.
762      */
763     public static PolynomialRobustEstimator create(
764             final List<PolynomialEvaluation> evaluations, final PolynomialRobustEstimatorListener listener,
765             final RobustEstimatorMethod method) {
766         return switch (method) {
767             case RANSAC -> new RANSACPolynomialRobustEstimator(evaluations, listener);
768             case LMEDS -> new LMedSPolynomialRobustEstimator(evaluations, listener);
769             case MSAC -> new MSACPolynomialRobustEstimator(evaluations, listener);
770             case PROMEDS -> new PROMedSPolynomialRobustEstimator(evaluations, listener);
771             default -> new PROSACPolynomialRobustEstimator(evaluations, listener);
772         };
773     }
774 
775     /**
776      * Creates a robust polynomial estimator using provided degree, evaluations,
777      * listener and method.
778      *
779      * @param degree      degree of polynomial to be estimated.
780      * @param evaluations collection of polynomial evaluations.
781      * @param listener    listener to be notified of events such as when estimation
782      *                    starts, ends or its progress significantly changes.
783      * @param method      method of a robust polynomial estimator.
784      * @return an instance of a robust polynomial estimator.
785      * @throws IllegalArgumentException if provided degree is less than 1.
786      */
787     public static PolynomialRobustEstimator create(
788             final int degree, final List<PolynomialEvaluation> evaluations,
789             final PolynomialRobustEstimatorListener listener, final RobustEstimatorMethod method) {
790         return switch (method) {
791             case RANSAC -> new RANSACPolynomialRobustEstimator(degree, evaluations, listener);
792             case LMEDS -> new LMedSPolynomialRobustEstimator(degree, evaluations, listener);
793             case MSAC -> new MSACPolynomialRobustEstimator(degree, evaluations, listener);
794             case PROMEDS -> new PROMedSPolynomialRobustEstimator(degree, evaluations, listener);
795             default -> new PROSACPolynomialRobustEstimator(degree, evaluations, listener);
796         };
797     }
798 
799     /**
800      * Computes geometric or algebraic distance between provided polynomial
801      * and evaluation.
802      *
803      * @param eval       polynomial evaluation.
804      * @param polynomial polynomial.
805      * @return distance.
806      */
807     protected double getDistance(final PolynomialEvaluation eval, final Polynomial polynomial) {
808         if (useGeometricDistance) {
809             return getGeometricOrAlgebraicDistance(eval, polynomial);
810         } else {
811             return getAlgebraicDistance(eval, polynomial);
812         }
813     }
814 
815     /**
816      * Computes algebraic distance between provided polynomial and evaluation.
817      *
818      * @param eval       polynomial evaluation.
819      * @param polynomial polynomial.
820      * @return algebraic distance.
821      */
822     protected double getAlgebraicDistance(final PolynomialEvaluation eval, final Polynomial polynomial) {
823         return switch (eval.getType()) {
824             case DIRECT_EVALUATION -> getAlgebraicDistance((DirectPolynomialEvaluation) eval, polynomial);
825             case DERIVATIVE_EVALUATION -> getAlgebraicDistance((DerivativePolynomialEvaluation) eval, polynomial);
826             case INTEGRAL_EVALUATION -> getAlgebraicDistance((IntegralPolynomialEvaluation) eval, polynomial);
827             case INTEGRAL_INTERVAL -> getAlgebraicDistance((IntegralIntervalPolynomialEvaluation) eval, polynomial);
828         };
829     }
830 
831     /**
832      * Computes algebraic distance of between provided polynomial and direct
833      * evaluation.
834      *
835      * @param eval       direct polynomial evaluation.
836      * @param polynomial polynomial.
837      * @return algebraic distance.
838      */
839     protected double getAlgebraicDistance(final DirectPolynomialEvaluation eval, final Polynomial polynomial) {
840         final var x = eval.getX();
841         final var y1 = eval.getEvaluation();
842         final var y2 = polynomial.evaluate(x);
843         return Math.abs(y2 - y1);
844     }
845 
846     /**
847      * Computes algebraic distance of a derivative between provided polynomial
848      * and evaluation.
849      *
850      * @param eval       derivative polynomial evaluation.
851      * @param polynomial polynomial.
852      * @return algebraic distance.
853      */
854     protected double getAlgebraicDistance(final DerivativePolynomialEvaluation eval, final Polynomial polynomial) {
855         final var x = eval.getX();
856         final var order = eval.getDerivativeOrder();
857         final var d1 = eval.getEvaluation();
858         final var d2 = polynomial.evaluateNthDerivative(x, order);
859         return Math.abs(d2 - d1);
860     }
861 
862     /**
863      * Computes algebraic distance of an integral between provided polynomial
864      * and evaluation.
865      *
866      * @param eval       integration polynomial evaluation.
867      * @param polynomial polynomial.
868      * @return algebraic distance.
869      */
870     protected double getAlgebraicDistance(final IntegralPolynomialEvaluation eval, final Polynomial polynomial) {
871         final var x = eval.getX();
872         final var order = eval.getIntegralOrder();
873         final var constants = eval.getConstants();
874         final var i1 = eval.getEvaluation();
875         final var i2 = polynomial.nthIntegrationAndReturnNew(order, constants).evaluate(x);
876         return Math.abs(i2 - i1);
877     }
878 
879     /**
880      * Computes algebraic distance of an integration interval between provided
881      * polynomial and evaluation.
882      *
883      * @param eval       integration interval polynomial evaluation.
884      * @param polynomial polynomial.
885      * @return algebraic distance.
886      */
887     protected double getAlgebraicDistance(final IntegralIntervalPolynomialEvaluation eval,
888                                           final Polynomial polynomial) {
889         final var startX = eval.getStartX();
890         final var endX = eval.getEndX();
891         final var order = eval.getIntegralOrder();
892         final var constants = eval.getConstants();
893         final var i1 = eval.getEvaluation();
894         final var i2 = polynomial.nthOrderIntegrateInterval(startX, endX, order, constants);
895         return Math.abs(i2 - i1);
896     }
897 
898     /**
899      * Commutes distance of evaluation respect to provided polynomial in
900      * a geometric sense if evaluation is direct, otherwise returns algebraic
901      * distance.
902      *
903      * @param eval       polynomial evaluation.
904      * @param polynomial polynomial.
905      * @return geometric distance for direct evaluation or algebraic distance
906      * otherwise.
907      */
908     protected double getGeometricOrAlgebraicDistance(final PolynomialEvaluation eval, final Polynomial polynomial) {
909         if (eval.getType() == PolynomialEvaluationType.DIRECT_EVALUATION) {
910             return getGeometricDistance((DirectPolynomialEvaluation) eval, polynomial);
911         } else {
912             return getAlgebraicDistance(eval, polynomial);
913         }
914     }
915 
916     /**
917      * Computes distance of evaluation respect to provided polynomial in
918      * a geometric sense by computing a tangent line to polynomial at point x
919      * and comparing the distance of such line to provided evaluation point.
920      *
921      * @param eval       polynomial evaluation.
922      * @param polynomial polynomial.
923      * @return geometric distance.
924      */
925     protected double getGeometricDistance(final DirectPolynomialEvaluation eval, final Polynomial polynomial) {
926         final var x = eval.getX();
927         final var y1 = eval.getEvaluation();
928         final var y2 = polynomial.evaluate(x);
929 
930         final var slope = polynomial.evaluateDerivative(x);
931         final double a;
932         final double b;
933         final double c;
934         if (Math.abs(slope) > 1.0) {
935             a = 1.0;
936             b = -1.0 / slope;
937             c = -x + y2 / slope;
938         } else {
939             a = -slope;
940             b = 1.0;
941             c = slope * x - y2;
942         }
943 
944         final var num = x * a + y1 * b + c;
945         final var den = Math.sqrt(a * a + b * b);
946 
947         return Math.abs(num / den);
948     }
949 
950     /**
951      * Sets list of polynomial evaluations.
952      * This method does not check whether estimator is locked.
953      *
954      * @param evaluations list of polynomial evaluations to estimate polynomial.
955      * @throws IllegalArgumentException if provided list of polynomials is null
956      *                                  or too small.
957      */
958     private void internalSetEvaluations(final List<PolynomialEvaluation> evaluations) {
959         if (evaluations == null || evaluations.size() < getMinNumberOfEvaluations()) {
960             throw new IllegalArgumentException();
961         }
962         this.evaluations = evaluations;
963     }
964 }