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
019import org.apache.commons.numbers.complex.Complex;
020import org.apache.commons.math4.legacy.analysis.polynomials.PolynomialFunction;
021import org.apache.commons.math4.legacy.exception.NoBracketingException;
022import org.apache.commons.math4.legacy.exception.NoDataException;
023import org.apache.commons.math4.legacy.exception.NullArgumentException;
024import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
025import org.apache.commons.math4.legacy.exception.TooManyEvaluationsException;
026import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
027import org.apache.commons.math4.core.jdkmath.JdkMath;
028
029/**
030 * Implements the <a href="http://mathworld.wolfram.com/LaguerresMethod.html">
031 * Laguerre's Method</a> for root finding of real coefficient polynomials.
032 * For reference, see
033 * <blockquote>
034 *  <b>A First Course in Numerical Analysis</b>,
035 *  ISBN 048641454X, chapter 8.
036 * </blockquote>
037 * Laguerre's method is global in the sense that it can start with any initial
038 * approximation and be able to solve all roots from that point.
039 * The algorithm requires a bracketing condition.
040 *
041 * @since 1.2
042 */
043public class LaguerreSolver extends AbstractPolynomialSolver {
044    /** Default absolute accuracy. */
045    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
046    /** Complex solver. */
047    private final ComplexSolver complexSolver = new ComplexSolver();
048
049    /**
050     * Construct a solver with default accuracy (1e-6).
051     */
052    public LaguerreSolver() {
053        this(DEFAULT_ABSOLUTE_ACCURACY);
054    }
055    /**
056     * Construct a solver.
057     *
058     * @param absoluteAccuracy Absolute accuracy.
059     */
060    public LaguerreSolver(double absoluteAccuracy) {
061        super(absoluteAccuracy);
062    }
063    /**
064     * Construct a solver.
065     *
066     * @param relativeAccuracy Relative accuracy.
067     * @param absoluteAccuracy Absolute accuracy.
068     */
069    public LaguerreSolver(double relativeAccuracy,
070                          double absoluteAccuracy) {
071        super(relativeAccuracy, absoluteAccuracy);
072    }
073    /**
074     * Construct a solver.
075     *
076     * @param relativeAccuracy Relative accuracy.
077     * @param absoluteAccuracy Absolute accuracy.
078     * @param functionValueAccuracy Function value accuracy.
079     */
080    public LaguerreSolver(double relativeAccuracy,
081                          double absoluteAccuracy,
082                          double functionValueAccuracy) {
083        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
084    }
085
086    /**
087     * {@inheritDoc}
088     */
089    @Override
090    public double doSolve()
091        throws TooManyEvaluationsException,
092               NumberIsTooLargeException,
093               NoBracketingException {
094        final double min = getMin();
095        final double max = getMax();
096        final double initial = getStartValue();
097        final double functionValueAccuracy = getFunctionValueAccuracy();
098
099        verifySequence(min, initial, max);
100
101        // Return the initial guess if it is good enough.
102        final double yInitial = computeObjectiveValue(initial);
103        if (JdkMath.abs(yInitial) <= functionValueAccuracy) {
104            return initial;
105        }
106
107        // Return the first endpoint if it is good enough.
108        final double yMin = computeObjectiveValue(min);
109        if (JdkMath.abs(yMin) <= functionValueAccuracy) {
110            return min;
111        }
112
113        // Reduce interval if min and initial bracket the root.
114        if (yInitial * yMin < 0) {
115            return laguerre(min, initial);
116        }
117
118        // Return the second endpoint if it is good enough.
119        final double yMax = computeObjectiveValue(max);
120        if (JdkMath.abs(yMax) <= functionValueAccuracy) {
121            return max;
122        }
123
124        // Reduce interval if initial and max bracket the root.
125        if (yInitial * yMax < 0) {
126            return laguerre(initial, max);
127        }
128
129        throw new NoBracketingException(min, max, yMin, yMax);
130    }
131
132    /**
133     * Find a real root in the given interval.
134     *
135     * Despite the bracketing condition, the root returned by
136     * {@link LaguerreSolver.ComplexSolver#solve(Complex[],Complex)} may
137     * not be a real zero inside {@code [min, max]}.
138     * For example, <code> p(x) = x<sup>3</sup> + 1, </code>
139     * with {@code min = -2}, {@code max = 2}, {@code initial = 0}.
140     * When it occurs, this code calls
141     * {@link LaguerreSolver.ComplexSolver#solveAll(Complex[],Complex)}
142     * in order to obtain all roots and picks up one real root.
143     *
144     * @param lo Lower bound of the search interval.
145     * @param hi Higher bound of the search interval.
146     * @return the point at which the function value is zero.
147     */
148    private double laguerre(double lo, double hi) {
149        final Complex[] c = real2Complex(getCoefficients());
150
151        final Complex initial = Complex.ofCartesian(0.5 * (lo + hi), 0);
152        final Complex z = complexSolver.solve(c, initial);
153        if (complexSolver.isRoot(lo, hi, z)) {
154            return z.getReal();
155        } else {
156            double r = Double.NaN;
157            // Solve all roots and select the one we are seeking.
158            Complex[] root = complexSolver.solveAll(c, initial);
159            for (int i = 0; i < root.length; i++) {
160                if (complexSolver.isRoot(lo, hi, root[i])) {
161                    r = root[i].getReal();
162                    break;
163                }
164            }
165            return r;
166        }
167    }
168
169    /**
170     * Find all complex roots for the polynomial with the given
171     * coefficients, starting from the given initial value.
172     * <p>
173     * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
174     *
175     * @param coefficients Polynomial coefficients.
176     * @param initial Start value.
177     * @return the point at which the function value is zero.
178     * @throws org.apache.commons.math4.legacy.exception.TooManyEvaluationsException
179     * if the maximum number of evaluations is exceeded.
180     * @throws NullArgumentException if the {@code coefficients} is
181     * {@code null}.
182     * @throws NoDataException if the {@code coefficients} array is empty.
183     * @since 3.1
184     */
185    public Complex[] solveAllComplex(double[] coefficients,
186                                     double initial)
187        throws NullArgumentException,
188               NoDataException,
189               TooManyEvaluationsException {
190        setup(Integer.MAX_VALUE,
191              new PolynomialFunction(coefficients),
192              Double.NEGATIVE_INFINITY,
193              Double.POSITIVE_INFINITY,
194              initial);
195        return complexSolver.solveAll(real2Complex(coefficients),
196                                      Complex.ofCartesian(initial, 0d));
197    }
198
199    /**
200     * Find a complex root for the polynomial with the given coefficients,
201     * starting from the given initial value.
202     * <p>
203     * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
204     *
205     * @param coefficients Polynomial coefficients.
206     * @param initial Start value.
207     * @return the point at which the function value is zero.
208     * @throws org.apache.commons.math4.legacy.exception.TooManyEvaluationsException
209     * if the maximum number of evaluations is exceeded.
210     * @throws NullArgumentException if the {@code coefficients} is
211     * {@code null}.
212     * @throws NoDataException if the {@code coefficients} array is empty.
213     * @since 3.1
214     */
215    public Complex solveComplex(double[] coefficients,
216                                double initial)
217        throws NullArgumentException,
218               NoDataException,
219               TooManyEvaluationsException {
220        setup(Integer.MAX_VALUE,
221              new PolynomialFunction(coefficients),
222              Double.NEGATIVE_INFINITY,
223              Double.POSITIVE_INFINITY,
224              initial);
225        return complexSolver.solve(real2Complex(coefficients),
226                                   Complex.ofCartesian(initial, 0d));
227    }
228
229    /**
230     * Class for searching all (complex) roots.
231     */
232    private class ComplexSolver {
233        /**
234         * Check whether the given complex root is actually a real zero
235         * in the given interval, within the solver tolerance level.
236         *
237         * @param min Lower bound for the interval.
238         * @param max Upper bound for the interval.
239         * @param z Complex root.
240         * @return {@code true} if z is a real zero.
241         */
242        public boolean isRoot(double min, double max, Complex z) {
243            if (isSequence(min, z.getReal(), max)) {
244                double tolerance = JdkMath.max(getRelativeAccuracy() * z.abs(), getAbsoluteAccuracy());
245                return JdkMath.abs(z.getImaginary()) <= tolerance ||
246                     z.abs() <= getFunctionValueAccuracy();
247            }
248            return false;
249        }
250
251        /**
252         * Find all complex roots for the polynomial with the given
253         * coefficients, starting from the given initial value.
254         *
255         * @param coefficients Polynomial coefficients.
256         * @param initial Start value.
257         * @return the point at which the function value is zero.
258         * @throws org.apache.commons.math4.legacy.exception.TooManyEvaluationsException
259         * if the maximum number of evaluations is exceeded.
260         * @throws NullArgumentException if the {@code coefficients} is
261         * {@code null}.
262         * @throws NoDataException if the {@code coefficients} array is empty.
263         */
264        public Complex[] solveAll(Complex[] coefficients, Complex initial)
265            throws NullArgumentException,
266                   NoDataException,
267                   TooManyEvaluationsException {
268            if (coefficients == null) {
269                throw new NullArgumentException();
270            }
271            final int n = coefficients.length - 1;
272            if (n == 0) {
273                throw new NoDataException(LocalizedFormats.POLYNOMIAL);
274            }
275            // Coefficients for deflated polynomial.
276            final Complex[] c = coefficients.clone();
277
278            // Solve individual roots successively.
279            final Complex[] root = new Complex[n];
280            for (int i = 0; i < n; i++) {
281                final Complex[] subarray = new Complex[n - i + 1];
282                System.arraycopy(c, 0, subarray, 0, subarray.length);
283                root[i] = solve(subarray, initial);
284                // Polynomial deflation using synthetic division.
285                Complex newc = c[n - i];
286                Complex oldc = null;
287                for (int j = n - i - 1; j >= 0; j--) {
288                    oldc = c[j];
289                    c[j] = newc;
290                    newc = oldc.add(newc.multiply(root[i]));
291                }
292            }
293
294            return root;
295        }
296
297        /**
298         * Find a complex root for the polynomial with the given coefficients,
299         * starting from the given initial value.
300         *
301         * @param coefficients Polynomial coefficients.
302         * @param initial Start value.
303         * @return the point at which the function value is zero.
304         * @throws org.apache.commons.math4.legacy.exception.TooManyEvaluationsException
305         * if the maximum number of evaluations is exceeded.
306         * @throws NullArgumentException if the {@code coefficients} is
307         * {@code null}.
308         * @throws NoDataException if the {@code coefficients} array is empty.
309         */
310        public Complex solve(Complex[] coefficients, Complex initial)
311            throws NullArgumentException,
312                   NoDataException,
313                   TooManyEvaluationsException {
314            if (coefficients == null) {
315                throw new NullArgumentException();
316            }
317
318            final int n = coefficients.length - 1;
319            if (n == 0) {
320                throw new NoDataException(LocalizedFormats.POLYNOMIAL);
321            }
322
323            final double absoluteAccuracy = getAbsoluteAccuracy();
324            final double relativeAccuracy = getRelativeAccuracy();
325            final double functionValueAccuracy = getFunctionValueAccuracy();
326
327            final Complex nC  = Complex.ofCartesian(n, 0);
328            final Complex n1C = Complex.ofCartesian(n - 1, 0);
329
330            Complex z = initial;
331            Complex oldz = Complex.ofCartesian(Double.POSITIVE_INFINITY,
332                                       Double.POSITIVE_INFINITY);
333            while (true) {
334                // Compute pv (polynomial value), dv (derivative value), and
335                // d2v (second derivative value) simultaneously.
336                Complex pv = coefficients[n];
337                Complex dv = Complex.ZERO;
338                Complex d2v = Complex.ZERO;
339                for (int j = n-1; j >= 0; j--) {
340                    d2v = dv.add(z.multiply(d2v));
341                    dv = pv.add(z.multiply(dv));
342                    pv = coefficients[j].add(z.multiply(pv));
343                }
344                d2v = d2v.multiply(2);
345
346                // Check for convergence.
347                final double tolerance = JdkMath.max(relativeAccuracy * z.abs(),
348                                                      absoluteAccuracy);
349                if ((z.subtract(oldz)).abs() <= tolerance) {
350                    return z;
351                }
352                if (pv.abs() <= functionValueAccuracy) {
353                    return z;
354                }
355
356                // Now pv != 0, calculate the new approximation.
357                final Complex g = dv.divide(pv);
358                final Complex g2 = g.multiply(g);
359                final Complex h = g2.subtract(d2v.divide(pv));
360                final Complex delta = n1C.multiply((nC.multiply(h)).subtract(g2));
361                // Choose a denominator larger in magnitude.
362                final Complex deltaSqrt = delta.sqrt();
363                final Complex dplus = g.add(deltaSqrt);
364                final Complex dminus = g.subtract(deltaSqrt);
365                final Complex denominator = dplus.abs() > dminus.abs() ? dplus : dminus;
366                // Perturb z if denominator is zero, for instance,
367                // p(x) = x^3 + 1, z = 0.
368                // This uses exact equality to zero. A tolerance may be required here.
369                if (denominator.equals(Complex.ZERO)) {
370                    z = z.add(Complex.ofCartesian(absoluteAccuracy, absoluteAccuracy));
371                    oldz = Complex.ofCartesian(Double.POSITIVE_INFINITY,
372                                       Double.POSITIVE_INFINITY);
373                } else {
374                    oldz = z;
375                    z = z.subtract(nC.divide(denominator));
376                }
377                incrementEvaluationCount();
378            }
379        }
380    }
381
382    /**
383     * Converts a {@code double[]} array to a {@code Complex[]} array.
384     *
385     * @param real array of numbers to be converted to their {@code Complex} equivalent
386     * @return {@code Complex} array
387     */
388    private static Complex[] real2Complex(double[] real) {
389        int index = 0;
390        final Complex[] c = new Complex[real.length];
391        for (final double d : real) {
392            c[index] = Complex.ofCartesian(d, 0);
393            index++;
394        }
395        return c;
396    }
397}