BracketingNthOrderBrentSolver.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.analysis.solvers;


  22. import org.hipparchus.analysis.UnivariateFunction;
  23. import org.hipparchus.exception.LocalizedCoreFormats;
  24. import org.hipparchus.exception.MathIllegalArgumentException;
  25. import org.hipparchus.exception.MathIllegalStateException;
  26. import org.hipparchus.util.FastMath;
  27. import org.hipparchus.util.Precision;

  28. /**
  29.  * This class implements a modification of the <a
  30.  * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
  31.  * <p>
  32.  * The changes with respect to the original Brent algorithm are:
  33.  * <ul>
  34.  *   <li>the returned value is chosen in the current interval according
  35.  *   to user specified {@link AllowedSolution},</li>
  36.  *   <li>the maximal order for the invert polynomial root search is
  37.  *   user-specified instead of being invert quadratic only</li>
  38.  * </ul><p>
  39.  * The given interval must bracket the root.</p>
  40.  *
  41.  */
  42. public class BracketingNthOrderBrentSolver
  43.     extends AbstractUnivariateSolver
  44.     implements BracketedUnivariateSolver<UnivariateFunction> {

  45.     /** Default absolute accuracy. */
  46.     private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;

  47.     /** Default maximal order. */
  48.     private static final int DEFAULT_MAXIMAL_ORDER = 5;

  49.     /** Maximal aging triggering an attempt to balance the bracketing interval. */
  50.     private static final int MAXIMAL_AGING = 2;

  51.     /** Reduction factor for attempts to balance the bracketing interval. */
  52.     private static final double REDUCTION_FACTOR = 1.0 / 16.0;

  53.     /** Maximal order. */
  54.     private final int maximalOrder;

  55.     /** The kinds of solutions that the algorithm may accept. */
  56.     private AllowedSolution allowed;

  57.     /**
  58.      * Construct a solver with default accuracy and maximal order (1e-6 and 5 respectively)
  59.      */
  60.     public BracketingNthOrderBrentSolver() {
  61.         this(DEFAULT_ABSOLUTE_ACCURACY, DEFAULT_MAXIMAL_ORDER);
  62.     }

  63.     /**
  64.      * Construct a solver.
  65.      *
  66.      * @param absoluteAccuracy Absolute accuracy.
  67.      * @param maximalOrder maximal order.
  68.      * @exception MathIllegalArgumentException if maximal order is lower than 2
  69.      */
  70.     public BracketingNthOrderBrentSolver(final double absoluteAccuracy,
  71.                                          final int maximalOrder)
  72.         throws MathIllegalArgumentException {
  73.         super(absoluteAccuracy);
  74.         if (maximalOrder < 2) {
  75.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  76.                                                    maximalOrder, 2);
  77.         }
  78.         this.maximalOrder = maximalOrder;
  79.         this.allowed = AllowedSolution.ANY_SIDE;
  80.     }

  81.     /**
  82.      * Construct a solver.
  83.      *
  84.      * @param relativeAccuracy Relative accuracy.
  85.      * @param absoluteAccuracy Absolute accuracy.
  86.      * @param maximalOrder maximal order.
  87.      * @exception MathIllegalArgumentException if maximal order is lower than 2
  88.      */
  89.     public BracketingNthOrderBrentSolver(final double relativeAccuracy,
  90.                                          final double absoluteAccuracy,
  91.                                          final int maximalOrder)
  92.         throws MathIllegalArgumentException {
  93.         super(relativeAccuracy, absoluteAccuracy);
  94.         if (maximalOrder < 2) {
  95.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  96.                                                    maximalOrder, 2);
  97.         }
  98.         this.maximalOrder = maximalOrder;
  99.         this.allowed = AllowedSolution.ANY_SIDE;
  100.     }

  101.     /**
  102.      * Construct a solver.
  103.      *
  104.      * @param relativeAccuracy Relative accuracy.
  105.      * @param absoluteAccuracy Absolute accuracy.
  106.      * @param functionValueAccuracy Function value accuracy.
  107.      * @param maximalOrder maximal order.
  108.      * @exception MathIllegalArgumentException if maximal order is lower than 2
  109.      */
  110.     public BracketingNthOrderBrentSolver(final double relativeAccuracy,
  111.                                          final double absoluteAccuracy,
  112.                                          final double functionValueAccuracy,
  113.                                          final int maximalOrder)
  114.         throws MathIllegalArgumentException {
  115.         super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
  116.         if (maximalOrder < 2) {
  117.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  118.                                                    maximalOrder, 2);
  119.         }
  120.         this.maximalOrder = maximalOrder;
  121.         this.allowed = AllowedSolution.ANY_SIDE;
  122.     }

  123.     /** Get the maximal order.
  124.      * @return maximal order
  125.      */
  126.     public int getMaximalOrder() {
  127.         return maximalOrder;
  128.     }

  129.     /**
  130.      * {@inheritDoc}
  131.      */
  132.     @Override
  133.     protected double doSolve() {
  134.         return doSolveInterval().getSide(allowed);
  135.     }

  136.     /**
  137.      * Find a root and return the containing interval.
  138.      *
  139.      * @return an interval containing the root such that both end points meet the
  140.      * convergence criteria.
  141.      */
  142.     protected Interval doSolveInterval() {
  143.         // prepare arrays with the first points
  144.         final double[] x = new double[maximalOrder + 1];
  145.         final double[] y = new double[maximalOrder + 1];
  146.         x[0] = getMin();
  147.         x[1] = getStartValue();
  148.         x[2] = getMax();
  149.         verifyInterval(x[0], x[2]);
  150.         if (x[1] < x[0] || x[2] < x[1]) {
  151.             throw new MathIllegalArgumentException(
  152.                     LocalizedCoreFormats.START_POINT_NOT_IN_INTERVAL, x[1], x[0], x[2]);
  153.         }

  154.         // evaluate initial guess
  155.         y[1] = computeObjectiveValue(x[1]);
  156.         if (y[1] == 0.0) {
  157.             // return the initial guess if it is a perfect root.
  158.             return new Interval(x[1], y[1], x[1], y[1]);
  159.         }

  160.         // evaluate first  endpoint
  161.         y[0] = computeObjectiveValue(x[0]);
  162.         if (y[0] == 0.0) {
  163.             // return the first endpoint if it is a perfect root.
  164.             return new Interval(x[0], y[0], x[0], y[0]);
  165.         }

  166.         int nbPoints;
  167.         int signChangeIndex;
  168.         if (y[0] * y[1] < 0) {

  169.             // reduce interval if it brackets the root
  170.             nbPoints        = 2;
  171.             signChangeIndex = 1;

  172.         } else {

  173.             // evaluate second endpoint
  174.             y[2] = computeObjectiveValue(x[2]);
  175.             if (y[2] == 0.0) {
  176.                 // return the second endpoint if it is a perfect root.
  177.                 return new Interval(x[2], y[2], x[2], y[2]);
  178.             }

  179.             if (y[1] * y[2] < 0) {
  180.                 // use all computed point as a start sampling array for solving
  181.                 nbPoints        = 3;
  182.                 signChangeIndex = 2;
  183.             } else {
  184.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.NOT_BRACKETING_INTERVAL,
  185.                                                        x[0], x[2], y[0], y[2]);
  186.             }

  187.         }

  188.         // prepare a work array for inverse polynomial interpolation
  189.         final double[] tmpX = new double[x.length];

  190.         // current tightest bracketing of the root
  191.         double xA    = x[signChangeIndex - 1];
  192.         double yA    = y[signChangeIndex - 1];
  193.         double absYA = FastMath.abs(yA);
  194.         int agingA   = 0;
  195.         double xB    = x[signChangeIndex];
  196.         double yB    = y[signChangeIndex];
  197.         double absYB = FastMath.abs(yB);
  198.         int agingB   = 0;

  199.         // search loop
  200.         while (true) {

  201.             // check convergence of bracketing interval
  202.             final double xTol = getAbsoluteAccuracy() +
  203.                                 getRelativeAccuracy() * FastMath.max(FastMath.abs(xA), FastMath.abs(xB));
  204.             if (xB - xA <= xTol ||
  205.                     FastMath.max(absYA, absYB) < getFunctionValueAccuracy() ||
  206.                     Precision.equals(xA, xB, 1)) {
  207.                 return new Interval(xA, yA, xB, yB);
  208.             }

  209.             // target for the next evaluation point
  210.             double targetY;
  211.             if (agingA >= MAXIMAL_AGING) {
  212.                 // we keep updating the high bracket, try to compensate this
  213.                 final int p = agingA - MAXIMAL_AGING;
  214.                 final double weightA = (1 << p) - 1;
  215.                 final double weightB = p + 1;
  216.                 targetY = (weightA * yA - weightB * REDUCTION_FACTOR * yB) / (weightA + weightB);
  217.             } else if (agingB >= MAXIMAL_AGING) {
  218.                 // we keep updating the low bracket, try to compensate this
  219.                 final int p = agingB - MAXIMAL_AGING;
  220.                 final double weightA = p + 1;
  221.                 final double weightB = (1 << p) - 1;
  222.                 targetY = (weightB * yB - weightA * REDUCTION_FACTOR * yA) / (weightA + weightB);
  223.             } else {
  224.                 // bracketing is balanced, try to find the root itself
  225.                 targetY = 0;
  226.             }

  227.             // make a few attempts to guess a root,
  228.             double nextX;
  229.             int start = 0;
  230.             int end   = nbPoints;
  231.             do {

  232.                 // guess a value for current target, using inverse polynomial interpolation
  233.                 System.arraycopy(x, start, tmpX, start, end - start);
  234.                 nextX = guessX(targetY, tmpX, y, start, end);

  235.                 if (!((nextX > xA) && (nextX < xB))) {
  236.                     // the guessed root is not strictly inside of the tightest bracketing interval

  237.                     // the guessed root is either not strictly inside the interval or it
  238.                     // is a NaN (which occurs when some sampling points share the same y)
  239.                     // we try again with a lower interpolation order
  240.                     if (signChangeIndex - start >= end - signChangeIndex) {
  241.                         // we have more points before the sign change, drop the lowest point
  242.                         ++start;
  243.                     } else {
  244.                         // we have more points after sign change, drop the highest point
  245.                         --end;
  246.                     }

  247.                     // we need to do one more attempt
  248.                     nextX = Double.NaN;

  249.                 }

  250.             } while (Double.isNaN(nextX) && (end - start > 1));

  251.             if (Double.isNaN(nextX)) {
  252.                 // fall back to bisection
  253.                 nextX = xA + 0.5 * (xB - xA);
  254.                 start = signChangeIndex - 1;
  255.                 end   = signChangeIndex;
  256.             }

  257.             // evaluate the function at the guessed root
  258.             final double nextY = computeObjectiveValue(nextX);
  259.             if (nextY == 0.0 || FastMath.abs(nextY) < getFunctionValueAccuracy() && allowed == AllowedSolution.ANY_SIDE) {
  260.                 // we have either:
  261.                 // - an exact root, so we don't we don't need to bother about the allowed solutions setting
  262.                 // - or an approximate root and we know allowed solutions setting if to retrieve the value closest to zero
  263.                 return new Interval(nextX, nextY, nextX, nextY);
  264.             }

  265.             if ((nbPoints > 2) && (end - start != nbPoints)) {

  266.                 // we have been forced to ignore some points to keep bracketing,
  267.                 // they are probably too far from the root, drop them from now on
  268.                 nbPoints = end - start;
  269.                 System.arraycopy(x, start, x, 0, nbPoints);
  270.                 System.arraycopy(y, start, y, 0, nbPoints);
  271.                 signChangeIndex -= start;

  272.             } else  if (nbPoints == x.length) {

  273.                 // we have to drop one point in order to insert the new one
  274.                 nbPoints--;

  275.                 // keep the tightest bracketing interval as centered as possible
  276.                 if (signChangeIndex >= (x.length + 1) / 2) {
  277.                     // we drop the lowest point, we have to shift the arrays and the index
  278.                     System.arraycopy(x, 1, x, 0, nbPoints);
  279.                     System.arraycopy(y, 1, y, 0, nbPoints);
  280.                     --signChangeIndex;
  281.                 }

  282.             }

  283.             // insert the last computed point
  284.             //(by construction, we know it lies inside the tightest bracketing interval)
  285.             System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
  286.             x[signChangeIndex] = nextX;
  287.             System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
  288.             y[signChangeIndex] = nextY;
  289.             ++nbPoints;

  290.             // update the bracketing interval
  291.             if (nextY * yA <= 0) {
  292.                 // the sign change occurs before the inserted point
  293.                 xB = nextX;
  294.                 yB = nextY;
  295.                 absYB = FastMath.abs(yB);
  296.                 ++agingA;
  297.                 agingB = 0;
  298.             } else {
  299.                 // the sign change occurs after the inserted point
  300.                 xA = nextX;
  301.                 yA = nextY;
  302.                 absYA = FastMath.abs(yA);
  303.                 agingA = 0;
  304.                 ++agingB;

  305.                 // update the sign change index
  306.                 signChangeIndex++;

  307.             }

  308.         }

  309.     }

  310.     /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
  311.      * <p>
  312.      * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
  313.      * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
  314.      * Q(y<sub>i</sub>) = x<sub>i</sub>.
  315.      * </p>
  316.      * @param targetY target value for y
  317.      * @param x reference points abscissas for interpolation,
  318.      * note that this array <em>is</em> modified during computation
  319.      * @param y reference points ordinates for interpolation
  320.      * @param start start index of the points to consider (inclusive)
  321.      * @param end end index of the points to consider (exclusive)
  322.      * @return guessed root (will be a NaN if two points share the same y)
  323.      */
  324.     private double guessX(final double targetY, final double[] x, final double[] y,
  325.                           final int start, final int end) {

  326.         // compute Q Newton coefficients by divided differences
  327.         for (int i = start; i < end - 1; ++i) {
  328.             final int delta = i + 1 - start;
  329.             for (int j = end - 1; j > i; --j) {
  330.                 x[j] = (x[j] - x[j-1]) / (y[j] - y[j - delta]);
  331.             }
  332.         }

  333.         // evaluate Q(targetY)
  334.         double x0 = 0;
  335.         for (int j = end - 1; j >= start; --j) {
  336.             x0 = x[j] + x0 * (targetY - y[j]);
  337.         }

  338.         return x0;

  339.     }

  340.     /** {@inheritDoc} */
  341.     @Override
  342.     public double solve(int maxEval, UnivariateFunction f, double min,
  343.                         double max, AllowedSolution allowedSolution)
  344.         throws MathIllegalArgumentException, MathIllegalStateException {
  345.         this.allowed = allowedSolution;
  346.         return super.solve(maxEval, f, min, max);
  347.     }

  348.     /** {@inheritDoc} */
  349.     @Override
  350.     public double solve(int maxEval, UnivariateFunction f, double min,
  351.                         double max, double startValue,
  352.                         AllowedSolution allowedSolution)
  353.         throws MathIllegalArgumentException, MathIllegalStateException {
  354.         this.allowed = allowedSolution;
  355.         return super.solve(maxEval, f, min, max, startValue);

  356.     }

  357.     /** {@inheritDoc} */
  358.     @Override
  359.     public Interval solveInterval(final int maxEval,
  360.                                 final UnivariateFunction f,
  361.                                 final double min,
  362.                                 final double max,
  363.                                 final double startValue)
  364.             throws MathIllegalArgumentException, MathIllegalStateException {
  365.         setup(maxEval, f, min, max, startValue);
  366.         this.allowed = null;
  367.         return doSolveInterval();
  368.     }

  369. }