private static MultiExtremum FindGlobalExtremum(Func <IReadOnlyList <double>, double> function, IReadOnlyList <Interval> volume, MultiExtremumSettings settings, bool negate) { if (function == null) { throw new ArgumentNullException(nameof(function)); } if (volume == null) { throw new ArgumentNullException(nameof(volume)); } MultiFunctor f = new MultiFunctor(function, negate); DifferentialEvolutionSettings deSettings = GetDefaultSettings(settings, volume.Count); MultiExtremum extremum = FindGlobalExtremum(f, volume, deSettings); return(extremum); }
// This method is due to Powell (http://en.wikipedia.org/wiki/Michael_J._D._Powell), but it is not what // is usually called Powell's Method (http://en.wikipedia.org/wiki/Powell%27s_method); Powell // developed that method in the 1960s, it was included in Numerical Recipes and is very popular. // This is a model trust algorithm developed by Powell in the 2000s. It typically uses many // fewer function evaluations, but does more intensive calculations between each evaluation. // This is basically the UOBYQA variant of Powell's new methods. It maintains a quadratic model // that interpolates between (d + 1) (d + 2) / 2 points. The model is trusted // within a given radius. At each step, it moves to the minimum of the model (or the boundary of // the trust region in that direction) and evaluates the function. The new value is incorporated // into the model and the trust region expanded or contracted depending on how accurate its // prediction of the function value was. // Papers on these methods are collected at http://mat.uc.pt/~zhang/software.html#powell_software. // The UOBYQA paper is here: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.28.1756. // The NEWUOA paper is here: http://www.damtp.cam.ac.uk/user/na/NA_papers/NA2004_08.pdf. // The CONDOR system (http://www.applied-mathematics.net/optimization/CONDORdownload.html) is based on these same ideas. // The thesis of CONDOR's author (http://www.applied-mathematics.net/mythesis/index.html) was also helpful. // It should be very easy to extend this method to constrained optimization, either by incorporating the bounds into // the step limits or by mapping hyper-space into a hyper-cube. private static MultiExtremum FindMinimum_ModelTrust(MultiFunctor f, IReadOnlyList <double> x, double s, MultiExtremumSettings settings) { // Construct an initial model. QuadraticInterpolationModel model = QuadraticInterpolationModel.Construct(f, x, s); double trustRadius = s; while (f.EvaluationCount < settings.EvaluationBudget) { // Find the minimum point of the model within the trust radius double[] z = model.FindMinimum(trustRadius); double expectedValue = model.Evaluate(z); double deltaExpected = model.MinimumValue - expectedValue; // Evaluate the function at the suggested minimum double[] point = model.ConvertPoint(z); double value = f.Evaluate(point); double delta = model.MinimumValue - value; double tol = settings.ComputePrecision(Math.Min(model.MinimumValue, value)); // Note value can be way off, so use better of old best and new value to compute tol. // When we didn't do this before, we got value = infinity, so tol = infinity, and thus terminated! if (delta > 0.0 && settings.Listener != null) { MultiExtremum report = new MultiExtremum(f.EvaluationCount, settings, point, value, Math.Max(Math.Abs(delta), 0.75 * tol), model.GetHessian()); settings.Listener(report); } // To terminate, we demand: a reduction, that the reduction be small, that the reduction be in line with // its expected value, that we have not run up against the trust boundary, and that the gradient is small. // I had wanted to demand delta > 0, but we run into some cases where delta keeps being very slightly // negative, typically orders of magnitude less than tol, causing the trust radius to shrink in an // endless cycle that causes our approximation to ultimately go sour, even though terminating on the original // very slightly negative delta would have produced an accurate estimate. So we tolerate this case for now. if ((delta <= tol) && (-0.25 * tol <= delta)) { // We demand that the model be decent, i.e. that the expected delta was within tol of the measured delta. if (Math.Abs(delta - deltaExpected) <= tol) { // We demand that the step not just be small because it ran up against the trust radius. // If it ran up against the trust radius, there is probably more to be hand by continuing. double zm = Blas1.dNrm2(z, 0, 1, z.Length); if (zm < trustRadius) { // Finally, we demand that the gradient be small. You might think this was obvious since // z was small, but if the Hessian is not positive definite // the interplay of the Hessian and the gradient can produce a small z even if the model looks nothing like a quadratic minimum. double gm = Blas1.dNrm2(model.GetGradient(), 0, 1, z.Length); if (gm * zm <= tol) { if (f.IsNegated) { value = -value; } return(new MultiExtremum(f.EvaluationCount, settings, point, value, Math.Max(Math.Abs(delta), 0.75 * tol), model.GetHessian())); } } } } // There are now three decisions to be made: // 1. How to change the trust radius // 2. Whether to accept the new point // 3. Which existing point to replace // If the actual change was very far from the expected change, reduce the trust radius. // If the expected change did a good job of predicting the actual change, increase the trust radius. if ((delta < 0.25 * deltaExpected) /*|| (8.0 * deltaExpected < delta)*/) { trustRadius = 0.5 * trustRadius; } else if ((0.75 * deltaExpected <= delta) /*&& (delta <= 2.0 * deltaExpected)*/) { trustRadius = 2.0 * trustRadius; } // It appears that the limits on delta being too large don't help, and even hurt if made too stringent. // Replace an old point with the new point. int iMax = 0; double fMax = model.values[0]; int iBad = 0; double fBad = model.ComputeBadness(0, z, point, value); for (int i = 1; i < model.values.Length; i++) { if (model.values[i] > fMax) { iMax = i; fMax = model.values[i]; } double bad = model.ComputeBadness(i, z, point, value); if (bad > fBad) { iBad = i; fBad = bad; } } // Use the new point as long as it is better than our worst existing point. if (value < fMax) { Debug.Assert(!Double.IsPositiveInfinity(value) && !Double.IsNaN(value)); model.ReplacePoint(iBad, point, z, value); } // There is some question about how best to choose which point to replace. // The largest value? The furthest away? The one closest to new min? } throw new NonconvergenceException(); }
// Differential evolution is a global optimization algorithm over continuous inputs that is adapted from genetic algorithms for finite inputs. // The idea is to maintain a population of input vectors ("agents") and to vary that population over cycles ("generations") according to rules that incorporate // random mutation but on average tend to bring them closer to optima ("fitter"). private static MultiExtremum FindGlobalExtremum(MultiFunctor f, IReadOnlyList <Interval> volume, DifferentialEvolutionSettings settings) { int d = volume.Count; // Choose a number of agents that increases with dimension and required precision. int m = settings.Population; Debug.WriteLine("d={0} m={1}", d, m); Random rng = new Random(3); // Start with random points in the allowed region. double[][] points = new double[m][]; double[] values = new double[m]; for (int i = 0; i < m; i++) { points[i] = new double[d]; for (int j = 0; j < d; j++) { points[i][j] = volume[j].LeftEndpoint + rng.NextDouble() * volume[j].Width; } values[i] = f.Evaluate(points[i]); } while (f.EvaluationCount < settings.EvaluationBudget) { double[][] newPoints = new double[m][]; double[] newValues = new double[m]; for (int i = 0; i < m; i++) { // Mutation // construct donor vector int a = i; while (a == i) { a = rng.Next(m); } int b = i; while ((b == i) || (b == a)) { b = rng.Next(m); } int c = i; while ((c == i) || (c == b) || (c == a)) { c = rng.Next(m); } double[] donor = new double[d]; for (int j = 0; j < d; j++) { donor[j] = points[a][j] + mutationFactor * (points[b][j] - points[c][j]); if (donor[j] < volume[j].LeftEndpoint) { donor[j] = volume[j].LeftEndpoint; } if (donor[j] > volume[j].RightEndpoint) { donor[j] = volume[j].RightEndpoint; } } // Recombination double[] trial = new double[d]; int k = rng.Next(d); for (int j = 0; j < d; j++) { if ((j == k) || (rng.NextDouble() < settings.CrossoverProbability)) { trial[j] = donor[j]; } else { trial[j] = points[i][j]; } } // Selection double value = f.Evaluate(trial); if (value <= values[i]) { newPoints[i] = trial; newValues[i] = value; } else { newPoints[i] = points[i]; newValues[i] = values[i]; } } points = newPoints; values = newValues; // Check termination criteria int minIndex = -1; double minValue = Double.MaxValue; double maxValue = Double.MinValue; for (int i = 0; i < m; i++) { if (values[i] < minValue) { minValue = values[i]; minIndex = i; } if (values[i] > maxValue) { maxValue = values[i]; } } double range = maxValue - minValue; double tol = settings.ComputePrecision(minValue); if (range <= tol) { MultiExtremum result = new MultiExtremum(f.EvaluationCount, settings, points[minIndex], f.IsNegated ? -values[minIndex] : values[minIndex], Math.Max(range, 0.75 * tol), null); return(result); } else if (settings.Listener != null) { MultiExtremum report = new MultiExtremum(f.EvaluationCount, settings, points[minIndex], f.IsNegated ? -values[minIndex] : values[minIndex], Math.Max(range, 0.75 * tol), null); settings.Listener(report); } } throw new NonconvergenceException(); }
// Differential evoluation is a global optimization algorithm over continuous inputs that is adapted from genetic algorithms for finite inputs. // The idea is maintain a population of input vectors ("agents") and to vary that population over cycles ("generations") according to rules that incorporate // random mutation but on average tend to bring them closer to optima ("fitter"). private static MultiExtremum FindGlobalExtremum(MultiFunctor f, IList<Interval> volume, DifferentialEvolutionSettings settings) { int d = volume.Count; // Choose a number of agents that increases with dimension and required precision. int m = settings.Population; Debug.WriteLine("d={0} m={1}", d, m); Random rng = new Random(3); //Random rng = new Random(1001110000); // Start with random points in the allowed region. double[][] points = new double[m][]; double[] values = new double[m]; for (int i = 0; i < m; i++) { points[i] = new double[d]; for (int j = 0; j < d; j++) { points[i][j] = volume[j].LeftEndpoint + rng.NextDouble() * volume[j].Width; } values[i] = f.Evaluate(points[i]); } while (f.EvaluationCount < settings.EvaluationBudget) { //double mutationFactor = 0.5 + 0.5 * rng.NextDouble(); double[][] newPoints = new double[m][]; double[] newValues = new double[m]; for (int i = 0; i < m; i++) { // Mutation // construct donor vector int a = i; while (a == i) a = rng.Next(m); int b = i; while ((b == i) || (b == a)) b = rng.Next(m); int c = i; while ((c == i) || (c == b) || (c == a)) c = rng.Next(m); double[] donor = new double[d]; for (int j = 0; j < d; j++) { donor[j] = points[a][j] + mutationFactor * (points[b][j] - points[c][j]); if (donor[j] < volume[j].LeftEndpoint) donor[j] = volume[j].LeftEndpoint; if (donor[j] > volume[j].RightEndpoint) donor[j] = volume[j].RightEndpoint; } // Recombination double[] trial = new double[d]; int k = rng.Next(d); for (int j = 0; j < d; j++) { if ((j == k) || (rng.NextDouble() < settings.CrossoverProbability)) { trial[j] = donor[j]; } else { trial[j] = points[i][j]; } } // Selection double value = f.Evaluate(trial); if (value <= values[i]) { newPoints[i] = trial; newValues[i] = value; } else { newPoints[i] = points[i]; newValues[i] = values[i]; } } points = newPoints; values = newValues; // Check termination criteria int minIndex = -1; double minValue = Double.MaxValue; double maxValue = Double.MinValue; for (int i = 0; i < m; i++) { if (values[i] < minValue) { minValue = values[i]; minIndex = i; } if (values[i] > maxValue) maxValue = values[i]; } double range = maxValue - minValue; double tol = settings.ComputePrecision(minValue); if (range <= tol) { MultiExtremum result = new MultiExtremum(f.EvaluationCount, settings, points[minIndex], f.IsNegated ? -values[minIndex] : values[minIndex], Math.Max(range, 0.75 * tol), null); return (result); } //settings.OnUpdate(new MultiExtremum(points[minIndex], values[minIndex], null, f.EvaluationCount)); } throw new NonconvergenceException(); }