1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * https://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * This is not the original file distributed by the Apache Software Foundation 20 * It has been modified by the Hipparchus project 21 */ 22 23 package org.hipparchus.optim.univariate; 24 25 import java.util.Arrays; 26 import java.util.Comparator; 27 28 import org.hipparchus.exception.LocalizedCoreFormats; 29 import org.hipparchus.exception.MathIllegalStateException; 30 import org.hipparchus.exception.MathIllegalArgumentException; 31 import org.hipparchus.optim.MaxEval; 32 import org.hipparchus.optim.OptimizationData; 33 import org.hipparchus.optim.nonlinear.scalar.GoalType; 34 import org.hipparchus.random.RandomGenerator; 35 36 /** 37 * Special implementation of the {@link UnivariateOptimizer} interface 38 * adding multi-start features to an existing optimizer. 39 * <br> 40 * This class wraps an optimizer in order to use it several times in 41 * turn with different starting points (trying to avoid being trapped 42 * in a local extremum when looking for a global one). 43 * 44 */ 45 public class MultiStartUnivariateOptimizer 46 extends UnivariateOptimizer { 47 /** Underlying classical optimizer. */ 48 private final UnivariateOptimizer optimizer; 49 /** Number of evaluations already performed for all starts. */ 50 private int totalEvaluations; 51 /** Number of starts to go. */ 52 private final int starts; 53 /** Random generator for multi-start. */ 54 private final RandomGenerator generator; 55 /** Found optima. */ 56 private UnivariatePointValuePair[] optima; 57 /** Optimization data. */ 58 private OptimizationData[] optimData; 59 /** 60 * Location in {@link #optimData} where the updated maximum 61 * number of evaluations will be stored. 62 */ 63 private int maxEvalIndex = -1; 64 /** 65 * Location in {@link #optimData} where the updated start value 66 * will be stored. 67 */ 68 private int searchIntervalIndex = -1; 69 70 /** 71 * Create a multi-start optimizer from a single-start optimizer. 72 * 73 * @param optimizer Single-start optimizer to wrap. 74 * @param starts Number of starts to perform. If {@code starts == 1}, 75 * the {@code optimize} methods will return the same solution as 76 * {@code optimizer} would. 77 * @param generator Random generator to use for restarts. 78 * @throws MathIllegalArgumentException if {@code starts < 1}. 79 */ 80 public MultiStartUnivariateOptimizer(final UnivariateOptimizer optimizer, 81 final int starts, 82 final RandomGenerator generator) { 83 super(optimizer.getConvergenceChecker()); 84 85 if (starts < 1) { 86 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, 87 starts, 1); 88 } 89 90 this.optimizer = optimizer; 91 this.starts = starts; 92 this.generator = generator; 93 } 94 95 /** {@inheritDoc} */ 96 @Override 97 public int getEvaluations() { 98 return totalEvaluations; 99 } 100 101 /** 102 * Gets all the optima found during the last call to {@code optimize}. 103 * The optimizer stores all the optima found during a set of 104 * restarts. The {@code optimize} method returns the best point only. 105 * This method returns all the points found at the end of each starts, 106 * including the best one already returned by the {@code optimize} method. 107 * <br> 108 * The returned array as one element for each start as specified 109 * in the constructor. It is ordered with the results from the 110 * runs that did converge first, sorted from best to worst 111 * objective value (i.e in ascending order if minimizing and in 112 * descending order if maximizing), followed by {@code null} elements 113 * corresponding to the runs that did not converge. This means all 114 * elements will be {@code null} if the {@code optimize} method did throw 115 * an exception. 116 * This also means that if the first element is not {@code null}, it is 117 * the best point found across all starts. 118 * 119 * @return an array containing the optima. 120 * @throws MathIllegalStateException if {@link #optimize(OptimizationData[]) 121 * optimize} has not been called. 122 */ 123 public UnivariatePointValuePair[] getOptima() { 124 if (optima == null) { 125 throw new MathIllegalStateException(LocalizedCoreFormats.NO_OPTIMUM_COMPUTED_YET); 126 } 127 return optima.clone(); 128 } 129 130 /** 131 * {@inheritDoc} 132 * 133 * @throws MathIllegalStateException if {@code optData} does not contain an 134 * instance of {@link MaxEval} or {@link SearchInterval}. 135 */ 136 @Override 137 public UnivariatePointValuePair optimize(OptimizationData... optData) { 138 // Store arguments in order to pass them to the internal optimizer. 139 optimData = optData.clone(); 140 // Set up base class and perform computations. 141 return super.optimize(optData); 142 } 143 144 /** {@inheritDoc} */ 145 @Override 146 protected UnivariatePointValuePair doOptimize() { 147 // Remove all instances of "MaxEval" and "SearchInterval" from the 148 // array that will be passed to the internal optimizer. 149 // The former is to enforce smaller numbers of allowed evaluations 150 // (according to how many have been used up already), and the latter 151 // to impose a different start value for each start. 152 for (int i = 0; i < optimData.length; i++) { 153 if (optimData[i] instanceof MaxEval) { 154 optimData[i] = null; 155 maxEvalIndex = i; 156 continue; 157 } 158 if (optimData[i] instanceof SearchInterval) { 159 optimData[i] = null; 160 searchIntervalIndex = i; 161 continue; 162 } 163 } 164 if (maxEvalIndex == -1) { 165 throw new MathIllegalStateException(LocalizedCoreFormats.ILLEGAL_STATE); 166 } 167 if (searchIntervalIndex == -1) { 168 throw new MathIllegalStateException(LocalizedCoreFormats.ILLEGAL_STATE); 169 } 170 171 RuntimeException lastException = null; 172 optima = new UnivariatePointValuePair[starts]; 173 totalEvaluations = 0; 174 175 final int maxEval = getMaxEvaluations(); 176 final double min = getMin(); 177 final double max = getMax(); 178 final double startValue = getStartValue(); 179 180 // Multi-start loop. 181 for (int i = 0; i < starts; i++) { 182 // CHECKSTYLE: stop IllegalCatch 183 try { 184 // Decrease number of allowed evaluations. 185 optimData[maxEvalIndex] = new MaxEval(maxEval - totalEvaluations); 186 // New start value. 187 final double s = (i == 0) ? 188 startValue : 189 min + generator.nextDouble() * (max - min); 190 optimData[searchIntervalIndex] = new SearchInterval(min, max, s); 191 // Optimize. 192 optima[i] = optimizer.optimize(optimData); 193 } catch (RuntimeException mue) { // NOPMD - caching a RuntimeException is intentional here, it will be rethrown later 194 lastException = mue; 195 optima[i] = null; 196 } 197 // CHECKSTYLE: resume IllegalCatch 198 199 totalEvaluations += optimizer.getEvaluations(); 200 } 201 202 sortPairs(getGoalType()); 203 204 if (optima[0] == null) { 205 throw lastException; // Cannot be null if starts >= 1. 206 } 207 208 // Return the point with the best objective function value. 209 return optima[0]; 210 } 211 212 /** 213 * Sort the optima from best to worst, followed by {@code null} elements. 214 * 215 * @param goal Goal type. 216 */ 217 private void sortPairs(final GoalType goal) { 218 Arrays.sort(optima, new Comparator<UnivariatePointValuePair>() { 219 /** {@inheritDoc} */ 220 @Override 221 public int compare(final UnivariatePointValuePair o1, 222 final UnivariatePointValuePair o2) { 223 if (o1 == null) { 224 return (o2 == null) ? 0 : 1; 225 } else if (o2 == null) { 226 return -1; 227 } 228 final double v1 = o1.getValue(); 229 final double v2 = o2.getValue(); 230 return (goal == GoalType.MINIMIZE) ? 231 Double.compare(v1, v2) : Double.compare(v2, v1); 232 } 233 }); 234 } 235 }