LaguerreSolver.java

  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. import org.apache.commons.numbers.complex.Complex;
  19. import org.apache.commons.math4.legacy.analysis.polynomials.PolynomialFunction;
  20. import org.apache.commons.math4.legacy.exception.NoBracketingException;
  21. import org.apache.commons.math4.legacy.exception.NoDataException;
  22. import org.apache.commons.math4.legacy.exception.NullArgumentException;
  23. import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
  24. import org.apache.commons.math4.legacy.exception.TooManyEvaluationsException;
  25. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
  26. import org.apache.commons.math4.core.jdkmath.JdkMath;

  27. /**
  28.  * Implements the <a href="http://mathworld.wolfram.com/LaguerresMethod.html">
  29.  * Laguerre's Method</a> for root finding of real coefficient polynomials.
  30.  * For reference, see
  31.  * <blockquote>
  32.  *  <b>A First Course in Numerical Analysis</b>,
  33.  *  ISBN 048641454X, chapter 8.
  34.  * </blockquote>
  35.  * Laguerre's method is global in the sense that it can start with any initial
  36.  * approximation and be able to solve all roots from that point.
  37.  * The algorithm requires a bracketing condition.
  38.  *
  39.  * @since 1.2
  40.  */
  41. public class LaguerreSolver extends AbstractPolynomialSolver {
  42.     /** Default absolute accuracy. */
  43.     private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
  44.     /** Complex solver. */
  45.     private final ComplexSolver complexSolver = new ComplexSolver();

  46.     /**
  47.      * Construct a solver with default accuracy (1e-6).
  48.      */
  49.     public LaguerreSolver() {
  50.         this(DEFAULT_ABSOLUTE_ACCURACY);
  51.     }
  52.     /**
  53.      * Construct a solver.
  54.      *
  55.      * @param absoluteAccuracy Absolute accuracy.
  56.      */
  57.     public LaguerreSolver(double absoluteAccuracy) {
  58.         super(absoluteAccuracy);
  59.     }
  60.     /**
  61.      * Construct a solver.
  62.      *
  63.      * @param relativeAccuracy Relative accuracy.
  64.      * @param absoluteAccuracy Absolute accuracy.
  65.      */
  66.     public LaguerreSolver(double relativeAccuracy,
  67.                           double absoluteAccuracy) {
  68.         super(relativeAccuracy, absoluteAccuracy);
  69.     }
  70.     /**
  71.      * Construct a solver.
  72.      *
  73.      * @param relativeAccuracy Relative accuracy.
  74.      * @param absoluteAccuracy Absolute accuracy.
  75.      * @param functionValueAccuracy Function value accuracy.
  76.      */
  77.     public LaguerreSolver(double relativeAccuracy,
  78.                           double absoluteAccuracy,
  79.                           double functionValueAccuracy) {
  80.         super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
  81.     }

  82.     /**
  83.      * {@inheritDoc}
  84.      */
  85.     @Override
  86.     public double doSolve()
  87.         throws TooManyEvaluationsException,
  88.                NumberIsTooLargeException,
  89.                NoBracketingException {
  90.         final double min = getMin();
  91.         final double max = getMax();
  92.         final double initial = getStartValue();
  93.         final double functionValueAccuracy = getFunctionValueAccuracy();

  94.         verifySequence(min, initial, max);

  95.         // Return the initial guess if it is good enough.
  96.         final double yInitial = computeObjectiveValue(initial);
  97.         if (JdkMath.abs(yInitial) <= functionValueAccuracy) {
  98.             return initial;
  99.         }

  100.         // Return the first endpoint if it is good enough.
  101.         final double yMin = computeObjectiveValue(min);
  102.         if (JdkMath.abs(yMin) <= functionValueAccuracy) {
  103.             return min;
  104.         }

  105.         // Reduce interval if min and initial bracket the root.
  106.         if (yInitial * yMin < 0) {
  107.             return laguerre(min, initial);
  108.         }

  109.         // Return the second endpoint if it is good enough.
  110.         final double yMax = computeObjectiveValue(max);
  111.         if (JdkMath.abs(yMax) <= functionValueAccuracy) {
  112.             return max;
  113.         }

  114.         // Reduce interval if initial and max bracket the root.
  115.         if (yInitial * yMax < 0) {
  116.             return laguerre(initial, max);
  117.         }

  118.         throw new NoBracketingException(min, max, yMin, yMax);
  119.     }

  120.     /**
  121.      * Find a real root in the given interval.
  122.      *
  123.      * Despite the bracketing condition, the root returned by
  124.      * {@link LaguerreSolver.ComplexSolver#solve(Complex[],Complex)} may
  125.      * not be a real zero inside {@code [min, max]}.
  126.      * For example, <code> p(x) = x<sup>3</sup> + 1, </code>
  127.      * with {@code min = -2}, {@code max = 2}, {@code initial = 0}.
  128.      * When it occurs, this code calls
  129.      * {@link LaguerreSolver.ComplexSolver#solveAll(Complex[],Complex)}
  130.      * in order to obtain all roots and picks up one real root.
  131.      *
  132.      * @param lo Lower bound of the search interval.
  133.      * @param hi Higher bound of the search interval.
  134.      * @return the point at which the function value is zero.
  135.      */
  136.     private double laguerre(double lo, double hi) {
  137.         final Complex[] c = real2Complex(getCoefficients());

  138.         final Complex initial = Complex.ofCartesian(0.5 * (lo + hi), 0);
  139.         final Complex z = complexSolver.solve(c, initial);
  140.         if (complexSolver.isRoot(lo, hi, z)) {
  141.             return z.getReal();
  142.         } else {
  143.             double r = Double.NaN;
  144.             // Solve all roots and select the one we are seeking.
  145.             Complex[] root = complexSolver.solveAll(c, initial);
  146.             for (int i = 0; i < root.length; i++) {
  147.                 if (complexSolver.isRoot(lo, hi, root[i])) {
  148.                     r = root[i].getReal();
  149.                     break;
  150.                 }
  151.             }
  152.             return r;
  153.         }
  154.     }

  155.     /**
  156.      * Find all complex roots for the polynomial with the given
  157.      * coefficients, starting from the given initial value.
  158.      * <p>
  159.      * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
  160.      *
  161.      * @param coefficients Polynomial coefficients.
  162.      * @param initial Start value.
  163.      * @return the point at which the function value is zero.
  164.      * @throws org.apache.commons.math4.legacy.exception.TooManyEvaluationsException
  165.      * if the maximum number of evaluations is exceeded.
  166.      * @throws NullArgumentException if the {@code coefficients} is
  167.      * {@code null}.
  168.      * @throws NoDataException if the {@code coefficients} array is empty.
  169.      * @since 3.1
  170.      */
  171.     public Complex[] solveAllComplex(double[] coefficients,
  172.                                      double initial)
  173.         throws NullArgumentException,
  174.                NoDataException,
  175.                TooManyEvaluationsException {
  176.         setup(Integer.MAX_VALUE,
  177.               new PolynomialFunction(coefficients),
  178.               Double.NEGATIVE_INFINITY,
  179.               Double.POSITIVE_INFINITY,
  180.               initial);
  181.         return complexSolver.solveAll(real2Complex(coefficients),
  182.                                       Complex.ofCartesian(initial, 0d));
  183.     }

  184.     /**
  185.      * Find a complex root for the polynomial with the given coefficients,
  186.      * starting from the given initial value.
  187.      * <p>
  188.      * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
  189.      *
  190.      * @param coefficients Polynomial coefficients.
  191.      * @param initial Start value.
  192.      * @return the point at which the function value is zero.
  193.      * @throws org.apache.commons.math4.legacy.exception.TooManyEvaluationsException
  194.      * if the maximum number of evaluations is exceeded.
  195.      * @throws NullArgumentException if the {@code coefficients} is
  196.      * {@code null}.
  197.      * @throws NoDataException if the {@code coefficients} array is empty.
  198.      * @since 3.1
  199.      */
  200.     public Complex solveComplex(double[] coefficients,
  201.                                 double initial)
  202.         throws NullArgumentException,
  203.                NoDataException,
  204.                TooManyEvaluationsException {
  205.         setup(Integer.MAX_VALUE,
  206.               new PolynomialFunction(coefficients),
  207.               Double.NEGATIVE_INFINITY,
  208.               Double.POSITIVE_INFINITY,
  209.               initial);
  210.         return complexSolver.solve(real2Complex(coefficients),
  211.                                    Complex.ofCartesian(initial, 0d));
  212.     }

  213.     /**
  214.      * Class for searching all (complex) roots.
  215.      */
  216.     private final class ComplexSolver {
  217.         /**
  218.          * Check whether the given complex root is actually a real zero
  219.          * in the given interval, within the solver tolerance level.
  220.          *
  221.          * @param min Lower bound for the interval.
  222.          * @param max Upper bound for the interval.
  223.          * @param z Complex root.
  224.          * @return {@code true} if z is a real zero.
  225.          */
  226.         public boolean isRoot(double min, double max, Complex z) {
  227.             if (isSequence(min, z.getReal(), max)) {
  228.                 double tolerance = JdkMath.max(getRelativeAccuracy() * z.abs(), getAbsoluteAccuracy());
  229.                 return JdkMath.abs(z.getImaginary()) <= tolerance ||
  230.                      z.abs() <= getFunctionValueAccuracy();
  231.             }
  232.             return false;
  233.         }

  234.         /**
  235.          * Find all complex roots for the polynomial with the given
  236.          * coefficients, starting from the given initial value.
  237.          *
  238.          * @param coefficients Polynomial coefficients.
  239.          * @param initial Start value.
  240.          * @return the point at which the function value is zero.
  241.          * @throws org.apache.commons.math4.legacy.exception.TooManyEvaluationsException
  242.          * if the maximum number of evaluations is exceeded.
  243.          * @throws NullArgumentException if the {@code coefficients} is
  244.          * {@code null}.
  245.          * @throws NoDataException if the {@code coefficients} array is empty.
  246.          */
  247.         public Complex[] solveAll(Complex[] coefficients, Complex initial)
  248.             throws NullArgumentException,
  249.                    NoDataException,
  250.                    TooManyEvaluationsException {
  251.             if (coefficients == null) {
  252.                 throw new NullArgumentException();
  253.             }
  254.             final int n = coefficients.length - 1;
  255.             if (n == 0) {
  256.                 throw new NoDataException(LocalizedFormats.POLYNOMIAL);
  257.             }
  258.             // Coefficients for deflated polynomial.
  259.             final Complex[] c = coefficients.clone();

  260.             // Solve individual roots successively.
  261.             final Complex[] root = new Complex[n];
  262.             for (int i = 0; i < n; i++) {
  263.                 final Complex[] subarray = new Complex[n - i + 1];
  264.                 System.arraycopy(c, 0, subarray, 0, subarray.length);
  265.                 root[i] = solve(subarray, initial);
  266.                 // Polynomial deflation using synthetic division.
  267.                 Complex newc = c[n - i];
  268.                 Complex oldc = null;
  269.                 for (int j = n - i - 1; j >= 0; j--) {
  270.                     oldc = c[j];
  271.                     c[j] = newc;
  272.                     newc = oldc.add(newc.multiply(root[i]));
  273.                 }
  274.             }

  275.             return root;
  276.         }

  277.         /**
  278.          * Find a complex root for the polynomial with the given coefficients,
  279.          * starting from the given initial value.
  280.          *
  281.          * @param coefficients Polynomial coefficients.
  282.          * @param initial Start value.
  283.          * @return the point at which the function value is zero.
  284.          * @throws org.apache.commons.math4.legacy.exception.TooManyEvaluationsException
  285.          * if the maximum number of evaluations is exceeded.
  286.          * @throws NullArgumentException if the {@code coefficients} is
  287.          * {@code null}.
  288.          * @throws NoDataException if the {@code coefficients} array is empty.
  289.          */
  290.         public Complex solve(Complex[] coefficients, Complex initial)
  291.             throws NullArgumentException,
  292.                    NoDataException,
  293.                    TooManyEvaluationsException {
  294.             if (coefficients == null) {
  295.                 throw new NullArgumentException();
  296.             }

  297.             final int n = coefficients.length - 1;
  298.             if (n == 0) {
  299.                 throw new NoDataException(LocalizedFormats.POLYNOMIAL);
  300.             }

  301.             final double absoluteAccuracy = getAbsoluteAccuracy();
  302.             final double relativeAccuracy = getRelativeAccuracy();
  303.             final double functionValueAccuracy = getFunctionValueAccuracy();

  304.             final Complex nC  = Complex.ofCartesian(n, 0);
  305.             final Complex n1C = Complex.ofCartesian(n - 1, 0);

  306.             Complex z = initial;
  307.             Complex oldz = Complex.ofCartesian(Double.POSITIVE_INFINITY,
  308.                                        Double.POSITIVE_INFINITY);
  309.             while (true) {
  310.                 // Compute pv (polynomial value), dv (derivative value), and
  311.                 // d2v (second derivative value) simultaneously.
  312.                 Complex pv = coefficients[n];
  313.                 Complex dv = Complex.ZERO;
  314.                 Complex d2v = Complex.ZERO;
  315.                 for (int j = n-1; j >= 0; j--) {
  316.                     d2v = dv.add(z.multiply(d2v));
  317.                     dv = pv.add(z.multiply(dv));
  318.                     pv = coefficients[j].add(z.multiply(pv));
  319.                 }
  320.                 d2v = d2v.multiply(2);

  321.                 // Check for convergence.
  322.                 final double tolerance = JdkMath.max(relativeAccuracy * z.abs(),
  323.                                                       absoluteAccuracy);
  324.                 if ((z.subtract(oldz)).abs() <= tolerance) {
  325.                     return z;
  326.                 }
  327.                 if (pv.abs() <= functionValueAccuracy) {
  328.                     return z;
  329.                 }

  330.                 // Now pv != 0, calculate the new approximation.
  331.                 final Complex g = dv.divide(pv);
  332.                 final Complex g2 = g.multiply(g);
  333.                 final Complex h = g2.subtract(d2v.divide(pv));
  334.                 final Complex delta = n1C.multiply((nC.multiply(h)).subtract(g2));
  335.                 // Choose a denominator larger in magnitude.
  336.                 final Complex deltaSqrt = delta.sqrt();
  337.                 final Complex dplus = g.add(deltaSqrt);
  338.                 final Complex dminus = g.subtract(deltaSqrt);
  339.                 final Complex denominator = dplus.abs() > dminus.abs() ? dplus : dminus;
  340.                 // Perturb z if denominator is zero, for instance,
  341.                 // p(x) = x^3 + 1, z = 0.
  342.                 // This uses exact equality to zero. A tolerance may be required here.
  343.                 if (denominator.equals(Complex.ZERO)) {
  344.                     z = z.add(Complex.ofCartesian(absoluteAccuracy, absoluteAccuracy));
  345.                     oldz = Complex.ofCartesian(Double.POSITIVE_INFINITY,
  346.                                        Double.POSITIVE_INFINITY);
  347.                 } else {
  348.                     oldz = z;
  349.                     z = z.subtract(nC.divide(denominator));
  350.                 }
  351.                 incrementEvaluationCount();
  352.             }
  353.         }
  354.     }

  355.     /**
  356.      * Converts a {@code double[]} array to a {@code Complex[]} array.
  357.      *
  358.      * @param real array of numbers to be converted to their {@code Complex} equivalent
  359.      * @return {@code Complex} array
  360.      */
  361.     private static Complex[] real2Complex(double[] real) {
  362.         int index = 0;
  363.         final Complex[] c = new Complex[real.length];
  364.         for (final double d : real) {
  365.             c[index] = Complex.ofCartesian(d, 0);
  366.             index++;
  367.         }
  368.         return c;
  369.     }
  370. }