View Javadoc
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 }