private double FindMinimumWithDerivative( FuncWithDerivative f, double a, double b, double u, double fu, double fpu, double v, double fv, double fpv, EvaluationSettings settings ) { double tol = 0.0; int count = 0; while (count < settings.EvaluationBudget) { Console.WriteLine("n = {0}, tol = {1}", count, tol); Console.WriteLine("[{0} f({1})={2}({3}) f({4})={5}({6}) {7}]", a, u, fu, fpu, v, fv, fpv, b); // a, b bracket minimum a < u, v < b and f(u), f(v) <= f(a), f(b) Debug.Assert(a < b); Debug.Assert((a <= u) && (u <= b)); //Debug.Assert((a <= v) && (v <= b)); Debug.Assert(fu <= fv); if ((b - a) <= 4.0 * tol) return (u); // compute the minimum of the interpolating Hermite cubic double x, fpp; CubicHermiteMinimum(u, fu, fpu, v, fv, fpv, out x, out fpp); Console.WriteLine("cubic x = {0}, fpp = {1}", x, fpp); // if the cubic had no minimum, or the minimum lies outside our bounds, fall back to bisection if (Double.IsNaN(x) || (x <= a) || (x >= b)) { // the derivative tells us which side to choose if (fpu > 0.0) { x = (a + u) / 2.0; } else { x = (u + b) / 2.0; } Console.WriteLine("bisection x = {0}", x); } // ensure we don't evaluate within tolerance of an existing point if (Math.Abs(x - u) < tol) { Console.WriteLine("shift from u (x={0})", x); x = (x > u) ? u + tol : u - tol; } if ((x - a) < tol) { Console.WriteLine("shift from a (x={0})", x); x = a + tol; } if ((b - x) < tol) { Console.WriteLine("shift from b (x={0})", x); x = b - tol; } // evaluate the function plus derivative at the predicted minimum double fx, fpx; f(x, out fx, out fpx); count++; Console.WriteLine("f({0}) = {1}({2})", x, fx, fpx); // check if we have converged double df = fu - fx; Console.WriteLine("df={0}", df); if ((Math.Abs(df) < settings.AbsolutePrecision) || (2.0 * Math.Abs(df) < settings.RelativePrecision * (Math.Abs(fu) + Math.Abs(fx)))) { Console.WriteLine("count = {0}", count); return (x); } if (fx < fu) { // x is the new lowest point: f(x) < f(u) < f(v) // this is the expected outcome // move the bracket if (x < u) { b = u; } else { a = u; } // x -> u -> v v = u; fv = fu; fpv = fpu; u = x; fu = fx; fpu = fpx; } else { // move the bracket if (x < u) { a = x; } else { b = x; } if (fx < fv) { // x lies between other two known points: f(u) < f(x) < f(v) // x -> v v = x; fv = fx; fpv = fpx; } else { // x is higher than both other points: f(u) < f(v) < f(x) // this is a really poor outcome; we expected to get a point lower than our other two and we got a point higher than both // next time we should bisect Console.WriteLine("bad point"); //throw new NotImplementedException(); //v = x; fv = fx; fpv = fpx; } } // if the user has specified a tollerance, use it if ((settings.RelativePrecision > 0.0 || settings.AbsolutePrecision > 0.0)) { tol = Math.Max(Math.Abs(u) * settings.RelativePrecision, settings.AbsolutePrecision); } else { // otherwise, try to get the tollerance from the curvature if (fpp > 0.0) { tol = Math.Sqrt(2.0 * 1.0E-14 * (Math.Abs(fu) + 1.0E-14) / fpp); } else { // but if we don't have a useable curvature either, wing it if (tol == 0.0) tol = 1.0E-7; } } } throw new NonconvergenceException(); }
private double FindMinimumWithDerivative( FuncWithDerivative f, double a, double b ) { // pick two points in the interval double u = 2.0 / 3.0 * a + 1.0 / 3.0 * b; double v = 1.0 / 3.0 * a + 2.0 / 3.0 * b; // evalue the function there double fu, fpu, fv, fpv; f(u, out fu, out fpu); f(v, out fv, out fpv); // move in the bound at the higher side if (fu > fv) { a = u; } else { b = v; } // if f(v) < f(u), swap the points to ensure that u and v are ordered as required if (fv < fu) { double t; t = u; u = v; v = t; t = fu; fu = fv; fv = t; t = fpu; fpu = fpv; fpv = t; } // An evaluation budget of 32 is sufficient for all our test cases except for |x|, which requires 82 (!) evaluations to converge. Parabolic fitting just does a very poor job // for this function (at all scales, since it is scale invariant). We should look into cubic fitting. return (FindMinimumWithDerivative(f, a, b, u, fu, fpu, v, fv, fpv, new EvaluationSettings() { EvaluationBudget = 64, AbsolutePrecision = 0.0, RelativePrecision = 0.0 })); }