/// <summary> /// TOMS Algorithm 748 uses a mixture of cubic, quadratic and linear (secant) interpolation to locate the root of f(x) /// Note: TOMS748 requires the root to be bracketed, so this routine will attempt to bracket the root. /// </summary> /// <param name="f">Function to solve</param> /// <param name="guess">The initial guess for the root.</param> /// <param name="step">The starting step size</param> /// <param name="fType">Unknown, increasing, or decreasing</param> /// <param name="min">Optional. The minimum location for the root, or NaN</param> /// <param name="max">Optional. The maximum location for the root, or NaN</param> /// <param name="areNear">A function that returns true when the required tolerance is reached, or null to use the default tolerance</param> /// <param name="maxIterations">The maximum number of iterations to keep refining the root estimate</param> /// <returns> /// Null, if any of the parameters were incorrect. /// The RootResults which contains the root if one was found, or the final state before failing. /// </returns> public static RootResults Toms748Bracket(Func <double, double> f, double guess, double step, FunctionShape fType, double min, double max, ToleranceFunc areNear, int maxIterations) { const RootResults nullResult = null; if (f == null) { Policies.ReportDomainError("Function cannot be null"); return(nullResult); } // NaN is valid here for min or max, so only fail if both have a value and min > max if (min > max) { Policies.ReportDomainError("Requires min <= max : min = {0}; max = {1}", min, max); return(nullResult); } // use the default if max iterations is negative if (maxIterations < 3) { Policies.ReportDomainError("Requires maxIterations >= 3: maxIterations = {0}", maxIterations); return(nullResult); } // use the default if tolerance is null if (areNear == null) { areNear = GetToleranceFunc(); } int iterations; RootBracketResult b = FindBracket(f, guess, step, fType, min, max, maxIterations, out iterations); if (b == null) { Policies.ReportDomainError("Invalid parameter in bracket"); return(nullResult); } if (!b.IsValid) { RootResults result = new RootResults() { Bracket = b, Iterations = iterations }; return(result); } var r = Toms748Int(f, b.XMin, b.FxMin, b.XMax, b.FxMax, areNear, maxIterations - iterations); r.Iterations += iterations; return(r); }
/// <summary> /// Uses the Bisection method to locate the root of f(x). /// Note: min and max must bracket the root. /// </summary> /// <param name="f">Function to solve</param> /// <param name="min">Root must lie within [min, max]</param> /// <param name="max">Root must lie within [min, max]</param> /// <param name="areNear">A function that returns true when the required tolerance is reached, or null to use the default tolerance</param> /// <param name="maxIterations">The maximum number of iterations to keep refining the root estimate</param> /// <returns> /// Null, if any of the parameters were incorrect. /// The RootResults which contains the root if one was found, or the final state before failing. /// </returns> public static RootResults Bisection(Func <double, double> f, double min, double max, ToleranceFunc areNear, int maxIterations) { const RootResults nullResult = null; if (f == null) { Policies.ReportDomainError("Function cannot be null"); return(nullResult); } if (!(min <= max)) { Policies.ReportDomainError("Requires min <= max : min = {0}; max = {1}", min, max); return(nullResult); } if (maxIterations < 3) { Policies.ReportDomainError("Requires maxIterations >= 3: maxIterations = {0}", maxIterations); return(nullResult); } // use the default if tolerance is null if (areNear == null) { areNear = GetToleranceFunc(); } // the root must be bracketed double fmin = f(min); double fmax = f(max); if (!IsBracket(fmin, fmax)) { RootResults results = new RootResults() { Iterations = 2, Bracket = new RootBracketResult(min, fmin, max, fmax) }; Policies.ReportDomainError("The root is not bracketed: f({0}) = {1}; f({2}) = {3}", min, fmin, max, fmax); return(results); } var r = BisectionInt(f, min, fmin, max, fmax, areNear, maxIterations - 2); r.Iterations += 2; return(r); }
/// <summary> /// Brent-Dekker method for finding roots. Robust and relatively quick. /// Uses a combination of inverse quadratic interpolation, secant interpolation, and bisection. /// <para>Note: Brent-Dekker requires that the root be bracketed. This routine will attempt to /// bracket the root before using Brent-Dekker.</para> /// </summary> /// <param name="f">function to solve</param> /// <param name="guess">the initial guess for the root.</param> /// <param name="step">the starting step size</param> /// <param name="fType">Unknown, increasing, or decreasing</param> /// <param name="min">Optional. The minimum location for the root, or NaN</param> /// <param name="max">Optional. The maximum location for the root, or NaN</param> /// <param name="relTolerance">The maximum relative tolerance</param> /// <param name="absTolerance">The maximum absolute tolerance</param> /// <param name="maxIterations">The maximum number of iterations to keep refining the root estimate</param> /// <returns> /// Null, if any of the parameters were incorrect. /// The RootResults which contains the root if one was found, or the final state before failing. /// </returns> public static RootResults BrentBracket(Func <double, double> f, double guess, double step, FunctionShape fType, double min, double max, double relTolerance, double absTolerance, int maxIterations) { const RootResults nullResult = null; if (!(relTolerance >= 0) || double.IsInfinity(relTolerance)) { Policies.ReportDomainError("Requires finite relative tolerance >= 0: relTolerance = {0}", relTolerance); return(nullResult); } if (!(absTolerance >= 0) || double.IsInfinity(absTolerance)) { Policies.ReportDomainError("Requires finite absolute tolerance >= 0: absTolerance = {0}", absTolerance); return(nullResult); } var areNear = GetToleranceFunc(relTolerance, absTolerance); return(BrentBracket(f, guess, step, fType, min, max, areNear, maxIterations)); }
/// <summary> /// Brent-Dekker uses a combination of inverse quadratic interpolation, secant interpolation, and bisection to locate the root of f(x). /// Note: min and max must bracket the root. /// </summary> /// <param name="f">function to solve</param> /// <param name="min">root must lie within [min, max]</param> /// <param name="max">root must lie within [min, max]</param> /// <param name="relTolerance">The maximum relative tolerance</param> /// <param name="absTolerance">The maximum absolute tolerance</param> /// <param name="maxIterations">The maximum number of iterations to keep refining the root estimate</param> /// <returns> /// Null, if any of the parameters were incorrect. /// The RootResults which contains the root if one was found, or the final state before failing. /// </returns> public static RootResults Brent(Func <double, double> f, double min, double max, double relTolerance, double absTolerance, int maxIterations) { const RootResults nullResult = null; if (!(relTolerance >= 0) || double.IsInfinity(relTolerance)) { Policies.ReportDomainError("Requires finite relative tolerance >= 0: relTolerance = {0}", relTolerance); return(nullResult); } if (!(absTolerance >= 0) || double.IsInfinity(absTolerance)) { Policies.ReportDomainError("Requires finite absolute tolerance >= 0: absTolerance = {0}", absTolerance); return(nullResult); } var tol = GetToleranceFunc(relTolerance, absTolerance); return(Brent(f, min, max, tol, maxIterations)); }
/// <summary> /// Halley's method for finding roots. /// Note: min and max must bracket the root. /// </summary> /// <param name="f">function to solve along with first and second derivative at point x</param> /// <param name="guess">the initial guess for the root</param> /// <param name="min">root must lie within [min, max]</param> /// <param name="max">root must lie within [min, max]</param> /// <param name="relTolerance">The maximum relative tolerance</param> /// <param name="absTolerance">The maximum absolute tolerance</param> /// <param name="maxIterations">The maximum number of iterations to keep refining the root estimate</param> /// <returns> /// Null, if any of the parameters were incorrect. /// The RootResults which contains the root if one was found, or the final state before failing. /// </returns> public static RootResults Halley(Func <double, ValueTuple <double, double, double> > f, double guess, double min, double max, double relTolerance, double absTolerance, int maxIterations) { const RootResults nullResult = null; if (!(relTolerance >= 0) || double.IsInfinity(relTolerance)) { Policies.ReportDomainError("Requires finite relative tolerance >= 0: relTolerance = {0}", relTolerance); return(nullResult); } if (!(absTolerance >= 0) || double.IsInfinity(absTolerance)) { Policies.ReportDomainError("Requires finite absolute tolerance >= 0: absTolerance = {0}", absTolerance); return(nullResult); } var areNear = GetToleranceFunc(relTolerance, absTolerance); return(Halley(f, guess, min, max, areNear, maxIterations)); }
// For more information, see: http://en.wikipedia.org/wiki/Brent's_method private static RootResults BrentInt(Func <double, double> f, double min, double fmin, double max, double fmax, ToleranceFunc areNear, int maxIterations) { // private routine so these conditions should already be true Debug.Assert(f != null, "Function cannot be null"); Debug.Assert(min < max, "Parameter min < max"); Debug.Assert(IsBracket(fmin, fmax), "Root is not bracketed"); // code based on pseudocode in http://en.wikipedia.org/wiki/Brent's_method double xSolution = double.NaN; int iterations = 0; double x = double.NaN; if (fmin == 0) { xSolution = min; goto exit; } if (fmax == 0) { xSolution = max; goto exit; } if (areNear(min, max)) { xSolution = min + (max - min) / 2.0; // don't have an exact solution so take a midpoint goto exit; } // b is chosen to be the closer point to the root (i.e. Abs(fb) < Abs(fa) ) // a is always of the opposite sign as b so that [a,b] or [b,a] contains the root // c = the previous value of b except initially when c = a // d = the previous value of c double a, b, c, fa, fb, fc, d = 0, df = 0; if (Math.Abs(fmin) < Math.Abs(fmax)) { a = max; fa = fmax; b = min; fb = fmin; } else { b = max; fb = fmax; a = min; fa = fmin; } c = a; fc = fa; // first step will be a secant step bool usedBisection = true; // last step was bisection -- is d set while (iterations < maxIterations) { if (fa != fc && fb != fc) { // use inverse quadratic interpolation step // xn = b; xnm1=a; xnm2 = c; x = (a * fb * fc) / ((fa - fb) * (fa - fc)) + (b * fa * fc) / ((fb - fa) * (fb - fc)) + (c * fa * fb) / ((fc - fa) * (fc - fb)); } else // fa != fb here because Brent requires fa,fb to have opposite signs // secant step // xn = b; xnm1=a; { x = b - fb * (b - a) / (fb - fa); } if (((3 * a + b) / 4 <= x && x <= b) || (usedBisection && (Math.Abs(x - b) >= Math.Abs(b - c) / 2 || areNear(b, c))) || //Math.Abs(b-c) < tol.AbsTolerance (!usedBisection && (Math.Abs(x - b) >= Math.Abs(c - d) / 2) || areNear(c, d))) //Math.Abs(c-d) < tol.AbsTolerance // bisection step { x = (a + b) / 2; usedBisection = true; } else { usedBisection = false; } double fx = f(x); iterations++; if (fx == 0) { xSolution = x; break; } if (double.IsNaN(fx)) { Policies.ReportDomainError("Requires that function returns a value: f({0}) = {1}", x, fx); break; } d = c; df = fc; c = b; fc = fb; if (IsBracket(fa, fx)) { b = x; fb = fx; } else { a = x; fa = fx; } // a is always further away from the root if (Math.Abs(fa) < Math.Abs(fb)) { double t, ft; t = b; ft = fb; b = a; fb = fa; a = t; fa = ft; } if (areNear(a, b)) { xSolution = x = a + 0.5 * (b - a); // don't have an exact solution, so return the midpoint break; } } min = a; fmin = fa; max = b; fmax = fb; exit: RootResults result = new RootResults(); if (double.IsNaN(xSolution)) { result.LastX = x; } else { result.SolutionX = xSolution; } result.Bracket = new RootBracketResult(min, fmin, max, fmax); result.Iterations = iterations; return(result); }
// See: http://en.wikipedia.org/wiki/Bisection_method private static RootResults BisectionInt(Func <double, double> f, double min, double fmin, double max, double fmax, ToleranceFunc areNear, int maxIterations) { // private routine so these conditions should already be true Debug.Assert(f != null); Debug.Assert(areNear != null); Debug.Assert(min < max); Debug.Assert(IsBracket(fmin, fmax)); Debug.Assert(maxIterations > 0); // check the initial conditions double xSolution = double.NaN; int iterations = 0; double mid = double.NaN; if (fmin == 0) { xSolution = min; goto exit; } if (fmax == 0) { xSolution = max; goto exit; } if (areNear(min, max)) { xSolution = min + (max - min) / 2.0; // don't have an exact solution so take a midpoint goto exit; } while (iterations < maxIterations) { // evaluate the function at the midpoint mid = min + (max - min) / 2; double fmid = f(mid); iterations++; if (fmid == 0) { xSolution = mid; break; } if (IsBracket(fmin, fmid)) { max = mid; fmax = fmid; } else { min = mid; fmin = fmid; } if (areNear(max, min)) { xSolution = min + (max - min) / 2.0; // don't have an exact solution so take a midpoint break; } } exit: RootResults result = new RootResults(); if (double.IsNaN(xSolution)) { result.LastX = mid; } else { result.SolutionX = xSolution; } result.Bracket = new RootBracketResult(min, fmin, max, fmax); result.Iterations = iterations; return(result); }
private static RootResults Toms748Int(Func <double, double> f, double min, double fmin, double max, double fmax, ToleranceFunc areNear, int max_iter) { // private routine so these conditions should already be true Debug.Assert(f != null, "Function cannot be null"); Debug.Assert(min <= max, $"Requires min <= max, min: {min}, max: {max}"); Debug.Assert(IsBracket(fmin, fmax), "Root is not bracketed"); // // Main entry point and logic for Toms Algorithm 748 // root finder. // double xSolution = double.NaN; int iterations = 0; double a = min; double fa = fmin; double b = max; double fb = fmax; if (fa == 0) { xSolution = a; goto exit; } if (fb == 0) { xSolution = b; goto exit; } if (areNear(a, b)) { xSolution = a + (b - a) / 2.0; // don't have an exact solution so take a midpoint goto exit; } double d, fd; // d is the third best guess to the root double e, fe; // e is the fourth best guess to the root // initialize fe = e = d = fd = double.NaN; double c; int stepNumber = 0; while (iterations < max_iter) { // the root is in [a, b]; save it double a0 = a; double b0 = b; switch (stepNumber) { case 0: // *Only* on the first iteration we take a secant step: c = SecantStep(a, b, fa, fb); break; case 1: // *Only* on the second iteration, we take a quadratic step: c = QuadraticStep(a, b, d, fa, fb, fd, 2); break; case 2: case 3: // // Starting with the third step taken // we can use either quadratic or cubic interpolation. // Cubic interpolation requires that all four function values // fa, fb, fd, and fe are distinct, should that not be the case // then variable prof will get set to true, and we'll end up // taking a quadratic step instead. // const double min_diff = DoubleLimits.MinNormalValue; bool prof = (Math.Abs(fa - fb) < min_diff) || (Math.Abs(fa - fd) < min_diff) || (Math.Abs(fa - fe) < min_diff) || (Math.Abs(fb - fd) < min_diff) || (Math.Abs(fb - fe) < min_diff) || (Math.Abs(fd - fe) < min_diff); // the first time use 2 newton steps; the second time use three int newtonSteps = (stepNumber == 2) ? 2 : 3; if (prof) { c = QuadraticStep(a, b, d, fa, fb, fd, newtonSteps); } else { c = CubicStep(a, b, d, e, fa, fb, fd, fe); if (!(c > a && c < b)) { c = QuadraticStep(a, b, d, fa, fb, fd, newtonSteps); } } break; case 4: // Now we take a double-length secant step: c = DoubleSecantStep(a, b, fa, fb); break; case 5: // // And finally... check to see if an additional bisection step is // to be taken, we do this if we're not converging fast enough: // const double mu = 0.5; if ((b - a) < mu * (b0 - a0)) { stepNumber = 2; continue; } c = a + (b - a) / 2; break; default: Policies.ReportEvaluationError("Switch out of bounds"); return(null); } // save our next best bracket e = d; fe = fd; // We require c in the interval [ a + |a|*minTol, b - |b|*minTol ] // If the interval is not valid: (b-a) < (|a|+|b|)*minTol, then take a midpoint const double minTol = DoubleLimits.MachineEpsilon * 10; double minA = (a < 0) ? a * (1 - minTol) : a * (1 + minTol); double minB = (b < 0) ? b * (1 + minTol) : b * (1 - minTol); if ((b - a) < minTol * (Math.Abs(a) + Math.Abs(b))) { c = a + (b - a) / 2; } else if (c < minA) { c = minA; } else if (c > minB) { c = minB; } // OK, lets invoke f(c): double fc = f(c); ++iterations; // if we have a zero then we have an exact solution to the root: if (fc == 0) { xSolution = c; break; } if (double.IsNaN(fc)) { Policies.ReportDomainError("Requires a continuous function: f({0}) = {1}", c, fc); break; } // // Non-zero fc, update the interval: // if (IsBracket(fa, fc)) { d = b; fd = fb; b = c; fb = fc; } else { d = a; d = fa; a = c; fa = fc; } if (areNear(a, b)) { // we're close enough, but we don't have an exact solution // so take a midpoint xSolution = a + (b - a) / 2.0; break; } // reset our step if necessary; if (++stepNumber > 5) { stepNumber = 2; } } exit: RootResults result = new RootResults(); result.SolutionX = xSolution; result.Bracket = new RootBracketResult(a, fa, b, fb); result.Iterations = iterations; return(result); }
/// <summary> /// Halley's method for finding roots. /// Note: min and max must bracket the root. /// </summary> /// <param name="f">function to solve along with first and second derivative at point x</param> /// <param name="guess">the initial guess for the root</param> /// <param name="min">root must lie within [min, max]</param> /// <param name="max">root must lie within [min, max]</param> /// <param name="areNear">A function that returns true when the required tolerance is reached, or null to use the default tolerance</param> /// <param name="maxIterations">The maximum number of iterations to keep refining the root estimate</param> /// <returns> /// Null, if any of the parameters were incorrect. /// The RootResults which contains the root if one was found, or the final state before failing. /// </returns> public static RootResults Halley(Func <double, ValueTuple <double, double, double> > f, double guess, double min, double max, ToleranceFunc areNear, int maxIterations) { const RootResults nullResult = null; if (f == null) { Policies.ReportDomainError("Function cannot be null"); return(nullResult); } // use the default if tolerance is null if (areNear == null) { areNear = GetToleranceFunc(); } if (maxIterations < 3) { Policies.ReportDomainError("Requires maxIterations >= 3: maxIterations = {0}", maxIterations); return(nullResult); } if (!(min <= max)) { Policies.ReportDomainError("Requires min <= max : min = {0}; max = {1}", min, max); return(nullResult); } if (!(guess >= min && guess <= max)) { Policies.ReportDomainError("Requires a valid initial guess between min and max: guess = {0}; min = {1}; max = {2}", guess, min, max); return(nullResult); } double xSolution = double.NaN; int iterations = 0; double f0, f1, f2; f0 = double.NaN; double x = guess; double delta = max - min; double delta1 = double.MaxValue; double delta2 = double.MaxValue; bool out_of_bounds_sentry = false; while (iterations < maxIterations) { double last_f0 = f0; delta2 = delta1; delta1 = delta; // get the value and first derivative iterations++; (f0, f1, f2) = f(x); if (f0 == 0) { xSolution = x; break; } if (double.IsNaN(f0)) { Policies.ReportDomainError("Requires that function returns a value: f({0}) = {1}", x, f0); break; } if (f1 == 0) { // Oops zero derivative!!! // this is initially null if (double.IsNaN(last_f0)) { // this must be the first iteration, pretend that we had a // previous one at either min or max: x = (guess == min) ? max : min; iterations++; (f0, f1, f2) = f(x); last_f0 = f0; delta = x - guess; } if (IsBracket(last_f0, f0)) { // we've crossed over so move in opposite direction to last step: if (delta < 0) { delta = (x - min) / 2; } else { delta = (x - max) / 2; } } else { // move in same direction as last step: if (delta < 0) { delta = (x - max) / 2; } else { delta = (x - min) / 2; } } } else { // Use Halley's method: // Let f = f(x{n}); f' = f'(x{n}); f'' = f''(x{n}); // // x{n+1} = x{n} - (2 * f * f')/(2 * f'^2 - f * f'' ) // OR // x{n+1} = x{n} - (2 * f)/(2 * f' - f * f''/ f' ) // OR // x{n+1} = x{n} - (f/f')/(1 - (1/2)*(f/f')(f''/f')) if (f2 != 0 && !double.IsInfinity(f2)) { double num = 2 * f0; double denom = 2 * f1 - f0 * (f2 / f1); delta = num / denom; if (double.IsInfinity(delta) || double.IsNaN(delta)) { // possible overflow, use Newton step: delta = f0 / f1; } if (delta * f1 / f0 < 0) { // probably cancellation error, try a Newton step instead: delta = f0 / f1; } } else { delta = f0 / f1; } } double convergence = Math.Abs(delta / delta2); if ((convergence > 0.8) && (convergence < 2)) { // last two steps haven't converged, try bisection: delta = (delta > 0) ? (x - min) / 2 : (x - max) / 2; if (Math.Abs(delta) > x) { delta = Math.Sign(delta) * x; // protect against huge jumps! } // reset delta2 so that this branch will *not* be taken on the // next iteration: delta2 = delta * 3; } guess = x; x -= delta; // check for out of bounds step: if (x < min) { double diff = ((Math.Abs(min) < 1) && (Math.Abs(x) > 1) && (double.MaxValue / Math.Abs(x) < Math.Abs(min))) ? 1000.0 : x / min; if (Math.Abs(diff) < 1) { diff = 1 / diff; } if (!out_of_bounds_sentry && (diff > 0) && (diff < 3)) { // Only a small out of bounds step, lets assume that the result // is probably approximately at min: delta = 0.99 * (guess - min); x = guess - delta; out_of_bounds_sentry = true; // only take this branch once! } else { delta = (guess - min) / 2; x = guess - delta; } } else if (x > max) { double diff = ((Math.Abs(max) < 1) && (Math.Abs(x) > 1) && (double.MaxValue / Math.Abs(x) < Math.Abs(max))) ? 1000.0 : x / max; if (Math.Abs(diff) < 1) { diff = 1 / diff; } if (!out_of_bounds_sentry && (diff > 0) && (diff < 3)) { // Only a small out of bounds step, lets assume that the result // is probably approximately at min: delta = 0.99 * (guess - max); x = guess - delta; out_of_bounds_sentry = true; // only take this branch once! } else { delta = (guess - max) / 2; x = guess - delta; } } // update brackets: if (delta > 0) { max = guess; } else { min = guess; } if (areNear(x, guess)) { xSolution = x; break; } } RootResults result = new RootResults() { SolutionX = xSolution, // Note: this halley implementation does not keep track of fmin, fmax // for the brackets Bracket = new RootBracketResult(min, double.NaN, max, double.NaN), Iterations = iterations }; return(result); }