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.algebra.AlgebraException;
19  import com.irurueta.algebra.Matrix;
20  import com.irurueta.algebra.Utils;
21  import com.irurueta.numerical.LockedException;
22  import com.irurueta.numerical.NotReadyException;
23  import com.irurueta.numerical.polynomials.Polynomial;
24  import com.irurueta.numerical.robust.WeightSelection;
25  import com.irurueta.sorting.SortingException;
26  
27  import java.util.List;
28  
29  /**
30   * This class implements a polynomial estimator using weighted evaluations.
31   * Weights can be used so that evaluations assumed to have a better quality
32   * (i.e. more precisely estimated) are considered to be more relevant.
33   * It is discouraged to use a large number of evaluations, even if they are
34   * correctly weighted, since as the number of evaluations increase so do the
35   * rounding errors.
36   */
37  @SuppressWarnings("DuplicatedCode")
38  public class WeightedPolynomialEstimator extends PolynomialEstimator {
39  
40      /**
41       * Default number of evaluations to be weighted and taken into account.
42       */
43      public static final int DEFAULT_MAX_EVALUATIONS = 50;
44  
45      /**
46       * Indicates if weights are sorted by default so that largest weighted
47       * evaluations are used first.
48       */
49      public static final boolean DEFAULT_SORT_WEIGHTS = true;
50  
51      /**
52       * Maximum number of evaluations to be weighted and taken into account.
53       */
54      private int maxEvaluations = DEFAULT_MAX_EVALUATIONS;
55  
56      /**
57       * Indicates if weights are sorted by default so that largest weighted
58       * evaluations are used first.
59       */
60      private boolean sortWeights = DEFAULT_SORT_WEIGHTS;
61  
62      /**
63       * Array containing weights for all evaluations.
64       */
65      private double[] weights;
66  
67      /**
68       * Constructor.
69       */
70      public WeightedPolynomialEstimator() {
71          super();
72      }
73  
74      /**
75       * Constructor.
76       *
77       * @param degree degree of polynomial to be estimated.
78       * @throws IllegalArgumentException if provided degree is less than 1.
79       */
80      public WeightedPolynomialEstimator(final int degree) {
81          super(degree);
82      }
83  
84      /**
85       * Constructor.
86       *
87       * @param evaluations collection of polynomial evaluations.
88       * @param weights     array containing a weight amount for each evaluation. The
89       *                    larger the value of a weight, the most significant the correspondence
90       *                    will be.
91       * @throws IllegalArgumentException if evaluations or weights are null or
92       *                                  don't have the same size.
93       */
94      public WeightedPolynomialEstimator(
95              final List<PolynomialEvaluation> evaluations, final double[] weights) {
96          super();
97          internalSetEvaluationsAndWeights(evaluations, weights);
98      }
99  
100     /**
101      * Constructor.
102      *
103      * @param listener listener to be notified of events.
104      */
105     public WeightedPolynomialEstimator(final PolynomialEstimatorListener listener) {
106         super(listener);
107     }
108 
109     /**
110      * Constructor.
111      *
112      * @param degree      degree of polynomial to be estimated.
113      * @param evaluations collection of polynomial evaluations.
114      * @param weights     array containing a weight amount for each evaluation. The
115      *                    larger the value of a weight, the most significant the correspondence
116      *                    will be.
117      * @throws IllegalArgumentException if evaluations or weights are null or
118      *                                  don't have the same size, or if provided degree is less than 1.
119      */
120     public WeightedPolynomialEstimator(
121             final int degree, final List<PolynomialEvaluation> evaluations, final double[] weights) {
122         super(degree);
123         internalSetEvaluationsAndWeights(evaluations, weights);
124     }
125 
126     /**
127      * Constructor.
128      *
129      * @param degree   degree of polynomial to be estimated.
130      * @param listener listener to be notified of events.
131      * @throws IllegalArgumentException if provided degree is less than 1.
132      */
133     public WeightedPolynomialEstimator(final int degree, final PolynomialEstimatorListener listener) {
134         super(degree, listener);
135     }
136 
137     /**
138      * Constructor.
139      *
140      * @param evaluations collection of polynomial evaluations.
141      * @param weights     array containing a weight amount for each evaluation. The
142      *                    larger the value of a weight, the most significant the correspondence
143      *                    will be.
144      * @param listener    listener to be notified of events.
145      * @throws IllegalArgumentException if evaluations or weights are null or
146      *                                  don't have the same size.
147      */
148     public WeightedPolynomialEstimator(
149             final List<PolynomialEvaluation> evaluations, final double[] weights,
150             final PolynomialEstimatorListener listener) {
151         super(listener);
152         internalSetEvaluationsAndWeights(evaluations, weights);
153     }
154 
155     /**
156      * Constructor.
157      *
158      * @param degree      degree of polynomial to be estimated.
159      * @param evaluations collection of polynomial evaluations.
160      * @param weights     array containing a weight amount for each evaluation. The
161      *                    larger the value of a weight, the most significant the correspondence
162      *                    will be.
163      * @param listener    listener to be notified of events.
164      * @throws IllegalArgumentException if evaluations or weights are null or
165      *                                  don't have the same size, or if provided degree is less than 1.
166      */
167     public WeightedPolynomialEstimator(
168             final int degree, final List<PolynomialEvaluation> evaluations, final double[] weights,
169             final PolynomialEstimatorListener listener) {
170         super(degree, listener);
171         internalSetEvaluationsAndWeights(evaluations, weights);
172     }
173 
174     /**
175      * Sets collection of polynomial evaluations and their corresponding point
176      * of evaluation used to determine a polynomial of required degree.
177      * This method override always throws an IllegalArgumentException because it
178      * is expected to provide both evaluations and their weights.
179      *
180      * @param evaluations collection of polynomial evaluations.
181      * @throws IllegalArgumentException always thrown.
182      */
183     @Override
184     public void setEvaluations(final List<PolynomialEvaluation> evaluations) {
185         throw new IllegalArgumentException("evaluations and weights must be provided at once");
186     }
187 
188     /**
189      * Sets collection of polynomial evaluations along with their corresponding
190      * weights.
191      *
192      * @param evaluations collection of polynomial evaluations.
193      * @param weights     array containing a weight amount for each polynomial
194      *                    evaluation. The larger the value of a weight, the most significant the
195      *                    evaluation will be.
196      * @throws LockedException          if estimator is locked.
197      * @throws IllegalArgumentException if evaluations or weights are null or
198      *                                  don't have the same size.
199      */
200     public void setEvaluationsAndWeights(
201             final List<PolynomialEvaluation> evaluations, final double[] weights) throws LockedException {
202         if (isLocked()) {
203             throw new LockedException();
204         }
205         internalSetEvaluationsAndWeights(evaluations, weights);
206     }
207 
208     /**
209      * Sets degree of polynomial to be estimated and collection of polynomial
210      * evaluations and their corresponding point of evaluation used to determine
211      * a polynomial of specified degree.
212      *
213      * @param degree      degree of polynomial to be estimated.
214      * @param evaluations collection of polynomial evaluations.
215      * @param weights     array containing a weight amount for each polynomial
216      *                    evaluation. The larger the value of a weight, the most significant the
217      *                    evaluation will be.
218      * @throws IllegalArgumentException if provided degree is less than 1 or
219      *                                  if evaluations or weights are null or don't have the same size.
220      * @throws LockedException          if this instance is locked.
221      */
222     public void setDegreeEvaluationsAndWeights(
223             final int degree, final List<PolynomialEvaluation> evaluations, final double[] weights)
224             throws LockedException {
225         setDegree(degree);
226         setEvaluationsAndWeights(evaluations, weights);
227     }
228 
229 
230     /**
231      * Returns array containing a weight amount for each polynomial evaluation.
232      * The larger the value of a weight, the most significant the correspondence
233      * will be.
234      *
235      * @return array containing weights for each correspondence.
236      */
237     public double[] getWeights() {
238         return weights;
239     }
240 
241     /**
242      * Returns boolean indicating whether weights have been provided and are
243      * available for retrieval.
244      *
245      * @return true if weights are available, false otherwise.
246      */
247     public boolean areWeightsAvailable() {
248         return weights != null;
249     }
250 
251     /**
252      * Returns maximum number of evaluations to be weighted and taken into
253      * account.
254      *
255      * @return maximum number of evaluations to be weighted.
256      */
257     public int getMaxEvaluations() {
258         return maxEvaluations;
259     }
260 
261     /**
262      * Sets maximum number of evaluations to be weighted and taken into account.
263      * This method must be called after setting degree, because the minimum
264      * number of required evaluations will be checked based on degree of
265      * polynomial to be estimated.
266      *
267      * @param maxEvaluations maximum number of evaluations to be weighted.
268      * @throws IllegalArgumentException if provided value is less than the
269      *                                  minimum number of required evaluations.
270      * @throws LockedException          if this instance is locked.
271      */
272     public void setMaxEvaluations(final int maxEvaluations) throws LockedException {
273         if (isLocked()) {
274             throw new LockedException();
275         }
276         if (maxEvaluations < getMinNumberOfEvaluations()) {
277             throw new IllegalArgumentException();
278         }
279         this.maxEvaluations = maxEvaluations;
280     }
281 
282     /**
283      * Indicates if weights are sorted by so that largest weighted evaluations
284      * are used first.
285      *
286      * @return true if weights are sorted, false otherwise.
287      */
288     public boolean isSortWeightsEnabled() {
289         return sortWeights;
290     }
291 
292     /**
293      * Specifies whether weights are sorted by so that largest weighted
294      * evaluations are used first.
295      *
296      * @param sortWeights true if weights are sorted, false otherwise.
297      * @throws LockedException if this instance is locked.
298      */
299     public void setSortWeightsEnabled(final boolean sortWeights) throws LockedException {
300         if (isLocked()) {
301             throw new LockedException();
302         }
303 
304         this.sortWeights = sortWeights;
305     }
306 
307     /**
308      * Indicates if this estimator is ready to start the estimation.
309      * Estimator will be ready once enough evaluations and weights are provided.
310      *
311      * @return true if estimator is ready, false otherwise.
312      */
313     @Override
314     public boolean isReady() {
315         return super.isReady() && areWeightsAvailable() && evaluations.size() == weights.length;
316     }
317 
318     /**
319      * Estimates a polynomial based on provided evaluations.
320      *
321      * @return estimated polynomial.
322      * @throws LockedException               if estimator is locked.
323      * @throws NotReadyException             if estimator is not ready.
324      * @throws PolynomialEstimationException if polynomial estimation fails.
325      */
326     @Override
327     public Polynomial estimate() throws LockedException, NotReadyException, PolynomialEstimationException {
328         if (isLocked()) {
329             throw new LockedException();
330         }
331         if (!isReady()) {
332             throw new NotReadyException();
333         }
334 
335         try {
336             locked = true;
337             if (listener != null) {
338                 listener.onEstimateStart(this);
339             }
340 
341             final var selection = WeightSelection.selectWeights(weights, sortWeights, maxEvaluations);
342             final var selected = selection.getSelected();
343             final var nEvaluations = selection.getNumSelected();
344 
345 
346             final var a = new Matrix(nEvaluations, degree + 1);
347             final var b = new Matrix(nEvaluations, 1);
348 
349             var index = 0;
350             var counter = 0;
351             double weight;
352             for (final var evaluation : evaluations) {
353                 if (selected[index]) {
354                     weight = weights[index];
355 
356                     switch (evaluation.getType()) {
357                         case DIRECT_EVALUATION:
358                             fillDirectEvaluation((DirectPolynomialEvaluation) evaluation, a, b, counter);
359                             break;
360                         case DERIVATIVE_EVALUATION:
361                             fillDerivativeEvaluation((DerivativePolynomialEvaluation) evaluation, a, b, counter);
362                             break;
363                         case INTEGRAL_EVALUATION:
364                             fillIntegralEvaluation((IntegralPolynomialEvaluation) evaluation, a, b, counter);
365                             break;
366                         case INTEGRAL_INTERVAL:
367                             fillIntegralIntervalEvaluation((IntegralIntervalPolynomialEvaluation) evaluation, a, b,
368                                     counter);
369                             break;
370                         default:
371                             continue;
372                     }
373 
374                     normalize(a, b, counter, weight);
375                     counter++;
376                 }
377 
378                 index++;
379             }
380 
381             final var params = Utils.solve(a, b);
382 
383             final var result = new Polynomial(params.toArray());
384 
385             if (listener != null) {
386                 listener.onEstimateEnd(this);
387             }
388 
389             return result;
390         } catch (final AlgebraException | SortingException e) {
391             throw new PolynomialEstimationException(e);
392         } finally {
393             locked = false;
394         }
395     }
396 
397     /**
398      * Returns type of polynomial estimator.
399      *
400      * @return type of polynomial estimator.
401      */
402     @Override
403     public PolynomialEstimatorType getType() {
404         return PolynomialEstimatorType.WEIGHTED_POLYNOMIAL_ESTIMATOR;
405     }
406 
407     /**
408      * Normalizes rows of system matrix and values matrix to increase accuracy
409      * of linear system of equations to be solved.
410      *
411      * @param a      system matrix.
412      * @param b      values matrix.
413      * @param row    row to normalize.
414      * @param weight weight.
415      */
416     private static void normalize(final Matrix a, final Matrix b, final int row, final double weight) {
417         var sqrNorm = 0.0;
418         for (var i = 0; i < a.getColumns(); i++) {
419             sqrNorm += Math.pow(a.getElementAt(row, i), 2.0);
420         }
421         sqrNorm += Math.pow(b.getElementAtIndex(row), 2.0);
422 
423         final var norm = Math.sqrt(sqrNorm);
424         final var factor = weight / norm;
425 
426         for (var i = 0; i < a.getColumns(); i++) {
427             a.setElementAt(row, i, a.getElementAt(row, i) * factor);
428         }
429         b.setElementAtIndex(row, b.getElementAtIndex(row) * factor);
430     }
431 
432     /**
433      * Internal method to set evaluations and weights.
434      *
435      * @param evaluations evaluations.
436      * @param weights     weights.
437      * @throws IllegalArgumentException if evaluations or weights are null or
438      *                                  don't have the same size.
439      */
440     private void internalSetEvaluationsAndWeights(
441             final List<PolynomialEvaluation> evaluations, final double[] weights) {
442         if (weights == null || evaluations == null || weights.length != evaluations.size()) {
443             throw new IllegalArgumentException();
444         }
445         this.evaluations = evaluations;
446         this.weights = weights;
447     }
448 }