/**
   * Internal function call that stores the result of a matrix multiply into the given
   * destinationMatrix.
   *
   * @param multiplicationMatrix matrix to postmultiply this by
   * @param destinationMatrix matrix to store the matrix multiplication result, must have rows ==
   *     this.getNumRows and columns == multiplicationMatrix.getNumColumns
   */
  protected void timesInto(
      final AbstractMTJMatrix multiplicationMatrix, AbstractMTJMatrix destinationMatrix) {
    int M = this.getNumRows();
    int N = multiplicationMatrix.getNumColumns();
    if ((M != destinationMatrix.getNumRows()) || (N != destinationMatrix.getNumColumns())) {
      throw new DimensionalityMismatchException("Multiplication dimensions do not agree.");
    }

    this.internalMatrix.mult(multiplicationMatrix.internalMatrix, destinationMatrix.internalMatrix);
  }
  /**
   * Solves for "X" in the equation this * X = B, where X is a DenseMatrix, "this" and "B" will be
   * converted to a DenseMatrix (if not already)
   *
   * @param B AbstractMTJMatrix, will be converted to a DenseMatrix
   * @return DenseMatrix of "X" in this * X = B
   */
  public final Matrix solve(AbstractMTJMatrix B) {
    DenseMatrix X = new DenseMatrix(this.getNumColumns(), B.getNumColumns());

    DenseMatrix Bdense;
    if (B instanceof DenseMatrix) {
      Bdense = (DenseMatrix) B;
    } else {
      Bdense = new DenseMatrix(B);
    }

    DenseMatrix Adense;
    if (this instanceof DenseMatrix) {
      Adense = (DenseMatrix) this;
    } else {
      Adense = new DenseMatrix(this);
    }

    boolean usePseudoInverse = false;
    try {
      Adense.solveInto(Bdense, X);
      usePseudoInverse = false;
    } catch (MatrixSingularException e) {
      Logger.getLogger(AbstractMTJMatrix.class.getName())
          .log(Level.WARNING, "AbstractMTJMatrix.solve(): Matrix is singular.");
      usePseudoInverse = true;
    }

    // Sometimes LAPACK will return NaNs or infs as the solutions, but MTJ
    // won't throw the exception, so we need to check for this.
    // If we detect this, then we'll use a pseudoinverse
    if (!usePseudoInverse) {
      for (int i = 0; i < X.getNumRows(); i++) {
        for (int j = 0; j < X.getNumColumns(); j++) {
          double v = X.getElement(i, j);
          if (Double.isNaN(v) || Double.isInfinite(v)) {
            Logger.getLogger(AbstractMTJMatrix.class.getName())
                .log(Level.WARNING, "AbstractMTJMatrix.solve(): Solver produced invalid results.");
            usePseudoInverse = true;
            break;
          }
        }
        if (usePseudoInverse) {
          break;
        }
      }
    }

    if (usePseudoInverse) {
      // The original LU solver produced a sucky answer, so let's use
      // the absurdly expensive SVD least-squares solution
      return Adense.pseudoInverse().times(Bdense);
    }

    return X;
  }
  /**
   * Internal routine for storing a submatrix into and AbstractMTJMatrix. Gets the embedded
   * submatrix inside of the Matrix, specified by the inclusive, zero-based indices such that the
   * result matrix will have size (maxRow-minRow+1) x (maxColum-minCcolumn+1)
   *
   * @param minRow Zero-based index into the rows of the Matrix, must be less than or equal to
   *     maxRow
   * @param maxRow Zero-based index into the rows of the Matrix, must be greater than or equal to
   *     minRow
   * @param minColumn Zero-based index into the rows of the Matrix, must be less than or equal to
   *     maxColumn
   * @param maxColumn Zero-based index into the rows of the Matrix, must be greater than or equal to
   *     minColumn
   * @param destinationMatrix the destination submatrix of dimension
   *     (maxRow-minRow+1)x(maxColumn-minColumn+1)
   */
  protected void getSubMatrixInto(
      int minRow, int maxRow, int minColumn, int maxColumn, AbstractMTJMatrix destinationMatrix) {

    if ((destinationMatrix.getNumRows() != (maxRow - minRow + 1))
        || (destinationMatrix.getNumColumns() != (maxColumn - minColumn + 1))) {
      throw new DimensionalityMismatchException("Submatrix is incorrect size.");
    }

    for (int i = minRow; i <= maxRow; i++) {
      for (int j = minColumn; j <= maxColumn; j++) {
        destinationMatrix.setElement(i - minRow, j - minColumn, this.getElement(i, j));
      }
    }
  }