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 import com.irurueta.numerical.optimization.BracketedSingleOptimizer; 19 import com.irurueta.numerical.optimization.BrentSingleOptimizer; 20 21 /** 22 * Class to estimate the most likely value from a series of samples assumed to 23 * be normally distributed. 24 * This implementation will first use an internal HistogramMaximumLikelihood 25 * to estimate the most likely value using a histogram, and then it will 26 * refine the solution by using a BrentSingleOptimizer in order to find the 27 * maximum of the probability distribution function assuming that such function 28 * is computed by aggregating small Gaussians (of size gaussianSigma) centered 29 * at the location of each sample. 30 */ 31 public class AccurateMaximumLikelihoodEstimator extends MaximumLikelihoodEstimator { 32 33 /** 34 * Boolean indicating if an initial solution should be obtained first by 35 * using the Histogram method. It is suggested to always enable this option. 36 */ 37 public static final boolean DEFAULT_USE_HISTOGRAM_INITIAL_SOLUTION = true; 38 39 /** 40 * Value to be considered as the machine precision. 41 */ 42 public static final double EPS = 1e-9; 43 44 /** 45 * Boolean that indicates that an initial coarse solution will be computed 46 * first by using an internal HistogramMaximumLikelihoodEstimator in order 47 * to initialize the internal BrentSingleOptimizer to obtain a more accurate 48 * solution. 49 */ 50 private boolean useHistogramInitialSolution; 51 52 /** 53 * Internal maximum likelihood estimator based on the Histogram method. 54 */ 55 private HistogramMaximumLikelihoodEstimator internalEstimator; 56 57 /** 58 * Internal optimizer to find the true maximum of the probability 59 * distribution function. Because a BrentSingleOptimizer is only guaranteed 60 * to obtain local minima/maxima, it is preferred to start the optimizer 61 * near the true solution to be found, for that reason it is suggested to 62 * always use the Histogram initial solution as coarse approximation to 63 * start the optimizer and get a more accurate solution. 64 */ 65 private BrentSingleOptimizer optimizer; 66 67 /** 68 * Constructor. 69 * 70 * @param gaussianSigma Gaussian sigma to be used on each sample. 71 * @param useHistogramInitialSolution Boolean indicating whether an internal 72 * HistogramMaximumLikelihoodEstimator will be used to obtain a coarse 73 * initial solution to initialize the BrentSingleOptimizer. It is suggested 74 * to set this value always to true. 75 * @throws IllegalArgumentException Raised if provided Gaussian sigma is 76 * negative or zero. 77 */ 78 public AccurateMaximumLikelihoodEstimator(final double gaussianSigma, final boolean useHistogramInitialSolution) { 79 super(gaussianSigma); 80 this.useHistogramInitialSolution = useHistogramInitialSolution; 81 internalEstimator = null; 82 optimizer = null; 83 } 84 85 /** 86 * Empty constructor. 87 */ 88 public AccurateMaximumLikelihoodEstimator() { 89 super(); 90 this.useHistogramInitialSolution = DEFAULT_USE_HISTOGRAM_INITIAL_SOLUTION; 91 internalEstimator = null; 92 optimizer = null; 93 } 94 95 /** 96 * Constructor 97 * 98 * @param inputData Array containing input data where most likely value must 99 * be estimated from. 100 * @param gaussianSigma Gaussian sigma to be used on each sample. 101 * @param useHistogramInitialSolution Boolean indicating whether an internal 102 * HistogramMaximumLikelihoodEstimator will be used to obtain a coarse 103 * initial solution to initialize the BrentSingleOptimizer. It is suggested 104 * to set this value always to true. 105 * @throws IllegalArgumentException Raised if provided Gaussian sigma is 106 * negative or zero. 107 */ 108 public AccurateMaximumLikelihoodEstimator( 109 final double[] inputData, final double gaussianSigma, final boolean useHistogramInitialSolution) { 110 super(inputData, gaussianSigma); 111 this.useHistogramInitialSolution = useHistogramInitialSolution; 112 internalEstimator = null; 113 optimizer = null; 114 } 115 116 /** 117 * Constructor. 118 * 119 * @param minValue Minimum value assumed to be contained within input data 120 * array. 121 * @param maxValue Maximum value assumed to be contained within input data 122 * array. 123 * @param inputData Array containing input data where most likely value must 124 * be estimated from. 125 * @param gaussianSigma Gaussian sigma to be used on each sample. 126 * @param useHistogramInitialSolution Boolean indicating whether an internal 127 * HistogramMaximumLikelihoodEstimator will be used to obtain a coarse 128 * initial solution to initialize the BrentSingleOptimizer. It is suggested 129 * to set this value always to true. 130 * @throws IllegalArgumentException Raised if provided Gaussian sigma is 131 * negative or zero, or if minValue < maxValue. 132 */ 133 public AccurateMaximumLikelihoodEstimator( 134 final double minValue, final double maxValue, final double[] inputData, final double gaussianSigma, 135 final boolean useHistogramInitialSolution) { 136 super(minValue, maxValue, inputData, gaussianSigma); 137 this.useHistogramInitialSolution = useHistogramInitialSolution; 138 internalEstimator = null; 139 optimizer = null; 140 } 141 142 /** 143 * Returns method to be used for maximum likelihood estimation, which for 144 * this class is MaximumLikelihoodEstimatorMethod. 145 * ACCURATE_MAXIMUM_LIKELIHOOD_ESTIMATOR. 146 * 147 * @return Method for maximum likelihood estimation. 148 */ 149 @Override 150 public MaximumLikelihoodEstimatorMethod getMethod() { 151 return MaximumLikelihoodEstimatorMethod.ACCURATE_MAXIMUM_LIKELIHOOD_ESTIMATOR; 152 } 153 154 /** 155 * Returns boolean that indicates that an initial coarse solution will be 156 * computed first by using an internal HistogramMaximumLikelihoodEstimator 157 * in order to initialize the internal BrentSingleOptimizer to obtain a more 158 * accurate solution. 159 * 160 * @return True if an initial coarse solution if found by the Histogram 161 * method, false otherwise. 162 */ 163 public boolean isHistogramInitialSolutionUsed() { 164 return useHistogramInitialSolution; 165 } 166 167 /** 168 * Sets boolean that indicates that an initial coarse solution will be 169 * computed first by using an internal HistogramMaximumLikelihoodEstimator 170 * in order to initialize the internal BrentSingleOptimizer to obtain a more 171 * accurate solution. 172 * 173 * @param used True if an initial coarse solution will be found by the 174 * Histogram method, false otherwise. 175 * @throws LockedException Exception raised if this instance is locked. 176 * This method can only be executed when computations finish and this 177 * instance becomes unlocked. 178 */ 179 public void setHistogramInitialSolutionUsed(final boolean used) throws LockedException { 180 if (isLocked()) { 181 throw new LockedException(); 182 } 183 useHistogramInitialSolution = used; 184 } 185 186 /** 187 * Starts the estimation of the most likely value contained within provided 188 * input data array. 189 * 190 * @return The most likely value. 191 * @throws LockedException Exception raised if this instance is locked. 192 * This method can only be executed when computations finish and this 193 * instance becomes unlocked. 194 * @throws NotReadyException Exception raised if this instance is not yet 195 * ready. 196 * @see #isReady() 197 */ 198 @Override 199 public double estimate() throws LockedException, NotReadyException { 200 if (isLocked()) { 201 throw new LockedException(); 202 } 203 if (!isReady()) { 204 throw new NotReadyException(); 205 } 206 207 locked = true; 208 209 final double minEvalPoint; 210 final double middleEvalPoint; 211 final double maxEvalPoint; 212 213 if (useHistogramInitialSolution) { 214 if (internalEstimator == null) { 215 internalEstimator = new HistogramMaximumLikelihoodEstimator(); 216 } 217 internalEstimator.setInputData(inputData); 218 internalEstimator.setGaussianSigma(gaussianSigma); 219 internalEstimator.computeMinMaxValues(); 220 221 middleEvalPoint = internalEstimator.estimate(); 222 223 var localMinValue = 0.0; 224 var localMaxValue = 0.0; 225 226 try { 227 localMinValue = internalEstimator.getMinValue(); 228 localMaxValue = internalEstimator.getMaxValue(); 229 } catch (final NotAvailableException ignore) { 230 // never happens 231 } 232 233 final var numberOfBins = internalEstimator.getNumberOfBins(); 234 235 final var delta = (localMaxValue - localMinValue) / (numberOfBins - 1); 236 237 // pick two values around initial coarse solution 238 minEvalPoint = middleEvalPoint - delta; 239 maxEvalPoint = middleEvalPoint + delta; 240 241 if (!areMinMaxAvailable) { 242 this.minValue = localMinValue; 243 this.maxValue = localMaxValue; 244 areMinMaxAvailable = true; 245 } 246 } else { 247 if (!areMinMaxAvailable) { 248 computeMinMaxValues(); 249 } 250 251 // use min/max values as a bracket to obtain optimal solution 252 minEvalPoint = minValue; 253 maxEvalPoint = maxValue; 254 middleEvalPoint = (minValue + maxValue) * 0.5; 255 } 256 257 if ((maxValue - minValue) < EPS) { 258 // min-max limits are almost equal, so we return it as the solution 259 locked = false; 260 return middleEvalPoint; 261 } 262 263 double solution; 264 try { 265 // Use an optimizer to find maximum value on histogram (PDF) 266 if (optimizer == null) { 267 optimizer = new BrentSingleOptimizer(new EvaluatorListener(), 268 BracketedSingleOptimizer.DEFAULT_MIN_EVAL_POINT, 269 BracketedSingleOptimizer.DEFAULT_MIDDLE_EVAL_POINT, 270 BracketedSingleOptimizer.DEFAULT_MAX_EVAL_POINT, 271 BrentSingleOptimizer.DEFAULT_TOLERANCE); 272 } 273 274 optimizer.setBracket(minEvalPoint, middleEvalPoint, maxEvalPoint); 275 optimizer.minimize(); 276 solution = optimizer.getResult(); 277 } catch (final Exception ignore) { 278 // if optimization fails, pick coarse solution if available 279 if (useHistogramInitialSolution) { 280 solution = middleEvalPoint; 281 } else { 282 // if coarse solution is not available, then compute it 283 internalEstimator.setInputData(inputData); 284 internalEstimator.setGaussianSigma(gaussianSigma); 285 internalEstimator.computeMinMaxValues(); 286 solution = internalEstimator.estimate(); 287 } 288 } 289 290 locked = false; 291 return solution; 292 } 293 294 /** 295 * Internal class used by the BrentSingleOptimizer in order to evaluate 296 * the aggregation of Gaussians for all the samples in input data array with 297 * a high degree of precision. 298 */ 299 private class EvaluatorListener implements SingleDimensionFunctionEvaluatorListener { 300 301 302 /** 303 * Evaluates the aggregation of Gaussians for all the samples in input 304 * data array, by assuming that each sample has an associated small 305 * Gaussian centered at the sample value and with a small sigma value. 306 * The aggregation of Gaussians will generate the averaged PDF function 307 * of all the input values. 308 * 309 * @param point Point where the aggregation of Gaussians will be 310 * evaluated 311 * @return The value of the aggregation of samples at provided point. 312 */ 313 @Override 314 public double evaluate(final double point) { 315 double out = 0.0; 316 double x; 317 for (final var data : inputData) { 318 x = point - data; 319 out += Math.exp(-x * x / (2.0 * gaussianSigma * gaussianSigma)) 320 / (Math.sqrt(2.0 * Math.PI) * gaussianSigma); 321 } 322 323 // negate value because optimizers always attempt to 324 // minimize function 325 return -out; 326 } 327 328 } 329 }