ProperLinearSystem.java

/*
 * $Id: LinearSystem.java,v 1.86 2008/07/17 07:30:03 koga Exp $
 *
 * Copyright (C) 2004 Koga Laboratory. All rights reserved.
 */
package org.mklab.tool.control;

import java.io.PrintWriter;
import java.io.Writer;
import java.util.List;

import org.mklab.nfc.matrix.AnyRealRationalPolynomialMatrix;
import org.mklab.nfc.matrix.ComplexNumericalMatrix;
import org.mklab.nfc.matrix.IntMatrix;
import org.mklab.nfc.matrix.RealNumericalMatrix;
import org.mklab.nfc.scalar.AnyRealRationalPolynomial;
import org.mklab.nfc.scalar.ComplexNumericalScalar;
import org.mklab.nfc.scalar.RealNumericalScalar;

/**
 * プロパーな線形システムを表すクラスです。
 * 
 * @author koga
 * @version $Revision: 1.86 $
 * @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 ProperLinearSystem<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>>
    extends AbstractLinearSystem<RS, RM, CS, CM> {

  /** シリアル番号 */
  private static final long serialVersionUID = 1002207140697213466L;

  // /**
  // * メインメソッド
  // *
  // * @param args コマンドライン引数
  // * @throws IOException ファイルに出力できない場合
  // * @throws ClassNotFoundException 読み込んだデータがLinearSystem型でない場合
  // */
  // public static void main(String[] args) throws IOException,
  // ClassNotFoundException {
  // final RM a = new DoubleMatrix(new double[][] { {0, 1}, {-3, -2}});
  // final RM b = new DoubleMatrix(new double[][] { {1, 0}, {0, 1}});
  // final RM c = new DoubleMatrix(new double[][] { {1, 0}, {0, 1}});
  // final RM d = new DoubleMatrix(new double[][] { {1, 1}, {1, 1}});
  // final ProperLinearSystem system = LinearSystemFactory.createLinearSystem(a,
  // b, c, d);
  // system.print(new OutputStreamWriter(System.out));
  //
  // try (final ObjectOutputStream out = new ObjectOutputStream(new
  // FileOutputStream("abcd.sys"))) { //$NON-NLS-1$
  // out.writeObject(system);
  //
  // System.out.println(""); //$NON-NLS-1$
  //
  // try (final ObjectInputStream in = new ObjectInputStream(new
  // FileInputStream("abcd.sys"))) { //$NON-NLS-1$
  // ProperLinearSystem system2 = (ProperLinearSystem)in.readObject();
  // system2.print(new OutputStreamWriter(System.out));
  // }
  // }
  // }

  /**
   * 新しく生成された<code>ProperLinearSystem</code>オブジェクトを初期化します。
   */
  private ProperLinearSystem() {
    // nothing to do
  }

  /**
   * 状態空間表現の係数行列から線形システムを生成します。
   * 
   * @param a システム行列
   * @param b 入力行列
   * @param c 出力行列
   * @param d ゲイン行列
   */
  ProperLinearSystem(final RM a, final RM b, final RM c, final RM d) {
    this(a, b, c, d, a.createUnit());
  }

  /**
   * 状態空間表現の係数行列から線形システムを生成します。
   * 
   * @param a システム行列
   * @param b 入力行列
   * @param c 出力行列
   * @param d ゲイン行列
   * @param e ディスクリプタ行列
   * 
   */
  ProperLinearSystem(final RM a, final RM b, final RM c, final RM d, final RM e) {
    this.proper = true;

    if (d.isZero() == false) {
      this.strictlyProper = false;
    }

    this.a = a;
    this.b = b;
    this.c = c;
    this.d = d;
    this.e = e;
    this.stateSize = a.getRowSize();
    this.inputSize = b.getColumnSize();
    this.outputSize = c.getRowSize();
    this.index = new IntMatrix(this.stateSize, 1);
  }

  /**
   * 伝達関数行列から線形システムを生成します。
   * 
   * @param transferFunctionMatrix 伝達関数行列(有理多項式行列)
   */
  ProperLinearSystem(final AnyRealRationalPolynomialMatrix<RS, RM, CS, CM> transferFunctionMatrix) {
    this.proper = true;

    final int rowSize = transferFunctionMatrix.getRowSize();
    final int columnSize = transferFunctionMatrix.getColumnSize();
    for (int i = 1; i <= rowSize; i++) {
      for (int j = 1; j <= columnSize; j++) {
        final AnyRealRationalPolynomial<RS, RM, CS, CM> g = transferFunctionMatrix.getElement(i, j);
        if (g.getNumeratorDegree() > g.getDenominatorDegree()) {
          throw new IllegalArgumentException("Not a proper system"); //$NON-NLS-1$
        }
        if (g.getNumeratorDegree() == g.getDenominatorDegree()) {
          this.strictlyProper = false;
        }
      }
    }

    final List<RM> abcd = Tfm2ss.tfm2ss(transferFunctionMatrix);
    this.a = abcd.get(0);
    this.b = abcd.get(1);
    this.c = abcd.get(2);
    this.d = abcd.get(3);
    this.e = this.a.createUnit();
    this.G = transferFunctionMatrix.createClone();
    this.stateSize = this.a.getRowSize();
    this.inputSize = this.b.getColumnSize();
    this.outputSize = this.c.getRowSize();
    this.index = new IntMatrix(this.stateSize, 1);
  }

  /**
   * 1入力1出力システムの伝達関数から線形システムを生成します。
   * 
   * @param transferFunction 1入力1出力システムの伝達関数(有理多項式)
   */
  ProperLinearSystem(final AnyRealRationalPolynomial<RS, RM, CS, CM> transferFunction) {
    this.proper = true;

    if (transferFunction.getNumeratorDegree() > transferFunction.getDenominatorDegree()) {
      throw new IllegalArgumentException("Not a proper system"); //$NON-NLS-1$
    }

    if (transferFunction.getNumeratorDegree() == transferFunction.getDenominatorDegree()) {
      this.strictlyProper = false;
    }

    final List<RM> abcd = Tfn2ss.tfn2ss(transferFunction);
    this.a = abcd.get(0);
    this.b = abcd.get(1);
    this.c = abcd.get(2);
    this.d = abcd.get(3);
    this.e = this.a.createUnit();
    this.G = new AnyRealRationalPolynomialMatrix<RS, RM, CS, CM>(new AnyRealRationalPolynomial[][] { { transferFunction.clone() } });
    this.stateSize = this.a.getRowSize();
    this.inputSize = this.b.getColumnSize();
    this.outputSize = this.c.getRowSize();
    this.index = new IntMatrix(this.stateSize, 1);
  }

  /**
   * 三対(入力数, 出力数, 状態数)の文字列に変換します。
   * 
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "ProperLinearSystem(" + this.inputSize + " inputs, " + this.outputSize + " outputs, " + this.stateSize //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        + " states)"; //$NON-NLS-1$
  }

  /**
   * @see org.mklab.tool.control.LinearSystem#print(java.io.Writer)
   */
  public void print(final Writer output) {
    printABCD(output);
  }

  /**
   * 出力ストリームにプロパーなシステムの係数行列A,B,C,Dを出力します。
   * 
   * @param output 出力ストリーム
   */
  private void printABCD(final Writer output) {
    final RM ac = this.a.appendDown(this.c);
    final RM bd = this.b.appendDown(this.d);
    ac.setElementFormat(this.a.getElementFormat());
    bd.setElementFormat(this.a.getElementFormat());

    final String[] acString = ac.getPrintingElementsString(Integer.MAX_VALUE).split("[\\r\\n]+"); //$NON-NLS-1$
    final String[] bdString = bd.getPrintingElementsString(Integer.MAX_VALUE).split("[\\r\\n]+"); //$NON-NLS-1$

    final String[] aString = new String[this.stateSize + 1];
    if (this.stateSize != 0) {
      System.arraycopy(acString, 0, aString, 0, this.stateSize + 1);
    }

    final String[] cString = new String[this.outputSize + 1];
    if (this.stateSize != 0) {
      cString[0] = aString[0];
      System.arraycopy(acString, this.stateSize + 1, cString, 1, this.outputSize);
      resetRowNumber(cString);
    }

    final String[] bString = new String[this.stateSize + 1];
    System.arraycopy(bdString, 0, bString, 0, this.stateSize + 1);

    final String[] dString = new String[this.outputSize + 1];
    dString[0] = bString[0];
    System.arraycopy(bdString, this.stateSize + 1, dString, 1, this.outputSize);

    resetRowNumber(dString);

    final PrintWriter pw = new PrintWriter(output);

    if (this.stateSize == 0) {
      for (int i = 0; i < this.outputSize + 1; i++) {
        pw.println(dString[i]);
      }
      return;
    }

    for (int i = 0; i < this.stateSize + 1; i++) {
      pw.print(aString[i]);
      pw.print("|"); //$NON-NLS-1$
      pw.println(bString[i]);
    }

    for (int i = 0; i < aString[0].length(); i++) {
      pw.print("-"); //$NON-NLS-1$
    }

    pw.print("+"); //$NON-NLS-1$

    for (int i = 0; i < bString[0].length(); i++) {
      pw.print("-"); //$NON-NLS-1$
    }

    pw.println(""); //$NON-NLS-1$

    for (int i = 0; i < this.outputSize + 1; i++) {
      pw.print(cString[i]);
      pw.print("|"); //$NON-NLS-1$
      pw.println(dString[i]);
    }

    pw.flush();
  }

  /**
   * @see org.mklab.nfc.matrix.GridElement#clone()
   */
  @Override
  public Object clone() {
    final ProperLinearSystem<RS, RM, CS, CM> inst = new ProperLinearSystem<>();
    inst.proper = this.proper;
    inst.strictlyProper = this.strictlyProper;
    inst.inputSize = this.inputSize;
    inst.stateSize = this.stateSize;
    inst.outputSize = this.outputSize;
    inst.timeDomainType = this.timeDomainType;
    inst.inputPortTags = this.inputPortTags;
    inst.outputPortTags = this.outputPortTags;
    inst.a = this.a == null ? null : this.a.createClone();
    inst.b = this.b == null ? null : this.b.createClone();
    inst.c = this.c == null ? null : this.c.createClone();
    inst.d = this.d == null ? null : this.d.createClone();
    inst.e = this.e == null ? null : this.e.createClone();
    inst.G = this.G == null ? null : this.G.createClone();
    return inst;
  }

  /**
   * @see org.mklab.tool.control.LinearSystem#getSymbolicStateSpaceRepresentation(boolean)
   */
  public String getSymbolicStateSpaceRepresentation(final boolean withInputOutput) {
    final StringBuffer string = new StringBuffer();

    if (0 < getStateSize()) {
      final int[] columnLengthes = getColumnLengthesOfABCD();
      final String[] symbolicState = getSymbolicState();

      string.append(getSymbolicStateEquation(withInputOutput));

      if (withInputOutput) {
        if (isContinuous()) {
          if (getSubSystemSize() == 1) {
            string.append("       "); //$NON-NLS-1$
          } else {
            string.append("     "); //$NON-NLS-1$
          }
        }

        if (isDiscrete()) {
          string.append("     "); //$NON-NLS-1$
        }

        final String format = "%" + (symbolicState[0].length() + 1 + 1 + 1) + "s"; //$NON-NLS-1$ //$NON-NLS-2$
        string.append(String.format(format, "")); //$NON-NLS-1$
      }

      string.append("["); //$NON-NLS-1$
      drawHorizontalLine(string, columnLengthes);
      string.append("]" + System.getProperty("line.separator")); //$NON-NLS-1$ //$NON-NLS-2$
    }

    string.append(getSymbolicOutputEquation(withInputOutput));

    return string.toString();
  }

  /**
   * @see org.mklab.tool.control.LinearSystem#getSymbolicStateEquation(boolean)
   */
  public String getSymbolicStateEquation(final boolean withInputOutput) {
    final StringBuffer string = new StringBuffer();

    final String[] symbolicState = getSymbolicState();
    final int[] columnLengthes = getColumnLengthesOfABCD();
    final String lineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$

    for (int row = 0; row < getSubSystemSize(); row++) {
      if (withInputOutput) {
        if (isContinuous()) {
          if (getSubSystemSize() == 1) {
            string.append("d/dt"); //$NON-NLS-1$
          } else if (getSubSystemSize() == 2) {
            if (row == 0) {
              string.append("d/"); //$NON-NLS-1$
            } else {
              string.append("dt"); //$NON-NLS-1$
            }
          } else {
            if (row == getSubSystemSize() / 2 - 1) {
              string.append("d "); //$NON-NLS-1$
            } else if (row == getSubSystemSize() / 2) {
              string.append("--"); //$NON-NLS-1$
            } else if (row == getSubSystemSize() / 2 + 1) {
              string.append("dt"); //$NON-NLS-1$
            } else {
              string.append("  "); //$NON-NLS-1$
            }
          }
        }

        string.append("[" + symbolicState[row] + "]"); //$NON-NLS-1$ //$NON-NLS-2$
        if (row == getSubSystemSize() / 2) {
          if (isContinuous()) {
            string.append("(t)="); //$NON-NLS-1$
          } else {
            string.append("(k+1)="); //$NON-NLS-1$
          }
        } else {
          if (isContinuous()) {
            string.append("    "); //$NON-NLS-1$
          } else {
            string.append("      "); //$NON-NLS-1$
          }
        }
      }

      string.append("["); //$NON-NLS-1$
      for (int column = 0; column < getSubSystemSize(); column++) {
        final String format = "%" + columnLengthes[column] + "s"; //$NON-NLS-1$ //$NON-NLS-2$
        string.append(String.format(format, this.getSymbolicAMatrix()[row][column]));
        if (column != getSubSystemSize() - 1) {
          string.append("  "); //$NON-NLS-1$
        }
      }
      string.append("|"); //$NON-NLS-1$

      for (int column = 0; column < getInputPortSize(); column++) {
        final String format = "%" + columnLengthes[column + getSubSystemSize()] + "s"; //$NON-NLS-1$ //$NON-NLS-2$
        string.append(String.format(format, this.getSymbolicBMatrix()[row][column]));
        if (column != getInputPortSize() - 1) {
          string.append("  "); //$NON-NLS-1$
        }
      }
      string.append("]"); //$NON-NLS-1$

      if (withInputOutput) {
        string.append("[" + symbolicState[row] + "]"); //$NON-NLS-1$ //$NON-NLS-2$
        if (row == getSubSystemSize() / 2) {
          if (isContinuous()) {
            string.append("(t)"); //$NON-NLS-1$
          } else {
            string.append("(k)"); //$NON-NLS-1$
          }
        }
      }

      string.append(lineSeparator);
    }

    return string.toString();
  }

  /**
   * @see org.mklab.tool.control.LinearSystem#getSymbolicOutputEquation(boolean)
   */
  public String getSymbolicOutputEquation(final boolean withInputOutput) {
    final StringBuffer string = new StringBuffer();

    final String[] symbolicInput = getSymbolicInput();
    final String[] symbolicOutput = getSymbolicOutput();
    final int[] columnLengthes = getColumnLengthesOfABCD();
    final String lineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$

    for (int row = 0; row < getOutputPortSize(); row++) {
      if (withInputOutput) {
        if (isContinuous()) {
          if (getSubSystemSize() == 1) {
            string.append("    "); //$NON-NLS-1$
          } else {
            string.append("  "); //$NON-NLS-1$
          }
        }

        string.append("[" + symbolicOutput[row] + "]"); //$NON-NLS-1$ //$NON-NLS-2$
        if (row == getOutputPortSize() / 2) {
          if (isContinuous()) {
            string.append("(t)="); //$NON-NLS-1$
          } else {
            string.append("(k)  ="); //$NON-NLS-1$
          }
        } else {
          if (isContinuous()) {
            string.append("    "); //$NON-NLS-1$
          } else {
            string.append("      "); //$NON-NLS-1$
          }
        }
      }

      string.append("["); //$NON-NLS-1$
      for (int column = 0; column < getSubSystemSize(); column++) {
        final String format = "%" + columnLengthes[column] + "s"; //$NON-NLS-1$ //$NON-NLS-2$
        string.append(String.format(format, this.getSymbolicCMatrix()[row][column]));
        if (column != getSubSystemSize() - 1) {
          string.append("  "); //$NON-NLS-1$
        }
      }

      if (0 < getStateSize()) {
        string.append("|"); //$NON-NLS-1$
      }

      for (int column = 0; column < getInputPortSize(); column++) {
        final String format = "%" + columnLengthes[column + getSubSystemSize()] + "s"; //$NON-NLS-1$ //$NON-NLS-2$
        string.append(String.format(format, this.getSymbolicDMatrix()[row][column]));
        if (column != getInputPortSize() - 1) {
          string.append("  "); //$NON-NLS-1$
        }
      }
      string.append("]"); //$NON-NLS-1$

      if (withInputOutput && row < symbolicInput.length) {
        string.append("[" + symbolicInput[row] + "]"); //$NON-NLS-1$ //$NON-NLS-2$
        if (row == getInputPortSize() / 2) {
          if (isContinuous()) {
            string.append("(t)"); //$NON-NLS-1$
          } else {
            string.append("(k)"); //$NON-NLS-1$
          }
        }
      }

      string.append(lineSeparator);
    }

    if (withInputOutput && symbolicOutput.length < symbolicInput.length) {
      final int lengthTolastLineSeparator = string.lastIndexOf(lineSeparator,
          string.length() - lineSeparator.length() - 1) + lineSeparator.length();
      final int widthOfInputVector = (1 + symbolicInput[0].length() + 1);
      int spaceLength = (string.length() - lengthTolastLineSeparator) - 2 - widthOfInputVector;
      if (getOutputPortSize() - 1 == getInputPortSize() / 2) {
        spaceLength -= 3;
      }
      final String format = "%" + spaceLength + "s"; //$NON-NLS-1$ //$NON-NLS-2$

      for (int i = 0; i < symbolicInput.length - symbolicOutput.length; i++) {
        string.append(String.format(format, "")); //$NON-NLS-1$
        final int row = symbolicOutput.length + i;
        string.append("[" + symbolicInput[row] + "]"); //$NON-NLS-1$ //$NON-NLS-2$

        if (row == getInputPortSize() / 2) {
          if (isContinuous()) {
            string.append("(t)"); //$NON-NLS-1$
          } else {
            string.append("(k)"); //$NON-NLS-1$
          }
        }
        string.append(lineSeparator);
      }
    }

    return string.toString();
  }

  /**
   * @see org.mklab.tool.control.LinearSystem#getTransferFunctionMatrix(boolean)
   */
  public AnyRealRationalPolynomialMatrix<RS, RM, CS, CM> getTransferFunctionMatrix(final boolean simplify) {
    final AnyRealRationalPolynomialMatrix<RS, RM, CS, CM> g = Ss2tfm.ss2tfm(this.a, this.b, this.c, this.d, simplify);
    if (isContinuous()) {
      g.setVariable("s"); //$NON-NLS-1$
    } else if (isDiscrete()) {
      g.setVariable("z"); //$NON-NLS-1$
    }
    return g;
  }

  /**
   * {@inheritDoc}
   */
  public boolean equals(Object opponent, RS tolerance) {
    return this.G.equals(((ProperLinearSystem<RS,RM,CS,CM>)opponent).G);
  }
}