MATH-1622: Simulated annealing entails at least one additional search.
Also ensure that the "best list" contains at least two points.
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/SimplexOptimizer.java b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/SimplexOptimizer.java
index 0373ae4..7140907 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/SimplexOptimizer.java
+++ b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/optim/nonlinear/scalar/noderiv/SimplexOptimizer.java
@@ -29,7 +29,7 @@
import org.apache.commons.math4.legacy.core.MathArrays;
import org.apache.commons.math4.legacy.analysis.MultivariateFunction;
import org.apache.commons.math4.legacy.exception.MathUnsupportedOperationException;
-import org.apache.commons.math4.legacy.exception.ConvergenceException;
+import org.apache.commons.math4.legacy.exception.MathInternalError;
import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
import org.apache.commons.math4.legacy.optim.ConvergenceChecker;
import org.apache.commons.math4.legacy.optim.OptimizationData;
@@ -90,9 +90,12 @@
* <p>
* Whenever {@link SimulatedAnnealing simulated annealing (SA)} is activated,
* and the SA phase has completed, convergence has probably not been reached
- * yet: In such cases, the {@link PopulationSize} argument to method
- * {@link #optimize(OptimizationData[]) optimize} will trigger an additional
- * "best list" search.
+ * yet; whenever it's the case, an additional (non-SA) search will be performed
+ * (using the current best simplex point as a start point).
+ * <p>
+ * Additional "best list" searches can be requested through setting the
+ * {@link PopulationSize} argument of the {@link #optimize(OptimizationData[])
+ * optimize} method.
*
* <p>
* This implementation does not directly support constrained optimization
@@ -113,8 +116,10 @@
private Simplex initialSimplex;
/** Simulated annealing setup (optional). */
private SimulatedAnnealing simulatedAnnealing = null;
- /** Number of additional optimizations (optional). */
- private int bestListSize = 0;
+ /** User-defined number of additional optimizations (optional). */
+ private int populationSize = 0;
+ /** Actual number of additional optimizations. */
+ private int additionalSearch = 0;
/** Callbacks. */
private final List<Observer> callbacks = new CopyOnWriteArrayList<>();
@@ -244,13 +249,18 @@
comparator);
}
- if (bestListSize != 0) {
+ if (additionalSearch != 0) {
+ // In "bestList", we must keep track of at least two points
+ // in order to be able to compute the new initial simplex for
+ // the additional search.
+ final int max = Math.max(additionalSearch, 2);
+
// Store best points.
for (int i = 0; i < currentSimplex.getSize(); i++) {
keepIfBetter(currentSimplex.get(i),
comparator,
bestList,
- bestListSize);
+ max);
}
}
@@ -258,19 +268,20 @@
}
// No convergence.
- if (bestList.isEmpty()) {
- // Bail out.
- throw new ConvergenceException();
- } else {
+
+ if (additionalSearch > 0) {
// Additional optimizations.
// Reference to counter in the "main" search in order to retrieve
// the total number of evaluations in the "best list" search.
final IntSupplier evalCount = () -> getEvaluations();
+
return bestListSearch(evalFunc,
comparator,
bestList,
evalCount);
}
+
+ throw new MathInternalError(); // Should never happen.
}
/**
@@ -301,7 +312,7 @@
} else if (data instanceof SimulatedAnnealing) {
simulatedAnnealing = (SimulatedAnnealing) data;
} else if (data instanceof PopulationSize) {
- bestListSize = ((PopulationSize) data).getPopulationSize();
+ populationSize = ((PopulationSize) data).getPopulationSize();
}
}
}
@@ -335,6 +346,7 @@
* {@link #optimize(OptimizationData[]) optimize} method.
* @throws NullPointerException if no initial simplex or no transform rule
* was passed to the {@link #optimize(OptimizationData[]) optimize} method.
+ * @throws IllegalArgumentException if {@link #populationSize} is negative.
*/
private void checkParameters() {
Objects.requireNonNull(updateRule, "Update rule");
@@ -344,6 +356,14 @@
getUpperBound() != null) {
throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT);
}
+
+ if (populationSize < 0) {
+ throw new IllegalArgumentException("Population size");
+ }
+
+ additionalSearch = simulatedAnnealing == null ?
+ Math.max(0, populationSize) :
+ Math.max(1, populationSize);
}
/**
@@ -441,22 +461,24 @@
*
* @param evalFunc Objective function.
* @param comp Fitness comparator.
- * @param starts Starting points.
+ * @param bestList Best points encountered during the "main" search.
+ * List is assumed to be ordered from best to worst.
* @param evalCount Evaluation counter.
* @return the optimum.
*/
private PointValuePair bestListSearch(MultivariateFunction evalFunc,
Comparator<PointValuePair> comp,
- List<PointValuePair> starts,
+ List<PointValuePair> bestList,
IntSupplier evalCount) {
- PointValuePair best = starts.get(0); // Overall best result.
+ PointValuePair best = bestList.get(0); // Overall best result.
// Additional local optimizations using each of the best
// points visited during the main search.
- for (PointValuePair p : starts) {
+ for (int i = 0; i < additionalSearch; i++) {
+ final PointValuePair start = bestList.get(i);
// Find shortest distance to the other points.
- final double dist = shortestDistance(p, starts);
- final double[] init = p.getPoint();
+ final double dist = shortestDistance(start, bestList);
+ final double[] init = start.getPoint();
// Create smaller initial simplex.
final Simplex simplex = Simplex.equalSidesAlongAxes(init.length,
SIMPLEX_SIDE_RATIO * dist);