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 }