001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.math4.legacy.analysis.solvers;
018
019
020import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
021import org.apache.commons.math4.legacy.exception.MathInternalError;
022import org.apache.commons.math4.legacy.exception.NoBracketingException;
023import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
024import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
025import org.apache.commons.math4.legacy.exception.TooManyEvaluationsException;
026import org.apache.commons.math4.core.jdkmath.JdkMath;
027import org.apache.commons.numbers.core.Precision;
028
029/**
030 * This class implements a modification of the <a
031 * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
032 * <p>
033 * The changes with respect to the original Brent algorithm are:
034 * <ul>
035 *   <li>the returned value is chosen in the current interval according
036 *   to user specified {@link AllowedSolution},</li>
037 *   <li>the maximal order for the invert polynomial root search is
038 *   user-specified instead of being invert quadratic only</li>
039 * </ul><p>
040 * The given interval must bracket the root.</p>
041 *
042 */
043public class BracketingNthOrderBrentSolver
044    extends AbstractUnivariateSolver
045    implements BracketedUnivariateSolver<UnivariateFunction> {
046
047    /** Default absolute accuracy. */
048    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
049
050    /** Default maximal order. */
051    private static final int DEFAULT_MAXIMAL_ORDER = 5;
052
053    /** Maximal aging triggering an attempt to balance the bracketing interval. */
054    private static final int MAXIMAL_AGING = 2;
055
056    /** Reduction factor for attempts to balance the bracketing interval. */
057    private static final double REDUCTION_FACTOR = 1.0 / 16.0;
058
059    /** Maximal order. */
060    private final int maximalOrder;
061
062    /** The kinds of solutions that the algorithm may accept. */
063    private AllowedSolution allowed;
064
065    /**
066     * Construct a solver with default accuracy and maximal order (1e-6 and 5 respectively).
067     */
068    public BracketingNthOrderBrentSolver() {
069        this(DEFAULT_ABSOLUTE_ACCURACY, DEFAULT_MAXIMAL_ORDER);
070    }
071
072    /**
073     * Construct a solver.
074     *
075     * @param absoluteAccuracy Absolute accuracy.
076     * @param maximalOrder maximal order.
077     * @exception NumberIsTooSmallException if maximal order is lower than 2
078     */
079    public BracketingNthOrderBrentSolver(final double absoluteAccuracy,
080                                         final int maximalOrder)
081        throws NumberIsTooSmallException {
082        super(absoluteAccuracy);
083        if (maximalOrder < 2) {
084            throw new NumberIsTooSmallException(maximalOrder, 2, true);
085        }
086        this.maximalOrder = maximalOrder;
087        this.allowed = AllowedSolution.ANY_SIDE;
088    }
089
090    /**
091     * Construct a solver.
092     *
093     * @param relativeAccuracy Relative accuracy.
094     * @param absoluteAccuracy Absolute accuracy.
095     * @param maximalOrder maximal order.
096     * @exception NumberIsTooSmallException if maximal order is lower than 2
097     */
098    public BracketingNthOrderBrentSolver(final double relativeAccuracy,
099                                         final double absoluteAccuracy,
100                                         final int maximalOrder)
101        throws NumberIsTooSmallException {
102        super(relativeAccuracy, absoluteAccuracy);
103        if (maximalOrder < 2) {
104            throw new NumberIsTooSmallException(maximalOrder, 2, true);
105        }
106        this.maximalOrder = maximalOrder;
107        this.allowed = AllowedSolution.ANY_SIDE;
108    }
109
110    /**
111     * Construct a solver.
112     *
113     * @param relativeAccuracy Relative accuracy.
114     * @param absoluteAccuracy Absolute accuracy.
115     * @param functionValueAccuracy Function value accuracy.
116     * @param maximalOrder maximal order.
117     * @exception NumberIsTooSmallException if maximal order is lower than 2
118     */
119    public BracketingNthOrderBrentSolver(final double relativeAccuracy,
120                                         final double absoluteAccuracy,
121                                         final double functionValueAccuracy,
122                                         final int maximalOrder)
123        throws NumberIsTooSmallException {
124        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
125        if (maximalOrder < 2) {
126            throw new NumberIsTooSmallException(maximalOrder, 2, true);
127        }
128        this.maximalOrder = maximalOrder;
129        this.allowed = AllowedSolution.ANY_SIDE;
130    }
131
132    /** Get the maximal order.
133     * @return maximal order
134     */
135    public int getMaximalOrder() {
136        return maximalOrder;
137    }
138
139    /**
140     * {@inheritDoc}
141     */
142    @Override
143    protected double doSolve()
144        throws TooManyEvaluationsException,
145               NumberIsTooLargeException,
146               NoBracketingException {
147        // prepare arrays with the first points
148        final double[] x = new double[maximalOrder + 1];
149        final double[] y = new double[maximalOrder + 1];
150        x[0] = getMin();
151        x[1] = getStartValue();
152        x[2] = getMax();
153        verifySequence(x[0], x[1], x[2]);
154
155        // evaluate initial guess
156        y[1] = computeObjectiveValue(x[1]);
157        if (Precision.equals(y[1], 0.0, 1)) {
158            // return the initial guess if it is a perfect root.
159            return x[1];
160        }
161
162        // evaluate first  endpoint
163        y[0] = computeObjectiveValue(x[0]);
164        if (Precision.equals(y[0], 0.0, 1)) {
165            // return the first endpoint if it is a perfect root.
166            return x[0];
167        }
168
169        int nbPoints;
170        int signChangeIndex;
171        if (y[0] * y[1] < 0) {
172
173            // reduce interval if it brackets the root
174            nbPoints        = 2;
175            signChangeIndex = 1;
176        } else {
177
178            // evaluate second endpoint
179            y[2] = computeObjectiveValue(x[2]);
180            if (Precision.equals(y[2], 0.0, 1)) {
181                // return the second endpoint if it is a perfect root.
182                return x[2];
183            }
184
185            if (y[1] * y[2] < 0) {
186                // use all computed point as a start sampling array for solving
187                nbPoints        = 3;
188                signChangeIndex = 2;
189            } else {
190                throw new NoBracketingException(x[0], x[2], y[0], y[2]);
191            }
192        }
193
194        // prepare a work array for inverse polynomial interpolation
195        final double[] tmpX = new double[x.length];
196
197        // current tightest bracketing of the root
198        double xA    = x[signChangeIndex - 1];
199        double yA    = y[signChangeIndex - 1];
200        double absYA = JdkMath.abs(yA);
201        int agingA   = 0;
202        double xB    = x[signChangeIndex];
203        double yB    = y[signChangeIndex];
204        double absYB = JdkMath.abs(yB);
205        int agingB   = 0;
206
207        // search loop
208        while (true) {
209
210            // check convergence of bracketing interval
211            final double xTol = getAbsoluteAccuracy() +
212                                getRelativeAccuracy() * JdkMath.max(JdkMath.abs(xA), JdkMath.abs(xB));
213            if (xB - xA <= xTol || JdkMath.max(absYA, absYB) < getFunctionValueAccuracy()) {
214                switch (allowed) {
215                case ANY_SIDE :
216                    return absYA < absYB ? xA : xB;
217                case LEFT_SIDE :
218                    return xA;
219                case RIGHT_SIDE :
220                    return xB;
221                case BELOW_SIDE :
222                    return (yA <= 0) ? xA : xB;
223                case ABOVE_SIDE :
224                    return (yA <  0) ? xB : xA;
225                default :
226                    // this should never happen
227                    throw new MathInternalError();
228                }
229            }
230
231            // target for the next evaluation point
232            double targetY;
233            if (agingA >= MAXIMAL_AGING) {
234                // we keep updating the high bracket, try to compensate this
235                final int p = agingA - MAXIMAL_AGING;
236                final double weightA = (1 << p) - 1;
237                final double weightB = p + 1;
238                targetY = (weightA * yA - weightB * REDUCTION_FACTOR * yB) / (weightA + weightB);
239            } else if (agingB >= MAXIMAL_AGING) {
240                // we keep updating the low bracket, try to compensate this
241                final int p = agingB - MAXIMAL_AGING;
242                final double weightA = p + 1;
243                final double weightB = (1 << p) - 1;
244                targetY = (weightB * yB - weightA * REDUCTION_FACTOR * yA) / (weightA + weightB);
245            } else {
246                // bracketing is balanced, try to find the root itself
247                targetY = 0;
248            }
249
250            // make a few attempts to guess a root,
251            double nextX;
252            int start = 0;
253            int end   = nbPoints;
254            do {
255
256                // guess a value for current target, using inverse polynomial interpolation
257                System.arraycopy(x, start, tmpX, start, end - start);
258                nextX = guessX(targetY, tmpX, y, start, end);
259
260                if (!(nextX > xA && nextX < xB)) {
261                    // the guessed root is not strictly inside of the tightest bracketing interval
262
263                    // the guessed root is either not strictly inside the interval or it
264                    // is a NaN (which occurs when some sampling points share the same y)
265                    // we try again with a lower interpolation order
266                    if (signChangeIndex - start >= end - signChangeIndex) {
267                        // we have more points before the sign change, drop the lowest point
268                        ++start;
269                    } else {
270                        // we have more points after sign change, drop the highest point
271                        --end;
272                    }
273
274                    // we need to do one more attempt
275                    nextX = Double.NaN;
276                }
277            } while (Double.isNaN(nextX) && end - start > 1);
278
279            if (Double.isNaN(nextX)) {
280                // fall back to bisection
281                nextX = xA + 0.5 * (xB - xA);
282                start = signChangeIndex - 1;
283                end   = signChangeIndex;
284            }
285
286            // evaluate the function at the guessed root
287            final double nextY = computeObjectiveValue(nextX);
288            if (Precision.equals(nextY, 0.0, 1)) {
289                // we have found an exact root, since it is not an approximation
290                // we don't need to bother about the allowed solutions setting
291                return nextX;
292            }
293
294            if (nbPoints > 2 && end - start != nbPoints) {
295
296                // we have been forced to ignore some points to keep bracketing,
297                // they are probably too far from the root, drop them from now on
298                nbPoints = end - start;
299                System.arraycopy(x, start, x, 0, nbPoints);
300                System.arraycopy(y, start, y, 0, nbPoints);
301                signChangeIndex -= start;
302            } else  if (nbPoints == x.length) {
303
304                // we have to drop one point in order to insert the new one
305                nbPoints--;
306
307                // keep the tightest bracketing interval as centered as possible
308                if (signChangeIndex >= (x.length + 1) / 2) {
309                    // we drop the lowest point, we have to shift the arrays and the index
310                    System.arraycopy(x, 1, x, 0, nbPoints);
311                    System.arraycopy(y, 1, y, 0, nbPoints);
312                    --signChangeIndex;
313                }
314            }
315
316            // insert the last computed point
317            //(by construction, we know it lies inside the tightest bracketing interval)
318            System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
319            x[signChangeIndex] = nextX;
320            System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
321            y[signChangeIndex] = nextY;
322            ++nbPoints;
323
324            // update the bracketing interval
325            if (nextY * yA <= 0) {
326                // the sign change occurs before the inserted point
327                xB = nextX;
328                yB = nextY;
329                absYB = JdkMath.abs(yB);
330                ++agingA;
331                agingB = 0;
332            } else {
333                // the sign change occurs after the inserted point
334                xA = nextX;
335                yA = nextY;
336                absYA = JdkMath.abs(yA);
337                agingA = 0;
338                ++agingB;
339
340                // update the sign change index
341                signChangeIndex++;
342            }
343        }
344    }
345
346    /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
347     * <p>
348     * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
349     * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
350     * Q(y<sub>i</sub>) = x<sub>i</sub>.
351     * </p>
352     * @param targetY target value for y
353     * @param x reference points abscissas for interpolation,
354     * note that this array <em>is</em> modified during computation
355     * @param y reference points ordinates for interpolation
356     * @param start start index of the points to consider (inclusive)
357     * @param end end index of the points to consider (exclusive)
358     * @return guessed root (will be a NaN if two points share the same y)
359     */
360    private double guessX(final double targetY, final double[] x, final double[] y,
361                          final int start, final int end) {
362
363        // compute Q Newton coefficients by divided differences
364        for (int i = start; i < end - 1; ++i) {
365            final int delta = i + 1 - start;
366            for (int j = end - 1; j > i; --j) {
367                x[j] = (x[j] - x[j-1]) / (y[j] - y[j - delta]);
368            }
369        }
370
371        // evaluate Q(targetY)
372        double x0 = 0;
373        for (int j = end - 1; j >= start; --j) {
374            x0 = x[j] + x0 * (targetY - y[j]);
375        }
376
377        return x0;
378    }
379
380    /** {@inheritDoc} */
381    @Override
382    public double solve(int maxEval, UnivariateFunction f, double min,
383                        double max, AllowedSolution allowedSolution)
384        throws TooManyEvaluationsException,
385               NumberIsTooLargeException,
386               NoBracketingException {
387        this.allowed = allowedSolution;
388        return super.solve(maxEval, f, min, max);
389    }
390
391    /** {@inheritDoc} */
392    @Override
393    public double solve(int maxEval, UnivariateFunction f, double min,
394                        double max, double startValue,
395                        AllowedSolution allowedSolution)
396        throws TooManyEvaluationsException,
397               NumberIsTooLargeException,
398               NoBracketingException {
399        this.allowed = allowedSolution;
400        return super.solve(maxEval, f, min, max, startValue);
401    }
402}