SparseMatrix.java

package org.mklab.sdpj.datastructure;

import org.mklab.nfc.matrix.ComplexNumericalMatrix;
import org.mklab.nfc.matrix.RealNumericalMatrix;
import org.mklab.nfc.scalar.ComplexNumericalScalar;
import org.mklab.nfc.scalar.RealNumericalScalar;
import org.mklab.sdpj.gpack.atlas.ATLAS;


/**
 * Class <code>SparseMatrix</code> defined a data structure of a sparse matrix.
 * <br>疎行列を表すクラスです。
 * 
 * @author takafumi
 * @param <RS> type of real scalar
 * @param <RM> type of real matrix
 * @param <CS> type of complex scalar
 * @param <CM> type of complex Matrix
 */
public class SparseMatrix<RS extends RealNumericalScalar<RS, RM, CS, CM>, RM extends RealNumericalMatrix<RS, RM, CS, CM>, CS extends ComplexNumericalScalar<RS, RM, CS, CM>, CM extends ComplexNumericalMatrix<RS, RM, CS, CM>> {

  /** 行数 */
  private int rowSize;
  /** 列数 */
  private int columnSize;

  /** 疎・密・対角の種類 */
  private SparseDenseDiagonal sparseOrDenseOrDiagonal;

  /** 非零の成分の個数 */
  private int nonZeroNumber;

  /** currently stored */
  public int nonZeroCount;

  /** use for calculation of F1,F2,F3 */
  public int nonZeroEffect;

  /** 密行列の成分 */
  public RS[] denseElements;
  /** 疎行列の成分の行番号 */
  public int[] rowIndex;
  /** 疎行列の成分の列番号 */
  public int[] columnIndex;
  /** 疎行列の成分 */
  public RS[] sparseElements;
  /** 対角行列の成分 */
  public RS[] diagonalElements;

  /**
   * 新しく生成された{@link SparseMatrix}オブジェクトを初期化します。
   */
  public SparseMatrix() {
    this.rowSize = 0;
    this.columnSize = 0;
    this.sparseOrDenseOrDiagonal = SparseDenseDiagonal.SPARSE;
    this.nonZeroNumber = 0;
    this.nonZeroCount = 0;
    this.nonZeroEffect = 0;
  }

  /**
   * 新しく生成された<code>SparseMatrix</code>オブジェクトを初期化します。
   * 
   * @param rowSize 行数
   * @param columnSize 列数
   * @param sparseOrDenseOrDiagonal 疎・密・対角の種類
   * @param nonZeroNumber 非零の成分の個数
   * @param unit unit
   */
  public SparseMatrix(int rowSize, int columnSize, SparseDenseDiagonal sparseOrDenseOrDiagonal, int nonZeroNumber, RS unit) {
    if (rowSize < 0 || columnSize < 0) {
      throw new RuntimeException("Dimensions are nonpositive"); //$NON-NLS-1$
    }

    this.rowSize = rowSize;
    this.columnSize = columnSize;
    this.sparseOrDenseOrDiagonal = sparseOrDenseOrDiagonal;

    //final RS unit = Tools.getUnitNumber();

    switch (sparseOrDenseOrDiagonal) {
      case SPARSE:
        this.nonZeroNumber = nonZeroNumber;
        this.nonZeroCount = 0;
        this.nonZeroEffect = 0;
        this.rowIndex = new int[nonZeroNumber];
        this.columnIndex = new int[nonZeroNumber];
        this.sparseElements = unit.createArray(nonZeroNumber);
        break;
      case DENSE:
        int length = rowSize * columnSize;
        this.nonZeroNumber = length;
        this.nonZeroCount = length;
        this.nonZeroEffect = length;
        this.denseElements = unit.createArray(nonZeroNumber);
        ATLAS.setValue(length, unit.createZero(), this.denseElements, 1);
        break;
      case DIAGONAL:
        if (rowSize != columnSize) {
          throw new RuntimeException("SparseMatrix:: Diagonal must be Square matrix"); //$NON-NLS-1$
        }
        this.nonZeroNumber = columnSize;
        this.nonZeroCount = columnSize;
        this.nonZeroEffect = columnSize;
        this.diagonalElements = unit.createArray(this.nonZeroNumber);
        ATLAS.setValue(columnSize, unit.createZero(), this.diagonalElements, 1);
        break;
      default:
        throw new IllegalArgumentException();
    }
  }

