AbstractLinearSystem.java

/*
 * Created on 2009/06/18
 * Copyright (C) 2009 Koga Laboratory. All rights reserved.
 *
 */
package org.mklab.tool.control;

import java.io.CharArrayWriter;
import java.io.Serializable;
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.ComplexNumericalScalar;
import org.mklab.nfc.scalar.RealNumericalScalar;
import org.mklab.tool.matrix.Diag;
import org.mklab.tool.matrix.Simplify;


/**
 * 線形システムの抽象クラスです。
 * 
 * @author Anan
  * @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 abstract class AbstractLinearSystem<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 Serializable, Cloneable, LinearSystem<RS,RM,CS,CM> {

  /** バージョン番号 */
  private static final long serialVersionUID = -7441324992413004621L;
  /** プロパーならばtrue */
  protected boolean proper = true;
  /** 厳密にプロバーならばtrue */
  protected boolean strictlyProper = true;
  /** 伝達関数行列 */
  //protected AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> G = null;
  protected AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> G = null;
  /** 時間領域でのシステムの種類 */
  protected TimeDomainType timeDomainType = TimeDomainType.CONTINUOUS;
  /** 入力数 */
  protected int inputSize;
  /** 状態数 */
  protected int stateSize;
  /** 出力数 */
  protected int outputSize;
  /** システム行列 */
  protected RM a = null;
  /** 入力行列 */
  protected RM b = null;
  /** 出力行列 */
  protected RM c = null;
  /** ゲイン行列 */
  protected RM d = null;
  /** E行列 */
  protected RM e = null;
  /** 指数 */
  protected IntMatrix index = null;

  /** システム行列の式 */
  private String[][] aSymbol;
  /** 入力行列の式 */
  private String[][] bSymbol;
  /** 出力行列の式 */
  private String[][] cSymbol;
  /** ゲイン行列の式 */
  private String[][] dSymbol;
  /** ディスクリプタ行列の式 */
  private String[][] eSymbol;

  /** 入力ポートのタグ */
  protected List<String> inputPortTags;
  /** 出力ポートのタグ */
  protected List<String> outputPortTags;
  /** 状態のタグ */
  private List<String> stateTags;

  /**
   * 2個のシステムが等しいか判定します。
   * 
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(final Object opponent) {
    if (this == opponent) {
      return true;
    }
    if (opponent == null) {
      return false;
    }
    if (opponent.getClass() != getClass()) {
      return false;
    }

    final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g1 = getTransferFunctionMatrix();
    final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g2 = ((LinearSystem<RS,RM,CS,CM>)opponent).getTransferFunctionMatrix();
    return g1.equals(g2);
  }

  /**
   * 2個のシステムが等しいか判定します。
   * 
   * @param opponent 対象となる線形システム
   * @param tolerance 許容誤差
   * @return 2個のシステムが等しいければtrue、そうでなければfalse
   */
  public boolean equals(final Object opponent, final double tolerance) {
    if (this == opponent) {
      return true;
    }
    if (opponent == null) {
      return false;
    }
    if (opponent.getClass() != getClass()) {
      return false;
    }

    final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g1 = getTransferFunctionMatrix();
    final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g2 = ((ProperLinearSystem<RS,RM,CS,CM>)opponent).getTransferFunctionMatrix();
    return g1.equals(g2, tolerance);
  }

  /**
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    int hashCode = 1;
    hashCode = 31 * hashCode + (int)(+serialVersionUID ^ (serialVersionUID >>> 32));
    hashCode = 31 * hashCode + (this.proper ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.strictlyProper ? 1231 : 1237);
    hashCode = 31 * hashCode + (this.G == null ? 0 : this.G.hashCode());
    hashCode = 31 * hashCode + this.inputSize;
    hashCode = 31 * hashCode + this.stateSize;
    hashCode = 31 * hashCode + this.outputSize;
    hashCode = 31 * hashCode + (this.a == null ? 0 : this.a.hashCode());
    hashCode = 31 * hashCode + (this.b == null ? 0 : this.b.hashCode());
    hashCode = 31 * hashCode + (this.c == null ? 0 : this.c.hashCode());
    hashCode = 31 * hashCode + (this.d == null ? 0 : this.d.hashCode());
    hashCode = 31 * hashCode + (this.inputPortTags == null ? 0 : this.inputPortTags.hashCode());
    hashCode = 31 * hashCode + (this.outputPortTags == null ? 0 : this.outputPortTags.hashCode());
    return hashCode;
  }

  /**
   * 行番号を1からに設定します。
   * 
   * @param matrix 行列の文字列
   */
  protected void resetRowNumber(final String[] matrix) {
    for (int i = 1; i < matrix.length; i++) {
      matrix[i] = matrix[i].replaceFirst("\\(\\s*\\d+\\)", String.format("(% 3d)", Integer.valueOf(i))); //$NON-NLS-1$//$NON-NLS-2$
    }
  }

  /**
   * フォーマットされた状態のタグを返します。
   * 
   * @return フォーマットされた状態のタグ
   */
  protected String[] getSymbolicState() {
    final int stateLength = getSymbolicStateLength();
    final int outputLength = getSymbolicOutputLength();
    final int length = Math.max(stateLength, outputLength);

    final String[] symbolicStates = new String[getSubSystemSize()];

    for (int i = 0; i < getSubSystemSize(); i++) {
      final String stateTag = this.stateTags.get(i);
      final String format = "%" + length + "s"; //$NON-NLS-1$ //$NON-NLS-2$
      symbolicStates[i] = String.format(format, stateTag);
    }

    return symbolicStates;
  }

  /**
   * フォーマットされた出力のタグを返します。
   * 
   * @return フォーマットされた出力のタグ
   */
  protected String[] getSymbolicOutput() {
    final int stateLength = getSymbolicStateLength();
    final int outputLength = getSymbolicOutputLength();
    final int length = Math.max(stateLength, outputLength);

    final String[] symbolicOutputs = new String[this.outputPortTags.size()];

    for (int i = 0; i < this.outputPortTags.size(); i++) {
      final String outputPortaTag = this.outputPortTags.get(i);
      final String format = "%" + length + "s"; //$NON-NLS-1$ //$NON-NLS-2$
      symbolicOutputs[i] = String.format(format, outputPortaTag);
    }

    return symbolicOutputs;
  }

  /**
   * フォーマットされた入力のタグを返します。
   * 
   * @return フォーマットされた入力のタグ
   */
  protected String[] getSymbolicInput() {
    final int stateLength = getSymbolicStateLength();
    final int inputLength = getSymbolicInputLength();
    final int length = Math.max(stateLength, inputLength);

    final String[] symbolicInputs = new String[this.inputPortTags.size()];

    for (int i = 0; i < this.inputPortTags.size(); i++) {
      final String inputPortaTag = this.inputPortTags.get(i);
      final String format = "%" + length + "s"; //$NON-NLS-1$ //$NON-NLS-2$
      symbolicInputs[i] = String.format(format, inputPortaTag);
    }

    return symbolicInputs;
  }

  /**
   * 状態のタグの最大長を返します。
   * 
   * @return 状態のタグの最大長
   */
  private int getSymbolicStateLength() {
    int stateLength = 0;
    for (final String stateTag : this.stateTags) {
      if (stateLength < stateTag.length()) {
        stateLength = stateTag.length();
      }
    }

    return stateLength;
  }

  /**
   * 入力ポートのタグの最大長を返します。
   * 
   * @return 入力ポートのタグの最大長
   */
  private int getSymbolicInputLength() {
    int inputLength = 0;
    for (final String inputPortTag : this.inputPortTags) {
      if (inputLength < inputPortTag.length()) {
        inputLength = inputPortTag.length();
      }
    }

    return inputLength;
  }

  /**
   * 出力ポートのタグの最大長を返します。
   * 
   * @return 出力ポートのタグの最大長
   */
  private int getSymbolicOutputLength() {
    int outputLength = 0;
    for (final String outputPortTag : this.outputPortTags) {
      if (outputLength < outputPortTag.length()) {
        outputLength = outputPortTag.length();
      }
    }

    return outputLength;
  }

  /**
   * Aの数式を返します。
   * 
   * @return Aの数式
   */
  public String getSymbolicA() {
    final StringBuffer string = new StringBuffer();
    final int[] columnLengthes = getColumnLengthesOfABCD();

    for (int row = 0; row < getSubSystemSize(); row++) {
      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.aSymbol[row][column]));
        if (column != getSubSystemSize() - 1) {
          string.append("  "); //$NON-NLS-1$
        }
      }
      string.append(System.getProperty("line.separator")); //$NON-NLS-1$
    }

    return string.toString();
  }

  /**
   * Bの数式を返します。
   * 
   * @return Bの数式
   */
  public String getSymbolicB() {
    final StringBuffer string = new StringBuffer();
    final int[] columnLengthes = getColumnLengthesOfABCD();

    for (int row = 0; row < getSubSystemSize(); row++) {
      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.bSymbol[row][column]));
        if (column != getInputSize() - 1) {
          string.append("  "); //$NON-NLS-1$
        }
      }
      string.append(System.getProperty("line.separator")); //$NON-NLS-1$
    }

    return string.toString();
  }

  /**
   * Cの数式を返します。
   * 
   * @return Cの数式
   */
  public String getSymbolicC() {
    final StringBuffer string = new StringBuffer();
    final int[] columnLengthes = getColumnLengthesOfABCD();

    for (int row = 0; row < getOutputPortSize(); row++) {
      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.cSymbol[row][column]));
        if (column != getSubSystemSize() - 1) {
          string.append("  "); //$NON-NLS-1$
        }
      }
      string.append(System.getProperty("line.separator")); //$NON-NLS-1$
    }

    return string.toString();
  }

  /**
   * Dの数式を返します。
   * 
   * @return Dの数式
   */
  public String getSymbolicD() {
    final StringBuffer string = new StringBuffer();
    final int[] columnLengthes = getColumnLengthesOfABCD();

    for (int row = 0; row < getOutputPortSize(); row++) {
      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.dSymbol[row][column]));
        if (column != getInputPortSize() - 1) {
          string.append("  "); //$NON-NLS-1$
        }
      }
      string.append(System.getProperty("line.separator")); //$NON-NLS-1$
    }

    return string.toString();
  }

  /**
   * Eの数式を返します。
   * 
   * @return Eの数式
   */
  public String getSymbolicE() {
    final StringBuffer string = new StringBuffer();
    final int[] columnLengthes = getColumnLengthesOfABCD();

    for (int row = 0; row < getSubSystemSize(); row++) {
      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.eSymbol[row][column]));
        if (column != getSubSystemSize() - 1) {
          string.append("  "); //$NON-NLS-1$
        }
      }
      string.append(System.getProperty("line.separator")); //$NON-NLS-1$
    }

    return string.toString();
  }

  /**
   * 上部と下部を分割する水平線を描きます。
   * 
   * @param string 生成する文字列
   * @param columnLengthes 各列の文字列の長さ
   */
  protected void drawHorizontalLine(final StringBuffer string, final int[] columnLengthes) {
    int firstLength = 0;
    for (int i = 0; i < getSubSystemSize(); i++) {
      firstLength += columnLengthes[i];
      if (i != getSubSystemSize() - 1) {
        firstLength += 2;
      }
    }

    int secondLength = 0;
    for (int i = 0; i < getInputPortSize(); i++) {
      secondLength += columnLengthes[i + getSubSystemSize()];
      if (i != getInputPortSize() - 1) {
        secondLength += 2;
      }
    }

    for (int i = 0; i < firstLength; i++) {
      string.append("-"); //$NON-NLS-1$
    }
    string.append("+"); //$NON-NLS-1$
    for (int i = 0; i < secondLength; i++) {
      string.append("-"); //$NON-NLS-1$
    }
  }

  /**
   * 列毎の成分の長さの最大値を返します。
   * 
   * @return 列毎の成分の長さの最大値
   */
  protected int[] getColumnLengthesOfABCD() {
    final int[] columnLengthes = new int[getSubSystemSize() + getInputPortSize()];

    for (int column = 0; column < getSubSystemSize(); column++) {
      final int aLength = getMaxLengthColumnWise(this.aSymbol, column);
      final int cLength = getMaxLengthColumnWise(this.cSymbol, column);
      columnLengthes[column] = aLength > cLength ? aLength : cLength;
    }

    for (int column = 0; column < getInputPortSize(); column++) {
      final int bLength = getMaxLengthColumnWise(this.bSymbol, column);
      final int dLength = getMaxLengthColumnWise(this.dSymbol, column);
      columnLengthes[column + getSubSystemSize()] = bLength > dLength ? bLength : dLength;
    }

    return columnLengthes;
  }

  /**
   * 指定された列の成分の最大の長さを返します。
   * 
   * @param matrix 対象となる行列
   * @param column 指定列
   * @return 指定された列の成分の最大の長さ
   */
  protected int getMaxLengthColumnWise(final String[][] matrix, final int column) {
    int length = 0;
    for (int row = 0; row < matrix.length; row++) {
      if (matrix[row] != null && matrix[row][column] != null && matrix[row][column].length() > length) {
        length = matrix[row][column].length();
      }
    }

    return length;
  }

  /**
   * プロパーであるか判定します。
   * 
   * @return プロパーならばtrue、そうでなければfalse
   */
  public boolean isProper() {
    return this.proper;
  }

  /**
   * 厳密にプロパーであるか判定します。
   * 
   * @return 厳密にプロパーならばtrue、そうでなければfalse
   */
  public boolean isStrictlyProper() {
    return this.strictlyProper;
  }

  /**
   * 1入力1出力(SISO)であるか判定します。
   * 
   * @return SISO(1入力1出力)システムならばtrue、そうでなければfalse
   */
  public boolean isSISO() {
    return getInputSize() == 1 && getOutputSize() == 1;
  }

  /**
   * A行列(システム行列)を返します。
   * 
   * @return A行列(システム行列)
   */
  public RM getA() {
    return this.a;
  }

  /**
   * B行列(入力行列)を返します。
   * 
   * @return B行列(入力行列)
   */
  public RM getB() {
    return this.b;
  }

  /**
   * C行列(出力行列)を返します。
   * 
   * @return C行列(出力行列)
   */
  public RM getC() {
    return this.c;
  }

  /**
   * E行列(ディスクリプタ行列)を返します。
   * 
   * @return the e
   */
  public RM getE() {
    return this.e;
  }

  /**
   * D行列(ゲイン行列)を返します。
   * 
   * @return D行列(ゲイン行列)
   */
  public RM getD() {
    return this.d;
  }

  /**
   * 状態の数を返します。
   * 
   * @return 状態の数
   */
  public int getStateSize() {
    return this.stateSize;
  }

  /**
   * 入力の数を返します。
   * 
   * @return 入力の数
   */
  public int getInputSize() {
    return this.inputSize;
  }

  /**
   * 出力の数を返します。
   * 
   * @return 出力の数
   */
  public int getOutputSize() {
    return this.outputSize;
  }

  /**
   * システムの伝達関数行列を返します。
   * 
   * <p>生成されるシステムを簡単化します。
   * 
   * @return 伝達関数行列
   */
  public AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> getTransferFunctionMatrix() {
    return getTransferFunctionMatrix(true);
  }

  /**
   * システム<code>opponent</code>との和(並列結合)でできるシステムを返します。
   * 
   * <p>結合システムは簡単化されます。
   * 
   * @param opponent 結合する(加えられる)システム
   * @return システム<code>opponent</code>との和(並列結合)でできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> add(final LinearSystem<RS,RM,CS,CM> opponent) {
    return add(opponent, true);
  }

  /**
   * システム<code>opponent</code>との和(並列結合)でできるシステムを返します。
   * 
   * @param opponent 結合する(加えられる)システム
   * @param simplify 簡単化するならばtrue、そうでなければfalse
   * @return システム<code>opponent</code>との和(並列結合)でできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> add(final LinearSystem<RS,RM,CS,CM> opponent, final boolean simplify) {
    if (isProper() && opponent.isProper()) {
      RM ansA = Diag.diag(this.a, ((AbstractLinearSystem<RS,RM,CS,CM>)opponent).a);
      RM ansB = this.b.appendDown(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).b);
      RM ansC = this.c.appendRight(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).c);
      RM ansD = this.d.add(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).d);

      if (simplify) {
        List<RM> abcd = Minreal.minreal(ansA, ansB, ansC, ansD);
        ansA = abcd.get(0);
        ansB = abcd.get(1);
        ansC = abcd.get(2);
        ansD = abcd.get(3);
      }
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG;
    if (isProper()) {
      ansG = getTransferFunctionMatrix(simplify).add(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).G);
    } else if (opponent.isProper()) {
      ansG = this.G.add(opponent.getTransferFunctionMatrix(simplify));
    } else {
      ansG = this.G.add(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).G);
    }

    if (simplify) {
      ansG = Simplify.simplify(ansG);
    }
    return LinearSystemFactory.createLinearSystem(ansG);
  }

  /**
   * 定数行列<code>constantMatrix</code>を右から掛けてできるシステムを返します。
   * 
   * @param constantMatrix 定数行列
   * @return 定数行列<code>constantMatrix</code>を右から掛けてできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> multiply(final RM constantMatrix) {
    if (isProper()) {
      final RM ansB = this.b.multiply(constantMatrix);
      final RM ansD = this.d.multiply(constantMatrix);

      return LinearSystemFactory.createLinearSystem(this.a.createClone(), ansB, this.c.createClone(), ansD);
    }
    
    AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> constantRationalPolynomialMatrix = this.G.getElement(1,1).getNumerator().createGrid(constantMatrix).toRational();
    
    return LinearSystemFactory.createLinearSystem(this.G.multiply(constantRationalPolynomialMatrix));
  }

  /**
   * 定数行列<code>constantMatrix</code>を左から掛けてできるシステムを返します。
   * 
   * @param constantMatrix 定数行列
   * @return 定数行列<code>constantMatrix</code>を左から掛けてできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> leftMultiply(final RM constantMatrix) {
    if (isProper()) {
      final RM ansC = constantMatrix.multiply(this.c);
      final RM ansD = constantMatrix.multiply(this.d);
      return LinearSystemFactory.createLinearSystem(this.a.createClone(), this.b.createClone(), ansC, ansD);
    }

    AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> constantRationalPolynomialMatrix = this.G.getElement(1,1).getNumerator().createGrid(constantMatrix).toRational();
    
    return LinearSystemFactory.createLinearSystem(constantRationalPolynomialMatrix.multiply(this.G));
  }

  /**
   * システム<code>opponent</code>との積(直列結合)(右からの積)でできるシステムを返します。
   * 
   * <p>結合システムを簡単化します。
   * 
   * @param opponent 結合するシステム(入力側に掛けられるシステム)
   * @return システム<code>opponent</code>との積(直列結合)(右からの積)でできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> multiply(final LinearSystem<RS,RM,CS,CM> opponent) {
    return multiply(opponent, true);
  }

  /**
   * システム<code>opponent</code>との積(直列結合)(右からの積)でできるシステムを返します。
   * 
   * @param opponent 結合するシステム(入力側に掛けられるシステム)
   * @param simplify 簡単化するならばtrue、そうでなければfalse
   * @return システム<code>opponent</code>との積(直列結合)(右からの積)でできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> multiply(final LinearSystem<RS,RM,CS,CM> opponent, final boolean simplify) {
    if (isProper() && opponent.isProper()) {
      if (isSISO() && !opponent.isSISO()) {
        final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g1 = getTransferFunctionMatrix(simplify);
        final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g2 = opponent.getTransferFunctionMatrix(simplify);
        AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG = g2.multiply(g1.getElement(1, 1));
        if (simplify) {
          ansG = Simplify.simplify(ansG);
        }
        return LinearSystemFactory.createLinearSystem(ansG);
      }

      if (!isSISO() && opponent.isSISO()) {
        final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g1 = getTransferFunctionMatrix(simplify);
        final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g2 = opponent.getTransferFunctionMatrix(simplify);
        AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG = g1.multiply(g2.getElement(1, 1));
        if (simplify) {
          ansG = Simplify.simplify(ansG);
        }
        return LinearSystemFactory.createLinearSystem(ansG);
      }

      RM ansA = this.a.appendRight(this.b.multiply(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).c)).appendDown(
          this.a.createZero(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).stateSize, this.stateSize).appendRight(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).a));
      RM ansB = this.b.multiply(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).d).appendDown(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).b);
      RM ansC = this.c.appendRight(this.d.multiply(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).c));
      RM ansD = this.d.multiply(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).d);
      if (simplify) {
        List<RM> abcd = Minreal.minreal(ansA, ansB, ansC, ansD);
        ansA = abcd.get(0);
        ansB = abcd.get(1);
        ansC = abcd.get(2);
        ansD = abcd.get(3);
      }
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g1, g2;
    if (isProper()) {
      g1 = getTransferFunctionMatrix(simplify);
      g2 = ((AbstractLinearSystem<RS,RM,CS,CM>)opponent).G;
    } else if (opponent.isProper()) {
      g1 = this.G;
      g2 = opponent.getTransferFunctionMatrix(simplify);
    } else {
      g1 = this.G;
      g2 = ((AbstractLinearSystem<RS,RM,CS,CM>)opponent).G;
    }

    AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG;
    if (isSISO()) {
      ansG = g2.multiply(g1.getElement(1, 1));
    } else if (opponent.isSISO()) {
      ansG = g1.multiply(g2.getElement(1, 1));
    } else {
      ansG = g1.multiply(g2);
    }
    if (simplify) {
      ansG = Simplify.simplify(ansG);
    }
    return LinearSystemFactory.createLinearSystem(ansG);
  }

  /**
   * システム<code>opponent</code>との差(符合が異なる並列結合)でできるシステムを返します。
   * 
   * <p>結合システムを簡単化します。
   * 
   * @param opponent 結合するシステム(引かれるシステム)
   * @return システム<code>opponent</code>との差(符合が異なる並列結合)でできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> subtract(final LinearSystem<RS,RM,CS,CM> opponent) {
    return subtract(opponent, true);
  }

  /**
   * システム<code>opponent</code>との差(符合が異なる並列結合)でできるシステムを返します。
   * 
   * @param opponent 結合するシステム(引かれるシステム)
   * @param simplify 簡単化するならばtrue、そうでなければfalse
   * @return システム<code>opponent</code>との差(符合が異なる並列結合)でできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> subtract(final LinearSystem<RS,RM,CS,CM> opponent, final boolean simplify) {
    if (isProper() && opponent.isProper()) {
      RM ansA = Diag.diag(this.a, ((AbstractLinearSystem<RS,RM,CS,CM>)opponent).a);
      RM ansB = this.b.appendDown(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).b);
      RM ansC = this.c.appendRight(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).c.unaryMinus());
      RM ansD = this.d.add(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).d.unaryMinus());
      if (simplify) {
        final List<RM> abcd = Minreal.minreal(ansA, ansB, ansC, ansD);
        ansA = abcd.get(0);
        ansB = abcd.get(1);
        ansC = abcd.get(2);
        ansD = abcd.get(3);
      }
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG;
    if (isProper()) {
      ansG = getTransferFunctionMatrix(simplify).subtract(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).G);
    } else if (opponent.isProper()) {
      ansG = this.G.subtract(opponent.getTransferFunctionMatrix(simplify));
    } else {
      ansG = this.G.subtract(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).G);
    }
    if (simplify) {
      ansG = Simplify.simplify(ansG);
    }
    return LinearSystemFactory.createLinearSystem(ansG);
  }

  /**
   * 入力または出力の符合を反転にしてできるシステムを返します。
   * 
   * @return 入力または出力の符合を反転にしてできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> unaryMinus() {
    if (isProper()) {
      final RM ansA = this.a.createClone();
      final RM ansB = this.b.unaryMinus();
      final RM ansC = this.c.createClone();
      final RM ansD = this.d.unaryMinus();
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    return LinearSystemFactory.createLinearSystem(this.G.unaryMinus());
  }

  /**
   * 逆システム(入力と出力を逆にしたシステム)を返します。
   * 
   * <p>生成されたシステムを簡単化します。
   * 
   * @return 逆システム(入力と出力を逆にしたシステム)
   */
  public LinearSystem<RS,RM,CS,CM> inverse() {
    return inverse(true);
  }

  /**
   * 逆システム(入力と出力を逆にしたシステム)を返します。
   * 
   * @param simplify 生成されたシステムを簡単化するならばtrue、そうでなければfalse
   * @return 逆システム(入力と出力を逆にしたシステム)
   * 
   */
  public LinearSystem<RS,RM,CS,CM> inverse(final boolean simplify) {
    if (isProper()) {
      RM ansA = this.a.subtract(this.b.multiply(this.d.leftDivide(this.c)));
      RM ansB = this.b.multiply(this.d.inverse());
      RM ansC = this.d.leftDivide(this.c).unaryMinus();
      RM ansD = this.d.inverse();
      if (simplify) {
        final List<RM> abcd = Minreal.minreal(ansA, ansB, ansC, ansD);
        ansA = abcd.get(0);
        ansB = abcd.get(1);
        ansC = abcd.get(2);
        ansD = abcd.get(3);
      }
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG = this.G.inverse();
    if (simplify) {
      ansG = Simplify.simplify(ansG);
    }
    return LinearSystemFactory.createLinearSystem(ansG);
  }

  /**
   * 転置してできるシステムを返します。
   * 
   * @return 転置してできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> transpose() {
    if (isProper()) {
      final RM ansA = this.a.transpose();
      final RM ansB = this.c.transpose();
      final RM ansC = this.b.transpose();
      final RM ansD = this.d.transpose();
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    return LinearSystemFactory.createLinearSystem(this.G.transpose());
  }

  /**
   * 複素共役転置してできるシステムを返します。
   * 
   * @return 複素共役転置してできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> conjugateTranspose() {
    if (isProper()) {
      final RM ansA = this.a.conjugateTranspose();
      final RM ansB = this.c.conjugateTranspose();
      final RM ansC = this.b.conjugateTranspose();
      final RM ansD = this.d.conjugateTranspose();
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    return LinearSystemFactory.createLinearSystem(this.G.conjugateTranspose());
  }

  /**
   * システム<code>feedbackElement</code>をフィードバック結合(負帰還)してできるシステムを返します。
   * 
   * <p>生成されるシステムを簡単化します。
   * 
   * @param feedbackElement フィードバック結合するシステム
   * @return システム<code>feedbackElement</code>をフィードバック結合(負帰還)してできるシステム
   * 
   *         <p>feedback-connected system
   */
  public LinearSystem<RS,RM,CS,CM> feedback(final LinearSystem<RS,RM,CS,CM> feedbackElement) {
    return feedback(feedbackElement, true, true);
  }

  /**
   * システム<code>feedbackElement</code>をフィードバック結合してできるシステムを返します。
   * 
   * <p>生成されるシステムを簡単化します。
   * 
   * @param feedbackElement フィードバック要素
   * @param negative ネガティブフィードバックならばtrue、そうでなければfalse
   * @return システム<code>feedbackElement</code>をフィードバック結合(負帰還)してできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> feedback(final LinearSystem<RS,RM,CS,CM> feedbackElement, final boolean negative) {
    return feedback(feedbackElement, negative, true);
  }

  /**
   * システムfeedbackElementをフィードバック結合してできるシステムを返します。
   * 
   * @param feedbackElement フィードバック要素
   * @param negative ネガティブフィードバックならばtrue、そうでなければfalse
   * @param simplify 生成されたシステムを簡単化するならばtrue、そうでなければfalse
   * @return フィードバック結合してできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> feedback(final LinearSystem<RS,RM,CS,CM> feedbackElement, final boolean negative, final boolean simplify) {
    if (isProper() && feedbackElement.isProper()) {
      RM ansA, ansB, ansC, ansD;
      if (negative) {
        // T1 = I + D2*D1
        // T2 = I + D1*D2
        final RM T1 = this.d.createUnit(this.outputSize).add(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).d.multiply(this.d));
        final RM T2 = this.d.createUnit(this.outputSize).add(this.d.multiply(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).d));

        if (T1.isFullRank() == false) {
          throw new RuntimeException(Messages.getString("LinearSystem.27")); //$NON-NLS-1$
        }
        if (T2.isFullRank() == false) {
          throw new RuntimeException(Messages.getString("LinearSystem.28")); //$NON-NLS-1$
        }

        // A = [[A1-B1/T1*D2*C1    -B1/T1*C2  ]
        //      [    B2/T2*C1   A2-B2/T2*D1*C2]];
        final RM a11 = this.a.subtract(this.b.divide(T1).multiply(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).d).multiply(this.c));
        final RM a12 = this.b.divide(T1).multiply(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).c).unaryMinus();
        final RM a21 = ((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).b.divide(T2).multiply(this.c);
        final RM a22 = ((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).a
            .subtract(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).b.divide(T2).multiply(this.d).multiply(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).c));
        ansA = a11.appendRight(a12).appendDown(a21.appendRight(a22));

        // B = [[  B1/T1 ]
        //      [B2/T2*D1]];
        final RM b1 = this.b.divide(T1);
        final RM b2 = ((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).b.divide(T2).multiply(this.d);
        ansB = b1.appendDown(b2);
        // [T2\C1, -T2\D1*C2];
        ansC = T2.leftDivide(this.c).appendRight(T2.leftDivide(this.d).multiply(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).c).unaryMinus());
        // [T2\D1]
        ansD = T2.leftDivide(this.d);
      } else {
        // T1 = I - D2*D1
        // T2 = I - D1*D2
        final RM T1 = this.d.createUnit(this.outputSize).subtract(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).d.multiply(this.d));
        final RM T2 = this.d.createUnit(this.outputSize).subtract(this.d.multiply(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).d));
        if (T1.isFullRank() == false) {
          throw new RuntimeException(Messages.getString("LinearSystem.29")); //$NON-NLS-1$
        }
        if (T2.isFullRank() == false) {
          throw new RuntimeException(Messages.getString("LinearSystem.30")); //$NON-NLS-1$
        }

        // A = [[A1+B1/T1*D2*C1    B1/T1*C2   ]
        //      [   B2/T2*C1    A2+B2/T2*D1*C2]];
        final RM a11 = this.a.add(this.b.divide(T1).multiply(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).d).multiply(this.c));
        final RM a12 = this.b.divide(T1).multiply(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).c);
        final RM a21 = ((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).b.divide(T2).multiply(this.c);
        final RM a22 = ((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).a.add(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).b.divide(T2).multiply(this.d).multiply(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).c));
        ansA = a11.appendRight(a12).appendDown(a21.appendRight(a22));

        // B = [[  B1/T1 ]
        //      [B2/T2*D1]];
        final RM b1 = this.b.divide(T1);
        final RM b2 = ((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).b.divide(T2).multiply(this.d);
        ansB = b1.appendDown(b2);
        // [T2\C1, T2\D1*C2];
        ansC = T2.leftDivide(this.c).appendRight(T2.leftDivide(this.d).multiply(((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).c));
        // [T2\D1]
        ansD = T2.leftDivide(this.d);
      }

      if (simplify) {
        final List<RM> abcd = Minreal.minreal(ansA, ansB, ansC, ansD);
        ansA = abcd.get(0);
        ansB = abcd.get(1);
        ansC = abcd.get(2);
        ansD = abcd.get(3);
      }
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g1, g2;

    if (isProper()) {
      g1 = getTransferFunctionMatrix(simplify);
      g2 = ((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).G;
    } else if (feedbackElement.isProper()) {
      g1 = this.G;
      g2 = feedbackElement.getTransferFunctionMatrix(simplify);
    } else {
      g1 = this.G;
      g2 = ((AbstractLinearSystem<RS,RM,CS,CM>)feedbackElement).G;
    }
    final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> g1g2 = g1.multiply(g2);

    AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG;

    if (negative) {
      ansG = g1g2.createUnit(g1g2.getRowSize()).add(g1g2).inverse().multiply(g1);
    } else {
      ansG = g1g2.createUnit(g1g2.getRowSize()).subtract(g1g2).inverse().multiply(g1);
    }

    if (simplify) {
      ansG = Simplify.simplify(ansG);
    }
    return LinearSystemFactory.createLinearSystem(ansG);
  }

  /**
   * 単一フィードバック(ネガティブフィードバック)してできるシステムを返します。
   * 
   * <p>生成されるシステムを簡単化します。
   * 
   * @return 単一フィードバック(ネガティブフィードバック)してできるシステム
   * 
   *         <p>unity feedback connected system
   */
  public LinearSystem<RS,RM,CS,CM> unityFeedback() {
    return unityFeedback(true, true);
  }

  /**
   * 単一フィードバックしてできるシステムを返します。
   * 
   * <p>生成されるシステムを簡単化します。
   * 
   * @param negative ネガティブフィードバックならばtrue、そうでなければfalse
   * 
   * @return フィードバック結合してできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> unityFeedback(final boolean negative) {
    return unityFeedback(negative, true);
  }

  /**
   * 単一フィードバックしてできるシステムを返します。
   * 
   * @param negative ネガティブフィードバックならばtrue、そうでなければfalse
   * @param simplify 生成されるシステムを簡単化するならばtrue、そうでなければfalse
   * @return フィードバック結合してできるシステム
   */
  public LinearSystem<RS,RM,CS,CM> unityFeedback(final boolean negative, final boolean simplify) {
    if (isProper()) {
      RM ansA, ansB, ansC, ansD;

      if (negative) {
        final RM T = this.d.createUnit(this.d.getRowSize()).add(this.d);
        if (T.isFullRank() == false) {
          throw new RuntimeException(Messages.getString("LinearSystem.31")); //$NON-NLS-1$
        }

        ansA = this.a.subtract(this.b.divide(T).multiply(this.c));
        ansB = this.b.multiply(T.inverse());
        ansC = this.c.subtract(this.d.divide(T).multiply(this.c));
        ansD = this.d.multiply(T.inverse());
      } else {
        final RM T = this.d.createUnit(this.d.getRowSize()).subtract(this.d);
        if (T.isFullRank() == false) {
          throw new RuntimeException(Messages.getString("LinearSystem.32")); //$NON-NLS-1$
        }

        ansA = this.a.add(this.b.divide(T).multiply(this.c));
        ansB = this.b.multiply(T.inverse());
        ansC = this.c.add(this.d.divide(T).multiply(this.c));
        ansD = this.d.multiply(T.inverse());
      }

      if (simplify) {
        final List<RM> abcd = Minreal.minreal(ansA, ansB, ansC, ansD);
        ansA = abcd.get(0);
        ansB = abcd.get(1);
        ansC = abcd.get(2);
        ansD = abcd.get(3);
      }
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG;
    if (negative) {
      ansG = this.G.createUnit(this.G.getRowSize()).add(this.G).inverse().multiply(this.G);
    } else {
      ansG = this.G.createUnit(this.G.getRowSize()).subtract(this.G).inverse().multiply(this.G);
    }

    if (simplify) {
      ansG = Simplify.simplify(ansG);
    }
    return LinearSystemFactory.createLinearSystem(ansG);
  }

  /**
   * システム<code>opponent</code>を行方向に結合した(出力を加算する)システムを返します。
   * 
   * <p>生成されるシステムを簡単化します。
   * 
   * @param opponent 結合するシステム
   * @return システム<code>opponent</code>を行方向に結合した(出力を加算する)システム
   * 
   *         <p>row-wise connected system
   */
  public LinearSystem<RS,RM,CS,CM> appendRight(final LinearSystem<RS,RM,CS,CM> opponent) {
    return appendRight(opponent, true);
  }

  /**
   * システム<code>opponent</code>を行方向に結合した(出力を加算する)システムを返します。
   * 
   * @param opponent 結合するシステム
   * @param simplify 生成されるシステムを簡単化するならばtrue、そうでなければfalse
   * @return システム<code>opponent</code>を行方向に結合した(出力を加算する)システム
   * 
   *         <p>row-wise connected system
   */
  public LinearSystem<RS,RM,CS,CM> appendRight(final LinearSystem<RS,RM,CS,CM> opponent, final boolean simplify) {
    if (isProper() && opponent.isProper()) {
      RM ansA = Diag.diag(this.a, ((AbstractLinearSystem<RS,RM,CS,CM>)opponent).a);
      RM ansB = Diag.diag(this.b, ((AbstractLinearSystem<RS,RM,CS,CM>)opponent).b);
      RM ansC = this.c.appendRight(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).c);
      RM ansD = this.d.appendRight(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).d);

      if (simplify) {
        final List<RM> abcd = Minreal.minreal(ansA, ansB, ansC, ansD);
        ansA = abcd.get(0);
        ansB = abcd.get(1);
        ansC = abcd.get(2);
        ansD = abcd.get(3);
      }
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG;
    if (this.isProper()) {
      ansG = getTransferFunctionMatrix(simplify).appendRight(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).G);
    } else if (opponent.isProper()) {
      ansG = this.G.appendRight(opponent.getTransferFunctionMatrix(simplify));
    } else {
      ansG = this.G.appendRight(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).G);
    }
    return LinearSystemFactory.createLinearSystem(ansG);
  }

  /**
   * システム<code>opponent</code>を列方向に結合した(同一の入力を加える)システムを返します。
   * 
   * <p>生成されるシステムを簡単化します。
   * 
   * @param opponent 結合するシステム
   * @return システム<code>opponent</code>を列方向に結合した(同一の入力を加える)システム
   * 
   *         <p>column-wise connected system
   */
  public LinearSystem<RS,RM,CS,CM> appendDown(final LinearSystem<RS,RM,CS,CM> opponent) {
    return appendDown(opponent, true);
  }

  /**
   * システム<code>opponent</code>を列方向に結合した(同一の入力を加える)システムを返します。
   * 
   * @param opponent 結合するシステム
   * @param simplify 生成されるシステムを簡単化するならばtrue、そうでなければfalse
   * @return システム<code>opponent</code>を列方向に結合した(同一の入力を加える)システム
   * 
   *         <p>column-wise connected system
   */
  public LinearSystem<RS,RM,CS,CM> appendDown(final LinearSystem<RS,RM,CS,CM> opponent, final boolean simplify) {
    if (isProper() && opponent.isProper()) {
      RM ansA = Diag.diag(this.a, ((AbstractLinearSystem<RS,RM,CS,CM>)opponent).a);
      RM ansB = this.b.appendDown(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).b);
      RM ansC = Diag.diag(this.c, ((AbstractLinearSystem<RS,RM,CS,CM>)opponent).c);
      RM ansD = this.d.appendDown(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).d);

      if (simplify) {
        final List<RM> abcd = Minreal.minreal(ansA, ansB, ansC, ansD);
        ansA = abcd.get(0);
        ansB = abcd.get(1);
        ansC = abcd.get(2);
        ansD = abcd.get(3);
      }
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG;
    if (this.isProper()) {
      ansG = getTransferFunctionMatrix(simplify).appendDown(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).G);
    } else if (opponent.isProper()) {
      ansG = this.G.appendDown(opponent.getTransferFunctionMatrix(simplify));
    } else {
      ansG = this.G.appendDown(((AbstractLinearSystem<RS,RM,CS,CM>)opponent).G);
    }
    return LinearSystemFactory.createLinearSystem(ansG);
  }

  /**
   * 最小実現したシステムを返します。
   * 
   * @return 最小実現したシステム
   * 
   *         <p>minimal realization
   */
  public LinearSystem<RS,RM,CS,CM> simplify() {
    RS sunit = this.a.getElement(1,1).createUnit();
    return simplify(sunit.create(-1));
  }

