RealRumpMethod.java

/*
 * Created on 2004/08/03
 * 
 * Copyright 2004. Created by hiroki
 */
package org.mklab.cga.eigen;

import org.mklab.cga.interval.matrix.IntervalComplexNumericalMatrix;
import org.mklab.cga.interval.matrix.IntervalRealNumericalMatrix;
import org.mklab.cga.interval.scalar.IntervalComplexNumericalScalar;
import org.mklab.cga.interval.scalar.IntervalRealNumericalScalar;
import org.mklab.cga.util.IntervalUtil;
import org.mklab.nfc.eig.EigenSolution;
import org.mklab.nfc.matrix.BooleanMatrix;
import org.mklab.nfc.matrix.ComplexNumericalMatrix;
import org.mklab.nfc.matrix.ElementHolder;
import org.mklab.nfc.matrix.IndexedMatrix;
import org.mklab.nfc.matrix.IntMatrix;
import org.mklab.nfc.matrix.RealNumericalMatrix;
import org.mklab.nfc.scalar.ComplexNumericalScalar;
import org.mklab.nfc.scalar.RealNumericalScalar;
import org.mklab.nfc.util.RoundMode;
import org.mklab.nfc.util.RoundModeManager;


/**
 * 多重および近接固有値の精度保証を行うクラスです。 Rumpの方法を実装しています。
 * 
 * @author hiroki
 * @param <RIS> 実区間スカラーの型
 * @param <RIM> 実区間行列の型
 * @param <CIS> 複素区間スカラーの型
 * @param <CIM> 複素区間行列の型
 * @param <RS> 実スカラーの型
 * @param <RM> 実行列の型
 * @param <CS> 複素スカラーの型
 * @param <CM> 複素行列の型
 */
