SecondDegreePolynomialRootsEstimator.java

/*
 * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.irurueta.numerical.roots;

import com.irurueta.algebra.Complex;
import com.irurueta.numerical.LockedException;
import com.irurueta.numerical.NotAvailableException;
import com.irurueta.numerical.NotReadyException;

/**
 * Class to estimate the roots of a second degree polynomial along with other
 * polynomial properties.
 * A second degree polynomial is defined by its parameters as p(x) = a * x^2 +
 * b * x + c, hence the polynomial can be simply be defined by an array of
 * length 3 [c, b, a]
 * This class is based on:
 * <a href="http://en.wikipedia.org/wiki/Quadratic_function">http://en.wikipedia.org/wiki/Quadratic_function</a>
 */
@SuppressWarnings("Duplicates")
public class SecondDegreePolynomialRootsEstimator extends PolynomialRootsEstimator {

    /**
     * Constant defining machine precision.
     */
    public static final double EPS = 1e-10;

    /**
     * Number of parameters valid for a second degree polynomial.
     */
    public static final int VALID_POLY_PARAMS_LENGTH = 3;

    /**
     * Array containing parameters of a second degree polynomial.
     */
    private double[] realPolyParams;

    /**
     * Empty constructor.
     */
    public SecondDegreePolynomialRootsEstimator() {
        super();
        realPolyParams = null;
    }

    /**
     * Constructor.
     *
     * @param polyParams Array containing polynomial parameters.
     * @throws IllegalArgumentException Raised if the length of the provided
     *                                  array is not valid.
     */
    public SecondDegreePolynomialRootsEstimator(final double[] polyParams) {
        super();
        internalSetPolynomialParameters(polyParams);
    }