  /**
   * This method is an override method of <code>toString</code>, for more details, 
   * refer to <code>toString</code> of <code>java</code>  
   * @see java.lang.Object#toString()
   * 
   */
  @Override
  public String toString() {
    StringBuffer buff = new StringBuffer();

    switch (getSparseOrDenseOrDiagonal()) {
      case SPARSE:
        buff.append("{"); //$NON-NLS-1$
        for (int k = 0; k < this.nonZeroCount; ++k) {
          int i = this.rowIndex[k];
          int j = this.columnIndex[k];
          RS value = this.sparseElements[k];
          buff.append("val[" + i + "," + j + "] = " + value.toString() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
        }
        buff.append("}\n"); //$NON-NLS-1$
        break;
      case DENSE:
        buff.append("{\n"); //$NON-NLS-1$
        for (int i = 0; i < this.rowSize - 1; ++i) {
          if (i == 0) {
            buff.append(" "); //$NON-NLS-1$
          } else {
            buff.append("  "); //$NON-NLS-1$
          }
          buff.append("{"); //$NON-NLS-1$
          for (int j = 0; j < this.columnSize - 1; ++j) {
            buff.append(this.denseElements[i + this.columnSize * j] + ","); //$NON-NLS-1$
          }
          buff.append(this.denseElements[i + this.columnSize * (getColumnSize() - 1)] + " },\n"); //$NON-NLS-1$
        }
        if (this.rowSize > 1) {
          buff.append("  {"); //$NON-NLS-1$
        }
        for (int j = 0; j < this.columnSize - 1; ++j) {
          buff.append(this.denseElements[(this.rowSize - 1) + this.columnSize * j] + ","); //$NON-NLS-1$
        }
        buff.append(this.denseElements[(this.rowSize - 1) + this.columnSize * (getColumnSize() - 1)] + " }"); //$NON-NLS-1$
        if (this.rowSize > 1) {
          buff.append("   }\n"); //$NON-NLS-1$
        } else {
          buff.append("\n"); //$NON-NLS-1$
        }
        break;
      case DIAGONAL:
        buff.append("{"); //$NON-NLS-1$
        for (int j = 0; j < this.columnSize - 1; ++j) {
          buff.append(this.diagonalElements[j] + ","); //$NON-NLS-1$
        }
        if (getColumnSize() > 0) {
          buff.append(this.diagonalElements[getColumnSize() - 1] + ","); //$NON-NLS-1$
        }
        buff.append("}\n"); //$NON-NLS-1$
        break;
    }
    return buff.toString();
  }

  /**
   * 密行列に変更します。
   * 
   * @param forceChange 強制的に変更するならばtrue
   */
  public void changeToDense(boolean forceChange) {
    if (isSparce() == false) {
      return;
    }

    if (forceChange == false && this.nonZeroCount < (this.rowSize * this.columnSize) * 0.20) {
      // if the number of elements are less than 20 percent, we don't change to Dense.
      return;
    }

    RS unit = getSparseElement(0).createUnit();

    this.sparseOrDenseOrDiagonal = SparseDenseDiagonal.DENSE;
    int length = this.rowSize * this.columnSize;
    this.denseElements = unit.createArray(length);
    ATLAS.setValue(length, unit.createZero(), this.denseElements, 1);

    for (int k = 0; k < this.nonZeroCount; ++k) {
      final int i = this.rowIndex[k];
      final int j = this.columnIndex[k];
      final RS value = this.sparseElements[k];
      if (i == j) {
        this.denseElements[i + this.columnSize * j] = value;
      } else {
        this.denseElements[i + this.columnSize * j] = value;
        this.denseElements[j + this.columnSize * i] = value;
      }
    }
    this.nonZeroCount = length;
    this.nonZeroEffect = length;
    this.nonZeroNumber = length;

    if (this.rowIndex != null) {
      this.rowIndex = null;
    }
    if (this.columnIndex != null) {
      this.columnIndex = null;
    }
    if (this.sparseElements != null) {
      this.sparseElements = null;
    }
  }

  /**
   * 疎・密・対角の種類を返します。
   * 
   * @return 疎・密・対角の種類
   */
  public SparseDenseDiagonal getSparseOrDenseOrDiagonal() {
    return this.sparseOrDenseOrDiagonal;
  }

  /**
   * 行数を返します。
   * 
   * @return 行数
   */
  public int getRowSize() {
    return this.rowSize;
  }

  /**
   * 列数を返します。
   * 
   * @return 列数
   */
  public int getColumnSize() {
    return this.columnSize;
  }

  /**
   * Return a nonzero number.
   * 
   * @return a nonzero number.
   */
  public int getNonZeroNumber() {
    return this.nonZeroNumber;
  }

  /**
   * 疎行列であるか判別します。
   * 
   * @return 疎行列ならばtrue、そうでなければfalse
   */
  public boolean isSparce() {
    return this.sparseOrDenseOrDiagonal == SparseDenseDiagonal.SPARSE;
  }

  /**
   * 密行列であるか判別します。
   * 
   * @return 密行列ならばtrue、そうでなければfalse
   */
  public boolean isDense() {
    return this.sparseOrDenseOrDiagonal == SparseDenseDiagonal.DENSE;
  }

  /**
   * 対角行列であるか判別します。
   * 
   * @return 対角行列ならばtrue、そうでなければfalse
   */
  public boolean isDiagonal() {
    return this.sparseOrDenseOrDiagonal == SparseDenseDiagonal.DIAGONAL;
  }

  /**
   * 密行列の成分を返します。
   * 
   * @param index 指数
   * @return 密行列の成分
   */
  public RS getDenseElement(int index) {
    return this.denseElements[index];
  }

  /**
   * 疎行列の成分を返します。
   * 
   * @param index 指数
   * @return 疎行列の成分
   */
  public RS getSparseElement(int index) {
    return this.sparseElements[index];
  }

  /**
   * 対角行列の成分を返します。
   * 
   * @param index 指数
   * @return 対角行列の成分
   */
  public RS getDiagonalElement(int index) {
    return this.diagonalElements[index];
  }

  /**
   * 成分の単位を返します。
   * 
   * @return 成分の単位
   */
  public RS getElementUnit() {
    if (isDense()) {
      return getDenseElement(0).createUnit();
    }
    if (isDiagonal()) {
      return getDiagonalElement(0).createUnit();
    }
    if (isSparce()) {
      return getSparseElement(0).createUnit();
    }

    throw new RuntimeException("Inappropariate matrix type"); //$NON-NLS-1$
  }

  /**
   * Returns a copy of instance of class <code>SparseMatrix</code>
   * 
   * @return a copy of instance of class <code>SparseMatrix</code>
   */
  public SparseMatrix<RS,RM,CS,CM> createClone() {
    SparseMatrix<RS,RM,CS,CM> inst = new SparseMatrix<>();
    inst.rowSize = this.rowSize;
    inst.columnSize = this.columnSize;
    inst.sparseOrDenseOrDiagonal = this.sparseOrDenseOrDiagonal == null ? null : this.sparseOrDenseOrDiagonal;
    inst.nonZeroNumber = this.nonZeroNumber;
    inst.nonZeroCount = this.nonZeroCount;
    inst.nonZeroEffect = this.nonZeroEffect;

    if (this.denseElements != null) {
      inst.denseElements = this.denseElements[0].createArray(this.denseElements.length);
      System.arraycopy(this.denseElements, 0, inst.denseElements, 0, this.denseElements.length);
      //inst.denseElements = GridUtil.clone(this.denseElements);
    } else {
      inst.denseElements = null;
    }

    if (this.rowIndex != null) {
      inst.rowIndex = new int[this.rowIndex.length];
      for (int i = 0; i < this.rowIndex.length; i++) {
        inst.rowIndex[i] = this.rowIndex[i];
      }
    } else {
      inst.rowIndex = null;
    }

    if (this.columnIndex != null) {
      inst.columnIndex = new int[this.columnIndex.length];
      for (int i = 0; i < this.columnIndex.length; i++) {
        inst.columnIndex[i] = this.columnIndex[i];
      }
    } else {
      inst.columnIndex = null;
    }

    if (this.sparseElements != null) {
      inst.sparseElements = this.sparseElements[0].createArray(this.sparseElements.length);
      System.arraycopy(this.sparseElements, 0, inst.sparseElements, 0, this.sparseElements.length);
      //inst.sparseElements = GridUtil.clone(this.sparseElements);

    } else {
      inst.sparseElements = null;
    }

    if (this.diagonalElements != null) {
      inst.diagonalElements = this.diagonalElements[0].createArray(this.diagonalElements.length);
      System.arraycopy(this.diagonalElements, 0, inst.diagonalElements, 0, this.diagonalElements.length);
      //inst.diagonalElements = GridUtil.clone(this.diagonalElements);
    } else {
      inst.diagonalElements = null;
    }
    return inst;
  }
}