View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.math4.legacy.analysis.solvers;
18  
19  import org.apache.commons.numbers.complex.Complex;
20  import org.apache.commons.math4.legacy.analysis.polynomials.PolynomialFunction;
21  import org.apache.commons.math4.legacy.exception.NoBracketingException;
22  import org.apache.commons.math4.legacy.exception.NoDataException;
23  import org.apache.commons.math4.legacy.exception.NullArgumentException;
24  import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
25  import org.apache.commons.math4.legacy.exception.TooManyEvaluationsException;
26  import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
27  import org.apache.commons.math4.core.jdkmath.JdkMath;
28  
29  /**
30   * Implements the <a href="http://mathworld.wolfram.com/LaguerresMethod.html">
31   * Laguerre's Method</a> for root finding of real coefficient polynomials.
32   * For reference, see
33   * <blockquote>
34   *  <b>A First Course in Numerical Analysis</b>,
35   *  ISBN 048641454X, chapter 8.
36   * </blockquote>
37   * Laguerre's method is global in the sense that it can start with any initial
38   * approximation and be able to solve all roots from that point.
39   * The algorithm requires a bracketing condition.
40   *
41   * @since 1.2
42   */
43  public class LaguerreSolver extends AbstractPolynomialSolver {
44      /** Default absolute accuracy. */
45      private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
46      /** Complex solver. */
47      private final ComplexSolver complexSolver = new ComplexSolver();
48  
49      /**
50       * Construct a solver with default accuracy (1e-6).
51       */
52      public LaguerreSolver() {
53          this(DEFAULT_ABSOLUTE_ACCURACY);
54      }
55      /**
56       * Construct a solver.
57       *
58       * @param absoluteAccuracy Absolute accuracy.
59       */
60      public LaguerreSolver(double absoluteAccuracy) {
61          super(absoluteAccuracy);
62      }
63      /**
64       * Construct a solver.
65       *
66       * @param relativeAccuracy Relative accuracy.
67       * @param absoluteAccuracy Absolute accuracy.
68       */
69      public LaguerreSolver(double relativeAccuracy,
70                            double absoluteAccuracy) {
71          super(relativeAccuracy, absoluteAccuracy);
72      }
73      /**
74       * Construct a solver.
75       *
76       * @param relativeAccuracy Relative accuracy.
77       * @param absoluteAccuracy Absolute accuracy.
78       * @param functionValueAccuracy Function value accuracy.
79       */
80      public LaguerreSolver(double relativeAccuracy,
81                            double absoluteAccuracy,
82                            double functionValueAccuracy) {
83          super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
84      }
85  
86      /**
87       * {@inheritDoc}
88       */
89      @Override
90      public double doSolve()
91          throws TooManyEvaluationsException,
92                 NumberIsTooLargeException,
93                 NoBracketingException {
94          final double min = getMin();
95          final double max = getMax();
96          final double initial = getStartValue();
97          final double functionValueAccuracy = getFunctionValueAccuracy();
98  
99          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 final 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 }