public bool Agrees(Extremum extremum) { return ( TestUtilities.IsNearlyEqual(extremum.Value, Value, 10.0 * TestUtilities.TargetPrecision) && TestUtilities.IsNearlyEqual(extremum.Location, Location, 10.0 * Math.Sqrt(TestUtilities.TargetPrecision)) && (Double.IsNaN(Curvature) || TestUtilities.IsNearlyEqual(extremum.Curvature, Curvature, 0.01)) ); }
/// <summary> /// Minimizes a function on a multi-dimensional space in the vicinity of a given point, subject to the given settings. /// </summary> /// <param name="f">The function.</param> /// <param name="x">The starting point for the search.</param> /// <param name="settings">The evaluation settings.</param> /// <returns>The minimum.</returns> /// <exception cref="ArgumentNullException"><paramref name="f"/>, <paramref name="x"/>, or <paramref name="settings"/> is null.</exception> /// <exception cref="NonconvergenceException">The minimum was not found to the required precision within the budgeted number of function evaluations.</exception> internal static SpaceExtremum FindMinimum(Func <double[], double> f, double[] x, EvaluationSettings settings) { if (f == null) { throw new ArgumentNullException("f"); } if (x == null) { throw new ArgumentNullException("x"); } if (settings == null) { throw new ArgumentNullException("settings"); } int d = x.Length; // put the function into a Functor we will use for line searches LineSearchFunctor fo = new LineSearchFunctor(f); // keep track of the (conjugate) minimization directions double[][] Q = new double[d][]; for (int i = 0; i < d; i++) { Q[i] = new double[d]; for (int j = 0; j < d; j++) { Q[i][j] = 0.0; } // pick a step size in each direction that represents a fraction of the input value Q[i][i] = (1.0 / 16.0) * (Math.Abs(x[i]) + (1.0 / 16.0)); } // keep track of the curvature in these directions double[] r = new double[d]; // keep track of the function value double y = f(x); // keep track of the total number of evaluations //int count = 0; bool skip = false; // interate until convergence while (fo.EvaluationCount < settings.EvaluationBudget) { // remember our starting position double[] x0 = new double[d]; Array.Copy(x, x0, d); double y0 = y; // keep track of direction of largest decrease double max_dy = 0.0; int max_i = 0; // now minimize in each direction for (int i = 0; i < d; i++) { // if we placed the net direction in the first slot last time, // we are already at the minimum along that direction, so don't // minimize along it if (skip) { skip = false; continue; } //if ((n > 0) && (i == 0) && (max_i == 0)) continue; //Console.WriteLine("i = {0}", i); //WriteVector(Q[i]); // form a line function //LineFunction f1 = new LineFunction(f, x, Q[i]); fo.Origin = x; fo.Direction = Q[i]; // minimize it Extremum m = FindMinimum(fo, 0.0, y, 1.0, new ExtremumSettings() { EvaluationBudget = settings.EvaluationBudget, AbsolutePrecision = 0.0, RelativePrecision = 0.0 }); //LineExtremum m = FindMinimum(new Func<double,double>(f1.Evaluate), 0.0, y, 1.0); // add to the evaluation count //count += f1.Count; // update the current position x = fo.ComputeLocation(m.Location); //x = f1.Position(m.Location); //WriteVector(x); r[i] = m.Curvature; // keep track of how much the function dropped, and // if this is the direction of largest decrease double dy = y - m.Value; //Console.WriteLine("dy = {0}", dy); if (dy > max_dy) { max_dy = dy; max_i = i; } y = m.Value; } //Console.WriteLine("max_i = {0}, max_dy = {1}", max_i, max_dy); //Console.WriteLine("y0 = {0}, y = {1}", y0, y); // figure out the net direction we have moved double[] dx = new double[d]; for (int i = 0; i < d; i++) { dx[i] = x[i] - x0[i]; } //Console.WriteLine("Finish:"); //WriteVector(x); //Console.WriteLine("Net direction:"); //WriteVector(dx); // check termination criteria // we do this before minimizing in the net direction because if dx=0 it loops forever double Dy = Math.Abs(y0 - y); if ((Dy < settings.AbsolutePrecision) || (2.0 * Dy < (Math.Abs(y) + Math.Abs(y0)) * settings.RelativePrecision)) { SymmetricMatrix A = ComputeCurvature(f, x); return(new SpaceExtremum(x, y, A)); } // attempt a minimization in the net direction fo.Origin = x; fo.Direction = dx; //LineFunction f2 = new LineFunction(f, x, dx); //LineExtremum mm = FindMinimum(new Func<double,double>(f2.Evaluate), 0.0, y, 1.0); Extremum mm = FindMinimum(fo, 0.0, y, 1.0, new ExtremumSettings() { EvaluationBudget = settings.EvaluationBudget, RelativePrecision = 0.0, AbsolutePrecision = 0.0 }); //count += f2.Count; //x = f2.Position(mm.Location); x = fo.ComputeLocation(mm.Location); y = mm.Value; // rotate this direction into the direction set /* * for (int i = 0; i < (d - 1); i++) { * Q[i] = Q[i + 1]; * r[i] = r[i + 1]; * } * Q[d - 1] = dx; * r[d - 1] = mm.Curvature; */ // this is the basic Powell procedure, and it leads to linear dependence // replace the direction of largest decrease with the net direction Q[max_i] = dx; r[max_i] = mm.Curvature; if (max_i == 0) { skip = true; } // this is powell's modification to avoid linear dependence // reset } throw new NonconvergenceException(); }
// Brent's algorithm: use 3-point parabolic interpolation, // switching to golden section if interval does not shrink fast enough // see Richard Brent, "Algorithms for Minimization Without Derivatives" // The bracket is [a, b] and the three lowest points are (u,fu), (v,fv), (w, fw) // Note that a or b may be u, v, or w. private static Extremum FindMinimum( Functor f, double a, double b, double u, double fu, double v, double fv, double w, double fw, ExtremumSettings settings ) { settings = SetExtremumDefaults(settings); double tol = 0.0; double fpp = Double.NaN; while (f.EvaluationCount < settings.EvaluationBudget) { Debug.WriteLine($"n={f.EvaluationCount} tol={tol}"); Debug.WriteLine($"[{a} f({u})={fu} f({v})={fv} f({w})={fw} {b}]"); // While a <= u <= b is guaranteed, a < v, w < b is not guaranteed, since the bracket can sometimes be made tight enough to exclude v or w. // For example, if u < v < w, then we can set b = v, placing w outside the bracket. Debug.Assert(a < b); Debug.Assert((a <= u) && (u <= b)); Debug.Assert((fu <= fv) && (fv <= fw)); if (settings.Listener != null) { Extremum result = new Extremum(u, fu, fpp, a, b, f.EvaluationCount, settings); settings.Listener(result); } // Expected final situation is a<tol><tol>u<tol><tol>b, leaving no point left to evaluate that is not within tol of an existing point. if ((b - a) <= 4.0 * tol) { return(new Extremum(u, fu, fpp, a, b, f.EvaluationCount, settings)); } ParabolicFit(u, fu, v, fv, w, fw, out double x, out fpp); Debug.WriteLine($"parabolic x={x} f''={fpp}"); if (Double.IsNaN(fpp) || (fpp <= 0.0) || (x < a) || (x > b)) { // The parabolic fit didn't work out, so do a golden section reduction instead. // To get the most reduction of the bracket, pick the larger of au and ub. // For self-similarity, pick a point inside it that divides it into two segments in the golden section ratio, // i.e. 0.3820 = \frac{1}{\phi + 1} and 0.6180 = \frac{\phi}{\phi+1}. // Put the smaller segment closer to u, so that x is closer to u, the best minimum so far. double au = u - a; double ub = b - u; if (au > ub) { x = u - au / (AdvancedMath.GoldenRatio + 1.0); } else { x = u + ub / (AdvancedMath.GoldenRatio + 1.0); } Debug.WriteLine($"golden section x={x}"); } // Ensure we don't evaluate within tolerance of an existing point. if (Math.Abs(x - u) < tol) { Debug.WriteLine($"shift from u (x={x})"); x = (x > u) ? u + tol : u - tol; } if ((x - a) < tol) { Debug.WriteLine($"shift from a (x={x})"); x = a + tol; } if ((b - x) < tol) { Debug.WriteLine($"shift from b (x={x})"); x = b - tol; } // Evaluate the function at the new point x. double fx = f.Evaluate(x); Debug.WriteLine($"f({x}) = {fx}"); Debug.WriteLine($"delta={fu-fx}"); // Update a, b and u, v, w based on new point x. if (fx < fu) { // The new point is lower than all the others; this is success // u now becomes a bracket point if (u < x) { a = u; } else { b = u; } // x -> u -> v -> w w = v; fw = fv; v = u; fv = fu; u = x; fu = fx; } else { if (fx == fu) { Debug.WriteLine($"f({x}) = f({u}) = {fx}"); } // x now becomes a bracket point if (x < u) { a = x; } else { b = x; } if (fx < fv) { // The new point is higher than u, but still lower than v and w. // This isn't what we expected, but we have lower points that before. // x -> v -> w w = v; fw = fv; v = x; fv = fx; } else if (fx < fw) { // x -> w w = x; fw = fx; } else { // The new point is higher than all our other points; this is the worst case. // We might still want to replace w with x because // (i) otherwise a parabolic fit will reproduce the same x and // (ii) w is quite likely far outside the new bracket and not telling us much about the behavior near u // But, tests with Rosenbrock function indicate this increases evaluation count, so hold off on this idea for now. // w = x; fw = fx; Debug.WriteLine("bad point"); } } if ((settings.RelativePrecision > 0.0 || settings.AbsolutePrecision > 0.0)) { // If the user has specified a tolerance, use it. tol = Math.Max(Math.Abs(u) * settings.RelativePrecision, settings.AbsolutePrecision); } else { // Otherwise, try to get the tolerance from the curvature. if (fpp > 0.0) { tol = Math.Sqrt(2.0 * Global.Accuracy * (Math.Abs(fu) + Global.Accuracy) / fpp); } else { // But if we don't have a useable curvature either, just wing it. if (tol == 0.0) { tol = Math.Sqrt(Global.Accuracy); } } } } throw new NonconvergenceException(); }