//  /**
//   * 最小実現したシステムを返します。
//   * 
//   * @param tolerance 許容誤差
//   * @return 最小実現したシステム
//   * 
//   *         <p>minimal realization
//   */
//  public LinearSystem<RS,RM,CS,CM> simplify(RS tolerance) {
//    return simplify(new RS(tolerance));
//  }

  /**
   * 最小実現したシステムを返します。
   * 
   * @param tolerance 許容誤差
   * @return 最小実現したシステム
   * 
   *         <p>minimal realization
   */
  public LinearSystem<RS,RM,CS,CM> simplify(RS tolerance) {
    if (isProper()) {
      final List<RM> abcd = Minreal.minreal(this.a, this.b, this.c, this.d, tolerance);
      RM ansA = abcd.get(0);
      RM ansB = abcd.get(1);
      RM ansC = abcd.get(2);
      RM ansD = abcd.get(3);
      return LinearSystemFactory.createLinearSystem(ansA, ansB, ansC, ansD);
    }

    final AnyRealRationalPolynomialMatrix<RS,RM,CS,CM> ansG = Simplify.simplify(this.G);
    return LinearSystemFactory.createLinearSystem(ansG);
  }

  /**
   * 時間領域でのシステムの種類を設定します。
   * 
   * @param type 時間領域でのシステムの種類
   */
  public void setTimeDomainType(final TimeDomainType type) {
    this.timeDomainType = type;
  }

  /**
   * 時間領域でのシステムの種類を返します。
   * 
   * @return 時間領域でのシステムの種類
   */
  public TimeDomainType getTimeDomainType() {
    return this.timeDomainType;
  }

  /**
   * 連続時間システムであるか判定します。
   * 
   * @return 連続時間システムならばtrue、そうでなければfalse
   */
  public boolean isContinuous() {
    return this.timeDomainType == TimeDomainType.CONTINUOUS;
  }

  /**
   * 離散時間システムであるか判定します。
   * 
   * @return 離散時間システムならばtrue、そうでなければfalse
   */
  public boolean isDiscrete() {
    return this.timeDomainType == TimeDomainType.DISCRETE;
  }

  /**
   * サンプル値システムであるか判定します。
   * 
   * @return サンプル値システムならばtrue、そうでなければfalse
   */
  public boolean isSampled() {
    return this.timeDomainType == TimeDomainType.SAMPLED;
  }

  /**
   * 数値の出力フォーマットを設定します。
   * 
   * @param format 数値の出力フォーマット
   */
  public void setFormat(final String format) {
    this.a.setElementFormat(format);
    this.b.setElementFormat(format);
    this.c.setElementFormat(format);
    this.d.setElementFormat(format);
    if (this.e != null) {
      this.e.setElementFormat(format);
    }
    if (this.G != null) {
      this.G.setElementFormat(format);
    }
  }

  /**
   * 数値の出力フォーマットを返します。
   * 
   * @return 数値の出力フォーマット
   */
  public String getFormat() {
    return this.a.getElementFormat();
  }

  /**
   * 状態空間実現の係数行列の数式を設定します。
   * 
   * @param aSymbol A行列の数式
   * @param bSymbol B行列の数式
   * @param cSymbol C行列の数式
   * @param dSymbol D行列の数式
   * @param eSymbol E行列の数式
   */
  public void setSymbolicString(final String[][] aSymbol, final String[][] bSymbol, final String[][] cSymbol, final String[][] dSymbol, final String[][] eSymbol) {
    this.aSymbol = aSymbol;
    this.bSymbol = bSymbol;
    this.cSymbol = cSymbol;
    this.dSymbol = dSymbol;
    this.eSymbol = eSymbol;
  }

  /**
   * サブシステムの数を返します。
   * 
   * @return サブシステムの数
   */
  protected int getSubSystemSize() {
    return this.aSymbol.length;
  }

  /**
   * 入力ポートの数を返します。
   * 
   * @return 入力ポートの数
   */
  protected int getInputPortSize() {
    return this.bSymbol.length != 0 ? this.bSymbol[0].length : this.dSymbol[0].length;
  }

  /**
   * 出力ポートの数を返します。
   * 
   * @return 出力ポートの数
   */
  protected int getOutputPortSize() {
    return this.cSymbol.length;
  }

  /**
   * 入力ポートのタグを返します。
   * 
   * @return 入力ポートのタグ
   */
  public List<String> getInputPortTags() {
    return this.inputPortTags;
  }

  /**
   * 入力ポートのタグを設定します。
   * 
   * @param inputPortTags 入力ポートのタグ
   */
  public void setInputPortTags(List<String> inputPortTags) {
    this.inputPortTags = inputPortTags;
  }

  /**
   * 出力ポートのタグを返します。
   * 
   * @return 出力ポートのタグ
   */
  public List<String> getOutputPortTags() {
    return this.outputPortTags;
  }

  /**
   * 出力ポートのタグを設定します。
   * 
   * @param outputPortTags 出力ポートのタグ
   */
  public void setOutputTags(List<String> outputPortTags) {
    this.outputPortTags = outputPortTags;
  }

  /**
   * 状態のタグを返します。
   * 
   * @return 状態のタグ
   */
  public List<String> getStateTags() {
    return this.stateTags;
  }

  /**
   * 状態のタグを設定します。
   * 
   * @param stateTags 状態のタグ
   */
  public void setStateTags(List<String> stateTags) {
    this.stateTags = stateTags;
  }

  /**
   * A行列の文字列をString[][]で返します。 (テストケースのための一時的なものです。)
   * 
   * @return String[][](aSymbol)
   */
  protected String[][] getSymbolicAMatrix() {
    return this.aSymbol;
  }

  /**
   * B行列の文字列をString[][]で返します。 (テストケースのための一時的なものです。)
   * 
   * @return String[][](bSymbol)
   */
  protected String[][] getSymbolicBMatrix() {
    return this.bSymbol;
  }

  /**
   * C行列の文字列をString[][]で返します。 (テストケースのための一時的なものです。)
   * 
   * @return String[][](cSymbol)
   */
  protected String[][] getSymbolicCMatrix() {
    return this.cSymbol;
  }

  /**
   * D行列の文字列をString[][]で返します。 (テストケースのための一時的なものです。)
   * 
   * @return String[][](dSymbol)
   */
  protected String[][] getSymbolicDMatrix() {
    return this.dSymbol;
  }

  /**
   * eSymbolを返します.(一時的実装)
   * 
   * @return eSymbol
   */
  protected String[][] getSymbolicEMatrix() {
    return this.eSymbol;
  }

  /**
   * @see org.mklab.nfc.matrix.GridElement#clone()
   */
  @Override
  abstract public Object clone();

  /**
   * 表示文字列を返します。
   * 
   * @return 表示文字列
   */
  public String getPrintingString() {
    try (final CharArrayWriter output = new CharArrayWriter()) {
      print(output);
      final String printString = output.toString();
      return printString;
    }
  }
  
  /**
   * 指数を返します。
   * @return 指数
   */
  public IntMatrix getIndex() {
    return this.index;
  }
  
  /**
   * 指数を設定します。
   * @param index 指数
   */
  public void setIndex(IntMatrix index) {
    this.index = index;
  }
}