    /**
     * Set array of second degree polynomial parameters.
     * A second degree polynomial is defined by p(x) = a * x^2 + b * x + c, and
     * the array must be provided as [c, b, a].
     * Note: This class only supports real polynomial parameters
     *
     * @param polyParams Array containing polynomial parameters.
     * @throws LockedException          Raised if this instance is locked.
     * @throws IllegalArgumentException Raised if the length of the provided
     *                                  array is not valid.
     */
    public void setPolynomialParameters(final double[] polyParams) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        internalSetPolynomialParameters(polyParams);
    }

    /**
     * Returns array of second degree polynomial parameters.
     * A second degree polynomial is defined by p(x) = a * x^2 + b * x + c, and
     * the array is returned as [c, b, a].
     * Note: This class only supports real polynomial parameters
     *
     * @return Array of first degree polynomial parameters
     * @throws NotAvailableException Raised if polynomial parameter have not yet
     *                               been provided
     */
    public double[] getRealPolynomialParameters() throws NotAvailableException {
        if (!arePolynomialParametersAvailable()) {
            throw new NotAvailableException();
        }
        return realPolyParams;
    }

    /**
     * Returns boolean indicating whether REAL polynomial parameters have been
     * provided and is available for retrieval.
     * Note: This class only supports real polynomial parameters
     *
     * @return True if available, false otherwise
     */
    @Override
    public boolean arePolynomialParametersAvailable() {
        return realPolyParams != null;
    }

    /**
     * This method will always raise a NotAvailableException because this class
     * only supports REAL polynomial parameters
     *
     * @return always throws NotAvailableException
     * @throws NotAvailableException always thrown
     */
    @Override
    public Complex[] getPolynomialParameters() throws NotAvailableException {
        throw new NotAvailableException();
    }

    /**
     * Estimates the roots of provided polynomial.
     *
     * @throws LockedException         Raised if this instance is locked estimating
     *                                 roots.
     * @throws NotReadyException       Raised if this instance is not ready because
     *                                 polynomial parameters have not been provided
     * @throws RootEstimationException Raised if roots cannot be estimated for
     *                                 some reason
     */
    @Override
    public void estimate() throws LockedException, NotReadyException, RootEstimationException {

        if (isLocked()) {
            throw new LockedException();
        }
        if (!isReady()) {
            throw new NotReadyException();
        }

        locked = true;

        roots = new Complex[VALID_POLY_PARAMS_LENGTH - 1];

        final var c = realPolyParams[0];
        final var b = realPolyParams[1];
        final var a = realPolyParams[2];

        final var x1 = new Complex();
        final var x2 = new Complex();
        solveQuadratic(a, b, c, x1, x2);

        if (Double.isNaN(x1.getReal()) || Double.isNaN(x1.getImaginary()) || Double.isNaN(x2.getReal())
                || Double.isNaN(x2.getImaginary())) {
            locked = false;
            throw new RootEstimationException();
        }
        if (x1.getReal() < x2.getReal()) {
            // x1 goes first
            roots[0] = x1;
            roots[1] = x2;
        } else {
            // x2 goes first
            roots[0] = x2;
            roots[1] = x1;
        }

        locked = false;
    }

    /**
     * Returns boolean indicating whether provided array of polynomial
     * parameters correspond to a valid second degree polynomial.
     * A second degree polynomial is defined by p(x) = a * x^2 + b * x + c, and
     * the array is returned as [a, b, a].
     * Note: This class only supports real polynomial parameters
     *
     * @param polyParams Array containing polynomial parameters
     * @return True if is a second degree polynomial, false otherwise
     */
    public static boolean isSecondDegree(final double[] polyParams) {
        final var length = polyParams.length;
        if (length >= VALID_POLY_PARAMS_LENGTH && Math.abs(polyParams[VALID_POLY_PARAMS_LENGTH - 1]) > EPS) {
            for (var i = VALID_POLY_PARAMS_LENGTH; i < length; i++) {
                if (Math.abs(polyParams[i]) > EPS) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Returns boolean indicating whether polynomial parameters provided to this
     * instance correspond to a valid second degree polynomial.
     * A second degree polynomial is defined by p(x) = a * x^2 + b * x + c, and
     * the array is returned as [c, b, a].
     * Note: This class only supports real polynomial parameters
     *
     * @return True if is a second degree polynomial, false otherwise
     * @throws NotReadyException Raised if this instance is not ready because
     *                           an array of polynomial parameters has not yet been provided.
     */
    public boolean isSecondDegree() throws NotReadyException {
        if (!isReady()) {
            throw new NotReadyException();
        }
        return isSecondDegree(realPolyParams);
    }

    /**
     * Returns boolean indicating whether the roots of the polynomial are two
     * distinct and real roots or not.
     * Because this class only supports polynomials with real parameters, we
     * know that for second degree polynomials that have two distinct roots,
     * its roots must be either real or complex conjugate.
     *
     * @param polyParams Array containing polynomial parameters
     * @return True if roots are distinct and real, false otherwise
     */
    public boolean hasTwoDistinctRealRoots(final double[] polyParams) {
        if (polyParams.length >= VALID_POLY_PARAMS_LENGTH) {
            return getDiscriminant(polyParams) > EPS;
        }
        return false;
    }

    /**
     * Returns boolean indicating whether the roots of the polynomial are two
     * distinct and real roots or not.
     * Because this class only supports polynomials with real parameters, we
     * know that for second degree polynomials that have two distinct roots,
     * its roots must be either real or complex conjugate.
     *
     * @return True if roots are distinct and real, false otherwise
     * @throws NotReadyException Raised if polynomial parameters haven't yet
     *                           been provided
     */
    public boolean hasTwoDistinctRealRoots() throws NotReadyException {
        if (!isReady()) {
            throw new NotReadyException();
        }
        return hasTwoDistinctRealRoots(realPolyParams);
    }

    /**
     * Returns boolean indicating whether a second degree polynomial has
     * multiple roots (for the 2nd degree case this means 2 equal roots)
     * This is true for polynomials of the form (x - r)^2 = x^2 - 2 * r * x +
     * r^2, where r is the double root
     *
     * @param polyParams Array containing polynomial parameters
     * @return True if it has double root, false otherwise
     */
    public static boolean hasDoubleRoot(final double[] polyParams) {
        if (polyParams.length >= VALID_POLY_PARAMS_LENGTH) {
            return Math.abs(getDiscriminant(polyParams)) <= EPS;
        }
        return false;
    }

    /**
     * Returns boolean indicating whether this second degree polynomial has
     * multiple roots (for the 2nd degree case this means 2 equal roots)
     * This is true for polynomials of the form (x - r)^2 = x^2 - 2 * r * x +
     * r^2, where r is the double root
     *
     * @return True if it has double root, false otherwise
     * @throws NotReadyException Raised if polynomial parameters haven't yet
     *                           been provided.
     */
    public boolean hasDoubleRoot() throws NotReadyException {
        if (!isReady()) {
            throw new NotReadyException();
        }
        return hasDoubleRoot(realPolyParams);
    }

    /**
     * Returns boolean indicating whether the roots of the polynomial are two
     * complex conjugate roots or not.
     * Because this class only supports polynomials with real parameters, we
     * know that for second degree polynomials that have two distinct roots,
     * its roots must be either real or complex conjugate.
     *
     * @param polyParams Array containing polynomial parameters
     * @return True if roots are complex conjugate, false otherwise
     */
    public static boolean hasTwoComplexConjugateRoots(final double[] polyParams) {
        if (polyParams.length >= VALID_POLY_PARAMS_LENGTH) {
            return getDiscriminant(polyParams) < -EPS;
        }
        return false;
    }

    /**
     * Returns boolean indicating whether the roots of the polynomial are two
     * complex conjugate roots or not.
     * Because this class only supports polynomials with real parameters, we
     * know that for second degree polynomials that have two distinct roots,
     * its roots must be either real or complex conjugate.
     *
     * @return True if roots are complex conjugate, false otherwise
     * @throws NotReadyException Raised if polynomial parameters haven't yet
     *                           been provided
     */
    public boolean hasTwoComplexConjugateRoots() throws NotReadyException {
        if (!isReady()) {
            throw new NotReadyException();
        }
        return hasTwoComplexConjugateRoots(realPolyParams);
    }

    /**
     * This method will always raise an IllegalArgumentException because this
     * class only supports REAL polynomial parameters
     */
    @Override
    protected void internalSetPolynomialParameters(final Complex[] polyParams) {
        // complex values are not supported
        throw new IllegalArgumentException();
    }

    /**
     * Internal method to compute the discriminant of a 2nd degree polynomial.
     * Discriminants are helpful to determine properties of a 2nd degree
     * polynomial
     *
     * @param polyParams Array containing polynomial parameters
     * @return Value of discriminant
     */
    private static double getDiscriminant(final double[] polyParams) {

        final var c = polyParams[0];
        final var b = polyParams[1];
        final var a = polyParams[2];

        return b * b - 4.0 * a * c;
    }

    /**
     * Finds 2nd degree polynomial roots
     *
     * @param a  1st parameter
     * @param b  2nd parameter
     * @param c  3rd parameter
     * @param x1 1st root (output parameter)
     * @param x2 2nd root (output parameter)
     */
    private void solveQuadratic(final double a, final double b, final double c, final Complex x1, final Complex x2) {

        final var discriminant = b * b - 4.0 * a * c;

        if (discriminant >= 0.0) {
            // real solutions (double or distinct)
            x1.setRealAndImaginary((-b + Math.sqrt(discriminant)) / (2.0 * a), 0.0);
            x2.setRealAndImaginary((-b - Math.sqrt(discriminant)) / (2.0 * a), 0.0);
        } else {
            // complex conjugate solutions
            final var real = -b / (2.0 * a);
            final var imag = Math.sqrt(Math.abs(discriminant)) / (2.0 * a);
            x1.setRealAndImaginary(real, imag);
            x2.setRealAndImaginary(real, -imag);
        }
    }

    /**
     * Internal method to set array of second degree polynomial parameters.
     * A second degree polynomial is defined by p(x) = a * x^2 + b * x + c, and
     * the array must be provided as [c, b, a].
     * Note: This class only supports real polynomial parameters
     * This method does not check if this instance is locked.
     *
     * @param polyParams Array containing polynomial parameters.
     * @throws IllegalArgumentException Raised if the length of the provided
     *                                  array is not valid.
     */
    private void internalSetPolynomialParameters(final double[] polyParams) {
        if (polyParams.length < VALID_POLY_PARAMS_LENGTH) {
            throw new IllegalArgumentException();
        }
        if (!isSecondDegree(polyParams)) {
            throw new IllegalArgumentException();
        }

        this.realPolyParams = polyParams;
    }
}