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.  *      https://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. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.analysis.solvers;

  22. import org.hipparchus.analysis.polynomials.PolynomialFunction;
  23. import org.hipparchus.complex.Complex;
  24. import org.hipparchus.complex.ComplexUtils;
  25. import org.hipparchus.exception.LocalizedCoreFormats;
  26. import org.hipparchus.exception.MathIllegalArgumentException;
  27. import org.hipparchus.exception.MathIllegalStateException;
  28. import org.hipparchus.exception.NullArgumentException;
  29. import org.hipparchus.util.FastMath;

  30. /**
  31.  * Implements the <a href="http://mathworld.wolfram.com/LaguerresMethod.html">
  32.  * Laguerre's Method</a> for root finding of real coefficient polynomials.
  33.  * For reference, see
  34.  * <blockquote>
  35.  *  <b>A First Course in Numerical Analysis</b>,
  36.  *  ISBN 048641454X, chapter 8.
  37.  * </blockquote>
  38.  * Laguerre's method is global in the sense that it can start with any initial
  39.  * approximation and be able to solve all roots from that point.
  40.  * The algorithm requires a bracketing condition.
  41.  *
  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.      * Construct a solver with default accuracy (1e-6).
  50.      */
  51.     public LaguerreSolver() {
  52.         this(DEFAULT_ABSOLUTE_ACCURACY);
  53.     }
  54.     /**
  55.      * Construct a solver.
  56.      *
  57.      * @param absoluteAccuracy Absolute accuracy.
  58.      */
  59.     public LaguerreSolver(double absoluteAccuracy) {
  60.         super(absoluteAccuracy);
  61.     }
  62.     /**
  63.      * Construct a solver.
  64.      *
  65.      * @param relativeAccuracy Relative accuracy.
  66.      * @param absoluteAccuracy Absolute accuracy.
  67.      */
  68.     public LaguerreSolver(double relativeAccuracy,
  69.                           double absoluteAccuracy) {
  70.         super(relativeAccuracy, absoluteAccuracy);
  71.     }
  72.     /**
  73.      * Construct a solver.
  74.      *
  75.      * @param relativeAccuracy Relative accuracy.
  76.      * @param absoluteAccuracy Absolute accuracy.
  77.      * @param functionValueAccuracy Function value accuracy.
  78.      */
  79.     public LaguerreSolver(double relativeAccuracy,
  80.                           double absoluteAccuracy,
  81.                           double functionValueAccuracy) {
  82.         super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
  83.     }

  84.     /**
  85.      * {@inheritDoc}
  86.      */
  87.     @Override
  88.     public double doSolve()
  89.         throws MathIllegalArgumentException, MathIllegalStateException {
  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 (FastMath.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 (FastMath.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 (FastMath.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 MathIllegalArgumentException(LocalizedCoreFormats.NOT_BRACKETING_INTERVAL,
  119.                                                min, max, yMin, yMax);
  120.     }

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

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

  156.     /**
  157.      * Find all complex roots for the polynomial with the given
  158.      * coefficients, starting from the given initial value.
  159.      * <p>
  160.      * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
  161.      *
  162.      * @param coefficients Polynomial coefficients.
  163.      * @param initial Start value.
  164.      * @return the point at which the function value is zero.
  165.      * @throws org.hipparchus.exception.MathIllegalStateException
  166.      * if the maximum number of evaluations is exceeded.
  167.      * @throws NullArgumentException if the {@code coefficients} is
  168.      * {@code null}.
  169.      * @throws MathIllegalArgumentException if the {@code coefficients} array is empty.
  170.      */
  171.     public Complex[] solveAllComplex(double[] coefficients,
  172.                                      double initial)
  173.         throws MathIllegalArgumentException, NullArgumentException,
  174.                MathIllegalStateException {
  175.         return solveAllComplex(coefficients, 100_000, initial);
  176.     }

  177.     /**
  178.      * Find all complex roots for the polynomial with the given
  179.      * coefficients, starting from the given initial value.
  180.      * <p>
  181.      * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
  182.      *
  183.      * @param coefficients Polynomial coefficients.
  184.      * @param maxEval Maximum number of evaluations.
  185.      * @param initial Start value.
  186.      * @return the point at which the function value is zero.
  187.      * @throws org.hipparchus.exception.MathIllegalStateException
  188.      * if the maximum number of evaluations is exceeded.
  189.      * @throws NullArgumentException if the {@code coefficients} is
  190.      * {@code null}.
  191.      * @throws MathIllegalArgumentException if the {@code coefficients} array is empty.
  192.      */
  193.     public Complex[] solveAllComplex(double[] coefficients,
  194.                                      int maxEval,
  195.                                      double initial)
  196.         throws MathIllegalArgumentException, NullArgumentException,
  197.                MathIllegalStateException {
  198.         setup(maxEval,
  199.               new PolynomialFunction(coefficients),
  200.               Double.NEGATIVE_INFINITY,
  201.               Double.POSITIVE_INFINITY,
  202.               initial);
  203.         return complexSolver.solveAll(ComplexUtils.convertToComplex(coefficients),
  204.                                       new Complex(initial, 0d));
  205.     }

  206.     /**
  207.      * Find a complex root for the polynomial with the given coefficients,
  208.      * starting from the given initial value.
  209.      * <p>
  210.      * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
  211.      *
  212.      * @param coefficients Polynomial coefficients.
  213.      * @param initial Start value.
  214.      * @return the point at which the function value is zero.
  215.      * @throws org.hipparchus.exception.MathIllegalStateException
  216.      * if the maximum number of evaluations is exceeded.
  217.      * @throws NullArgumentException if the {@code coefficients} is
  218.      * {@code null}.
  219.      * @throws MathIllegalArgumentException if the {@code coefficients} array is empty.
  220.      */
  221.     public Complex solveComplex(double[] coefficients,
  222.                                 double initial)
  223.         throws MathIllegalArgumentException, NullArgumentException,
  224.                MathIllegalStateException {
  225.         setup(Integer.MAX_VALUE,
  226.               new PolynomialFunction(coefficients),
  227.               Double.NEGATIVE_INFINITY,
  228.               Double.POSITIVE_INFINITY,
  229.               initial);
  230.         return complexSolver.solve(ComplexUtils.convertToComplex(coefficients),
  231.                                    new Complex(initial, 0d));
  232.     }

  233.     /**
  234.      * Class for searching all (complex) roots.
  235.      */
  236.     private class ComplexSolver {
  237.         /**
  238.          * Check whether the given complex root is actually a real zero
  239.          * in the given interval, within the solver tolerance level.
  240.          *
  241.          * @param min Lower bound for the interval.
  242.          * @param max Upper bound for the interval.
  243.          * @param z Complex root.
  244.          * @return {@code true} if z is a real zero.
  245.          */
  246.         public boolean isRoot(double min, double max, Complex z) {
  247.             if (isSequence(min, z.getReal(), max)) {
  248.                 final double zAbs = z.norm();
  249.                 double tolerance = FastMath.max(getRelativeAccuracy() * zAbs, getAbsoluteAccuracy());
  250.                 return (FastMath.abs(z.getImaginary()) <= tolerance) ||
  251.                      (zAbs <= getFunctionValueAccuracy());
  252.             }
  253.             return false;
  254.         }

  255.         /**
  256.          * Find all complex roots for the polynomial with the given
  257.          * coefficients, starting from the given initial value.
  258.          *
  259.          * @param coefficients Polynomial coefficients.
  260.          * @param initial Start value.
  261.          * @return the point at which the function value is zero.
  262.          * @throws org.hipparchus.exception.MathIllegalStateException
  263.          * if the maximum number of evaluations is exceeded.
  264.          * @throws NullArgumentException if the {@code coefficients} is
  265.          * {@code null}.
  266.          * @throws MathIllegalArgumentException if the {@code coefficients} array is empty.
  267.          */
  268.         public Complex[] solveAll(Complex[] coefficients, Complex initial)
  269.             throws MathIllegalArgumentException, NullArgumentException,
  270.                    MathIllegalStateException {
  271.             if (coefficients == null) {
  272.                 throw new NullArgumentException();
  273.             }
  274.             final int n = coefficients.length - 1;
  275.             if (n == 0) {
  276.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.POLYNOMIAL);
  277.             }
  278.             // Coefficients for deflated polynomial.
  279.             final Complex[] c = new Complex[n + 1];
  280.             System.arraycopy(coefficients, 0, c, 0, n + 1);

  281.             // Solve individual roots successively.
  282.             final Complex[] root = new Complex[n];
  283.             for (int i = 0; i < n; i++) {
  284.                 final Complex[] subarray = new Complex[n - i + 1];
  285.                 System.arraycopy(c, 0, subarray, 0, subarray.length);
  286.                 root[i] = solve(subarray, initial);
  287.                 // Polynomial deflation using synthetic division.
  288.                 Complex newc = c[n - i];
  289.                 Complex oldc;
  290.                 for (int j = n - i - 1; j >= 0; j--) {
  291.                     oldc = c[j];
  292.                     c[j] = newc;
  293.                     newc = oldc.add(newc.multiply(root[i]));
  294.                 }
  295.             }

  296.             return root;
  297.         }

  298.         /**
  299.          * Find a complex root for the polynomial with the given coefficients,
  300.          * starting from the given initial value.
  301.          *
  302.          * @param coefficients Polynomial coefficients.
  303.          * @param initial Start value.
  304.          * @return the point at which the function value is zero.
  305.          * @throws org.hipparchus.exception.MathIllegalStateException
  306.          * if the maximum number of evaluations is exceeded.
  307.          * @throws NullArgumentException if the {@code coefficients} is
  308.          * {@code null}.
  309.          * @throws MathIllegalArgumentException if the {@code coefficients} array is empty.
  310.          */
  311.         public Complex solve(Complex[] coefficients, Complex initial)
  312.             throws MathIllegalArgumentException, NullArgumentException,
  313.                    MathIllegalStateException {
  314.             if (coefficients == null) {
  315.                 throw new NullArgumentException();
  316.             }

  317.             final int n = coefficients.length - 1;
  318.             if (n == 0) {
  319.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.POLYNOMIAL);
  320.             }

  321.             final double absoluteAccuracy = getAbsoluteAccuracy();
  322.             final double relativeAccuracy = getRelativeAccuracy();
  323.             final double functionValueAccuracy = getFunctionValueAccuracy();

  324.             final Complex nC  = new Complex(n, 0);
  325.             final Complex n1C = new Complex(n - 1, 0);

  326.             Complex z = initial;
  327.             Complex oldz = new Complex(Double.POSITIVE_INFINITY,
  328.                                        Double.POSITIVE_INFINITY);
  329.             while (true) {
  330.                 // Compute pv (polynomial value), dv (derivative value), and
  331.                 // d2v (second derivative value) simultaneously.
  332.                 Complex pv = coefficients[n];
  333.                 Complex dv = Complex.ZERO;
  334.                 Complex d2v = Complex.ZERO;
  335.                 for (int j = n-1; j >= 0; j--) {
  336.                     d2v = dv.add(z.multiply(d2v));
  337.                     dv = pv.add(z.multiply(dv));
  338.                     pv = coefficients[j].add(z.multiply(pv));
  339.                 }
  340.                 d2v = d2v.multiply(new Complex(2.0, 0.0));

  341.                 // Check for convergence.
  342.                 final double tolerance = FastMath.max(relativeAccuracy * z.norm(),
  343.                                                       absoluteAccuracy);
  344.                 if ((z.subtract(oldz)).norm() <= tolerance) {
  345.                     return z;
  346.                 }
  347.                 if (pv.norm() <= functionValueAccuracy) {
  348.                     return z;
  349.                 }

  350.                 // Now pv != 0, calculate the new approximation.
  351.                 final Complex G = dv.divide(pv);
  352.                 final Complex G2 = G.multiply(G);
  353.                 final Complex H = G2.subtract(d2v.divide(pv));
  354.                 final Complex delta = n1C.multiply((nC.multiply(H)).subtract(G2));
  355.                 // Choose a denominator larger in magnitude.
  356.                 final Complex deltaSqrt = delta.sqrt();
  357.                 final Complex dplus = G.add(deltaSqrt);
  358.                 final Complex dminus = G.subtract(deltaSqrt);
  359.                 final Complex denominator = dplus.norm() > dminus.norm() ? dplus : dminus;
  360.                 // Perturb z if denominator is zero, for instance,
  361.                 // p(x) = x^3 + 1, z = 0.
  362.                 if (denominator.isZero()) {
  363.                     z = z.add(new Complex(absoluteAccuracy, absoluteAccuracy));
  364.                     oldz = new Complex(Double.POSITIVE_INFINITY,
  365.                                        Double.POSITIVE_INFINITY);
  366.                 } else {
  367.                     oldz = z;
  368.                     z = z.subtract(nC.divide(denominator));
  369.                 }
  370.                 incrementEvaluationCount();
  371.             }
  372.         }
  373.     }
  374. }