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