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.math.analysis;
18
19 import org.apache.commons.math.ConvergenceException;
20 import org.apache.commons.math.FunctionEvaluationException;
21 import org.apache.commons.math.MaxIterationsExceededException;
22 import org.apache.commons.math.complex.Complex;
23
24 /**
25 * Implements the <a href="http://mathworld.wolfram.com/LaguerresMethod.html">
26 * Laguerre's Method</a> for root finding of real coefficient polynomials.
27 * For reference, see <b>A First Course in Numerical Analysis</b>,
28 * ISBN 048641454X, chapter 8.
29 * <p>
30 * Laguerre's method is global in the sense that it can start with any initial
31 * approximation and be able to solve all roots from that point.</p>
32 *
33 * @version $Revision: 620312 $ $Date: 2008-02-10 12:28:59 -0700 (Sun, 10 Feb 2008) $
34 * @since 1.2
35 */
36 public class LaguerreSolver extends UnivariateRealSolverImpl {
37
38 /** serializable version identifier */
39 private static final long serialVersionUID = -3775334783473775723L;
40
41 /** polynomial function to solve */
42 private PolynomialFunction p;
43
44 /**
45 * Construct a solver for the given function.
46 *
47 * @param f function to solve
48 * @throws IllegalArgumentException if function is not polynomial
49 */
50 public LaguerreSolver(UnivariateRealFunction f) throws
51 IllegalArgumentException {
52
53 super(f, 100, 1E-6);
54 if (f instanceof PolynomialFunction) {
55 p = (PolynomialFunction)f;
56 } else {
57 throw new IllegalArgumentException("Function is not polynomial.");
58 }
59 }
60
61 /**
62 * Returns a copy of the polynomial function.
63 *
64 * @return a fresh copy of the polynomial function
65 */
66 public PolynomialFunction getPolynomialFunction() {
67 return new PolynomialFunction(p.getCoefficients());
68 }
69
70 /**
71 * Find a real root in the given interval with initial value.
72 * <p>
73 * Requires bracketing condition.</p>
74 *
75 * @param min the lower bound for the interval
76 * @param max the upper bound for the interval
77 * @param initial the start value to use
78 * @return the point at which the function value is zero
79 * @throws ConvergenceException if the maximum iteration count is exceeded
80 * or the solver detects convergence problems otherwise
81 * @throws FunctionEvaluationException if an error occurs evaluating the
82 * function
83 * @throws IllegalArgumentException if any parameters are invalid
84 */
85 public double solve(double min, double max, double initial) throws
86 ConvergenceException, FunctionEvaluationException {
87
88 // check for zeros before verifying bracketing
89 if (p.value(min) == 0.0) { return min; }
90 if (p.value(max) == 0.0) { return max; }
91 if (p.value(initial) == 0.0) { return initial; }
92
93 verifyBracketing(min, max, p);
94 verifySequence(min, initial, max);
95 if (isBracketing(min, initial, p)) {
96 return solve(min, initial);
97 } else {
98 return solve(initial, max);
99 }
100 }
101
102 /**
103 * Find a real root in the given interval.
104 * <p>
105 * Despite the bracketing condition, the root returned by solve(Complex[],
106 * Complex) may not be a real zero inside [min, max]. For example,
107 * p(x) = x^3 + 1, min = -2, max = 2, initial = 0. We can either try
108 * another initial value, or, as we did here, call solveAll() to obtain
109 * all roots and pick up the one that we're looking for.</p>
110 *
111 * @param min the lower bound for the interval
112 * @param max the upper bound for the interval
113 * @return the point at which the function value is zero
114 * @throws ConvergenceException if the maximum iteration count is exceeded
115 * or the solver detects convergence problems otherwise
116 * @throws FunctionEvaluationException if an error occurs evaluating the
117 * function
118 * @throws IllegalArgumentException if any parameters are invalid
119 */
120 public double solve(double min, double max) throws ConvergenceException,
121 FunctionEvaluationException {
122
123 // check for zeros before verifying bracketing
124 if (p.value(min) == 0.0) { return min; }
125 if (p.value(max) == 0.0) { return max; }
126 verifyBracketing(min, max, p);
127
128 double coefficients[] = p.getCoefficients();
129 Complex c[] = new Complex[coefficients.length];
130 for (int i = 0; i < coefficients.length; i++) {
131 c[i] = new Complex(coefficients[i], 0.0);
132 }
133 Complex initial = new Complex(0.5 * (min + max), 0.0);
134 Complex z = solve(c, initial);
135 if (isRootOK(min, max, z)) {
136 setResult(z.getReal(), iterationCount);
137 return result;
138 }
139
140 // solve all roots and select the one we're seeking
141 Complex[] root = solveAll(c, initial);
142 for (int i = 0; i < root.length; i++) {
143 if (isRootOK(min, max, root[i])) {
144 setResult(root[i].getReal(), iterationCount);
145 return result;
146 }
147 }
148
149 // should never happen
150 throw new ConvergenceException();
151 }
152
153 /**
154 * Returns true iff the given complex root is actually a real zero
155 * in the given interval, within the solver tolerance level.
156 *
157 * @param min the lower bound for the interval
158 * @param max the upper bound for the interval
159 * @param z the complex root
160 * @return true iff z is the sought-after real zero
161 */
162 protected boolean isRootOK(double min, double max, Complex z) {
163 double tolerance = Math.max(relativeAccuracy * z.abs(), absoluteAccuracy);
164 return (isSequence(min, z.getReal(), max)) &&
165 (Math.abs(z.getImaginary()) <= tolerance ||
166 z.abs() <= functionValueAccuracy);
167 }
168
169 /**
170 * Find all complex roots for the polynomial with the given coefficients,
171 * starting from the given initial value.
172 *
173 * @param coefficients the polynomial coefficients array
174 * @param initial the start value to use
175 * @return the point at which the function value is zero
176 * @throws ConvergenceException if the maximum iteration count is exceeded
177 * or the solver detects convergence problems otherwise
178 * @throws FunctionEvaluationException if an error occurs evaluating the
179 * function
180 * @throws IllegalArgumentException if any parameters are invalid
181 */
182 public Complex[] solveAll(double coefficients[], double initial) throws
183 ConvergenceException, FunctionEvaluationException {
184
185 Complex c[] = new Complex[coefficients.length];
186 Complex z = new Complex(initial, 0.0);
187 for (int i = 0; i < c.length; i++) {
188 c[i] = new Complex(coefficients[i], 0.0);
189 }
190 return solveAll(c, z);
191 }
192
193 /**
194 * Find all complex roots for the polynomial with the given coefficients,
195 * starting from the given initial value.
196 *
197 * @param coefficients the polynomial coefficients array
198 * @param initial the start value to use
199 * @return the point at which the function value is zero
200 * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
201 * or the solver detects convergence problems otherwise
202 * @throws FunctionEvaluationException if an error occurs evaluating the
203 * function
204 * @throws IllegalArgumentException if any parameters are invalid
205 */
206 public Complex[] solveAll(Complex coefficients[], Complex initial) throws
207 MaxIterationsExceededException, FunctionEvaluationException {
208
209 int n = coefficients.length - 1;
210 int iterationCount = 0;
211 if (n < 1) {
212 throw new IllegalArgumentException
213 ("Polynomial degree must be positive: degree=" + n);
214 }
215 Complex c[] = new Complex[n+1]; // coefficients for deflated polynomial
216 for (int i = 0; i <= n; i++) {
217 c[i] = coefficients[i];
218 }
219
220 // solve individual root successively
221 Complex root[] = new Complex[n];
222 for (int i = 0; i < n; i++) {
223 Complex subarray[] = new Complex[n-i+1];
224 System.arraycopy(c, 0, subarray, 0, subarray.length);
225 root[i] = solve(subarray, initial);
226 // polynomial deflation using synthetic division
227 Complex newc = c[n-i];
228 Complex oldc = null;
229 for (int j = n-i-1; j >= 0; j--) {
230 oldc = c[j];
231 c[j] = newc;
232 newc = oldc.add(newc.multiply(root[i]));
233 }
234 iterationCount += this.iterationCount;
235 }
236
237 resultComputed = true;
238 this.iterationCount = iterationCount;
239 return root;
240 }
241
242 /**
243 * Find a complex root for the polynomial with the given coefficients,
244 * starting from the given initial value.
245 *
246 * @param coefficients the polynomial coefficients array
247 * @param initial the start value to use
248 * @return the point at which the function value is zero
249 * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
250 * or the solver detects convergence problems otherwise
251 * @throws FunctionEvaluationException if an error occurs evaluating the
252 * function
253 * @throws IllegalArgumentException if any parameters are invalid
254 */
255 public Complex solve(Complex coefficients[], Complex initial) throws
256 MaxIterationsExceededException, FunctionEvaluationException {
257
258 int n = coefficients.length - 1;
259 if (n < 1) {
260 throw new IllegalArgumentException
261 ("Polynomial degree must be positive: degree=" + n);
262 }
263 Complex N = new Complex((double)n, 0.0);
264 Complex N1 = new Complex((double)(n-1), 0.0);
265
266 int i = 1;
267 Complex pv = null;
268 Complex dv = null;
269 Complex d2v = null;
270 Complex G = null;
271 Complex G2 = null;
272 Complex H = null;
273 Complex delta = null;
274 Complex denominator = null;
275 Complex z = initial;
276 Complex oldz = new Complex(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
277 while (i <= maximalIterationCount) {
278 // Compute pv (polynomial value), dv (derivative value), and
279 // d2v (second derivative value) simultaneously.
280 pv = coefficients[n];
281 dv = Complex.ZERO;
282 d2v = Complex.ZERO;
283 for (int j = n-1; j >= 0; j--) {
284 d2v = dv.add(z.multiply(d2v));
285 dv = pv.add(z.multiply(dv));
286 pv = coefficients[j].add(z.multiply(pv));
287 }
288 d2v = d2v.multiply(new Complex(2.0, 0.0));
289
290 // check for convergence
291 double tolerance = Math.max(relativeAccuracy * z.abs(),
292 absoluteAccuracy);
293 if ((z.subtract(oldz)).abs() <= tolerance) {
294 resultComputed = true;
295 iterationCount = i;
296 return z;
297 }
298 if (pv.abs() <= functionValueAccuracy) {
299 resultComputed = true;
300 iterationCount = i;
301 return z;
302 }
303
304 // now pv != 0, calculate the new approximation
305 G = dv.divide(pv);
306 G2 = G.multiply(G);
307 H = G2.subtract(d2v.divide(pv));
308 delta = N1.multiply((N.multiply(H)).subtract(G2));
309 // choose a denominator larger in magnitude
310 Complex deltaSqrt = delta.sqrt();
311 Complex dplus = G.add(deltaSqrt);
312 Complex dminus = G.subtract(deltaSqrt);
313 denominator = dplus.abs() > dminus.abs() ? dplus : dminus;
314 // Perturb z if denominator is zero, for instance,
315 // p(x) = x^3 + 1, z = 0.
316 if (denominator.equals(new Complex(0.0, 0.0))) {
317 z = z.add(new Complex(absoluteAccuracy, absoluteAccuracy));
318 oldz = new Complex(Double.POSITIVE_INFINITY,
319 Double.POSITIVE_INFINITY);
320 } else {
321 oldz = z;
322 z = z.subtract(N.divide(denominator));
323 }
324 i++;
325 }
326 throw new MaxIterationsExceededException(maximalIterationCount);
327 }
328 }