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.math3.analysis.solvers;
018
019
020import org.apache.commons.math3.Field;
021import org.apache.commons.math3.RealFieldElement;
022import org.apache.commons.math3.analysis.RealFieldUnivariateFunction;
023import org.apache.commons.math3.exception.MathInternalError;
024import org.apache.commons.math3.exception.NoBracketingException;
025import org.apache.commons.math3.exception.NullArgumentException;
026import org.apache.commons.math3.exception.NumberIsTooSmallException;
027import org.apache.commons.math3.util.IntegerSequence;
028import org.apache.commons.math3.util.MathArrays;
029import org.apache.commons.math3.util.MathUtils;
030import org.apache.commons.math3.util.Precision;
031
032/**
033 * This class implements a modification of the <a
034 * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
035 * <p>
036 * The changes with respect to the original Brent algorithm are:
037 * <ul>
038 *   <li>the returned value is chosen in the current interval according
039 *   to user specified {@link AllowedSolution}</li>
040 *   <li>the maximal order for the invert polynomial root search is
041 *   user-specified instead of being invert quadratic only</li>
042 * </ul><p>
043 * The given interval must bracket the root.</p>
044 *
045 * @param <T> the type of the field elements
046 * @since 3.6
047 */
048public class FieldBracketingNthOrderBrentSolver<T extends RealFieldElement<T>>
049    implements BracketedRealFieldUnivariateSolver<T> {
050
051   /** Maximal aging triggering an attempt to balance the bracketing interval. */
052    private static final int MAXIMAL_AGING = 2;
053
054    /** Field to which the elements belong. */
055    private final Field<T> field;
056
057    /** Maximal order. */
058    private final int maximalOrder;
059
060    /** Function value accuracy. */
061    private final T functionValueAccuracy;
062
063    /** Absolute accuracy. */
064    private final T absoluteAccuracy;
065
066    /** Relative accuracy. */
067    private final T relativeAccuracy;
068
069    /** Evaluations counter. */
070    private IntegerSequence.Incrementor evaluations;
071
072    /**
073     * Construct a solver.
074     *
075     * @param relativeAccuracy Relative accuracy.
076     * @param absoluteAccuracy Absolute accuracy.
077     * @param functionValueAccuracy Function value accuracy.
078     * @param maximalOrder maximal order.
079     * @exception NumberIsTooSmallException if maximal order is lower than 2
080     */
081    public FieldBracketingNthOrderBrentSolver(final T relativeAccuracy,
082                                              final T absoluteAccuracy,
083                                              final T functionValueAccuracy,
084                                              final int maximalOrder)
085        throws NumberIsTooSmallException {
086        if (maximalOrder < 2) {
087            throw new NumberIsTooSmallException(maximalOrder, 2, true);
088        }
089        this.field                 = relativeAccuracy.getField();
090        this.maximalOrder          = maximalOrder;
091        this.absoluteAccuracy      = absoluteAccuracy;
092        this.relativeAccuracy      = relativeAccuracy;
093        this.functionValueAccuracy = functionValueAccuracy;
094        this.evaluations           = IntegerSequence.Incrementor.create();
095    }
096
097    /** Get the maximal order.
098     * @return maximal order
099     */
100    public int getMaximalOrder() {
101        return maximalOrder;
102    }
103
104    /**
105     * Get the maximal number of function evaluations.
106     *
107     * @return the maximal number of function evaluations.
108     */
109    public int getMaxEvaluations() {
110        return evaluations.getMaximalCount();
111    }
112
113    /**
114     * Get the number of evaluations of the objective function.
115     * The number of evaluations corresponds to the last call to the
116     * {@code optimize} method. It is 0 if the method has not been
117     * called yet.
118     *
119     * @return the number of evaluations of the objective function.
120     */
121    public int getEvaluations() {
122        return evaluations.getCount();
123    }
124
125    /**
126     * Get the absolute accuracy.
127     * @return absolute accuracy
128     */
129    public T getAbsoluteAccuracy() {
130        return absoluteAccuracy;
131    }
132
133    /**
134     * Get the relative accuracy.
135     * @return relative accuracy
136     */
137    public T getRelativeAccuracy() {
138        return relativeAccuracy;
139    }
140
141    /**
142     * Get the function accuracy.
143     * @return function accuracy
144     */
145    public T getFunctionValueAccuracy() {
146        return functionValueAccuracy;
147    }
148
149    /**
150     * Solve for a zero in the given interval.
151     * A solver may require that the interval brackets a single zero root.
152     * Solvers that do require bracketing should be able to handle the case
153     * where one of the endpoints is itself a root.
154     *
155     * @param maxEval Maximum number of evaluations.
156     * @param f Function to solve.
157     * @param min Lower bound for the interval.
158     * @param max Upper bound for the interval.
159     * @param allowedSolution The kind of solutions that the root-finding algorithm may
160     * accept as solutions.
161     * @return a value where the function is zero.
162     * @exception NullArgumentException if f is null.
163     * @exception NoBracketingException if root cannot be bracketed
164     */
165    public T solve(final int maxEval, final RealFieldUnivariateFunction<T> f,
166                   final T min, final T max, final AllowedSolution allowedSolution)
167        throws NullArgumentException, NoBracketingException {
168        return solve(maxEval, f, min, max, min.add(max).divide(2), allowedSolution);
169    }
170
171    /**
172     * Solve for a zero in the given interval, start at {@code startValue}.
173     * A solver may require that the interval brackets a single zero root.
174     * Solvers that do require bracketing should be able to handle the case
175     * where one of the endpoints is itself a root.
176     *
177     * @param maxEval Maximum number of evaluations.
178     * @param f Function to solve.
179     * @param min Lower bound for the interval.
180     * @param max Upper bound for the interval.
181     * @param startValue Start value to use.
182     * @param allowedSolution The kind of solutions that the root-finding algorithm may
183     * accept as solutions.
184     * @return a value where the function is zero.
185     * @exception NullArgumentException if f is null.
186     * @exception NoBracketingException if root cannot be bracketed
187     */
188    public T solve(final int maxEval, final RealFieldUnivariateFunction<T> f,
189                   final T min, final T max, final T startValue,
190                   final AllowedSolution allowedSolution)
191        throws NullArgumentException, NoBracketingException {
192
193        // Checks.
194        MathUtils.checkNotNull(f);
195
196        // Reset.
197        evaluations = evaluations.withMaximalCount(maxEval).withStart(0);
198        T zero = field.getZero();
199        T nan  = zero.add(Double.NaN);
200
201        // prepare arrays with the first points
202        final T[] x = MathArrays.buildArray(field, maximalOrder + 1);
203        final T[] y = MathArrays.buildArray(field, maximalOrder + 1);
204        x[0] = min;
205        x[1] = startValue;
206        x[2] = max;
207
208        // evaluate initial guess
209        evaluations.increment();
210        y[1] = f.value(x[1]);
211        if (Precision.equals(y[1].getReal(), 0.0, 1)) {
212            // return the initial guess if it is a perfect root.
213            return x[1];
214        }
215
216        // evaluate first endpoint
217        evaluations.increment();
218        y[0] = f.value(x[0]);
219        if (Precision.equals(y[0].getReal(), 0.0, 1)) {
220            // return the first endpoint if it is a perfect root.
221            return x[0];
222        }
223
224        int nbPoints;
225        int signChangeIndex;
226        if (y[0].multiply(y[1]).getReal() < 0) {
227
228            // reduce interval if it brackets the root
229            nbPoints        = 2;
230            signChangeIndex = 1;
231
232        } else {
233
234            // evaluate second endpoint
235            evaluations.increment();
236            y[2] = f.value(x[2]);
237            if (Precision.equals(y[2].getReal(), 0.0, 1)) {
238                // return the second endpoint if it is a perfect root.
239                return x[2];
240            }
241
242            if (y[1].multiply(y[2]).getReal() < 0) {
243                // use all computed point as a start sampling array for solving
244                nbPoints        = 3;
245                signChangeIndex = 2;
246            } else {
247                throw new NoBracketingException(x[0].getReal(), x[2].getReal(),
248                                                y[0].getReal(), y[2].getReal());
249            }
250
251        }
252
253        // prepare a work array for inverse polynomial interpolation
254        final T[] tmpX = MathArrays.buildArray(field, x.length);
255
256        // current tightest bracketing of the root
257        T xA    = x[signChangeIndex - 1];
258        T yA    = y[signChangeIndex - 1];
259        T absXA = xA.abs();
260        T absYA = yA.abs();
261        int agingA   = 0;
262        T xB    = x[signChangeIndex];
263        T yB    = y[signChangeIndex];
264        T absXB = xB.abs();
265        T absYB = yB.abs();
266        int agingB   = 0;
267
268        // search loop
269        while (true) {
270
271            // check convergence of bracketing interval
272            T maxX = absXA.subtract(absXB).getReal() < 0 ? absXB : absXA;
273            T maxY = absYA.subtract(absYB).getReal() < 0 ? absYB : absYA;
274            final T xTol = absoluteAccuracy.add(relativeAccuracy.multiply(maxX));
275            if (xB.subtract(xA).subtract(xTol).getReal() <= 0 ||
276                maxY.subtract(functionValueAccuracy).getReal() < 0) {
277                switch (allowedSolution) {
278                case ANY_SIDE :
279                    return absYA.subtract(absYB).getReal() < 0 ? xA : xB;
280                case LEFT_SIDE :
281                    return xA;
282                case RIGHT_SIDE :
283                    return xB;
284                case BELOW_SIDE :
285                    return yA.getReal() <= 0 ? xA : xB;
286                case ABOVE_SIDE :
287                    return yA.getReal() < 0 ? xB : xA;
288                default :
289                    // this should never happen
290                    throw new MathInternalError(null);
291                }
292            }
293
294            // target for the next evaluation point
295            T targetY;
296            if (agingA >= MAXIMAL_AGING) {
297                // we keep updating the high bracket, try to compensate this
298                targetY = yB.divide(16).negate();
299            } else if (agingB >= MAXIMAL_AGING) {
300                // we keep updating the low bracket, try to compensate this
301                targetY = yA.divide(16).negate();
302            } else {
303                // bracketing is balanced, try to find the root itself
304                targetY = zero;
305            }
306
307            // make a few attempts to guess a root,
308            T nextX;
309            int start = 0;
310            int end   = nbPoints;
311            do {
312
313                // guess a value for current target, using inverse polynomial interpolation
314                System.arraycopy(x, start, tmpX, start, end - start);
315                nextX = guessX(targetY, tmpX, y, start, end);
316
317                if (!((nextX.subtract(xA).getReal() > 0) && (nextX.subtract(xB).getReal() < 0))) {
318                    // the guessed root is not strictly inside of the tightest bracketing interval
319
320                    // the guessed root is either not strictly inside the interval or it
321                    // is a NaN (which occurs when some sampling points share the same y)
322                    // we try again with a lower interpolation order
323                    if (signChangeIndex - start >= end - signChangeIndex) {
324                        // we have more points before the sign change, drop the lowest point
325                        ++start;
326                    } else {
327                        // we have more points after sign change, drop the highest point
328                        --end;
329                    }
330
331                    // we need to do one more attempt
332                    nextX = nan;
333
334                }
335
336            } while (Double.isNaN(nextX.getReal()) && (end - start > 1));
337
338            if (Double.isNaN(nextX.getReal())) {
339                // fall back to bisection
340                nextX = xA.add(xB.subtract(xA).divide(2));
341                start = signChangeIndex - 1;
342                end   = signChangeIndex;
343            }
344
345            // evaluate the function at the guessed root
346            evaluations.increment();
347            final T nextY = f.value(nextX);
348            if (Precision.equals(nextY.getReal(), 0.0, 1)) {
349                // we have found an exact root, since it is not an approximation
350                // we don't need to bother about the allowed solutions setting
351                return nextX;
352            }
353
354            if ((nbPoints > 2) && (end - start != nbPoints)) {
355
356                // we have been forced to ignore some points to keep bracketing,
357                // they are probably too far from the root, drop them from now on
358                nbPoints = end - start;
359                System.arraycopy(x, start, x, 0, nbPoints);
360                System.arraycopy(y, start, y, 0, nbPoints);
361                signChangeIndex -= start;
362
363            } else  if (nbPoints == x.length) {
364
365                // we have to drop one point in order to insert the new one
366                nbPoints--;
367
368                // keep the tightest bracketing interval as centered as possible
369                if (signChangeIndex >= (x.length + 1) / 2) {
370                    // we drop the lowest point, we have to shift the arrays and the index
371                    System.arraycopy(x, 1, x, 0, nbPoints);
372                    System.arraycopy(y, 1, y, 0, nbPoints);
373                    --signChangeIndex;
374                }
375
376            }
377
378            // insert the last computed point
379            //(by construction, we know it lies inside the tightest bracketing interval)
380            System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
381            x[signChangeIndex] = nextX;
382            System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
383            y[signChangeIndex] = nextY;
384            ++nbPoints;
385
386            // update the bracketing interval
387            if (nextY.multiply(yA).getReal() <= 0) {
388                // the sign change occurs before the inserted point
389                xB = nextX;
390                yB = nextY;
391                absYB = yB.abs();
392                ++agingA;
393                agingB = 0;
394            } else {
395                // the sign change occurs after the inserted point
396                xA = nextX;
397                yA = nextY;
398                absYA = yA.abs();
399                agingA = 0;
400                ++agingB;
401
402                // update the sign change index
403                signChangeIndex++;
404
405            }
406
407        }
408
409    }
410
411    /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
412     * <p>
413     * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
414     * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
415     * Q(y<sub>i</sub>) = x<sub>i</sub>.
416     * </p>
417     * @param targetY target value for y
418     * @param x reference points abscissas for interpolation,
419     * note that this array <em>is</em> modified during computation
420     * @param y reference points ordinates for interpolation
421     * @param start start index of the points to consider (inclusive)
422     * @param end end index of the points to consider (exclusive)
423     * @return guessed root (will be a NaN if two points share the same y)
424     */
425    private T guessX(final T targetY, final T[] x, final T[] y,
426                     final int start, final int end) {
427
428        // compute Q Newton coefficients by divided differences
429        for (int i = start; i < end - 1; ++i) {
430            final int delta = i + 1 - start;
431            for (int j = end - 1; j > i; --j) {
432                x[j] = x[j].subtract(x[j-1]).divide(y[j].subtract(y[j - delta]));
433            }
434        }
435
436        // evaluate Q(targetY)
437        T x0 = field.getZero();
438        for (int j = end - 1; j >= start; --j) {
439            x0 = x[j].add(x0.multiply(targetY.subtract(y[j])));
440        }
441
442        return x0;
443
444    }
445
446}