RQDecomposer.java

/*
 * Copyright (C) 2012 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.algebra;

/**
 * This decomposer computes RQ decomposition, which consists on factoring
 * provided input matrix into an upper triangular matrix (R) and an orthogonal
 * matrix (Q). In other words, if input matrix is A, then A = R * Q
 */
@SuppressWarnings("DuplicatedCode")
public class RQDecomposer extends Decomposer {

    /**
     * Internal QR decomposer used behind the scenes to compute RQ
     * decomposition. Notice that QR and RQ decompositions are related and for
     * that reason QRDecomposer is used
     */
    private final QRDecomposer internalDecomposer;

    /**
     * Constructor of this class.
     */
    public RQDecomposer() {
        super();
        internalDecomposer = new QRDecomposer();
    }

    /**
     * Constructor of this class.
     *
     * @param inputMatrix Reference to input matrix to be decomposed.
     */
    public RQDecomposer(final Matrix inputMatrix) {
        super(inputMatrix);
        internalDecomposer = new QRDecomposer();
    }

    /**
     * Returns decomposer type corresponding to RQ decomposition.
     *
     * @return Decomposer type.
     */
    @Override
    public DecomposerType getDecomposerType() {
        return DecomposerType.RQ_DECOMPOSITION;
    }

    /**
     * Sets reference to input matrix to be decomposed.
     *
     * @param inputMatrix Reference to input matrix to be decomposed.
     * @throws LockedException Exception thrown if attempting to call this
     *                         method while this instance remains locked.
     */
    @Override
    public void setInputMatrix(final Matrix inputMatrix) throws LockedException {
        super.setInputMatrix(inputMatrix);
        internalDecomposer.setInputMatrix(inputMatrix);
    }


    /**
     * Returns boolean indicating whether decomposition has been computed and
     * results can be retrieved.
     * Attempting to retrieve decomposition results when not available, will
     * probably raise a NotAvailableException.
     *
     * @return Boolean indicating whether decomposition has been computed and
     * results can be retrieved.
     */
    @Override
    public boolean isDecompositionAvailable() {
        return internalDecomposer.isDecompositionAvailable();
    }

    /**
     * This method computes RQ matrix decomposition, which consists on factoring
     * provided input matrix into an upper triangular matrix (R) and an
     * orthogonal matrix (Q).
     * In other words, if input matrix is A, then: A = R * Q
     * Note: During execution of this method, this instance will be locked, and
     * hence attempting to set some parameters might raise a LockedException.
     * Note: After execution of this method, RQ decomposition will be available
     * and operations such as retrieving R and Q matrices will be able to be
     * done. Attempting to call any of such operations before calling this
     * method wil raise a NotAvailableException because they require computation
     * of QR decomposition first.
     *
     * @throws NotReadyException   Exception thrown if attempting to call this
     *                             method when this instance is not ready (i.e. no input matrix has been
     *                             provided).
     * @throws LockedException     Exception thrown if this decomposer is already
     *                             locked before calling this method. Notice that this method will actually
     *                             lock this instance while it is being executed.
     * @throws DecomposerException Exception thrown if for any reason
     *                             decomposition fails while being executed, like when provided input matrix
     *                             has fewer rows than columns.
     */
    @Override
    public void decompose() throws NotReadyException, LockedException, DecomposerException {

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

        Matrix tmp = null;

        locked = true;

        final var rows = inputMatrix.getRows();
        final var columns = inputMatrix.getColumns();

        try {
            tmp = new Matrix(columns, rows);

            for (var j = 0; j < columns; j++) {
                for (var i = 0; i < rows; i++) {
                    tmp.setElementAt(j, rows - i - 1,
                            inputMatrix.getElementAt(i, j));
                }
            }
        } catch (final WrongSizeException ignore) {
            // never happens
        }

        internalDecomposer.setInputMatrix(tmp);
        internalDecomposer.decompose();

        locked = false;
    }

    /**
     * Returns upper triangular factor matrix.
     * RQ decomposition decomposes input matrix into R (upper triangular
     * matrix), and Q, which is an orthogonal matrix.
     *
     * @return Upper triangular factor matrix.
     * @throws NotAvailableException Exception thrown if attempting to call this
     *                               method before computing RQ decomposition. To avoid this exception call
     *                               decompose() method first.
     * @see #decompose()
     */
    public Matrix getR() throws NotAvailableException {

        if (!isDecompositionAvailable()) {
            throw new NotAvailableException();
        }

        final var rows = inputMatrix.getRows();
        final var columns = inputMatrix.getColumns();

        final var r2 = internalDecomposer.getR();

        // Left-right flipped identity
        // Instance initialized to zero
        Matrix flipI = null;
        try {
            flipI = new Matrix(rows, rows);

            flipI.initialize(0.0);

            for (var j = 0; j < rows; j++) {
                for (var i = 0; i < rows; i++) {
                    if (i == rows - j - 1) flipI.setElementAt(i, j, 1.0);
                }
            }
        } catch (final WrongSizeException ignore) {
            //never happens
        }

        // Big permutation
        Matrix perm = null;
        if (flipI != null) {
            try {
                perm = Matrix.identity(columns, columns);

                // Copy flipped identity into top-left corner
                perm.setSubmatrix(0, 0, rows - 1, rows - 1,
                        flipI);

                perm.multiply(r2); //perm * r2
                perm.multiply(flipI); //perm * r2 * flipI
                perm.transpose();

            } catch (final WrongSizeException ignore) {
                // never happens
            }
        }
        return perm;
    }

    /**
     * Returns the economy-sized orthogonal factor matrix.
     * RQ decomposition decomposes input matrix into R, which is an upper
     * triangular matrix and Q (orthogonal matrix)
     *
     * @return Orthogonal factor matrix.
     * @throws NotAvailableException Exception thrown if attempting to call this
     *                               method before computing RQ decomposition. To avoid this exception call
     *                               decompose() method first.
     * @see #decompose()
     */
    public Matrix getQ() throws NotAvailableException {
        if (!isDecompositionAvailable()) {
            throw new NotAvailableException();
        }

        final var rows = inputMatrix.getRows();
        final var columns = inputMatrix.getColumns();

        final var q2 = internalDecomposer.getQ();

        // Left-right flipped identity
        // Instance initialized to zero
        Matrix flipI = null;
        try {
            flipI = new Matrix(rows, rows);

            flipI.initialize(0.0);

            for (int j = 0; j < rows; j++) {
                for (int i = 0; i < rows; i++) {
                    if (i == rows - j - 1) {
                        flipI.setElementAt(i, j, 1.0);
                    }
                }
            }
        } catch (final WrongSizeException ignore) {
            //never happens
        }


        // Big permutation
        Matrix perm = null;
        try {
            perm = Matrix.identity(columns, columns);

            // Copy flipped identity into top-left corner
            perm.setSubmatrix(0, 0, rows - 1, rows - 1, flipI);

        } catch (final WrongSizeException ignore) {
            // never happens
        }


        Matrix q = null;
        if (perm != null) {
            try {
                q = q2.multiplyAndReturnNew(perm); // qTrans = q2 * perm
                q.transpose();
            } catch (final WrongSizeException ignore) {
                //never happens
            }
        }

        return q;
    }
}