View Javadoc
1   /*
2    * Copyright (C) 2012 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;
17  
18  /**
19   * Abstract class to estimate the most likely value from a series of data
20   * assumed to be normally distributed.
21   * Assuming such condition, the subclasses of this class should be able to find
22   * the maximum of the probability distribution of all provided input data.
23   * Such probability distribution function is computed by aggregating all the
24   * samples as a series of Gaussian functions with a small sigma and centered at
25   * the exact value of the sample.
26   * Subclasses of this class will either build a histogram of such aggregation of
27   * Gaussians to find the most likely value, or might even find a more accurate
28   * result by refining the maximum using a maximum detector method.
29   * <p>
30   * It is suggested to always use the latter (implemented as
31   * AccurateMaximumLikelihoodEstimator), as it will provide much more accurate
32   * results at a slightly higher computational cost.
33   */
34  public abstract class MaximumLikelihoodEstimator {
35  
36      /**
37       * Default Gaussian sigma assigned to each sample.
38       */
39      public static final double DEFAULT_GAUSSIAN_SIGMA = 1.0;
40  
41      /**
42       * Minimum allowed Gaussian sigma to be set for each sample. Attempting to
43       * set Gaussian sigmas lower than this value will raise an exception.
44       */
45      public static final double MIN_GAUSSIAN_SIGMA = 0.0;
46  
47      /**
48       * Default method to find the most likely value. By default, the accurate
49       * method is used which refines the solution found by using a histogram and
50       * a maximum detector.
51       */
52      public static final MaximumLikelihoodEstimatorMethod DEFAULT_METHOD =
53              MaximumLikelihoodEstimatorMethod.ACCURATE_MAXIMUM_LIKELIHOOD_ESTIMATOR;
54  
55      /**
56       * Array containing input data to be used to find the most likely value.
57       */
58      protected double[] inputData;
59  
60      /**
61       * Minimum value found on provided input data array.
62       */
63      protected double minValue;
64  
65      /**
66       * Maximum value found on provided input data array.
67       */
68      protected double maxValue;
69  
70      /**
71       * Boolean indicating whether minimum and maximum values in array are
72       * already available.
73       */
74      protected boolean areMinMaxAvailable;
75  
76      /**
77       * Boolean indicating whether this instance is locked because some
78       * computations are being done. While this instance is locked, attempting to
79       * change its status or parameters will raise an exception.
80       */
81      protected boolean locked;
82  
83      /**
84       * Actual Gaussian sigma to be used on each sample when aggregating Gaussian
85       * functions centered at each input data sample value.
86       */
87      protected double gaussianSigma;
88  
89      /**
90       * Constructor.
91       *
92       * @param gaussianSigma Gaussian sigma to be used on each sample
93       * @throws IllegalArgumentException Raised if provided Gaussian sigma is
94       *                                  negative or zero.
95       */
96      protected MaximumLikelihoodEstimator(final double gaussianSigma) {
97          inputData = null;
98          minValue = maxValue = 0.0;
99          areMinMaxAvailable = false;
100         locked = false;
101         internalSetGaussianSigma(gaussianSigma);
102     }
103 
104     /**
105      * Empty constructor.
106      */
107     protected MaximumLikelihoodEstimator() {
108         inputData = null;
109         minValue = maxValue = 0.0;
110         areMinMaxAvailable = false;
111         locked = false;
112         gaussianSigma = DEFAULT_GAUSSIAN_SIGMA;
113     }
114 
115     /**
116      * Constructor.
117      *
118      * @param inputData     Array containing input data where most likely value must
119      *                      be estimated from.
120      * @param gaussianSigma Gaussian sigma to be used on each sample.
121      * @throws IllegalArgumentException Raised if provided Gaussian sigma is
122      *                                  negative or zero.
123      */
124     protected MaximumLikelihoodEstimator(final double[] inputData, final double gaussianSigma) {
125         this.inputData = inputData;
126         minValue = maxValue = 0.0;
127         areMinMaxAvailable = false;
128         locked = false;
129         internalSetGaussianSigma(gaussianSigma);
130     }
131 
132     /**
133      * Constructor.
134      *
135      * @param minValue      Minimum value assumed to be contained within input data
136      *                      array.
137      * @param maxValue      Maximum value assumed to be contained within input data
138      *                      array.
139      * @param inputData     Array containing input data where most likely value must
140      *                      be estimated from.
141      * @param gaussianSigma Gaussian sigma to be used on each sample.
142      * @throws IllegalArgumentException Raised if provided Gaussian sigma is
143      *                                  negative or zero, or if minValue &lt; maxValue.
144      */
145     protected MaximumLikelihoodEstimator(final double minValue, final double maxValue, final double[] inputData,
146                                          final double gaussianSigma) {
147         this.inputData = inputData;
148         locked = false;
149         internalSetMinMaxValues(minValue, maxValue);
150         internalSetGaussianSigma(gaussianSigma);
151     }
152 
153     /**
154      * Returns method to be used for maximum likelihood estimation on subclasses
155      * of this class.
156      *
157      * @return Method for maximum likelihood estimation.
158      */
159     public abstract MaximumLikelihoodEstimatorMethod getMethod();
160 
161     /**
162      * Returns minimum value found on provided input data array.
163      *
164      * @return Minimum value found on provided input data array.
165      * @throws NotAvailableException Raised if this value has not yet been set
166      *                               or computed.
167      */
168     public double getMinValue() throws NotAvailableException {
169         if (!areMinMaxValuesAvailable()) {
170             throw new NotAvailableException();
171         }
172         return minValue;
173     }
174 
175     /**
176      * Returns maximum value found on provided input data array.
177      *
178      * @return Maximum value found on provided input data array.
179      * @throws NotAvailableException Raised if this value has not yet been set
180      *                               or computed.
181      */
182     public double getMaxValue() throws NotAvailableException {
183         if (!areMinMaxValuesAvailable()) {
184             throw new NotAvailableException();
185         }
186         return maxValue;
187     }
188 
189     /**
190      * Sets minimum and maximum value assumed to be found in input data array.
191      *
192      * @param minValue Minimum value in input data array.
193      * @param maxValue Maximum value in input data array.
194      * @throws LockedException          Exception raised if this instance is locked.
195      *                                  This method can only be executed when computations finish and this
196      *                                  instance becomes unlocked.
197      * @throws IllegalArgumentException Exception raised if minValue &lt; maxValue.
198      */
199     public void setMinMaxValues(final double minValue, final double maxValue) throws LockedException {
200         if (isLocked()) {
201             throw new LockedException();
202         }
203         internalSetMinMaxValues(minValue, maxValue);
204     }
205 
206     /**
207      * Returns boolean indicating whether minimum and maximum values in array
208      * are already available.
209      *
210      * @return Boolean indicating whether minimum and maximum values in array
211      * are already available.
212      */
213     public boolean areMinMaxValuesAvailable() {
214         return areMinMaxAvailable;
215     }
216 
217     /**
218      * Returns boolean indicating whether this instance is locked because some
219      * computations are being done. While this instance is locked, attempting to
220      * change its status or parameters will raise an exception.
221      *
222      * @return Returns boolean indicating whether this instance is locked.
223      */
224     public boolean isLocked() {
225         return locked;
226     }
227 
228     /**
229      * Returns array containing input data to be used to find the most likely
230      * value.
231      *
232      * @return Returns array containing input data.
233      * @throws NotAvailableException Exception raised if input data has not yet
234      *                               been provided.
235      */
236     public double[] getInputData() throws NotAvailableException {
237         if (!isInputDataAvailable()) {
238             throw new NotAvailableException();
239         }
240         return inputData;
241     }
242 
243     /**
244      * Sets array containing input data to be used to find the most likely
245      * value.
246      *
247      * @param inputData Array containing input data.
248      * @throws LockedException Exception raised if this instance is locked.
249      *                         This method can only be executed when computations finish and this
250      *                         instance becomes unlocked.
251      */
252     public void setInputData(final double[] inputData) throws LockedException {
253         if (isLocked()) {
254             throw new LockedException();
255         }
256         this.inputData = inputData;
257     }
258 
259     /**
260      * Sets array containing input data to be used to find the most likely
261      * value along with the minimum and maximum values assumed to be contained
262      * in it.
263      *
264      * @param inputData Array containing input data.
265      * @param minValue  Minimum value assumed to be contained in provided input
266      *                  data array.
267      * @param maxValue  Maximum value assumed to be contained in provided input
268      *                  data array.
269      * @throws LockedException          Exception raised if this instance is locked.
270      *                                  This method can only be executed when computations finish and this
271      *                                  instance becomes unlocked.
272      * @throws IllegalArgumentException Exception raised if minValue &lt; maxValue.
273      */
274     public void setInputData(final double[] inputData, final double minValue, final double maxValue)
275             throws LockedException {
276         setMinMaxValues(minValue, maxValue);
277         this.inputData = inputData;
278     }
279 
280     /**
281      * Returns boolean indicating whether input data has already been provided
282      * or not.
283      *
284      * @return True if input data is available and can be retrieved.
285      */
286     public boolean isInputDataAvailable() {
287         return inputData != null;
288     }
289 
290     /**
291      * Returns boolean indicating if enough parameters have been provided in
292      * order to start the computation of the maximum likelihood value.
293      * Usually providing input data is enough to make this instance ready, but
294      * this is dependent of specific implementations of subclasses.
295      *
296      * @return True if this instance is ready to start the computation of the
297      * maximum likelihood value.
298      */
299     public boolean isReady() {
300         return isInputDataAvailable();
301     }
302 
303     /**
304      * Returns Gaussian sigma to be used on each sample when aggregating
305      * Gaussian functions centered at each input data sample value.
306      *
307      * @return Gaussian sigma to be used on each sample.
308      */
309     public double getGaussianSigma() {
310         return gaussianSigma;
311     }
312 
313     /**
314      * Sets Gaussian sigma to be used on each sample when aggregating Gaussian
315      * functions centered at each input data sample value.
316      *
317      * @param gaussianSigma Gaussian sigma to be used on each sample.
318      * @throws LockedException          Exception raised if this instance is locked.
319      *                                  This method can only be executed when computations finish and this
320      *                                  instance becomes unlocked
321      * @throws IllegalArgumentException Exception raised if provided Gaussian
322      *                                  sigma is negative or zero.
323      */
324     public void setGaussianSigma(final double gaussianSigma) throws LockedException {
325         if (isLocked()) {
326             throw new LockedException();
327         }
328         internalSetGaussianSigma(gaussianSigma);
329     }
330 
331     /**
332      * Starts the estimation of the most likely value contained within provided
333      * input data array.
334      *
335      * @return The most likely value.
336      * @throws LockedException   Exception raised if this instance is locked.
337      *                           This method can only be executed when computations finish and this
338      *                           instance becomes unlocked.
339      * @throws NotReadyException Exception raised if this instance is not yet
340      *                           ready
341      * @see #isReady()
342      */
343     public abstract double estimate() throws LockedException, NotReadyException;
344 
345     /**
346      * Creates an instance of a subclass of this class based on provided method
347      * and using provided Gaussian sigma.
348      *
349      * @param gaussianSigma Gaussian sigma to be set for each sample.
350      * @param method        Method to estimate maximum likelihood value.
351      * @return A maximum likelihood estimator.
352      * @throws IllegalArgumentException Raised if provided Gaussian sigma is
353      *                                  negative or zero.
354      */
355     public static MaximumLikelihoodEstimator create(final double gaussianSigma,
356                                                     final MaximumLikelihoodEstimatorMethod method) {
357         if (method == MaximumLikelihoodEstimatorMethod.HISTOGRAM_MAXIMUM_LIKELIHOOD_ESTIMATOR) {
358             return new HistogramMaximumLikelihoodEstimator(gaussianSigma,
359                     HistogramMaximumLikelihoodEstimator.DEFAULT_NUMBER_OF_BINS);
360         } else {
361             return new AccurateMaximumLikelihoodEstimator(gaussianSigma,
362                     AccurateMaximumLikelihoodEstimator.DEFAULT_USE_HISTOGRAM_INITIAL_SOLUTION);
363         }
364     }
365 
366     /**
367      * Creates an instance of a subclass of this class using default maximum
368      * likelihood estimation method and provided Gaussian sigma.
369      *
370      * @param gaussianSigma Gaussian sigma to be set for each sample.
371      * @return A maximum likelihood estimator.
372      * @throws IllegalArgumentException Raised if provided Gaussian sigma is
373      *                                  negative or zero.
374      */
375     public static MaximumLikelihoodEstimator create(final double gaussianSigma) {
376         return create(gaussianSigma, DEFAULT_METHOD);
377     }
378 
379     /**
380      * Creates an instance of a subclass of this class using default maximum
381      * likelihood estimation method and default Gaussian sigma.
382      *
383      * @return A maximum likelihood estimator.
384      */
385     public static MaximumLikelihoodEstimator create() {
386         return create(DEFAULT_GAUSSIAN_SIGMA);
387     }
388 
389     /**
390      * Creates an instance of a subclass of this class based on provided method
391      * and using provided Gaussian sigma and input data array.
392      *
393      * @param inputData     Array containing input data to be used for the
394      *                      estimation of the maximum likelihood value.
395      * @param gaussianSigma Gaussian sigma to be set for each sample.
396      * @param method        Method to estimate maximum likelihood value
397      * @return A maximum likelihood estimator.
398      * @throws IllegalArgumentException Raised if provided Gaussian sigma is
399      *                                  negative or zero.
400      */
401     public static MaximumLikelihoodEstimator create(
402             final double[] inputData, final double gaussianSigma, final MaximumLikelihoodEstimatorMethod method) {
403         if (method == MaximumLikelihoodEstimatorMethod.HISTOGRAM_MAXIMUM_LIKELIHOOD_ESTIMATOR) {
404             return new HistogramMaximumLikelihoodEstimator(inputData, gaussianSigma,
405                     HistogramMaximumLikelihoodEstimator.DEFAULT_NUMBER_OF_BINS);
406         } else {
407             return new AccurateMaximumLikelihoodEstimator(inputData, gaussianSigma,
408                     AccurateMaximumLikelihoodEstimator.DEFAULT_USE_HISTOGRAM_INITIAL_SOLUTION);
409         }
410     }
411 
412     /**
413      * Creates an instance of a subclass of this class using default maximum
414      * likelihood method, provided Gaussian sigma and input data array
415      *
416      * @param inputData     Array containing input data to be used for the
417      *                      estimation of the maximum likelihood value.
418      * @param gaussianSigma Gaussian sigma to be set for each sample.
419      * @return A maximum likelihood estimator
420      * @throws IllegalArgumentException Raised if provided Gaussian sigma is
421      *                                  negative or zero.
422      */
423     public static MaximumLikelihoodEstimator create(final double[] inputData, final double gaussianSigma) {
424         return create(inputData, gaussianSigma, DEFAULT_METHOD);
425     }
426 
427     /**
428      * Creates an instance of a subclass of this class using default maximum
429      * likelihood method and Gaussian sigma, and provided input data array
430      *
431      * @param inputData Array containing input data to be used for the
432      *                  estimation of the maximum likelihood value.
433      * @return A maximum likelihood estimator
434      * @throws IllegalArgumentException Raised if provided Gaussian sigma is
435      *                                  negative or zero.
436      */
437     public static MaximumLikelihoodEstimator create(final double[] inputData) {
438         return create(inputData, DEFAULT_GAUSSIAN_SIGMA);
439     }
440 
441     /**
442      * Creates an instance of a subclass of this class based on provided method
443      * and using provided Gaussian sigma, input data array and minimum/maximum
444      * values assumed to be contained in provided array.
445      *
446      * @param minValue      Minimum value assumed to be contained in input data array
447      * @param maxValue      Maximum value assumed to be contained in input data array
448      * @param inputData     Array containing input data to be used for the
449      *                      estimation of the maximum likelihood value.
450      * @param gaussianSigma Gaussian sigma to be set for each sample.
451      * @param method        Method to estimate maximum likelihood value
452      * @return A maximum likelihood estimator
453      * @throws IllegalArgumentException Raised if provided Gaussian sigma is
454      *                                  negative or zero, or if minValue &lt; maxValue.
455      */
456     public static MaximumLikelihoodEstimator create(
457             final double minValue, final double maxValue, final double[] inputData, final double gaussianSigma,
458             final MaximumLikelihoodEstimatorMethod method) {
459         if (method == MaximumLikelihoodEstimatorMethod.HISTOGRAM_MAXIMUM_LIKELIHOOD_ESTIMATOR) {
460             return new HistogramMaximumLikelihoodEstimator(minValue, maxValue, inputData, gaussianSigma,
461                     HistogramMaximumLikelihoodEstimator.DEFAULT_NUMBER_OF_BINS);
462         } else {
463             return new AccurateMaximumLikelihoodEstimator(minValue, maxValue, inputData, gaussianSigma,
464                     AccurateMaximumLikelihoodEstimator.DEFAULT_USE_HISTOGRAM_INITIAL_SOLUTION);
465         }
466     }
467 
468     /**
469      * Creates an instance of a subclass of this class using default maximum
470      * likelihood method and using provided Gaussian sigma, input data array and
471      * minimum/maximum values assumed to be contained in provided array.
472      *
473      * @param minValue      Minimum value assumed to be contained in input data array
474      * @param maxValue      Maximum value assumed to be contained in input data array
475      * @param inputData     Array containing input data to be used for the
476      *                      estimation of the maximum likelihood value.
477      * @param gaussianSigma Gaussian sigma to be set for each sample.
478      * @return A maximum likelihood estimator
479      * @throws IllegalArgumentException Raised if provided Gaussian sigma is
480      *                                  negative or zero, or if minValue &lt; maxValue.
481      */
482     public static MaximumLikelihoodEstimator create(
483             final double minValue, final double maxValue, final double[] inputData, final double gaussianSigma) {
484         return create(minValue, maxValue, inputData, gaussianSigma, DEFAULT_METHOD);
485     }
486 
487     /**
488      * Creates an instance of a subclass of this class using default maximum
489      * likelihood method and default Gaussian sigma, and using provided input
490      * data array and minimum/maximum values assumed to be contained in provided
491      * array.
492      *
493      * @param minValue  Minimum value assumed to be contained in input data array
494      * @param maxValue  Maximum value assumed to be contained in input data array
495      * @param inputData Array containing input data to be used for the
496      *                  estimation of the maximum likelihood value.
497      * @return A maximum likelihood estimator
498      * @throws IllegalArgumentException Raised if provided Gaussian sigma is
499      *                                  negative or zero, or if minValue &lt; maxValue.
500      */
501     public static MaximumLikelihoodEstimator create(
502             final double minValue, final double maxValue, final double[] inputData) {
503         return create(minValue, maxValue, inputData, DEFAULT_GAUSSIAN_SIGMA);
504     }
505 
506     /**
507      * Internal method to compute minimum and maximum values of provided input
508      * data array.
509      */
510     protected void computeMinMaxValues() {
511         if (!isInputDataAvailable()) {
512             return;
513         }
514 
515         minValue = Integer.MAX_VALUE;
516         maxValue = -Integer.MAX_VALUE;
517 
518         double value;
519         for (final var data : inputData) {
520             value = data;
521             if (value < minValue) {
522                 minValue = value;
523             }
524             if (value > maxValue) {
525                 maxValue = value;
526             }
527         }
528 
529         areMinMaxAvailable = true;
530     }
531 
532     /**
533      * Method to set internally minimum and maximum value found in input data
534      * array.
535      *
536      * @param minValue Minimum value in input data array.
537      * @param maxValue Maximum value in input data array.
538      * @throws IllegalArgumentException Exception raised if minValue &lt; maxValue.
539      */
540     private void internalSetMinMaxValues(final double minValue, final double maxValue) {
541         if (minValue > maxValue) {
542             throw new IllegalArgumentException();
543         }
544 
545         this.minValue = minValue;
546         this.maxValue = maxValue;
547         areMinMaxAvailable = true;
548     }
549 
550     /**
551      * Internal method to set Gaussian sigma to be used on each sample when
552      * aggregating Gaussian functions centered at each input data sample value.
553      *
554      * @param gaussianSigma Gaussian sigma to be used on each sample.
555      * @throws IllegalArgumentException Exception raised if provided Gaussian
556      *                                  sigma is negative or zero.
557      */
558     private void internalSetGaussianSigma(final double gaussianSigma) {
559         if (gaussianSigma <= MIN_GAUSSIAN_SIGMA) {
560             throw new IllegalArgumentException();
561         }
562         this.gaussianSigma = gaussianSigma;
563     }
564 }