FieldBracketingNthOrderBrentSolver.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.CalculusFieldElement;
  23. import org.hipparchus.Field;
  24. import org.hipparchus.analysis.CalculusFieldUnivariateFunction;
  25. import org.hipparchus.exception.LocalizedCoreFormats;
  26. import org.hipparchus.exception.MathIllegalArgumentException;
  27. import org.hipparchus.exception.MathIllegalStateException;
  28. import org.hipparchus.exception.NullArgumentException;
  29. import org.hipparchus.util.Incrementor;
  30. import org.hipparchus.util.MathArrays;
  31. import org.hipparchus.util.MathUtils;

  32. /**
  33.  * This class implements a modification of the <a
  34.  * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
  35.  * <p>
  36.  * The changes with respect to the original Brent algorithm are:
  37.  * <ul>
  38.  *   <li>the returned value is chosen in the current interval according
  39.  *   to user specified {@link AllowedSolution}</li>
  40.  *   <li>the maximal order for the invert polynomial root search is
  41.  *   user-specified instead of being invert quadratic only</li>
  42.  * </ul><p>
  43.  * The given interval must bracket the root.</p>
  44.  *
  45.  * @param <T> the type of the field elements
  46.  */
  47. public class FieldBracketingNthOrderBrentSolver<T extends CalculusFieldElement<T>>
  48.     implements BracketedRealFieldUnivariateSolver<T> {

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

  51.     /** Field to which the elements belong. */
  52.     private final Field<T> field;

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

  55.     /** Function value accuracy. */
  56.     private final T functionValueAccuracy;

  57.     /** Absolute accuracy. */
  58.     private final T absoluteAccuracy;

  59.     /** Relative accuracy. */
  60.     private final T relativeAccuracy;

  61.     /** Evaluations counter. */
  62.     private Incrementor evaluations;

  63.     /**
  64.      * Construct a solver.
  65.      *
  66.      * @param relativeAccuracy Relative accuracy.
  67.      * @param absoluteAccuracy Absolute accuracy.
  68.      * @param functionValueAccuracy Function value accuracy.
  69.      * @param maximalOrder maximal order.
  70.      * @exception MathIllegalArgumentException if maximal order is lower than 2
  71.      */
  72.     public FieldBracketingNthOrderBrentSolver(final T relativeAccuracy,
  73.                                               final T absoluteAccuracy,
  74.                                               final T functionValueAccuracy,
  75.                                               final int maximalOrder)
  76.         throws MathIllegalArgumentException {
  77.         if (maximalOrder < 2) {
  78.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  79.                                                    maximalOrder, 2);
  80.         }
  81.         this.field                 = relativeAccuracy.getField();
  82.         this.maximalOrder          = maximalOrder;
  83.         this.absoluteAccuracy      = absoluteAccuracy;
  84.         this.relativeAccuracy      = relativeAccuracy;
  85.         this.functionValueAccuracy = functionValueAccuracy;
  86.         this.evaluations           = new Incrementor();
  87.     }

  88.     /** Get the maximal order.
  89.      * @return maximal order
  90.      */
  91.     public int getMaximalOrder() {
  92.         return maximalOrder;
  93.     }

  94.     /**
  95.      * Get the maximal number of function evaluations.
  96.      *
  97.      * @return the maximal number of function evaluations.
  98.      */
  99.     @Override
  100.     public int getMaxEvaluations() {
  101.         return evaluations.getMaximalCount();
  102.     }

  103.     /**
  104.      * Get the number of evaluations of the objective function.
  105.      * The number of evaluations corresponds to the last call to the
  106.      * {@code optimize} method. It is 0 if the method has not been
  107.      * called yet.
  108.      *
  109.      * @return the number of evaluations of the objective function.
  110.      */
  111.     @Override
  112.     public int getEvaluations() {
  113.         return evaluations.getCount();
  114.     }

  115.     /**
  116.      * Get the absolute accuracy.
  117.      * @return absolute accuracy
  118.      */
  119.     @Override
  120.     public T getAbsoluteAccuracy() {
  121.         return absoluteAccuracy;
  122.     }

  123.     /**
  124.      * Get the relative accuracy.
  125.      * @return relative accuracy
  126.      */
  127.     @Override
  128.     public T getRelativeAccuracy() {
  129.         return relativeAccuracy;
  130.     }

  131.     /**
  132.      * Get the function accuracy.
  133.      * @return function accuracy
  134.      */
  135.     @Override
  136.     public T getFunctionValueAccuracy() {
  137.         return functionValueAccuracy;
  138.     }

  139.     /**
  140.      * Solve for a zero in the given interval.
  141.      * A solver may require that the interval brackets a single zero root.
  142.      * Solvers that do require bracketing should be able to handle the case
  143.      * where one of the endpoints is itself a root.
  144.      *
  145.      * @param maxEval Maximum number of evaluations.
  146.      * @param f Function to solve.
  147.      * @param min Lower bound for the interval.
  148.      * @param max Upper bound for the interval.
  149.      * @param allowedSolution The kind of solutions that the root-finding algorithm may
  150.      * accept as solutions.
  151.      * @return a value where the function is zero.
  152.      * @exception NullArgumentException if f is null.
  153.      * @exception MathIllegalArgumentException if root cannot be bracketed
  154.      */
  155.     @Override
  156.     public T solve(final int maxEval, final CalculusFieldUnivariateFunction<T> f,
  157.                    final T min, final T max, final AllowedSolution allowedSolution)
  158.         throws MathIllegalArgumentException, NullArgumentException {
  159.         return solve(maxEval, f, min, max, min.add(max).divide(2), allowedSolution);
  160.     }

  161.     /**
  162.      * Solve for a zero in the given interval, start at {@code startValue}.
  163.      * A solver may require that the interval brackets a single zero root.
  164.      * Solvers that do require bracketing should be able to handle the case
  165.      * where one of the endpoints is itself a root.
  166.      *
  167.      * @param maxEval Maximum number of evaluations.
  168.      * @param f Function to solve.
  169.      * @param min Lower bound for the interval.
  170.      * @param max Upper bound for the interval.
  171.      * @param startValue Start value to use.
  172.      * @param allowedSolution The kind of solutions that the root-finding algorithm may
  173.      * accept as solutions.
  174.      * @return a value where the function is zero.
  175.      * @exception NullArgumentException if f is null.
  176.      * @exception MathIllegalArgumentException if root cannot be bracketed
  177.      */
  178.     @Override
  179.     public T solve(final int maxEval, final CalculusFieldUnivariateFunction<T> f,
  180.                    final T min, final T max, final T startValue,
  181.                    final AllowedSolution allowedSolution)
  182.         throws MathIllegalArgumentException, NullArgumentException {
  183.         // find interval containing root
  184.         return solveInterval(maxEval, f, min, max, startValue).getSide(allowedSolution);
  185.     }

  186.     /** {@inheritDoc} */
  187.     @Override
  188.     public Interval<T> solveInterval(int maxEval,
  189.                                      CalculusFieldUnivariateFunction<T> f,
  190.                                      T min,
  191.                                      T max,
  192.                                      T startValue)
  193.             throws MathIllegalArgumentException, MathIllegalStateException {

  194.         // Checks.
  195.         MathUtils.checkNotNull(f);

  196.         // Reset.
  197.         evaluations = evaluations.withMaximalCount(maxEval);
  198.         T zero = field.getZero();
  199.         T nan  = zero.add(Double.NaN);

  200.         // prepare arrays with the first points
  201.         final T[] x = MathArrays.buildArray(field, maximalOrder + 1);
  202.         final T[] y = MathArrays.buildArray(field, maximalOrder + 1);
  203.         x[0] = min;
  204.         x[1] = startValue;
  205.         x[2] = max;

  206.         // evaluate initial guess
  207.         evaluations.increment();
  208.         y[1] = f.value(x[1]);
  209.         if (y[1].getReal() == 0.0) {
  210.             // return the initial guess if it is a perfect root.
  211.             return new Interval<>(x[1], y[1], x[1], y[1]);
  212.         }

  213.         // evaluate first endpoint
  214.         evaluations.increment();
  215.         y[0] = f.value(x[0]);
  216.         if (y[0].getReal() == 0.0) {
  217.             // return the first endpoint if it is a perfect root.
  218.             return new Interval<>(x[0], y[0], x[0], y[0]);
  219.         }

  220.         int nbPoints;
  221.         int signChangeIndex;
  222.         if (y[0].multiply(y[1]).getReal() < 0) {

  223.             // reduce interval if it brackets the root
  224.             nbPoints        = 2;
  225.             signChangeIndex = 1;

  226.         } else {

  227.             // evaluate second endpoint
  228.             evaluations.increment();
  229.             y[2] = f.value(x[2]);
  230.             if (y[2].getReal() == 0.0) {
  231.                 // return the second endpoint if it is a perfect root.
  232.                 return new Interval<>(x[2], y[2], x[2], y[2]);
  233.             }

  234.             if (y[1].multiply(y[2]).getReal() < 0) {
  235.                 // use all computed point as a start sampling array for solving
  236.                 nbPoints        = 3;
  237.                 signChangeIndex = 2;
  238.             } else {
  239.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.NOT_BRACKETING_INTERVAL,
  240.                                                        x[0].getReal(), x[2].getReal(),
  241.                                                        y[0].getReal(), y[2].getReal());
  242.             }

  243.         }

  244.         // prepare a work array for inverse polynomial interpolation
  245.         final T[] tmpX = MathArrays.buildArray(field, x.length);

  246.         // current tightest bracketing of the root
  247.         T xA    = x[signChangeIndex - 1];
  248.         T yA    = y[signChangeIndex - 1];
  249.         T absXA = xA.abs();
  250.         T absYA = yA.abs();
  251.         int agingA   = 0;
  252.         T xB    = x[signChangeIndex];
  253.         T yB    = y[signChangeIndex];
  254.         T absXB = xB.abs();
  255.         T absYB = yB.abs();
  256.         int agingB   = 0;

  257.         // search loop
  258.         while (true) {

  259.             // check convergence of bracketing interval
  260.             T maxX = absXA.subtract(absXB).getReal() < 0 ? absXB : absXA;
  261.             T maxY = absYA.subtract(absYB).getReal() < 0 ? absYB : absYA;
  262.             final T xTol = absoluteAccuracy.add(relativeAccuracy.multiply(maxX));
  263.             final T midpoint = xA.add(xB.subtract(xA).divide(2));
  264.             if (xB.subtract(xA).subtract(xTol).getReal() <= 0 ||
  265.                 maxY.subtract(functionValueAccuracy).getReal() < 0 ||
  266.                     xA.equals(midpoint) || xB.equals(midpoint)) {
  267.                 return new Interval<>(xA, yA, xB, yB);
  268.             }

  269.             // target for the next evaluation point
  270.             T targetY;
  271.             if (agingA >= MAXIMAL_AGING) {
  272.                 // we keep updating the high bracket, try to compensate this
  273.                 targetY = yB.divide(16).negate();
  274.             } else if (agingB >= MAXIMAL_AGING) {
  275.                 // we keep updating the low bracket, try to compensate this
  276.                 targetY = yA.divide(16).negate();
  277.             } else {
  278.                 // bracketing is balanced, try to find the root itself
  279.                 targetY = zero;
  280.             }

  281.             // make a few attempts to guess a root,
  282.             T nextX;
  283.             int start = 0;
  284.             int end   = nbPoints;
  285.             do {

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

  289.                 if (!((nextX.subtract(xA).getReal() > 0) && (nextX.subtract(xB).getReal() < 0))) {
  290.                     // the guessed root is not strictly inside of the tightest bracketing interval

  291.                     // the guessed root is either not strictly inside the interval or it
  292.                     // is a NaN (which occurs when some sampling points share the same y)
  293.                     // we try again with a lower interpolation order
  294.                     if (signChangeIndex - start >= end - signChangeIndex) {
  295.                         // we have more points before the sign change, drop the lowest point
  296.                         ++start;
  297.                     } else {
  298.                         // we have more points after sign change, drop the highest point
  299.                         --end;
  300.                     }

  301.                     // we need to do one more attempt
  302.                     nextX = nan;

  303.                 }

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

  305.             if (Double.isNaN(nextX.getReal())) {
  306.                 // fall back to bisection
  307.                 nextX = xA.add(xB.subtract(xA).divide(2));
  308.                 start = signChangeIndex - 1;
  309.                 end   = signChangeIndex;
  310.             }

  311.             // evaluate the function at the guessed root
  312.             evaluations.increment();
  313.             final T nextY = f.value(nextX);
  314.             if (nextY.getReal() == 0.0) {
  315.                 // we have found an exact root, since it is not an approximation
  316.                 // we don't need to bother about the allowed solutions setting
  317.                 return new Interval<>(nextX, nextY, nextX, nextY);
  318.             }

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

  320.                 // we have been forced to ignore some points to keep bracketing,
  321.                 // they are probably too far from the root, drop them from now on
  322.                 nbPoints = end - start;
  323.                 System.arraycopy(x, start, x, 0, nbPoints);
  324.                 System.arraycopy(y, start, y, 0, nbPoints);
  325.                 signChangeIndex -= start;

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

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

  329.                 // keep the tightest bracketing interval as centered as possible
  330.                 if (signChangeIndex >= (x.length + 1) / 2) {
  331.                     // we drop the lowest point, we have to shift the arrays and the index
  332.                     System.arraycopy(x, 1, x, 0, nbPoints);
  333.                     System.arraycopy(y, 1, y, 0, nbPoints);
  334.                     --signChangeIndex;
  335.                 }

  336.             }

  337.             // insert the last computed point
  338.             //(by construction, we know it lies inside the tightest bracketing interval)
  339.             System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
  340.             x[signChangeIndex] = nextX;
  341.             System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
  342.             y[signChangeIndex] = nextY;
  343.             ++nbPoints;

  344.             // update the bracketing interval
  345.             if (nextY.multiply(yA).getReal() <= 0) {
  346.                 // the sign change occurs before the inserted point
  347.                 xB = nextX;
  348.                 yB = nextY;
  349.                 absYB = yB.abs();
  350.                 ++agingA;
  351.                 agingB = 0;
  352.             } else {
  353.                 // the sign change occurs after the inserted point
  354.                 xA = nextX;
  355.                 yA = nextY;
  356.                 absYA = yA.abs();
  357.                 agingA = 0;
  358.                 ++agingB;

  359.                 // update the sign change index
  360.                 signChangeIndex++;

  361.             }

  362.         }

  363.     }

  364.     /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
  365.      * <p>
  366.      * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
  367.      * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
  368.      * Q(y<sub>i</sub>) = x<sub>i</sub>.
  369.      * </p>
  370.      * @param targetY target value for y
  371.      * @param x reference points abscissas for interpolation,
  372.      * note that this array <em>is</em> modified during computation
  373.      * @param y reference points ordinates for interpolation
  374.      * @param start start index of the points to consider (inclusive)
  375.      * @param end end index of the points to consider (exclusive)
  376.      * @return guessed root (will be a NaN if two points share the same y)
  377.      */
  378.     private T guessX(final T targetY, final T[] x, final T[] y,
  379.                        final int start, final int end) {

  380.         // compute Q Newton coefficients by divided differences
  381.         for (int i = start; i < end - 1; ++i) {
  382.             final int delta = i + 1 - start;
  383.             for (int j = end - 1; j > i; --j) {
  384.                 x[j] = x[j].subtract(x[j-1]).divide(y[j].subtract(y[j - delta]));
  385.             }
  386.         }

  387.         // evaluate Q(targetY)
  388.         T x0 = field.getZero();
  389.         for (int j = end - 1; j >= start; --j) {
  390.             x0 = x[j].add(x0.multiply(targetY.subtract(y[j])));
  391.         }

  392.         return x0;

  393.     }

  394. }