public class RealRumpMethod<RIS extends IntervalRealNumericalScalar<RIS, RIM, CIS, CIM, RS, RM, CS, CM>, RIM extends IntervalRealNumericalMatrix<RIS, RIM, CIS, CIM, RS, RM, CS, CM>, CIS extends IntervalComplexNumericalScalar<RIS, RIM, CIS, CIM, RS, RM, CS, CM>, CIM extends IntervalComplexNumericalMatrix<RIS, RIM, CIS, CIM, RS, RM, CS, CM>, 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>>
    implements RealEigenVerifier<RIS, RIM, CIS, CIM, RS, RM, CS, CM> {

  /** 対象となる行列 */
  private RM A;

  /** 対象となる区間行列 */
  private RIM IA;

  /** 精度保証付き固有値 */
  private CIM eigenValue;

  /** 精度保証付き固有ベクトル(不変部分空間の基底ベクトル) */
  private CIM eigenVector;

  /**
   * 新しく生成された<code>RumpMethod</code>オブジェクトを初期化します。
   * 
   * @param IA interval matrix
   */
  public RealRumpMethod(RIM IA) {
    this.IA = IA;
    this.A = IA.getMiddle();
  }
  
  /**
   * Creates interval matrix
   * 
   * @param matrix real matrix
   * @return interval real matrix
   */
  private RIM createInterval(RM matrix) {
    return this.IA.create(matrix);
  }

  /**
   * Creates interval matrix
   * 
   * @param mid middle
   * @param rad radius
   * @return rim
   */
  private RIM createMidRad(RM mid, RM rad) {
    return this.IA.createMidRad(mid, rad);
  }

  /**
   * Creates interval matrix
   * 
   * @param mid middle
   * @param rad radius
   * @return rad radius
   */
  private CIM createMidRad(CM mid, RM rad) {
    return this.IA.toComplex().createMidRad(mid, rad);
  }

  /**
   * Creates interval matrix
   * 
   * @param matrix complex matrix
   * @return interval complex matrix
   */
  private CIM createInterval(CM matrix) {
    return this.IA.toComplex().create(matrix);
  }

  /**
   * Creates interval matrix
   * 
   * @param matrix complex matrix
   * @return interval complex matrix
   */
  private CIS createInterval(CS matrix) {
    return this.IA.getElement(1,1).toComplex().create(matrix);
  }

  /**
   * Creates interval scalar with infimum and supremum.
   * 
   * @param inf infimum
   * @param sup supremum
   * @return interval scalara
   */
  private RIS createIntervalInfSup(double inf, double sup) {
    RS type = this.A.getElement(1,1);
    RIS itype = this.IA.getElement(1,1);
    return itype.createInfSup(type.create(inf), type.create(sup));
  }

  /**
   * Creates interval scalar with middle and radius.
   * 
   * @param inf infimum
   * @param sup supremum
   * @return interval scalara
   */
  private RIS createMidRad(double inf, double sup) {
    RS type = this.A.getElement(1,1);
    RIS itype = this.IA.getElement(1,1);
    return itype.createMidRad(type.create(inf), type.create(sup));
  }

  //  /**
  //   * @see org.mklab.cga.eigen.EigenVerifier#getSolution()
  //   */
  //  public EigenVerifiedSolution<RIS,RIM,CIS,CIM,RS,RM,CS,CM> getSolution() {
  //    return new EigenVerifiedSolution<>(this.eigenValue, this.eigenVector);
  //  }

  /**
   * {@inheritDoc}
   */
  public CIM getEigenValue() {
    return this.eigenValue;
  }

  /**
   * {@inheritDoc}
   */
  public CIM getEigenVector() {
    return this.eigenVector;
  }

//  /**
//   * {@inheritDoc}
//   */
//  public void solve() {
//  }

//  /**
//   * {@inheritDoc}
//   */
//  public void solveAsSparse() {
//    if (this.A != null) {
//      solveAsSparse(this.A);
//    } else if (this.IA != null) {
//      solveAsSparse(this.IA);
//    } else {
//      throw new RuntimeException("Target matrix is not given."); //$NON-NLS-1$
//    }
//  }

//  /**
//   * {@inheritDoc}
//   */
//  public void solve(final RM matrix) {
//    final EigenSolution<RS, RM, CS, CM> eigen = (matrix).eigenDecompose();
//    CM value = eigen.getValues();
//    CM vector = eigen.getVectors();
//    this.eigenValue = this.IA.toComplex().create(value.createZero(value.getRowSize(), 1));
//    this.eigenVector = this.IA.toComplex().create(vector.createZero(vector.getRowSize(), vector.getColumnSize()));
//
//    for (int i = 0; i < value.getRowSize(); i++) {
//      final CS rambda = value.getElement(i + 1, i + 1);
//      final CM xs = vector.getColumnVector(i + 1);
//      final CIM[] ans = solve(matrix, rambda, xs);
//      this.eigenValue.setRowVector(i + 1, ans[0]);// 固有値
//      this.eigenVector.setColumnVector(i + 1, ans[1]);// 不変部分空間の基底
//    }
//  }

  /**
   * {@inheritDoc}
   */
  public void solve() {
    final EigenSolution<RS, RM, CS, CM> eigen = this.A.eigenDecompose();
    CM value = eigen.getValue();
    CM vector = eigen.getVector();
    this.eigenValue = this.IA.toComplex().create(value.createZero(value.getRowSize(), 1));
    this.eigenVector = this.IA.toComplex().create(vector.createZero(vector.getRowSize(), vector.getColumnSize()));

    for (int i = 0; i < value.getRowSize(); i++) {
      final CS rambda = value.getElement(i + 1, i + 1);
      final CM xs = vector.getColumnVector(i + 1);
      final CIM[] ans = solve(this.A, rambda, xs);
      this.eigenValue.setRowVector(i + 1, ans[0]);// 固有値
      this.eigenVector.setColumnVector(i + 1, ans[1]);// 不変部分空間の基底
    }
  }

  /**
   * 多重および近接固有値の精度保証を行います。
   * 
   * 一つの固有値に対し、精度保証を行います。
   * 
   * 近接する固有値の境界点を求めたい場合は、 <code>xs</code> には 近接する固有値の固有ベクトル列を設定してください。
   * 
   * @param matrix 固有値を求めたい行列
   * @param lambda Aの固有値のひとつ
   * @param xs lambdaに対応する固有ベクトル
   * @return {精度保証付き固有値,精度保証付き固有ベクトル(不変部分空間の基底)}
   */
  private CIM[] solve(final RM matrix, CS lambda, final CM xs) {
    return solve(this.IA.create(matrix), lambda, xs);
  }

  /**
   * 精度保証付き固有値と精度保証付き固有ベクトル求めます。
   * 
   * @param IA 固有値を求めたい区間行列
   * @param lambda Aの固有値のひとつ
   * @param xs lambdaに対応する固有ベクトル
   * @return {精度保証付き固有値,精度保証付き固有ベクトル(不変部分空間の基底)}
   */
  @SuppressWarnings("unchecked")
  private CIM[] solve(RIM IA, CS lambda, CM xs) {
    RoundModeManager manager = RoundModeManager.getManager();
    RoundMode oldRoundMode = manager.getRoundMode();

    int n = xs.getRowSize();// 行数
    int k = xs.getColumnSize();// 列数

    manager.setRoundMode(RoundMode.ROUND_NEAR);

    CM xss = (xs).absElementWise().sumRowWise();
    IndexedMatrix<CS, CM> NI = (xss).sortColumnWise();

    IntMatrix I = NI.getIndices();
    IntMatrix u = I.getRowVectors(1, n - k).transpose();
    IntMatrix v = I.getRowVectors(n - k + 1, n).transpose();
    RM midA = IA.getMiddle();

    // TODO Aが実数行列じゃないときもこれでいいの?
    CM R = midA.toComplex().subtract(midA.createUnit(n).toComplex().multiply(lambda));// R=midA-lambda*I
    R.setColumnVectors(v, xs.unaryMinus()); // R(:, v) = -xs
    CM y = R.leftDivide(midA.toComplex().multiply(xs).subtract(xs.multiply(lambda)));// y = R\(midA*xs-lambda*xs)

    xs.setRowVectors(u, (xs.getRowVectors(u).subtract(y.getRowVectors(u))));// xs(u,:)= xs(u,:)-y(u,:)

    // lambda - sum(diag(y(v,:)))/k
    CS locallambda = lambda.subtract((y.getRowVectors(v).diagonalToVector().sumColumnWise()).getElement(1, 1).divide(k));
    R = midA.toComplex().subtract(midA.toComplex().createUnit(n).multiply(locallambda));// R=midA-lambda*I
    R.setColumnVectors(v, xs.unaryMinus());// R(:,v)=-xs
    R = R.inverse();// R=inv(R)

    RIM II =  this.IA.create(midA.createUnit(n));// 単位行列のIntervalMatrixの作成
    CIS I_lambda = createInterval(locallambda);

    CIM IR = this.IA.toComplex().create(R);
    CIM IC = IA.toComplex().subtract(II.toComplex().multiply(I_lambda));// C=A-intval(lambda)*I
    CIM IZ = (createInterval(R.unaryMinus())).multiply(IC.multiply(createInterval(xs)));// Z=-R*(C*xs)
    IC.setColumnVectors(v, createInterval(xs.unaryMinus()));// C(:, v)=-xs
    IC = II.toComplex().subtract(IR.multiply(IC));// C=I-RC
    CIM IY = IZ.createClone();// Y=Z

    CIM Eps = createInterval(IY.abssElementWise().multiply(0.1)).multiply(createIntervalInfSup(-1.0, 1.0).toComplex()).addElementWise(createMidRad(0, Double.MIN_VALUE).toComplex());
    // TODO こうしたらいいのかな?(NumericalMatrixElementIntervalの実装が必要みたい)
    CIM Z_row = IZ.getRowVectors(v);
    RM Z_row_abs = Z_row.abssElementWise().getRealPart();
    BooleanMatrix Z_flag = Z_row_abs.compareElementWise(".>", 0.1); //$NON-NLS-1$
    int z_sum = Z_flag.find().length();

    double m = 0;
    double mmax = Math.min(15 * (z_sum + 1), 20);

    boolean ready = false;

    CIM X = null;
    CIM XX = createInterval(R.createZero(IY.getRowSize(), IY.getColumnSize()));

    while ((!ready) && (m < mmax) && (!IY.isNanElementWise().anyTrue())) {
      m = m + 1;
      X = IY.add(Eps);
      XX.copy(X);
      XX.setRowVectors(v,createInterval(R.createZero(v.getColumnSize(), XX.getColumnSize())));
      IY = IZ.add(IC.multiply(X)).add(IR.multiply(XX.multiply(X.getRowVectors(v))));
      ready = IntervalUtil.in0forRump(IY, X).allTrue();
    }

    CIM L = null;

    if (ready) {
      RM M = (IY.getRowVectors(v)).abssElementWise().getRealPart();

      if (v.length() == 1) {
        CS[] value = locallambda.createArray(1);
        value[0] = locallambda;
        CM mid = locallambda.createGrid(value);
        L =  createMidRad(mid, M);
      } else {
        EigenSolution<RS, RM, CS, CM> EV = (M).eigenDecompose();
        ElementHolder<?> list = ((EV.getValue().diagonalToVector()).absElementWise()).maximum();
        int index = list.getRow();

        CM Perronx = EV.getVector().getColumnVector(index);

        manager.setRoundMode(RoundMode.ROUND_UP);

        /* upper bound for Perron root */
        RM rad = ((M.toComplex().multiply(Perronx).divideElementWise(Perronx))).maxColumnWise().getRealPart();

        manager.setRoundMode(RoundMode.ROUND_NEAR);
        CS[] value = locallambda.createArray(1);
        value[0] = locallambda;
        CM mid = locallambda.createGrid(value);
        L = createMidRad(mid, rad);
      }

      IY.setRowVectors(v, IY.createZero(v.getColumnSize(), IY.getColumnSize()));
      X = createInterval(xs).add(IY);
    } else {
      RS nanElement = IA.getMiddle().getElement(1).getNaN();
      RS[] value = nanElement.createArray(1);
      value[0] = nanElement;
      CM nan = IA.getMiddle().getElement(1).createGrid(value).toComplex();
      CM nanL = xs.createZero(1, 1);
      nanL.setRowVector(1, nan);

      CM nanX = xs.createZero(IA.getRowSize(), 1);
      for (int i = 0; i < IA.getColumnSize(); i++) {
        nanX.setRowVector(i + 1, nanL);
      }

      L = createMidRad(nanL, nanL.getRealPart());
      X = createMidRad(nanX, nanX.getRealPart());

      System.err.println("no inclusion achieved"); //$NON-NLS-1$
      //throw new RuntimeException("no inclusion achieved"); //$NON-NLS-1$
    }

    manager.setRoundMode(oldRoundMode);
    CIM[] ans = (CIM[])new IntervalComplexNumericalMatrix[2];
    ans[0] = L;
    ans[1] = X;
    return ans;
  }

  /**
   * @param IA 固有値を求めたい区間行列
   * @param lambda Aの固有値のひとつ
   * @param xs lambdaに対応する固有ベクトル
   * @return {精度保証付き固有値,精度保証付き固有ベクトル(不変部分空間の基底)}
   */
  private CIM[] solveSparse(RIM IA, CS lambda, CM xs) {
    RoundModeManager manager = RoundModeManager.getManager();
    RoundMode oldRoundMode = manager.getRoundMode();

    int n = xs.getRowSize();// 行数
    int k = xs.getColumnSize();// 列数

    manager.setRoundMode(RoundMode.ROUND_NEAR);

    CM xss = (xs).absElementWise().sumRowWise();
    IndexedMatrix<CS, CM> NI = (xss).sortColumnWise();
    IntMatrix I = NI.getIndices();
    IntMatrix v = I.getRowVectors(n - k + 1, n).transpose();
    RM midA = IA.getMiddle();

    CS locallambda = lambda.clone();
    CM R = midA.toComplex().subtract(midA.createUnit(n).toComplex().multiply(locallambda));// R=midA-lambda*I
    R.setColumnVectors(v, xs.unaryMinus());// R(:,v)=-xs
    CIM G =  IA.toComplex().create(R).inverse();

    CIM IZ = IA.toComplex().unaryMinus().multiply(createInterval(xs)).add(createInterval(xs.multiply(lambda)));
    CIM IY = IZ.createClone();// Y=Z

    // TODO ここ精度に依存しそう
    RIM Eps = createInterval(IY.abssElementWise().getRealPart().multiply(0.1)).multiply(createIntervalInfSup(-1.0, 1.0)).addElementWise(createMidRad(0, Double.MIN_VALUE));
    CIM Z_row = IZ.getRowVectors(v);
    CM Z_row_abs = Z_row.abssElementWise();
    BooleanMatrix Z_flag = Z_row_abs.compareElementWise(".>", 0.1); //$NON-NLS-1$
    int z_sum = Z_flag.find().length();

    double m = 0;
    double mmax = Math.min(15 * (z_sum + 1), 20);

    boolean ready = false;

    CIM X = null;
    CIM XX = createInterval(R.createZero(IY.getRowSize(), IY.getColumnSize()));

    while ((!ready) && (m < mmax) && (!IY.isNanElementWise().anyTrue())) {
      m = m + 1;
      X = IY.add(Eps.toComplex());
      XX.copy(X);
      XX.setRowVectors(v, XX.createZero(v.getColumnSize(), XX.getColumnSize()));
      CIM b = IZ.add(XX.multiply(X.getRowVectors(v)));
      IY = G.inverse().multiply(b);
      ready = IntervalUtil.in0forRump(IY, X).allTrue();
    }

    CIM L = null;

    if (ready) {
      RM M = (IY.getRowVectors(v)).abssElementWise().getRealPart();

      if (v.length() == 1) {
        CS[] value = locallambda.createArray(1);
        value[0] = locallambda;
        CM mid = locallambda.createGrid(value);
        L = createMidRad(mid, M);
      } else {
        EigenSolution<RS, RM, CS, CM> EV = (M).eigenDecompose();
        ElementHolder<?> list = (EV.getValue().diagonalToVector()).absElementWise().maximum();
        int index = list.getRow();

        CM Perronx = EV.getVector().getColumnVector(index);

        manager.setRoundMode(RoundMode.ROUND_UP);

        /* upper bound for Perron root */
        RM rad = ((M.toComplex().multiply(Perronx).divideElementWise(Perronx))).maxColumnWise().getRealPart();

        manager.setRoundMode(RoundMode.ROUND_NEAR);
        CS[] value = locallambda.createArray(1);
        value[0] = locallambda;
        CM mid = locallambda.createGrid(value);
        L = createMidRad(mid, rad);
      }

      IY.setRowVectors(v, createInterval(R.createZero(v.getColumnSize(), IY.getColumnSize())));
      X = createInterval(xs).add(IY);
    } else {
      CS nanElement = IA.getMiddle().getElement(1).getNaN().toComplex();
      CS[] value = nanElement.createArray(1);
      value[0] = nanElement;
      CM nan = IA.getMiddle().getElement(1).toComplex().createGrid(value);
      CM nanL = xs.createZero(1, 1);
      nanL.setRowVector(1, nan);

      CM nanX = xs.createZero(IA.getRowSize(), 1);
      for (int i = 0; i < IA.getColumnSize(); i++) {
        nanX.setRowVector(i + 1, nanL);
      }

      L = createMidRad(nanL, nanL.getRealPart());
      X = createMidRad(nanX, nanX.getRealPart());

      System.err.println("no inclusion achieved"); //$NON-NLS-1$
      //throw new RuntimeException("no inclusion achieved"); //$NON-NLS-1$
    }

    manager.setRoundMode(oldRoundMode);
    CIM[] ans = (CIM[])new IntervalComplexNumericalMatrix[2];
    ans[0] = L;
    ans[1] = X;
    return ans;
  }

//  /**
//   * @see org.mklab.cga.eigen.EigenVerifier#solveAsSparse(org.mklab.nfc.matrix.NumericalMatrix)
//   */
//  public void solveAsSparse(RM A) {
//    throw new UnsupportedOperationException();
//  }

  /**
   * {@inheritDoc}
   */
  public void solveAsSparse() {
    final EigenSolution<RS, RM, CS, CM> eigen = (this.IA.getMiddle()).eigenDecompose();
    CM value = eigen.getValue();
    CM vector = eigen.getVector();
    this.eigenValue = createInterval(value.createZero(value.getRowSize(), 1));
    this.eigenVector = createInterval(vector.createZero(vector.getRowSize(), vector.getColumnSize()));

    for (int i = 0; i < value.getRowSize(); i++) {
      final CS rambda = (value).getElement(i + 1, i + 1);
      final CM xs = vector.getColumnVector(i + 1);
      final CIM[] ans = solveSparse(this.IA, rambda, xs);
      this.eigenValue.setRowVector(i + 1, ans[0]);// 固有値
      this.eigenVector.setColumnVector(i + 1, ans[1]);// 不変部分空間の基底
    }
  }

//  /**
//   * 不変部分空間の基底を求めます。
//   * 
//   * @param a 不変部分空間の基底を求める行列
//   * @return 不変部分空間の基底
//   */
//  CIM getBasis(RM a) {
//    solve(a);
//    return this.eigenVector;
//  }

//  /**
//   * 不変部分空間の基底を求めます。
//   * 
//   * @return 不変部分空間の基底
//   */
//  CIM getBasis() {
//    solve();
//    return this.eigenVector;
//  }
  
  /**
   * Returns A.
   * @return A
   */
  protected RIM getA() {
    return this.IA;
  }

}