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 }