1 /*
2 * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.irurueta.numerical.roots;
17
18 import com.irurueta.algebra.Complex;
19 import com.irurueta.numerical.LockedException;
20 import com.irurueta.numerical.NotAvailableException;
21 import com.irurueta.numerical.NotReadyException;
22
23 /**
24 * Class to estimate the roots of a second degree polynomial along with other
25 * polynomial properties.
26 * A second degree polynomial is defined by its parameters as p(x) = a * x^2 +
27 * b * x + c, hence the polynomial can be simply be defined by an array of
28 * length 3 [c, b, a]
29 * This class is based on:
30 * <a href="http://en.wikipedia.org/wiki/Quadratic_function">http://en.wikipedia.org/wiki/Quadratic_function</a>
31 */
32 @SuppressWarnings("Duplicates")
33 public class SecondDegreePolynomialRootsEstimator extends PolynomialRootsEstimator {
34
35 /**
36 * Constant defining machine precision.
37 */
38 public static final double EPS = 1e-10;
39
40 /**
41 * Number of parameters valid for a second degree polynomial.
42 */
43 public static final int VALID_POLY_PARAMS_LENGTH = 3;
44
45 /**
46 * Array containing parameters of a second degree polynomial.
47 */
48 private double[] realPolyParams;
49
50 /**
51 * Empty constructor.
52 */
53 public SecondDegreePolynomialRootsEstimator() {
54 super();
55 realPolyParams = null;
56 }
57
58 /**
59 * Constructor.
60 *
61 * @param polyParams Array containing polynomial parameters.
62 * @throws IllegalArgumentException Raised if the length of the provided
63 * array is not valid.
64 */
65 public SecondDegreePolynomialRootsEstimator(final double[] polyParams) {
66 super();
67 internalSetPolynomialParameters(polyParams);
68 }
69
70 /**
71 * Set array of second degree polynomial parameters.
72 * A second degree polynomial is defined by p(x) = a * x^2 + b * x + c, and
73 * the array must be provided as [c, b, a].
74 * Note: This class only supports real polynomial parameters
75 *
76 * @param polyParams Array containing polynomial parameters.
77 * @throws LockedException Raised if this instance is locked.
78 * @throws IllegalArgumentException Raised if the length of the provided
79 * array is not valid.
80 */
81 public void setPolynomialParameters(final double[] polyParams) throws LockedException {
82 if (isLocked()) {
83 throw new LockedException();
84 }
85 internalSetPolynomialParameters(polyParams);
86 }
87
88 /**
89 * Returns array of second degree polynomial parameters.
90 * A second degree polynomial is defined by p(x) = a * x^2 + b * x + c, and
91 * the array is returned as [c, b, a].
92 * Note: This class only supports real polynomial parameters
93 *
94 * @return Array of first degree polynomial parameters
95 * @throws NotAvailableException Raised if polynomial parameter have not yet
96 * been provided
97 */
98 public double[] getRealPolynomialParameters() throws NotAvailableException {
99 if (!arePolynomialParametersAvailable()) {
100 throw new NotAvailableException();
101 }
102 return realPolyParams;
103 }
104
105 /**
106 * Returns boolean indicating whether REAL polynomial parameters have been
107 * provided and is available for retrieval.
108 * Note: This class only supports real polynomial parameters
109 *
110 * @return True if available, false otherwise
111 */
112 @Override
113 public boolean arePolynomialParametersAvailable() {
114 return realPolyParams != null;
115 }
116
117 /**
118 * This method will always raise a NotAvailableException because this class
119 * only supports REAL polynomial parameters
120 *
121 * @return always throws NotAvailableException
122 * @throws NotAvailableException always thrown
123 */
124 @Override
125 public Complex[] getPolynomialParameters() throws NotAvailableException {
126 throw new NotAvailableException();
127 }
128
129 /**
130 * Estimates the roots of provided polynomial.
131 *
132 * @throws LockedException Raised if this instance is locked estimating
133 * roots.
134 * @throws NotReadyException Raised if this instance is not ready because
135 * polynomial parameters have not been provided
136 * @throws RootEstimationException Raised if roots cannot be estimated for
137 * some reason
138 */
139 @Override
140 public void estimate() throws LockedException, NotReadyException, RootEstimationException {
141
142 if (isLocked()) {
143 throw new LockedException();
144 }
145 if (!isReady()) {
146 throw new NotReadyException();
147 }
148
149 locked = true;
150
151 roots = new Complex[VALID_POLY_PARAMS_LENGTH - 1];
152
153 final var c = realPolyParams[0];
154 final var b = realPolyParams[1];
155 final var a = realPolyParams[2];
156
157 final var x1 = new Complex();
158 final var x2 = new Complex();
159 solveQuadratic(a, b, c, x1, x2);
160
161 if (Double.isNaN(x1.getReal()) || Double.isNaN(x1.getImaginary()) || Double.isNaN(x2.getReal())
162 || Double.isNaN(x2.getImaginary())) {
163 locked = false;
164 throw new RootEstimationException();
165 }
166 if (x1.getReal() < x2.getReal()) {
167 // x1 goes first
168 roots[0] = x1;
169 roots[1] = x2;
170 } else {
171 // x2 goes first
172 roots[0] = x2;
173 roots[1] = x1;
174 }
175
176 locked = false;
177 }
178
179 /**
180 * Returns boolean indicating whether provided array of polynomial
181 * parameters correspond to a valid second degree polynomial.
182 * A second degree polynomial is defined by p(x) = a * x^2 + b * x + c, and
183 * the array is returned as [a, b, a].
184 * Note: This class only supports real polynomial parameters
185 *
186 * @param polyParams Array containing polynomial parameters
187 * @return True if is a second degree polynomial, false otherwise
188 */
189 public static boolean isSecondDegree(final double[] polyParams) {
190 final var length = polyParams.length;
191 if (length >= VALID_POLY_PARAMS_LENGTH && Math.abs(polyParams[VALID_POLY_PARAMS_LENGTH - 1]) > EPS) {
192 for (var i = VALID_POLY_PARAMS_LENGTH; i < length; i++) {
193 if (Math.abs(polyParams[i]) > EPS) {
194 return false;
195 }
196 }
197 return true;
198 }
199 return false;
200 }
201
202 /**
203 * Returns boolean indicating whether polynomial parameters provided to this
204 * instance correspond to a valid second degree polynomial.
205 * A second degree polynomial is defined by p(x) = a * x^2 + b * x + c, and
206 * the array is returned as [c, b, a].
207 * Note: This class only supports real polynomial parameters
208 *
209 * @return True if is a second degree polynomial, false otherwise
210 * @throws NotReadyException Raised if this instance is not ready because
211 * an array of polynomial parameters has not yet been provided.
212 */
213 public boolean isSecondDegree() throws NotReadyException {
214 if (!isReady()) {
215 throw new NotReadyException();
216 }
217 return isSecondDegree(realPolyParams);
218 }
219
220 /**
221 * Returns boolean indicating whether the roots of the polynomial are two
222 * distinct and real roots or not.
223 * Because this class only supports polynomials with real parameters, we
224 * know that for second degree polynomials that have two distinct roots,
225 * its roots must be either real or complex conjugate.
226 *
227 * @param polyParams Array containing polynomial parameters
228 * @return True if roots are distinct and real, false otherwise
229 */
230 public boolean hasTwoDistinctRealRoots(final double[] polyParams) {
231 if (polyParams.length >= VALID_POLY_PARAMS_LENGTH) {
232 return getDiscriminant(polyParams) > EPS;
233 }
234 return false;
235 }
236
237 /**
238 * Returns boolean indicating whether the roots of the polynomial are two
239 * distinct and real roots or not.
240 * Because this class only supports polynomials with real parameters, we
241 * know that for second degree polynomials that have two distinct roots,
242 * its roots must be either real or complex conjugate.
243 *
244 * @return True if roots are distinct and real, false otherwise
245 * @throws NotReadyException Raised if polynomial parameters haven't yet
246 * been provided
247 */
248 public boolean hasTwoDistinctRealRoots() throws NotReadyException {
249 if (!isReady()) {
250 throw new NotReadyException();
251 }
252 return hasTwoDistinctRealRoots(realPolyParams);
253 }
254
255 /**
256 * Returns boolean indicating whether a second degree polynomial has
257 * multiple roots (for the 2nd degree case this means 2 equal roots)
258 * This is true for polynomials of the form (x - r)^2 = x^2 - 2 * r * x +
259 * r^2, where r is the double root
260 *
261 * @param polyParams Array containing polynomial parameters
262 * @return True if it has double root, false otherwise
263 */
264 public static boolean hasDoubleRoot(final double[] polyParams) {
265 if (polyParams.length >= VALID_POLY_PARAMS_LENGTH) {
266 return Math.abs(getDiscriminant(polyParams)) <= EPS;
267 }
268 return false;
269 }
270
271 /**
272 * Returns boolean indicating whether this second degree polynomial has
273 * multiple roots (for the 2nd degree case this means 2 equal roots)
274 * This is true for polynomials of the form (x - r)^2 = x^2 - 2 * r * x +
275 * r^2, where r is the double root
276 *
277 * @return True if it has double root, false otherwise
278 * @throws NotReadyException Raised if polynomial parameters haven't yet
279 * been provided.
280 */
281 public boolean hasDoubleRoot() throws NotReadyException {
282 if (!isReady()) {
283 throw new NotReadyException();
284 }
285 return hasDoubleRoot(realPolyParams);
286 }
287
288 /**
289 * Returns boolean indicating whether the roots of the polynomial are two
290 * complex conjugate roots or not.
291 * Because this class only supports polynomials with real parameters, we
292 * know that for second degree polynomials that have two distinct roots,
293 * its roots must be either real or complex conjugate.
294 *
295 * @param polyParams Array containing polynomial parameters
296 * @return True if roots are complex conjugate, false otherwise
297 */
298 public static boolean hasTwoComplexConjugateRoots(final double[] polyParams) {
299 if (polyParams.length >= VALID_POLY_PARAMS_LENGTH) {
300 return getDiscriminant(polyParams) < -EPS;
301 }
302 return false;
303 }
304
305 /**
306 * Returns boolean indicating whether the roots of the polynomial are two
307 * complex conjugate roots or not.
308 * Because this class only supports polynomials with real parameters, we
309 * know that for second degree polynomials that have two distinct roots,
310 * its roots must be either real or complex conjugate.
311 *
312 * @return True if roots are complex conjugate, false otherwise
313 * @throws NotReadyException Raised if polynomial parameters haven't yet
314 * been provided
315 */
316 public boolean hasTwoComplexConjugateRoots() throws NotReadyException {
317 if (!isReady()) {
318 throw new NotReadyException();
319 }
320 return hasTwoComplexConjugateRoots(realPolyParams);
321 }
322
323 /**
324 * This method will always raise an IllegalArgumentException because this
325 * class only supports REAL polynomial parameters
326 */
327 @Override
328 protected void internalSetPolynomialParameters(final Complex[] polyParams) {
329 // complex values are not supported
330 throw new IllegalArgumentException();
331 }
332
333 /**
334 * Internal method to compute the discriminant of a 2nd degree polynomial.
335 * Discriminants are helpful to determine properties of a 2nd degree
336 * polynomial
337 *
338 * @param polyParams Array containing polynomial parameters
339 * @return Value of discriminant
340 */
341 private static double getDiscriminant(final double[] polyParams) {
342
343 final var c = polyParams[0];
344 final var b = polyParams[1];
345 final var a = polyParams[2];
346
347 return b * b - 4.0 * a * c;
348 }
349
350 /**
351 * Finds 2nd degree polynomial roots
352 *
353 * @param a 1st parameter
354 * @param b 2nd parameter
355 * @param c 3rd parameter
356 * @param x1 1st root (output parameter)
357 * @param x2 2nd root (output parameter)
358 */
359 private void solveQuadratic(final double a, final double b, final double c, final Complex x1, final Complex x2) {
360
361 final var discriminant = b * b - 4.0 * a * c;
362
363 if (discriminant >= 0.0) {
364 // real solutions (double or distinct)
365 x1.setRealAndImaginary((-b + Math.sqrt(discriminant)) / (2.0 * a), 0.0);
366 x2.setRealAndImaginary((-b - Math.sqrt(discriminant)) / (2.0 * a), 0.0);
367 } else {
368 // complex conjugate solutions
369 final var real = -b / (2.0 * a);
370 final var imag = Math.sqrt(Math.abs(discriminant)) / (2.0 * a);
371 x1.setRealAndImaginary(real, imag);
372 x2.setRealAndImaginary(real, -imag);
373 }
374 }
375
376 /**
377 * Internal method to set array of second degree polynomial parameters.
378 * A second degree polynomial is defined by p(x) = a * x^2 + b * x + c, and
379 * the array must be provided as [c, b, a].
380 * Note: This class only supports real polynomial parameters
381 * This method does not check if this instance is locked.
382 *
383 * @param polyParams Array containing polynomial parameters.
384 * @throws IllegalArgumentException Raised if the length of the provided
385 * array is not valid.
386 */
387 private void internalSetPolynomialParameters(final double[] polyParams) {
388 if (polyParams.length < VALID_POLY_PARAMS_LENGTH) {
389 throw new IllegalArgumentException();
390 }
391 if (!isSecondDegree(polyParams)) {
392 throw new IllegalArgumentException();
393 }
394
395 this.realPolyParams = polyParams;
396 }
397 }