FieldBracketingNthOrderBrentSolver.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /*
- * This is not the original file distributed by the Apache Software Foundation
- * It has been modified by the Hipparchus project
- */
- package org.hipparchus.analysis.solvers;
- import org.hipparchus.CalculusFieldElement;
- import org.hipparchus.Field;
- import org.hipparchus.analysis.CalculusFieldUnivariateFunction;
- import org.hipparchus.exception.LocalizedCoreFormats;
- import org.hipparchus.exception.MathIllegalArgumentException;
- import org.hipparchus.exception.MathIllegalStateException;
- import org.hipparchus.exception.NullArgumentException;
- import org.hipparchus.util.Incrementor;
- import org.hipparchus.util.MathArrays;
- import org.hipparchus.util.MathUtils;
- /**
- * This class implements a modification of the <a
- * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
- * <p>
- * The changes with respect to the original Brent algorithm are:
- * <ul>
- * <li>the returned value is chosen in the current interval according
- * to user specified {@link AllowedSolution}</li>
- * <li>the maximal order for the invert polynomial root search is
- * user-specified instead of being invert quadratic only</li>
- * </ul><p>
- * The given interval must bracket the root.</p>
- *
- * @param <T> the type of the field elements
- */
- public class FieldBracketingNthOrderBrentSolver<T extends CalculusFieldElement<T>>
- implements BracketedRealFieldUnivariateSolver<T> {
- /** Maximal aging triggering an attempt to balance the bracketing interval. */
- private static final int MAXIMAL_AGING = 2;
- /** Field to which the elements belong. */
- private final Field<T> field;
- /** Maximal order. */
- private final int maximalOrder;
- /** Function value accuracy. */
- private final T functionValueAccuracy;
- /** Absolute accuracy. */
- private final T absoluteAccuracy;
- /** Relative accuracy. */
- private final T relativeAccuracy;
- /** Evaluations counter. */
- private Incrementor evaluations;
- /**
- * Construct a solver.
- *
- * @param relativeAccuracy Relative accuracy.
- * @param absoluteAccuracy Absolute accuracy.
- * @param functionValueAccuracy Function value accuracy.
- * @param maximalOrder maximal order.
- * @exception MathIllegalArgumentException if maximal order is lower than 2
- */
- public FieldBracketingNthOrderBrentSolver(final T relativeAccuracy,
- final T absoluteAccuracy,
- final T functionValueAccuracy,
- final int maximalOrder)
- throws MathIllegalArgumentException {
- if (maximalOrder < 2) {
- throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
- maximalOrder, 2);
- }
- this.field = relativeAccuracy.getField();
- this.maximalOrder = maximalOrder;
- this.absoluteAccuracy = absoluteAccuracy;
- this.relativeAccuracy = relativeAccuracy;
- this.functionValueAccuracy = functionValueAccuracy;
- this.evaluations = new Incrementor();
- }
- /** Get the maximal order.
- * @return maximal order
- */
- public int getMaximalOrder() {
- return maximalOrder;
- }
- /**
- * Get the maximal number of function evaluations.
- *
- * @return the maximal number of function evaluations.
- */
- @Override
- public int getMaxEvaluations() {
- return evaluations.getMaximalCount();
- }
- /**
- * Get the number of evaluations of the objective function.
- * The number of evaluations corresponds to the last call to the
- * {@code optimize} method. It is 0 if the method has not been
- * called yet.
- *
- * @return the number of evaluations of the objective function.
- */
- @Override
- public int getEvaluations() {
- return evaluations.getCount();
- }
- /**
- * Get the absolute accuracy.
- * @return absolute accuracy
- */
- @Override
- public T getAbsoluteAccuracy() {
- return absoluteAccuracy;
- }
- /**
- * Get the relative accuracy.
- * @return relative accuracy
- */
- @Override
- public T getRelativeAccuracy() {
- return relativeAccuracy;
- }
- /**
- * Get the function accuracy.
- * @return function accuracy
- */
- @Override
- public T getFunctionValueAccuracy() {
- return functionValueAccuracy;
- }
- /**
- * Solve for a zero in the given interval.
- * A solver may require that the interval brackets a single zero root.
- * Solvers that do require bracketing should be able to handle the case
- * where one of the endpoints is itself a root.
- *
- * @param maxEval Maximum number of evaluations.
- * @param f Function to solve.
- * @param min Lower bound for the interval.
- * @param max Upper bound for the interval.
- * @param allowedSolution The kind of solutions that the root-finding algorithm may
- * accept as solutions.
- * @return a value where the function is zero.
- * @exception NullArgumentException if f is null.
- * @exception MathIllegalArgumentException if root cannot be bracketed
- */
- @Override
- public T solve(final int maxEval, final CalculusFieldUnivariateFunction<T> f,
- final T min, final T max, final AllowedSolution allowedSolution)
- throws MathIllegalArgumentException, NullArgumentException {
- return solve(maxEval, f, min, max, min.add(max).divide(2), allowedSolution);
- }
- /**
- * Solve for a zero in the given interval, start at {@code startValue}.
- * A solver may require that the interval brackets a single zero root.
- * Solvers that do require bracketing should be able to handle the case
- * where one of the endpoints is itself a root.
- *
- * @param maxEval Maximum number of evaluations.
- * @param f Function to solve.
- * @param min Lower bound for the interval.
- * @param max Upper bound for the interval.
- * @param startValue Start value to use.
- * @param allowedSolution The kind of solutions that the root-finding algorithm may
- * accept as solutions.
- * @return a value where the function is zero.
- * @exception NullArgumentException if f is null.
- * @exception MathIllegalArgumentException if root cannot be bracketed
- */
- @Override
- public T solve(final int maxEval, final CalculusFieldUnivariateFunction<T> f,
- final T min, final T max, final T startValue,
- final AllowedSolution allowedSolution)
- throws MathIllegalArgumentException, NullArgumentException {
- // find interval containing root
- return solveInterval(maxEval, f, min, max, startValue).getSide(allowedSolution);
- }
- /** {@inheritDoc} */
- @Override
- public Interval<T> solveInterval(int maxEval,
- CalculusFieldUnivariateFunction<T> f,
- T min,
- T max,
- T startValue)
- throws MathIllegalArgumentException, MathIllegalStateException {
- // Checks.
- MathUtils.checkNotNull(f);
- // Reset.
- evaluations = evaluations.withMaximalCount(maxEval);
- T zero = field.getZero();
- T nan = zero.add(Double.NaN);
- // prepare arrays with the first points
- final T[] x = MathArrays.buildArray(field, maximalOrder + 1);
- final T[] y = MathArrays.buildArray(field, maximalOrder + 1);
- x[0] = min;
- x[1] = startValue;
- x[2] = max;
- // evaluate initial guess
- evaluations.increment();
- y[1] = f.value(x[1]);
- if (y[1].getReal() == 0.0) {
- // return the initial guess if it is a perfect root.
- return new Interval<>(x[1], y[1], x[1], y[1]);
- }
- // evaluate first endpoint
- evaluations.increment();
- y[0] = f.value(x[0]);
- if (y[0].getReal() == 0.0) {
- // return the first endpoint if it is a perfect root.
- return new Interval<>(x[0], y[0], x[0], y[0]);
- }
- int nbPoints;
- int signChangeIndex;
- if (y[0].multiply(y[1]).getReal() < 0) {
- // reduce interval if it brackets the root
- nbPoints = 2;
- signChangeIndex = 1;
- } else {
- // evaluate second endpoint
- evaluations.increment();
- y[2] = f.value(x[2]);
- if (y[2].getReal() == 0.0) {
- // return the second endpoint if it is a perfect root.
- return new Interval<>(x[2], y[2], x[2], y[2]);
- }
- if (y[1].multiply(y[2]).getReal() < 0) {
- // use all computed point as a start sampling array for solving
- nbPoints = 3;
- signChangeIndex = 2;
- } else {
- throw new MathIllegalArgumentException(LocalizedCoreFormats.NOT_BRACKETING_INTERVAL,
- x[0].getReal(), x[2].getReal(),
- y[0].getReal(), y[2].getReal());
- }
- }
- // prepare a work array for inverse polynomial interpolation
- final T[] tmpX = MathArrays.buildArray(field, x.length);
- // current tightest bracketing of the root
- T xA = x[signChangeIndex - 1];
- T yA = y[signChangeIndex - 1];
- T absXA = xA.abs();
- T absYA = yA.abs();
- int agingA = 0;
- T xB = x[signChangeIndex];
- T yB = y[signChangeIndex];
- T absXB = xB.abs();
- T absYB = yB.abs();
- int agingB = 0;
- // search loop
- while (true) {
- // check convergence of bracketing interval
- T maxX = absXA.subtract(absXB).getReal() < 0 ? absXB : absXA;
- T maxY = absYA.subtract(absYB).getReal() < 0 ? absYB : absYA;
- final T xTol = absoluteAccuracy.add(relativeAccuracy.multiply(maxX));
- final T midpoint = xA.add(xB.subtract(xA).divide(2));
- if (xB.subtract(xA).subtract(xTol).getReal() <= 0 ||
- maxY.subtract(functionValueAccuracy).getReal() < 0 ||
- xA.equals(midpoint) || xB.equals(midpoint)) {
- return new Interval<>(xA, yA, xB, yB);
- }
- // target for the next evaluation point
- T targetY;
- if (agingA >= MAXIMAL_AGING) {
- // we keep updating the high bracket, try to compensate this
- targetY = yB.divide(16).negate();
- } else if (agingB >= MAXIMAL_AGING) {
- // we keep updating the low bracket, try to compensate this
- targetY = yA.divide(16).negate();
- } else {
- // bracketing is balanced, try to find the root itself
- targetY = zero;
- }
- // make a few attempts to guess a root,
- T nextX;
- int start = 0;
- int end = nbPoints;
- do {
- // guess a value for current target, using inverse polynomial interpolation
- System.arraycopy(x, start, tmpX, start, end - start);
- nextX = guessX(targetY, tmpX, y, start, end);
- if (!((nextX.subtract(xA).getReal() > 0) && (nextX.subtract(xB).getReal() < 0))) {
- // the guessed root is not strictly inside of the tightest bracketing interval
- // the guessed root is either not strictly inside the interval or it
- // is a NaN (which occurs when some sampling points share the same y)
- // we try again with a lower interpolation order
- if (signChangeIndex - start >= end - signChangeIndex) {
- // we have more points before the sign change, drop the lowest point
- ++start;
- } else {
- // we have more points after sign change, drop the highest point
- --end;
- }
- // we need to do one more attempt
- nextX = nan;
- }
- } while (Double.isNaN(nextX.getReal()) && (end - start > 1));
- if (Double.isNaN(nextX.getReal())) {
- // fall back to bisection
- nextX = xA.add(xB.subtract(xA).divide(2));
- start = signChangeIndex - 1;
- end = signChangeIndex;
- }
- // evaluate the function at the guessed root
- evaluations.increment();
- final T nextY = f.value(nextX);
- if (nextY.getReal() == 0.0) {
- // we have found an exact root, since it is not an approximation
- // we don't need to bother about the allowed solutions setting
- return new Interval<>(nextX, nextY, nextX, nextY);
- }
- if ((nbPoints > 2) && (end - start != nbPoints)) {
- // we have been forced to ignore some points to keep bracketing,
- // they are probably too far from the root, drop them from now on
- nbPoints = end - start;
- System.arraycopy(x, start, x, 0, nbPoints);
- System.arraycopy(y, start, y, 0, nbPoints);
- signChangeIndex -= start;
- } else if (nbPoints == x.length) {
- // we have to drop one point in order to insert the new one
- nbPoints--;
- // keep the tightest bracketing interval as centered as possible
- if (signChangeIndex >= (x.length + 1) / 2) {
- // we drop the lowest point, we have to shift the arrays and the index
- System.arraycopy(x, 1, x, 0, nbPoints);
- System.arraycopy(y, 1, y, 0, nbPoints);
- --signChangeIndex;
- }
- }
- // insert the last computed point
- //(by construction, we know it lies inside the tightest bracketing interval)
- System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
- x[signChangeIndex] = nextX;
- System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
- y[signChangeIndex] = nextY;
- ++nbPoints;
- // update the bracketing interval
- if (nextY.multiply(yA).getReal() <= 0) {
- // the sign change occurs before the inserted point
- xB = nextX;
- yB = nextY;
- absYB = yB.abs();
- ++agingA;
- agingB = 0;
- } else {
- // the sign change occurs after the inserted point
- xA = nextX;
- yA = nextY;
- absYA = yA.abs();
- agingA = 0;
- ++agingB;
- // update the sign change index
- signChangeIndex++;
- }
- }
- }
- /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
- * <p>
- * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
- * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
- * Q(y<sub>i</sub>) = x<sub>i</sub>.
- * </p>
- * @param targetY target value for y
- * @param x reference points abscissas for interpolation,
- * note that this array <em>is</em> modified during computation
- * @param y reference points ordinates for interpolation
- * @param start start index of the points to consider (inclusive)
- * @param end end index of the points to consider (exclusive)
- * @return guessed root (will be a NaN if two points share the same y)
- */
- private T guessX(final T targetY, final T[] x, final T[] y,
- final int start, final int end) {
- // compute Q Newton coefficients by divided differences
- for (int i = start; i < end - 1; ++i) {
- final int delta = i + 1 - start;
- for (int j = end - 1; j > i; --j) {
- x[j] = x[j].subtract(x[j-1]).divide(y[j].subtract(y[j - delta]));
- }
- }
- // evaluate Q(targetY)
- T x0 = field.getZero();
- for (int j = end - 1; j >= start; --j) {
- x0 = x[j].add(x0.multiply(targetY.subtract(y[j])));
- }
- return x0;
- }
- }