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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 }