/**
   * Function to perform LU decomposition on a given matrix.
   *
   * @param in matrix object
   * @return array of matrix blocks
   * @throws DMLRuntimeException if DMLRuntimeException occurs
   */
  private static MatrixBlock[] computeLU(MatrixObject in) throws DMLRuntimeException {
    if (in.getNumRows() != in.getNumColumns()) {
      throw new DMLRuntimeException(
          "LU Decomposition can only be done on a square matrix. Input matrix is rectangular (rows="
              + in.getNumRows()
              + ", cols="
              + in.getNumColumns()
              + ")");
    }

    Array2DRowRealMatrix matrixInput = DataConverter.convertToArray2DRowRealMatrix(in);

    // Perform LUP decomposition
    LUDecomposition ludecompose = new LUDecomposition(matrixInput);
    RealMatrix P = ludecompose.getP();
    RealMatrix L = ludecompose.getL();
    RealMatrix U = ludecompose.getU();

    // Read the results into native format
    MatrixBlock mbP = DataConverter.convertToMatrixBlock(P.getData());
    MatrixBlock mbL = DataConverter.convertToMatrixBlock(L.getData());
    MatrixBlock mbU = DataConverter.convertToMatrixBlock(U.getData());

    return new MatrixBlock[] {mbP, mbL, mbU};
  }
  public static void matmult(
      MatrixObject left1,
      MatrixObject right1,
      MatrixObject output,
      boolean isLeftTransposed1,
      boolean isRightTransposed1)
      throws DMLRuntimeException {
    if (isInSparseFormat(left1) || isInSparseFormat(right1)) {
      throw new DMLRuntimeException("Sparse GPU matrix multiplication is not implemented");
    }

    // Since CuBLAS expects inputs in column-major format,
    // reverse the order of matrix-multiplication and take care of dimension mismatch.
    MatrixObject left = right1;
    MatrixObject right = left1;
    boolean isLeftTransposed = isRightTransposed1;
    boolean isRightTransposed = isLeftTransposed1;

    char transa = isLeftTransposed ? 'T' : 'N';
    char transb = isRightTransposed ? 'T' : 'N';
    // Note: the dimensions are swapped
    int m = (int) (isLeftTransposed ? left.getNumRows() : left.getNumColumns());
    int n = (int) (isRightTransposed ? right.getNumColumns() : right.getNumRows());
    int k = (int) (isLeftTransposed ? left.getNumColumns() : left.getNumRows());
    int k1 = (int) (isRightTransposed ? right.getNumRows() : right.getNumColumns());
    if (k != k1) throw new DMLRuntimeException("Dimension mismatch: " + k + " != " + k1);

    if (m == -1 || n == -1 || k == -1) throw new DMLRuntimeException("Incorrect dimensions");

    double alpha = 1;
    double beta = 0;

    int lda = isLeftTransposed ? k : m;
    int ldb = isRightTransposed ? n : k;
    int ldc = m;

    if (!left.getGPUObject().isAllocated() || !right.getGPUObject().isAllocated())
      throw new DMLRuntimeException(
          "One of input is not allocated:"
              + left.getGPUObject().isAllocated()
              + " "
              + right.getGPUObject().isAllocated());
    if (!output.getGPUObject().isAllocated())
      throw new DMLRuntimeException(
          "Output is not allocated:" + output.getGPUObject().isAllocated());

    Pointer A = ((JCudaObject) left.getGPUObject()).jcudaPointer;
    Pointer B = ((JCudaObject) right.getGPUObject()).jcudaPointer;
    Pointer C = ((JCudaObject) output.getGPUObject()).jcudaPointer;

    JCublas.cublasDgemm(transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc);
  }
  /**
   * @param hop
   * @param vars
   * @return
   * @throws DMLRuntimeException
   */
  private static long getIntValueDataLiteral(Hop hop, LocalVariableMap vars)
      throws DMLRuntimeException {
    long value = -1;

    try {
      if (hop instanceof LiteralOp) {
        value = HopRewriteUtils.getIntValue((LiteralOp) hop);
      } else if (hop instanceof UnaryOp && ((UnaryOp) hop).getOp() == OpOp1.NROW) {
        // get the dimension information from the matrix object because the hop
        // dimensions might not have been updated during recompile
        MatrixObject mo = (MatrixObject) vars.get(hop.getInput().get(0).getName());
        value = mo.getNumRows();
      } else if (hop instanceof UnaryOp && ((UnaryOp) hop).getOp() == OpOp1.NCOL) {
        // get the dimension information from the matrix object because the hop
        // dimensions might not have been updated during recompile
        MatrixObject mo = (MatrixObject) vars.get(hop.getInput().get(0).getName());
        value = mo.getNumColumns();
      } else {
        ScalarObject sdat = (ScalarObject) vars.get(hop.getName());
        value = sdat.getLongValue();
      }
    } catch (HopsException ex) {
      throw new DMLRuntimeException("Failed to get int value for literal replacement", ex);
    }

    return value;
  }
  /**
   * @param c
   * @param vars
   * @return
   * @throws DMLRuntimeException
   */
  private static LiteralOp replaceLiteralFullUnaryAggregate(Hop c, LocalVariableMap vars)
      throws DMLRuntimeException {
    LiteralOp ret = null;

    // full unary aggregate w/ matrix less than 10^6 cells
    if (c instanceof AggUnaryOp
        && isReplaceableUnaryAggregate((AggUnaryOp) c)
        && c.getInput().get(0) instanceof DataOp
        && vars.keySet().contains(c.getInput().get(0).getName())) {
      Hop data = c.getInput().get(0);
      MatrixObject mo = (MatrixObject) vars.get(data.getName());

      // get the dimension information from the matrix object because the hop
      // dimensions might not have been updated during recompile
      if (mo.getNumRows() * mo.getNumColumns() < REPLACE_LITERALS_MAX_MATRIX_SIZE) {
        MatrixBlock mBlock = mo.acquireRead();
        double value = replaceUnaryAggregate((AggUnaryOp) c, mBlock);
        mo.release();

        // literal substitution (always double)
        ret = new LiteralOp(value);
      }
    }

    return ret;
  }
  /**
   * Function to perform Eigen decomposition on a given matrix. Input must be a symmetric matrix.
   *
   * @param in matrix object
   * @return array of matrix blocks
   * @throws DMLRuntimeException if DMLRuntimeException occurs
   */
  private static MatrixBlock[] computeEigen(MatrixObject in) throws DMLRuntimeException {
    if (in.getNumRows() != in.getNumColumns()) {
      throw new DMLRuntimeException(
          "Eigen Decomposition can only be done on a square matrix. Input matrix is rectangular (rows="
              + in.getNumRows()
              + ", cols="
              + in.getNumColumns()
              + ")");
    }

    Array2DRowRealMatrix matrixInput = DataConverter.convertToArray2DRowRealMatrix(in);

    EigenDecomposition eigendecompose = new EigenDecomposition(matrixInput);
    RealMatrix eVectorsMatrix = eigendecompose.getV();
    double[][] eVectors = eVectorsMatrix.getData();
    double[] eValues = eigendecompose.getRealEigenvalues();

    // Sort the eigen values (and vectors) in increasing order (to be compatible w/ LAPACK.DSYEVR())
    int n = eValues.length;
    for (int i = 0; i < n; i++) {
      int k = i;
      double p = eValues[i];
      for (int j = i + 1; j < n; j++) {
        if (eValues[j] < p) {
          k = j;
          p = eValues[j];
        }
      }
      if (k != i) {
        eValues[k] = eValues[i];
        eValues[i] = p;
        for (int j = 0; j < n; j++) {
          p = eVectors[j][i];
          eVectors[j][i] = eVectors[j][k];
          eVectors[j][k] = p;
        }
      }
    }

    MatrixBlock mbValues = DataConverter.convertToMatrixBlock(eValues, true);
    MatrixBlock mbVectors = DataConverter.convertToMatrixBlock(eVectors);

    return new MatrixBlock[] {mbValues, mbVectors};
  }
  /**
   * @param c
   * @param vars
   * @return
   * @throws DMLRuntimeException
   */
  private static LiteralOp replaceLiteralFullUnaryAggregateRightIndexing(
      Hop c, LocalVariableMap vars) throws DMLRuntimeException {
    LiteralOp ret = null;

    // full unary aggregate w/ indexed matrix less than 10^6 cells
    if (c instanceof AggUnaryOp
        && isReplaceableUnaryAggregate((AggUnaryOp) c)
        && c.getInput().get(0) instanceof IndexingOp
        && c.getInput().get(0).getInput().get(0) instanceof DataOp) {
      IndexingOp rix = (IndexingOp) c.getInput().get(0);
      Hop data = rix.getInput().get(0);
      Hop rl = rix.getInput().get(1);
      Hop ru = rix.getInput().get(2);
      Hop cl = rix.getInput().get(3);
      Hop cu = rix.getInput().get(4);

      if (data instanceof DataOp
          && vars.keySet().contains(data.getName())
          && isIntValueDataLiteral(rl, vars)
          && isIntValueDataLiteral(ru, vars)
          && isIntValueDataLiteral(cl, vars)
          && isIntValueDataLiteral(cu, vars)) {
        long rlval = getIntValueDataLiteral(rl, vars);
        long ruval = getIntValueDataLiteral(ru, vars);
        long clval = getIntValueDataLiteral(cl, vars);
        long cuval = getIntValueDataLiteral(cu, vars);

        MatrixObject mo = (MatrixObject) vars.get(data.getName());

        // get the dimension information from the matrix object because the hop
        // dimensions might not have been updated during recompile
        if (mo.getNumRows() * mo.getNumColumns() < REPLACE_LITERALS_MAX_MATRIX_SIZE) {
          MatrixBlock mBlock = mo.acquireRead();
          MatrixBlock mBlock2 =
              mBlock.sliceOperations(
                  (int) (rlval - 1),
                  (int) (ruval - 1),
                  (int) (clval - 1),
                  (int) (cuval - 1),
                  new MatrixBlock());
          double value = replaceUnaryAggregate((AggUnaryOp) c, mBlock2);
          mo.release();

          // literal substitution (always double)
          ret = new LiteralOp(value);
        }
      }
    }

    return ret;
  }
  /**
   * @param c
   * @param vars
   * @return
   * @throws DMLRuntimeException
   */
  private static LiteralOp replaceLiteralValueTypeCastRightIndexing(Hop c, LocalVariableMap vars)
      throws DMLRuntimeException {
    LiteralOp ret = null;

    // as.scalar/right indexing w/ literals/vars and matrix less than 10^6 cells
    if (c instanceof UnaryOp
        && ((UnaryOp) c).getOp() == OpOp1.CAST_AS_SCALAR
        && c.getInput().get(0) instanceof IndexingOp
        && c.getInput().get(0).getDataType() == DataType.MATRIX) {
      IndexingOp rix = (IndexingOp) c.getInput().get(0);
      Hop data = rix.getInput().get(0);
      Hop rl = rix.getInput().get(1);
      Hop ru = rix.getInput().get(2);
      Hop cl = rix.getInput().get(3);
      Hop cu = rix.getInput().get(4);
      if (rix.dimsKnown()
          && rix.getDim1() == 1
          && rix.getDim2() == 1
          && data instanceof DataOp
          && vars.keySet().contains(data.getName())
          && isIntValueDataLiteral(rl, vars)
          && isIntValueDataLiteral(ru, vars)
          && isIntValueDataLiteral(cl, vars)
          && isIntValueDataLiteral(cu, vars)) {
        long rlval = getIntValueDataLiteral(rl, vars);
        long clval = getIntValueDataLiteral(cl, vars);

        MatrixObject mo = (MatrixObject) vars.get(data.getName());

        // get the dimension information from the matrix object because the hop
        // dimensions might not have been updated during recompile
        if (mo.getNumRows() * mo.getNumColumns() < REPLACE_LITERALS_MAX_MATRIX_SIZE) {
          MatrixBlock mBlock = mo.acquireRead();
          double value = mBlock.getValue((int) rlval - 1, (int) clval - 1);
          mo.release();

          // literal substitution (always double)
          ret = new LiteralOp(value);
        }
      }
    }

    return ret;
  }
  public void setMetaData(String varName, long nrows, long ncols) throws DMLRuntimeException {
    MatrixObject mo = getMatrixObject(varName);
    if (mo.getNumRows() == nrows && mo.getNumColumns() == ncols) return;

    MetaData oldMetaData = mo.getMetaData();
    if (oldMetaData == null || !(oldMetaData instanceof MatrixFormatMetaData))
      throw new DMLRuntimeException("Metadata not available");

    MatrixCharacteristics mc =
        new MatrixCharacteristics(
            (long) nrows,
            (long) ncols,
            (int) mo.getNumRowsPerBlock(),
            (int) mo.getNumColumnsPerBlock());
    mo.setMetaData(
        new MatrixFormatMetaData(
            mc,
            ((MatrixFormatMetaData) oldMetaData).getOutputInfo(),
            ((MatrixFormatMetaData) oldMetaData).getInputInfo()));
  }
 public static boolean isInSparseFormat(MatrixObject mo) {
   if (mo.getGPUObject() != null && mo.getGPUObject().isAllocated())
     return mo.getGPUObject().isInSparseFormat();
   return MatrixBlock.evalSparseFormatInMemory(mo.getNumRows(), mo.getNumColumns(), mo.getNnz());
 }