SystemBuilder.java

/*
 * $Id: ControlSystem.java,v 1.234 2008/07/15 15:27:15 koga Exp $
 *
 * Copyright (C) 2004 Koga Laboratory. All rights reserved.
 *
 */

package org.mklab.tool.control.system;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.mklab.nfc.matrix.ComplexNumericalMatrix;
import org.mklab.nfc.matrix.RealNumericalMatrix;
import org.mklab.nfc.ode.ContinuousAlgebraicSystem;
import org.mklab.nfc.ode.ContinuousDiscreteAlgebraicSystem;
import org.mklab.nfc.ode.DifferenceSystem;
import org.mklab.nfc.ode.DifferentialDifferenceSystem;
import org.mklab.nfc.ode.DiscreteAlgebraicSystem;
import org.mklab.nfc.ode.ExplicitDifferentialSystem;
import org.mklab.nfc.rpn.ReversePolishNotationProcessor;
import org.mklab.nfc.scalar.ComplexNumericalScalar;
import org.mklab.nfc.scalar.RealNumericalScalar;
import org.mklab.tool.control.LinearSystem;
import org.mklab.tool.control.system.continuous.BlockContinuousSystem;
import org.mklab.tool.control.system.continuous.ContinuousDynamicSystem;
import org.mklab.tool.control.system.discrete.BlockDiscreteSystem;
import org.mklab.tool.control.system.discrete.DiscreteDynamicSystem;
import org.mklab.tool.control.system.math.ConstantSystem;
import org.mklab.tool.control.system.math.DeMultiplexer;
import org.mklab.tool.control.system.math.DeMultiplexerGroup;
import org.mklab.tool.control.system.math.Multiplexer;
import org.mklab.tool.control.system.math.MultiplexerGroup;
import org.mklab.tool.control.system.math.NegativeUnitSystem;
import org.mklab.tool.control.system.math.UnitSystem;
import org.mklab.tool.control.system.sampled.BlockSampledDataSystem;
import org.mklab.tool.control.system.sampled.BlockSamplingSystem;
import org.mklab.tool.control.system.sampled.SampledDataDynamicSystem;
import org.mklab.tool.control.system.sink.Exporter;
import org.mklab.tool.control.system.sink.OutputPort;
import org.mklab.tool.control.system.source.Importer;
import org.mklab.tool.control.system.source.InputPort;


/**
 * システムを作成するビルダーです。
 * 
 * @author Koga Laboratory
 * @version $Revision: 1.234 $, 2004/11/10
 * @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 SystemBuilder<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 AdjacencyMatrix<RS,RM,CS,CM> adjacencyMatrix;

  /** 隣接行列が表わすブロックシステム */
  private BlockSystem<RS,RM,CS,CM> blockSystem;
  
  /** unit of scalar */
  private RS sunit;

  /**
   * 新しく生成された<code>SystemBuilder</code>オブジェクトを初期化します。
   * @param sunit unit of scalar
   */
  private SystemBuilder(RS sunit) {
    this.sunit = sunit;
  }

  /**
   * 新しく生成された<code>SystemBuilder</code>オブジェクトを初期化します。
   * 
   * @param system システムオペレータ
   * @param sunit unit of scalar
   */
  @SuppressWarnings("boxing")
  public SystemBuilder(final SystemOperator<RS,RM,CS,CM> system, RS sunit) {
    this.sunit = sunit;
    final SystemOperator<RS,RM,CS,CM> zero = ZeroSystem.getInstance(this.sunit);
    final SystemOperator<RS,RM,CS,CM>[][] matrix = new SystemOperator[][] { {zero, system}, {zero, zero}};

    final AdjacencyMatrix<RS,RM,CS,CM> adjMatrix = new AdjacencyMatrix<>(matrix, this.sunit);
    adjMatrix.setInputNodes(new ArrayList<>(Arrays.asList(1)));
    adjMatrix.setOutputNodes(new ArrayList<>(Arrays.asList(2)));

    final Map<Integer, String> inputPortTags = new TreeMap<>();
    inputPortTags.put(1, "u_1"); //$NON-NLS-1$

    final Map<Integer, String> outputPortTags = new TreeMap<>();
    outputPortTags.put(2, "y_1"); //$NON-NLS-1$

    adjMatrix.setInputPortTags(inputPortTags);
    adjMatrix.setOutputPortTags(outputPortTags);

    this.adjacencyMatrix = adjMatrix;
    this.blockSystem = this.adjacencyMatrix.getBlockSystem();
  }

  /**
   * 新しく生成された<code>SystemBuilder</code>オブジェクトを初期化します。
   * 
   * <p>入力ノードの番号は1、出力ノードの番号は行列の次数となります。
   * 
   * @param matrix 隣接行列
   * @param sunit unit of scalar
   */
  @SuppressWarnings("boxing")
  public SystemBuilder(final AdjacencyMatrix<RS,RM,CS,CM> matrix, RS sunit) {
    this(matrix, new ArrayList<>(Arrays.asList(1)), new ArrayList<>(Arrays.asList(matrix.getRowSize())), new SystemBuilderOption(), sunit);
  }

  /**
   * 新しく生成された<code>SystemBuilder</code>オブジェクトを初期化します。
   * 
   * @param matrix 隣接行列
   * @param inputNodes 入力ポートのノード番号のリスト(番号は1から始まります)
   * @param outputNodes 出力ポートのノード番号のリスト(番号は1から始まります)
   * @param sunit unit of scalar
   */
  public SystemBuilder(final AdjacencyMatrix<RS,RM,CS,CM> matrix, final List<Integer> inputNodes, final List<Integer> outputNodes, RS sunit) {
    this(matrix, inputNodes, outputNodes, new ArrayList<Integer>(), new ArrayList<Integer>(), new SystemBuilderOption(), sunit);
  }

  /**
   * 新しく生成された<code>SystemBuilder</code>オブジェクトを初期化します。
   * 
   * <p>入力ノードの番号は1、出力ノードの番号は行列の次数となります。
   * 
   * @param matrix 隣接行列
   * @param option オプション
   * @param sunit unit of scalar
   */
  @SuppressWarnings("boxing")
  public SystemBuilder(final AdjacencyMatrix<RS,RM,CS,CM> matrix, SystemBuilderOption option, RS sunit) {
    this(matrix, new ArrayList<>(Arrays.asList(1)), new ArrayList<>(Arrays.asList(matrix.getRowSize())), option, sunit);
  }

  /**
   * 新しく生成された<code>SystemBuilder</code>オブジェクトを初期化します。
   * 
   * @param matrix 隣接行列
   * @param inputNodes 入力ポートのノード番号のリスト(番号は1から始まります)
   * @param outputNodes 出力ポートのノード番号のリスト(番号は1から始まります)
   * @param option オプション
   * @param sunit unit of scalar
   */
  public SystemBuilder(final AdjacencyMatrix<RS,RM,CS,CM> matrix, final List<Integer> inputNodes, final List<Integer> outputNodes, SystemBuilderOption option, RS sunit) {
    this(matrix, inputNodes, outputNodes, new ArrayList<Integer>(), new ArrayList<Integer>(), option, sunit);
  }

  /**
   * 新しく生成された<code>SystemBuilder</code>オブジェクトを初期化します。
   * 
   * @param matrix 隣接行列
   * @param inputNodes 入力ポートのノード番号のリスト(番号は1から始まります)
   * @param outputNodes 出力ポートのノード番号のリスト(番号は1から始まります)
   * @param sourceNodes Sourceのノード番号のリスト(番号は1から始まります)
   * @param sinkNodes Sinkのノード番号のリスト(番号は1から始まります)
   * @param option オプション
   * @param sunit unit of scalar
   */
  public SystemBuilder(final AdjacencyMatrix<RS,RM,CS,CM> matrix, final List<Integer> inputNodes, final List<Integer> outputNodes, final List<Integer> sourceNodes, final List<Integer> sinkNodes,
      SystemBuilderOption option, RS sunit) {
    this.sunit = sunit; 
    final Map<Integer, String> inputPortTags = new TreeMap<>();
    int inputNumber = 1;
    for (final Integer inputNode : inputNodes) {
      inputPortTags.put(inputNode, "u_" + inputNumber++); //$NON-NLS-1$
    }

    final Map<Integer, String> outputPortTags = new TreeMap<>();
    int outputNumber = 1;
    for (final Integer outputNode : outputNodes) {
      outputPortTags.put(outputNode, "y_" + outputNumber++); //$NON-NLS-1$
    }

    matrix.setupSystemIDForElements();

    createSystem(matrix, inputNodes, outputNodes, sourceNodes, sinkNodes, inputPortTags, outputPortTags, option);
  }

  /**
   * 新しく生成された<code>SystemBuilder</code>オブジェクトを初期化します。
   * 
   * @param stringMatrix 隣接関係を保持する文字列行列
   * @param sunit unit of scalar
   */
  public SystemBuilder(final AdjacencyStringMatrix<RS,RM,CS,CM> stringMatrix, RS sunit) {
    this(stringMatrix, new SystemBuilderOption(), sunit);
  }

  /**
   * 新しく生成された<code>SystemBuilder</code>オブジェクトを初期化します。
   * 
   * @param stringMatrix 隣接関係を保持する文字列行列
   * @param option オプション
   * @param sunit unit of scalar
   */
  public SystemBuilder(final AdjacencyStringMatrix<RS,RM,CS,CM> stringMatrix, SystemBuilderOption option, RS sunit) {
    this.sunit = sunit;
    final List<Integer> inputSourceNodes = stringMatrix.getSortedInputSourceNodes();
    final List<Integer> outputSinkNodes = stringMatrix.getSortedOutputSinkNodes();

    final int inputSourceSize = inputSourceNodes.size();
    final int outputSinkSize = outputSinkNodes.size();

    final int inputSourceOutputSinkLines = 2;
    final int innerSize = stringMatrix.size() - inputSourceOutputSinkLines;
    final int size = innerSize + inputSourceSize + outputSinkSize;
    final SystemOperator<RS,RM,CS,CM>[][] systemMatrix = new SystemOperator[size][size];

    setupDeMultiplexer(systemMatrix, stringMatrix, inputSourceSize);
    setupMultiplexer(systemMatrix, stringMatrix, inputSourceSize);
    setupSiSoSystem(systemMatrix, stringMatrix, inputSourceSize);
    setupMiMoSystem(systemMatrix, stringMatrix, inputSourceSize);

    // 接続先の無いポート、信号の数が未定のポートの信号を大きさを決定します。
    setupDeMultiplexer(systemMatrix, stringMatrix, inputSourceSize);
    setupMultiplexer(systemMatrix, stringMatrix, inputSourceSize);

    final Map<Integer, String> inputPortTags = getInputPortTags(stringMatrix, inputSourceNodes);
    final Map<Integer, String> outputPortTags = getOutputPortTags(stringMatrix, outputSinkNodes, inputSourceSize);

    final List<List<Integer>> inputNodesSourceNodes = setupInputSource(systemMatrix, stringMatrix, inputSourceNodes);
    final List<List<Integer>> outputNodesSinkNodes = setupOutputSink(systemMatrix, stringMatrix, outputSinkNodes, inputSourceSize);
    final List<Integer> inputNodes = inputNodesSourceNodes.get(0);
    final List<Integer> sourceNodes = inputNodesSourceNodes.get(1);
    final List<Integer> outputNodes = outputNodesSinkNodes.get(0);
    final List<Integer> sinkNodes = outputNodesSinkNodes.get(1);

    final AdjacencyMatrix<RS,RM,CS,CM> matrix = new AdjacencyMatrix<>(systemMatrix, sunit);

    createSystem(matrix, inputNodes, outputNodes, sourceNodes, sinkNodes, inputPortTags, outputPortTags, option);
  }

  /**
   * データを設定します。
   * 
   * @param matrix 隣接行列
   * @param inputNodes 入力ポートのノード番号のリスト(番号は1から始まります)
   * @param outputNodes 出力ポートのノード番号のリスト(番号は1から始まります)
   * @param sourceNodes Sourceのノード番号のリスト(番号は1から始まります)
   * @param sinkNodes Sinkのノード番号のリスト(番号は1から始まります)
   * @param inputPortTags 入力ポートのノード番号(番号は1から始まります)とタグのマップ
   * @param outputPortTags 出力ポートのノード番号(番号は1から始まります)とタグのマップ
   * @param option オプション
   */
  private void createSystem(final AdjacencyMatrix<RS,RM,CS,CM> matrix, final List<Integer> inputNodes, final List<Integer> outputNodes, final List<Integer> sourceNodes, final List<Integer> sinkNodes,
      final Map<Integer, String> inputPortTags, final Map<Integer, String> outputPortTags, SystemBuilderOption option) {
    final boolean requiringLinearSystem = option.isRequiringLinearSystem();
    final boolean contractingAllConstantEdges = option.isContractingAllConstantEdges();
    final boolean requiringReachableSubSystem = option.isRequiringReachableSubSystem();
    final boolean requiringDescriptor = option.isRequiringDescriptor();
    final boolean requiringPrimitiveExpression = option.isRequiringPrimitiveExpression();

    matrix.setInputNodes(inputNodes);
    matrix.setOutputNodes(outputNodes);
    matrix.setSourceNodes(sourceNodes);
    matrix.setSinkNodes(sinkNodes);
    matrix.setInputPortTags(inputPortTags);
    matrix.setOutputPortTags(outputPortTags);
    matrix.setRequiringLinearSystem(requiringLinearSystem);
    matrix.setRequiringDescriptor(requiringDescriptor);
    matrix.setRequiringPrimitiveExpression(requiringPrimitiveExpression);

    AdjacencyMatrix<RS,RM,CS,CM> allSystem = matrix.expandAllBlockSystem();

    if (requiringLinearSystem) {
      allSystem = allSystem.getLinearSystemFromInputToOutput(requiringReachableSubSystem);
    }

    AdjacencyMatrix<RS,RM,CS,CM> contractedSystem = allSystem;

    do {
      allSystem = contractedSystem;
      contractedSystem = allSystem.contractConstantEdge(true, true);
    } while (allSystem != contractedSystem);

    do {
      allSystem = contractedSystem;
      contractedSystem = allSystem.contractConstantEdge(false, true);
    } while (allSystem != contractedSystem);

    if (requiringLinearSystem || contractingAllConstantEdges) {
      do {
        allSystem = contractedSystem;
        contractedSystem = allSystem.contractConstantEdge(false, false);
      } while (allSystem != contractedSystem);
    }

    allSystem.setupUndefinedMultiplexer();
    allSystem.setupUndefinedDeMultiplexer();

    this.adjacencyMatrix = allSystem;
    this.blockSystem = this.adjacencyMatrix.getBlockSystem();

    if (requiringLinearSystem || contractingAllConstantEdges) {
      this.blockSystem.setZeroSizeToUnDefinedInputPortOutputPort();
    }
  }

  /**
   * SourceブロックからSinkブロックまでのパスに存在する(シミュレーション計算用)システムを返します。
   * 
   * @return SourceブロックからSinkブロックまでのパスに存在する(シミュレーション計算用)システム
   */
  public SystemBuilder<RS,RM,CS,CM> getSystemForSimulation() {
    final SystemBuilder<RS,RM,CS,CM> selectedSystem = new SystemBuilder<>(this.sunit);
    selectedSystem.adjacencyMatrix = this.adjacencyMatrix.getSystemBetweenSourceAndSink();
    selectedSystem.blockSystem = selectedSystem.adjacencyMatrix.getBlockSystem();
    return selectedSystem;
  }

  /**
   * SISOシステムを隣接行列に設定します。
   * 
   * @param systemMatrix 隣接行列
   * @param matrix 隣接関係を保持する文字列行列
   * @param inputPortSize 入力ノードの数
   */
  private void setupSiSoSystem(final SystemOperator<RS,RM,CS,CM>[][] systemMatrix, final AdjacencyStringMatrix<RS,RM,CS,CM> matrix, final int inputPortSize) {
    final int inputPortOutputPortLines = 2;
    final int innerSize = matrix.size() - inputPortOutputPortLines;

    for (int row = 2; row <= innerSize + 1; row++) {
      for (int column = 2; column <= innerSize + 1; column++) {
        final String weight = matrix.getWeightOfEdge(row, column);
        final int systemRow = row - 2 + inputPortSize;
        final int systemColumn = column - 2 + inputPortSize;

        if (weight == null || weight.equals("")) { //$NON-NLS-1$
          systemMatrix[systemRow][systemColumn] = ZeroSystem.getInstance(this.sunit);
        } else if (weight.equals("P")) { //$NON-NLS-1$
          final int degree = Math.max(matrix.getNodeDegree(row), matrix.getNodeDegree(column));
          systemMatrix[systemRow][systemColumn] = new UnitSystem<>(degree, this.sunit);
        } else if (weight.equals("N")) { //$NON-NLS-1$
          final int degree = Math.max(matrix.getNodeDegree(row), matrix.getNodeDegree(column));
          systemMatrix[systemRow][systemColumn] = new NegativeUnitSystem<>(degree, this.sunit);
        } else if (weight.startsWith("G")) { //$NON-NLS-1$
          systemMatrix[systemRow][systemColumn] = matrix.getControlSystem(weight).getSystemOperator();
        } else {
          continue;
        }
        systemMatrix[systemRow][systemColumn].setID("" + systemRow + "," + systemColumn); //$NON-NLS-1$ //$NON-NLS-2$
      }
    }
  }

  /**
   * MIMOシステムを隣接行列に設定します。
   * 
   * @param systemMatrix 隣接行列
   * @param matrix 隣接関係を保持する文字列行列
   * @param inputPortSize 入力ノードの数
   */
  private void setupMiMoSystem(final SystemOperator<RS,RM,CS,CM>[][] systemMatrix, final AdjacencyStringMatrix<RS,RM,CS,CM> matrix, final int inputPortSize) {
    final int inputPortOutputPortLines = 2;
    final int innerSize = matrix.size() - inputPortOutputPortLines;

    for (int row = 2; row <= innerSize + 1; row++) {
      for (int column = 2; column <= innerSize + 1; column++) {
        final String weight = matrix.getWeightOfEdge(row, column);

        if (weight.startsWith("S") == false) { //$NON-NLS-1$
          continue;
        }

        final int systemRow = row - 2 + inputPortSize;
        final int systemColumn = column - 2 + inputPortSize;

        if (column <= innerSize) {
          final String rightWeight = matrix.getWeightOfEdge(row, column + 1);
          if (weight.equals(rightWeight)) {
            continue;
          }
        }

        if (3 <= row) {
          final String upperWeight = matrix.getWeightOfEdge(row - 1, column);
          if (weight.equals(upperWeight)) {
            continue;
          }
        }

        SystemOperator<RS,RM,CS,CM> system = matrix.getControlSystem(weight).getSystemOperator();
        systemMatrix[systemRow][systemColumn] = system;
        systemMatrix[systemRow][systemColumn].setID("" + systemRow + "," + systemColumn); //$NON-NLS-1$ //$NON-NLS-2$
      }
    }
  }

  /**
   * OutputポートとSinkシステムを隣接行列に設定します。
   * 
   * @param systemMatrix 隣接行列
   * @param matrix 隣接関係を保持する文字列行列
   * @param outputSinkNodes 出力ポートとSinkのノードの番号のリスト(番号は1から始まります)
   * @param inputSourceSize 入力ポートとSourceの合計数
   * @return 出力ノードの(隣接行列内での)番号のリスト(番号は1から始まります)
   */
  @SuppressWarnings("boxing")
  private List<List<Integer>> setupOutputSink(final SystemOperator<RS,RM,CS,CM>[][] systemMatrix, final AdjacencyStringMatrix<RS,RM,CS,CM> matrix, final List<Integer> outputSinkNodes, final int inputSourceSize) {
    final int inputPortOutputPortLines = 2;
    final int innerSize = matrix.size() - inputPortOutputPortLines;

    final List<Integer> outputNodes = new ArrayList<>();
    final List<Integer> sinkNodes = new ArrayList<>();

    int systemColumn2 = innerSize + inputSourceSize;
    for (final int outputSinkNode : outputSinkNodes) {
      final String weight = matrix.getWeightOfEdge(outputSinkNode, matrix.size());
      if (weight == null || weight.equals("")) { //$NON-NLS-1$
        throw new IllegalArgumentException(Messages.getString("ControlSystem.13")); //$NON-NLS-1$
      }

      final int systemRow2 = outputSinkNode - 2 + inputSourceSize;
      final SystemOperator<RS,RM,CS,CM> system = matrix.getControlSystem(weight).getSystemOperator();

      if (system instanceof OutputPort) {
        outputNodes.add(systemColumn2 + 1);

        final int degree = matrix.getNodeDegree(outputSinkNode);
        if (degree < 0) {
          systemMatrix[systemRow2][systemColumn2] = new UnitSystem<>(-1, this.sunit);
        } else {
          systemMatrix[systemRow2][systemColumn2] = new UnitSystem<>(degree, this.sunit);
        }
        ((UnitSystem<RS,RM,CS,CM>)systemMatrix[systemRow2][systemColumn2]).setTag(((OutputPort<RS,RM,CS,CM>)system).getTag());
      } else {
        sinkNodes.add(systemColumn2 + 1);

        systemMatrix[systemRow2][systemColumn2] = system;
      }

      systemMatrix[systemRow2][systemColumn2].setID("" + systemRow2 + "," + systemColumn2); //$NON-NLS-1$ //$NON-NLS-2$

      systemColumn2++;
    }

    List<List<Integer>> outputSink = new ArrayList<>();
    outputSink.add(outputNodes);
    outputSink.add(sinkNodes);

    return outputSink;
  }

  /**
   * 出力ポートのノード番号(番号は1から始まります)とタグのマップを返します。
   * 
   * @param matrix 隣接関係を保持する文字列行列
   * @param outputSinkNodes 出力ポートとSinkブロックのノードの番号のリスト(番号は1から始まります)
   * @param inputSourceSize 入力ポートとSourceの合計数
   * @return 出力ポートのノード番号(番号は1から始まります)とタグのマップ
   */
  @SuppressWarnings("boxing")
  private Map<Integer, String> getOutputPortTags(final AdjacencyStringMatrix<RS,RM,CS,CM> matrix, final List<Integer> outputSinkNodes, final int inputSourceSize) {
    final Map<Integer, String> outputPortTags = new TreeMap<>();

    final int inputPortOutputPortLines = 2;
    final int innerSize = matrix.size() - inputPortOutputPortLines;

    int systemColumn2 = innerSize + inputSourceSize;
    for (final int outputSinkNode : outputSinkNodes) {
      final String weight = matrix.getWeightOfEdge(outputSinkNode, matrix.size());

      if (weight == null || weight.equals("")) { //$NON-NLS-1$
        throw new IllegalArgumentException(Messages.getString("ControlSystem.13")); //$NON-NLS-1$
      }

      final SystemOperator<RS,RM,CS,CM> system = matrix.getControlSystem(weight).getSystemOperator();

      if (system instanceof OutputPort) {
        final String tag = ((OutputPort<RS,RM,CS,CM>)system).getTag();
        outputPortTags.put(systemColumn2 + 1, tag);
      }

      systemColumn2++;
    }
    return outputPortTags;
  }

  /**
   * InputポートとSourceシステムを隣接行列に設定します。
   * 
   * @param systemMatrix 隣接行列
   * @param matrix 隣接関係を保持する文字列行列
   * @param inputSourceNodes 入力ポートとSourceのノード番号のリスト(番号は1から始まります)
   * @return 入力ノードの(隣接行列内での)番号のリスト(番号は1から始まります)
   */
  @SuppressWarnings("boxing")
  private List<List<Integer>> setupInputSource(final SystemOperator<RS,RM,CS,CM>[][] systemMatrix, final AdjacencyStringMatrix<RS,RM,CS,CM> matrix, final List<Integer> inputSourceNodes) {
    final int inputSourceSize = inputSourceNodes.size();
    final List<Integer> inputNodes = new ArrayList<>();
    final List<Integer> sourceNodes = new ArrayList<>();

    int systemRow1 = 0;
    for (final int inputSourceNode : inputSourceNodes) {
      final String weight = matrix.getWeightOfEdge(1, inputSourceNode);
      if (weight == null || weight.equals("")) { //$NON-NLS-1$
        throw new IllegalArgumentException(Messages.getString("ControlSystem.18")); //$NON-NLS-1$
      }

      final int systemColumn1 = inputSourceNode - 2 + inputSourceSize;
      final SystemOperator<RS,RM,CS,CM> system = matrix.getControlSystem(weight).getSystemOperator();

      if (system instanceof InputPort) {
        inputNodes.add(systemRow1 + 1);
        //inputNodes.add(systemColumn1+1);

        final int degree = matrix.getNodeDegree(inputSourceNode);
        if (degree < 0) {
          systemMatrix[systemRow1][systemColumn1] = new UnitSystem<>(-1, this.sunit);
        } else {
          systemMatrix[systemRow1][systemColumn1] = new UnitSystem<>(degree, this.sunit);
        }
        ((UnitSystem<RS,RM,CS,CM>)systemMatrix[systemRow1][systemColumn1]).setTag(((InputPort<RS,RM,CS,CM>)system).getTag());
      } else {
        sourceNodes.add(systemRow1 + 1);
        //sourceNodes.add(systemColumn1+1);

        systemMatrix[systemRow1][systemColumn1] = system;
      }

      systemMatrix[systemRow1][systemColumn1].setID("" + systemRow1 + "," + systemColumn1); //$NON-NLS-1$ //$NON-NLS-2$

      systemRow1++;
    }

    List<List<Integer>> inputSource = new ArrayList<>();
    inputSource.add(inputNodes);
    inputSource.add(sourceNodes);

    return inputSource;
  }

  /**
   * 入力ポートのノード番号(番号は1から始まります)とタグのマップを返します。
   * 
   * @param matrix 隣接関係を保持する文字列行列
   * @param inputSourceNodes 入力ノードの番号のリスト(番号は1から始まります)
   * @return 入力ポートのノード番号(番号は1から始まります)とタグのマップ
   */
  @SuppressWarnings("boxing")
  private Map<Integer, String> getInputPortTags(final AdjacencyStringMatrix<RS,RM,CS,CM> matrix, final List<Integer> inputSourceNodes) {
    //final int inputSourceSize = inputSourceNodes.size();
    final Map<Integer, String> inputPortTags = new TreeMap<>();

    int systemRow1 = 0;
    for (final int inputSourceNode : inputSourceNodes) {
      final String weight = matrix.getWeightOfEdge(1, inputSourceNode);

      if (weight == null || weight.equals("")) { //$NON-NLS-1$
        throw new IllegalArgumentException(Messages.getString("ControlSystem.18")); //$NON-NLS-1$
      }

      final SystemOperator<RS,RM,CS,CM> system = matrix.getControlSystem(weight).getSystemOperator();

      if (system instanceof InputPort) {
        final String tag = ((InputPort<RS,RM,CS,CM>)system).getTag();
        inputPortTags.put(systemRow1 + 1, tag);

        //final int systemColumn1 = inputSourceNode - 2 + inputSourceSize;
        //inputPortTags.put(systemColumn1+1, tag);
      }

      systemRow1++;
    }
    return inputPortTags;
  }

  /**
   * 多重器を隣接行列に設定します。
   * 
   * @param systemMatrix 隣接行列
   * @param matrix 隣接関係を保持する文字列行列
   * @param inputPortSize 入力ノードの数
   */
  @SuppressWarnings("boxing")
  private void setupMultiplexer(final SystemOperator<RS,RM,CS,CM>[][] systemMatrix, final AdjacencyStringMatrix<RS,RM,CS,CM> matrix, final int inputPortSize) {
    final int inputPortOutputPortLines = 2;
    final int innerSize = matrix.size() - inputPortOutputPortLines;

    int multiplexerInputPortSize = 0;

    for (int column = 2; column <= innerSize + 1; column++) {
      final Map<Integer, Integer> muxInputs = new TreeMap<>();
      for (int row = 2; row <= innerSize + 1; row++) {
        // 隣接関係を保持する文字列行列から値(文字列)を取り出す.
        final String weight = matrix.getWeightOfEdge(row, column);

        if (weight.startsWith("M") == false) { //$NON-NLS-1$
          continue;
        }

        muxInputs.put(Integer.parseInt(weight.substring(1, weight.indexOf("/"))), matrix.getNodeDegree(row)); //$NON-NLS-1$
        multiplexerInputPortSize = Integer.parseInt(weight.substring(weight.indexOf("/") + 1)); //$NON-NLS-1$
      }

      if (muxInputs.size() == 0) {
        continue;
      }

      // 接続元のない入力ポートがある場合、信号の大きさを0とする。
      for (int i = 1; i <= multiplexerInputPortSize; i++) {
        if (muxInputs.containsKey(i)) {
          continue;
        }
        muxInputs.put(i, 0);
      }

      // 出力ポートのノードの次数が未定の場合
      if (matrix.getNodeDegree(column) == -1) {
        int outputSize = 0;
        boolean allInputSizeDefined = true;
        for (int inputSize : muxInputs.values()) {
          if (inputSize == -1) {
            allInputSizeDefined = false;
            break;
          }
          outputSize += inputSize;
        }
        if (allInputSizeDefined) {
          matrix.setNodeDegree(column, outputSize);
        }
      }

      setupUnconnectedPort(matrix, muxInputs, multiplexerInputPortSize, column);

      final List<Integer> inputSizes = new ArrayList<>(muxInputs.values());

      MultiplexerGroup<RS,RM,CS,CM> group = null;

      for (int row = 2; row <= innerSize + 1; row++) {
        // 隣接関係を保持する文字列行列から値(文字列)を取り出す.
        final String weight = matrix.getWeightOfEdge(row, column);

        if (weight.startsWith("M") == false) { //$NON-NLS-1$
          continue;
        }

        final int inputNumber = Integer.parseInt(weight.substring(1, weight.indexOf("/"))); //$NON-NLS-1$

        final int systemRow = row - 2 + inputPortSize;
        final int systemColumn = column - 2 + inputPortSize;

        if (group == null) {
          group = new MultiplexerGroup<>();
        }

        systemMatrix[systemRow][systemColumn] = new Multiplexer<>(inputSizes, inputNumber, this.sunit);
        systemMatrix[systemRow][systemColumn].setID("" + systemRow + "," + systemColumn); //$NON-NLS-1$ //$NON-NLS-2$
        ((Multiplexer<RS,RM,CS,CM>)systemMatrix[systemRow][systemColumn]).setGroup(group);
        group.add((Multiplexer<RS,RM,CS,CM>)systemMatrix[systemRow][systemColumn]);
        if (matrix.getNodeDegree(column) != -1) {
          systemMatrix[systemRow][systemColumn].setOutputSize(matrix.getNodeDegree(column));
        }

        matrix.setNodeDegree(row, inputSizes.get(inputNumber - 1));
      }
    }
  }

  /**
   * 分離器を隣接行列に設定します。
   * 
   * @param systemMatrix 隣接行列
   * @param matrix 隣接関係を保持する文字列行列
   * @param inputPortSize 入力ノードの数
   */
  @SuppressWarnings("boxing")
  private void setupDeMultiplexer(final SystemOperator<RS,RM,CS,CM>[][] systemMatrix, final AdjacencyStringMatrix<RS,RM,CS,CM> matrix, final int inputPortSize) {
    final int inputPortOutputPortLines = 2;
    final int innerSize = matrix.size() - inputPortOutputPortLines;

    int deMultiplexerOutputPortSize = 0;

    for (int row = 2; row <= innerSize + 1; row++) {
      final Map<Integer, Integer> deMuxOutputs = new TreeMap<>();
      for (int column = 2; column <= innerSize + 1; column++) {
        // 隣接関係を保持する文字列行列から値(文字列)を取り出す.
        final String weight = matrix.getWeightOfEdge(row, column);

        if (weight.startsWith("D") == false) { //$NON-NLS-1$
          continue;
        }

        deMuxOutputs.put(Integer.parseInt(weight.substring(1, weight.indexOf("/"))), matrix.getNodeDegree(column)); //$NON-NLS-1$
        deMultiplexerOutputPortSize = Integer.parseInt(weight.substring(weight.indexOf("/") + 1)); //$NON-NLS-1$
      }

      if (deMuxOutputs.size() == 0) {
        continue;
      }

      // 接続先のない出力ポート、信号の大きさを1とする。
      for (int i = 1; i <= deMultiplexerOutputPortSize; i++) {
        if (deMuxOutputs.containsKey(i)) {
          continue;
        }
        deMuxOutputs.put(i, 1);
      }

      // 入力ポートのノードの次数が未定の場合
      if (matrix.getNodeDegree(row) == -1) {
        int inputSize = 0;
        boolean allOutputSizeDefined = true;
        for (int outputSize : deMuxOutputs.values()) {
          if (outputSize == -1) {
            allOutputSizeDefined = false;
            break;
          }
          inputSize += outputSize;
        }
        if (allOutputSizeDefined) {
          matrix.setNodeDegree(row, inputSize);
        }
      }

      setupUnconnectedPort(matrix, deMuxOutputs, deMultiplexerOutputPortSize, row);

      final List<Integer> outputSizes = new ArrayList<>(deMuxOutputs.values());

      DeMultiplexerGroup<RS,RM,CS,CM> group = null;

      for (int column = 2; column <= innerSize + 1; column++) {
        // 隣接関係を保持する文字列行列から値(文字列)を取り出す.
        final String weight = matrix.getWeightOfEdge(row, column);

        if (weight.startsWith("D") == false) { //$NON-NLS-1$
          continue;
        }

        final int outputNumber = Integer.parseInt(weight.substring(1, weight.indexOf("/"))); //$NON-NLS-1$

        final int systemRow = row - 2 + inputPortSize;
        final int systemColumn = column - 2 + inputPortSize;

        if (group == null) {
          group = new DeMultiplexerGroup<>();
        }

        systemMatrix[systemRow][systemColumn] = new DeMultiplexer<>(outputSizes, outputNumber, this.sunit);
        systemMatrix[systemRow][systemColumn].setID("" + systemRow + "," + systemColumn); //$NON-NLS-1$ //$NON-NLS-2$
        ((DeMultiplexer<RS,RM,CS,CM>)systemMatrix[systemRow][systemColumn]).setGroup(group);
        group.add((DeMultiplexer<RS,RM,CS,CM>)systemMatrix[systemRow][systemColumn]);
        if (matrix.getNodeDegree(row) != -1) {
          systemMatrix[systemRow][systemColumn].setInputSize(matrix.getNodeDegree(row));
        }

        matrix.setNodeDegree(column, outputSizes.get(outputNumber - 1));
      }
    }
  }

  /**
   * 接続先のないポート、信号の数が未定のポートの信号を大きさを決定し、 <code>ports</code>に追加登録します。
   * 
   * @param matrix 隣接文字列行列
   * @param ports ポート番号とポートの大きさのマップ
   * @param portSize ポートの数
   * @param node 対象とする隣接行列のノード番号
   */
  @SuppressWarnings("boxing")
  private void setupUnconnectedPort(final AdjacencyStringMatrix<RS,RM,CS,CM> matrix, final Map<Integer, Integer> ports, int portSize, int node) {
    final int allSize = matrix.getNodeDegree(node);

    // 反対側のポートのノードの次数が未定の場合
    if (allSize == -1) {
      return;
    }

    int tentativeSize = 0;
    for (int size : ports.values()) {
      if (size == -1) {
        continue;
      }
      tentativeSize += size;
    }

    // 接続先のないポート、信号の数が未定のポート
    final List<Integer> undefinedSizePorts = new ArrayList<>();

    int definedPortSize = 0;

    // 接続先のないポート、信号の数が未定のポートがある場合、未決定分の信号の大きさを等分割する。
    for (int i = 1; i <= portSize; i++) {
      if (ports.containsKey(i) && ports.get(i) != -1) {
        definedPortSize += ports.get(i);
        continue;
      }
      undefinedSizePorts.add(i);
    }

    int noConnectionPortSize = 0;
    if (undefinedSizePorts.size() > 0) {
      noConnectionPortSize = (allSize - tentativeSize) / undefinedSizePorts.size();

      for (int port : undefinedSizePorts) {
        ports.put(port, noConnectionPortSize);
      }
    }

    for (int i = 0; i < allSize - definedPortSize - noConnectionPortSize * undefinedSizePorts.size(); i++) {
      ports.put(undefinedSizePorts.get(i), noConnectionPortSize + 1);
    }
  }

  /**
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object opponent) {
    if (opponent == this) {
      return true;
    }
    if (opponent == null) {
      return false;
    }
    if (opponent.getClass() != getClass()) {
      return false;
    }

    return this.adjacencyMatrix.equals(((SystemBuilder<RS,RM,CS,CM>)opponent).adjacencyMatrix);
  }

  /**
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    return this.blockSystem.hashCode();
  }

  /**
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    if (isDynamic()) {
      return Messages.getString("ControlSystem.27") + getInputSize() + Messages.getString("ControlSystem.28") + getOutputSize() + Messages.getString("ControlSystem.29") + getStateSize() + Messages.getString("ControlSystem.30"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
    }
    return Messages.getString("ControlSystem.31") + getInputSize() + Messages.getString("ControlSystem.32") + getOutputSize() + Messages.getString("ControlSystem.33"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
  }

  /**
   * 入力の数を返します。
   * 
   * @return 入力の数
   */
  public int getInputSize() {
    if (isSingleSystem()) {
      return (this.adjacencyMatrix.getElement(1, 2)).getInputSize();
    }
    return this.blockSystem.getInputSize();
  }

  /**
   * 入力の数を設定します。
   * 
   * @param inputSize 入力の数
   */
  public void setInputSize(final int inputSize) {
    if (isSingleSystem()) {
      (this.adjacencyMatrix.getElement(1, 2)).setInputSize(inputSize);
    }
    this.blockSystem.setInputSize(inputSize);
  }

  /**
   * 出力の数を返します。
   * 
   * @return 出力の数
   */
  public int getOutputSize() {
    if (isSingleSystem()) {
      return (this.adjacencyMatrix.getElement(1, 2)).getOutputSize();
    }
    return this.blockSystem.getOutputSize();
  }

  /**
   * 出力の数を設定します。
   * 
   * @param outputSize 出力の数
   */
  public void setOutputSize(final int outputSize) {
    if (isSingleSystem()) {
      (this.adjacencyMatrix.getElement(1, 2)).setOutputSize(outputSize);
    }
    this.blockSystem.setOutputSize(outputSize);
  }

  /**
   * 状態の数を返します。
   * 
   * @return 状態の数
   */
  public int getStateSize() {
    if (isSingleSystem()) {
      return (this.adjacencyMatrix.getElement(1, 2)).getStateSize();
    }
    return this.blockSystem.getStateSize();
  }

  /**
   * 動的システムであるか判定します。
   * 
   * @return 動的システムならばtrue、そうでなければfalse
   */
  public boolean isDynamic() {
    return this.blockSystem.isDynamic();
  }

  /**
   * 静的システムであるか判定します。
   * 
   * @return 静的システムならばtrue、そうでなければfalse
   */
  public boolean isStatic() {
    return this.blockSystem.isStatic();
  }

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

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

  /**
   * 全てのシステムのサンプリング周期が一致するシステムであるか判定します。
   * 
   * @return 全てのシステムのサンプリング周期が一致するシステムならばtrue、そうでなければfalse
   */
  public boolean isSingleRate() {
    if (!(this.blockSystem instanceof BlockSamplingSystem)) {
      return false;
    }

    return ((BlockSamplingSystem<RS,RM,CS,CM>)this.blockSystem).isSingleRate();
  }

  /**
   * 全てのシステムに共通するサンプリング周期を返します。
   * 
   * @return 全てのシステムに共通するサンプリング周期
   */
  public RS getSingleSamplingInterval() {
    if (isSingleRate() == false) {
      throw new RuntimeException(Messages.getString("ControlSystem.34")); //$NON-NLS-1$
    }

    return ((BlockSamplingSystem<RS,RM,CS,CM>)this.blockSystem).getSingleSamplingInterval();
  }

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

  /**
   * 線形システムであるか判定します。
   * 
   * @return 線形システムならばtrue、そうでなければfalse
   */
  public boolean isLinear() {
    return this.blockSystem.isLinear();
  }

  /**
   * 直達項がある(出力が入力に直接依存する)か判定します。
   * 
   * @return 直達項があれば(出力が入力に直接依存すれば)true、そうでなければfalse
   */
  public boolean hasDirectFeedthrough() {
    return this.blockSystem.hasDirectFeedthrough();
  }

  /**
   * 初期状態を設定します。
   * 
   * @param initialState 初期状態
   */
  public void setInitialState(final RM initialState) {
    if (isStatic()) {
      throw new UnsupportedOperationException(Messages.getString("ControlSystem.35")); //$NON-NLS-1$
    }

    if (this.blockSystem instanceof ContinuousDynamicSystem) {
      ((ContinuousDynamicSystem<RS,RM,CS,CM>)this.blockSystem).setInitialState(initialState);
    } else if (this.blockSystem instanceof DiscreteDynamicSystem) {
      ((DiscreteDynamicSystem<RS,RM,CS,CM>)this.blockSystem).setInitialState(initialState);
    } else {
      throw new UnsupportedOperationException(Messages.getString("ControlSystem.36")); //$NON-NLS-1$
    }
  }

  /**
   * 初期状態を返します。
   * 
   * @return 初期状態
   */
  public RM getInitialState() {
    if (isStatic()) {
      throw new UnsupportedOperationException(Messages.getString("ControlSystem.37")); //$NON-NLS-1$
    }

    if (this.blockSystem instanceof ContinuousDynamicSystem) {
      return ((ContinuousDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getInitialState();
    }
    if (this.blockSystem instanceof DiscreteDynamicSystem) {
      return ((DiscreteDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getInitialState();
    }
    throw new UnsupportedOperationException(Messages.getString("ControlSystem.38")); //$NON-NLS-1$
  }

  /**
   * 連続時間部分システムの初期状態を返します。
   * 
   * @return 連続時間部分システムの初期状態
   */
  public RM getContinuousInitialState() {
    if (isStatic()) {
      throw new UnsupportedOperationException(Messages.getString("ControlSystem.39")); //$NON-NLS-1$
    }

    if (this.blockSystem instanceof SampledDataDynamicSystem) {
      return ((SampledDataDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getContinuousInitialState();
    }
    if (this.blockSystem instanceof ContinuousDynamicSystem) {
      return ((ContinuousDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getInitialState();
    }
    throw new UnsupportedOperationException(Messages.getString("ControlSystem.40")); //$NON-NLS-1$
  }

  /**
   * 離散時間部分システムの初期状態を返します。
   * 
   * @return 離散時間部分システムの初期状態
   */
  public RM getDiscreteInitialState() {
    if (isStatic()) {
      throw new UnsupportedOperationException(Messages.getString("ControlSystem.41")); //$NON-NLS-1$
    }

    if (this.blockSystem instanceof SampledDataDynamicSystem) {
      return ((SampledDataDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getDiscreteInitialState();
    }
    if (this.blockSystem instanceof DiscreteDynamicSystem) {
      return ((DiscreteDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getInitialState();
    }
    throw new UnsupportedOperationException(Messages.getString("ControlSystem.42")); //$NON-NLS-1$
  }

  /**
   * 現在の状態を返します。
   * 
   * @return 現在の状態
   */
  public RM getState() {
    if (isStatic()) {
      throw new UnsupportedOperationException(Messages.getString("ControlSystem.43")); //$NON-NLS-1$
    }

    if (this.blockSystem instanceof ContinuousDynamicSystem) {
      return ((ContinuousDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getState();
    }
    if (this.blockSystem instanceof DiscreteDynamicSystem) {
      return ((DiscreteDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getState();
    }
    throw new UnsupportedOperationException(Messages.getString("ControlSystem.44")); //$NON-NLS-1$
  }

  /**
   * 連続時間部分システムの現在の状態を返します。
   * 
   * @return 連続時間部分システムの現在の状態
   */
  public RM getContinuousState() {
    if (isStatic()) {
      throw new UnsupportedOperationException(Messages.getString("ControlSystem.45")); //$NON-NLS-1$
    }

    if (this.blockSystem instanceof SampledDataDynamicSystem) {
      return ((SampledDataDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getContinuousState();
    }
    if (this.blockSystem instanceof ContinuousDynamicSystem) {
      return ((ContinuousDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getState();
    }
    throw new UnsupportedOperationException(Messages.getString("ControlSystem.46")); //$NON-NLS-1$
  }

  /**
   * 離散時間部分システムの現在の状態を返します。
   * 
   * @return 離散時間部分システムの現在の状態
   */
  public RM getDiscreteState() {
    if (isStatic()) {
      throw new UnsupportedOperationException(Messages.getString("ControlSystem.47")); //$NON-NLS-1$
    }

    if (this.blockSystem instanceof SampledDataDynamicSystem) {
      return ((SampledDataDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getDiscreteState();
    }
    if (this.blockSystem instanceof DiscreteDynamicSystem) {
      return ((DiscreteDynamicSystem<RS,RM,CS,CM>)this.blockSystem).getState();
    }
    throw new UnsupportedOperationException(Messages.getString("ControlSystem.48")); //$NON-NLS-1$
  }

  /**
   * システム<code>opponent</code>との差(符合が異なる並列結合)でできるシステムを返します。
   * 
   * @param opponent 結合するシステム(引くシステム)
   * @return 結合システム(this - opponent)
   */
  public SystemBuilder<RS,RM,CS,CM> subtract(final SystemBuilder<RS,RM,CS,CM> opponent) {
    // 隣接行列1
    final AdjacencyMatrix<RS,RM,CS,CM> matrix1 = this.adjacencyMatrix;
    final int size1 = matrix1.length();
    final int inputSize1 = this.getInputSize();
    final int outputSize1 = this.getOutputSize();

    // 隣接行列2
    final AdjacencyMatrix<RS,RM,CS,CM> matrix2 = opponent.adjacencyMatrix;
    final int size2 = matrix2.length();
    final int inputSize2 = opponent.getInputSize();
    final int outputSize2 = opponent.getOutputSize();

    // 新しい隣接行列
    final AdjacencyMatrix<RS,RM,CS,CM> matrix = new AdjacencyMatrix<>(size1 + size2 + 2, size1 + size2 + 2, this.sunit);

    matrix.setElement(1, 2, new UnitSystem<>(inputSize1, this.sunit));
    matrix.setElement(1, size1 + 2, new UnitSystem<>(inputSize2, this.sunit));
    matrix.setElement(size1 + 1, size1 + size2 + 2, new UnitSystem<>(outputSize1, this.sunit));
    matrix.setElement(size1 + size2 + 1, size1 + size2 + 2, new ConstantSystem<>(this.sunit.createUnitGrid(outputSize2, outputSize2).unaryMinus()));

    // 隣接行列1を代入
    matrix.setSubMatrix(2, 2 + size1 - 1, 2, 2 + size1 - 1, matrix1);

    // 隣接行列2を代入
    matrix.setSubMatrix(2 + size1, 2 + size1 + size2 - 1, 2 + size1, 2 + size1 + size2 - 1, matrix2);

    return (new SystemBuilder<>(matrix, this.sunit));
  }

  /**
   * システム<code>opponent</code>との積(直列結合)でできるシステムを返します。
   * 
   * <p> opponent --- this ---
   * 
   * @param opponent 結合するシステム(入力側に掛けるシステム)
   * @return 結合システム (this * opponent)
   */
  public SystemBuilder<RS,RM,CS,CM> multiply(final SystemBuilder<RS,RM,CS,CM> opponent) {
    if (this.isAutoSize() && this.getInputSize() == -1) {
      this.setInputSize(opponent.getOutputSize());
    }

    if (opponent.isAutoSize() && opponent.getOutputSize() == -1) {
      opponent.setOutputSize(this.getInputSize());
    }

    // SystemEquationMatrix1
    final AdjacencyMatrix<RS,RM,CS,CM> matrix1 = this.adjacencyMatrix;
    final int size1 = matrix1.length();

    // SystemEquationMatrix2
    final AdjacencyMatrix<RS,RM,CS,CM> matrix2 = opponent.adjacencyMatrix;
    final int size2 = matrix2.length();
    final int outputSize2 = opponent.getOutputSize();

    // 新しく作成されるSystemEquationMatrix
    final AdjacencyMatrix<RS,RM,CS,CM> matrix = new AdjacencyMatrix<>(size1 + size2, size1 + size2, this.sunit);

    // 単位行列を代入
    matrix.setElement(size2, size2 + 1, new UnitSystem<>(outputSize2, this.sunit));

    // matrix2を代入
    matrix.setSubMatrix(1, size2, 1, size2, matrix2);

    // matrix1を代入
    matrix.setSubMatrix(size2 + 1, size2 + size1, size2 + 1, size2 + size1, matrix1);

    return (new SystemBuilder<>(matrix, this.sunit));
  }

  /**
   * 符合を逆にしてできるシステムを返します。
   * 
   * @return 符合を逆にしてできるシステム
   */
  public SystemBuilder<RS,RM,CS,CM> unaryMinus() {
    // SystemEquationMatrix取得
    final AdjacencyMatrix<RS,RM,CS,CM> matrix1 = this.adjacencyMatrix;
    final int size1 = matrix1.length();
    final int outputSize1 = this.getOutputSize();

    final AdjacencyMatrix<RS,RM,CS,CM> newMatrix = new AdjacencyMatrix<>(size1 + 2, size1 + 2, this.sunit);

    // 元の隣接行列を代入
    newMatrix.setSubMatrix(1, size1, 1, size1, matrix1);

    // 単位行列を代入
    newMatrix.setElement(size1, size1 + 1, new UnitSystem<>(outputSize1, this.sunit));

    // 出力を負にするための単位行列を代入
    newMatrix.setElement(size1 + 1, size1 + 2, new ConstantSystem<>(this.sunit.createUnitGrid(outputSize1, outputSize1).unaryMinus()));

    return new SystemBuilder<>(newMatrix, this.sunit);
  }

  /**
   * 単一(ネガティブ)フィードバック結合でできるシステムを返します。
   * 
   * @return 閉ループシステム
   */
  public SystemBuilder<RS,RM,CS,CM> unityFeedback() {
    return unityFeedback(true);
  }

  /**
   * 単一フィードバック結合でできるシステムを返します。
   * 
   * @param negative true: ネガティブフィードバック, false: ポジティブフィードバック
   * @return 閉ループシステム
   */
  public SystemBuilder<RS,RM,CS,CM> unityFeedback(final boolean negative) {
    if (this.isAutoSize() && this.getInputSize() == -1) {
      this.setInputSize(getOutputSize());
    }
    if (this.isAutoSize() && this.getOutputSize() == -1) {
      this.setOutputSize(getInputSize());
    }

    // SystemEquationMatrix取得
    final AdjacencyMatrix<RS,RM,CS,CM> matrix1 = this.adjacencyMatrix;
    final int size1 = matrix1.length();
    final int inputSize1 = this.getInputSize();
    final int outputSize1 = this.getOutputSize();

    final AdjacencyMatrix<RS,RM,CS,CM> newMatrix = new AdjacencyMatrix<>(size1 + 3, size1 + 3, this.sunit);

    // 元の隣接行列を代入
    newMatrix.setSubMatrix(2, 2 + size1 - 1, 2, 2 + size1 - 1, matrix1);

    // 単位行列を代入
    newMatrix.setElement(1, 2, new UnitSystem<>(inputSize1, this.sunit));
    newMatrix.setElement(size1 + 1, size1 + 2, new UnitSystem<>(outputSize1, this.sunit));
    newMatrix.setElement(size1 + 1, size1 + 3, new UnitSystem<>(outputSize1, this.sunit));

    // 単位行列又は負の単位行列を代入
    if (negative) {
      newMatrix.setElement(2 + size1, 2, new ConstantSystem<>(this.sunit.createUnitGrid(inputSize1, inputSize1).unaryMinus()));
    } else {
      newMatrix.setElement(2 + size1, 2, new ConstantSystem<>(this.sunit.createUnitGrid(inputSize1, inputSize1)));
    }

    return new SystemBuilder<>(newMatrix, this.sunit);
  }

  /**
   * (ネガティブ)フィードバック結合でできるシステムを返します。
   * 
   * @param feedbackElement フィードバック結合するシステム
   * @return 閉ループシステム
   */
  public SystemBuilder<RS,RM,CS,CM> feedback(final SystemBuilder<RS,RM,CS,CM> feedbackElement) {
    return feedback(feedbackElement, true);
  }

  /**
   * フィードバック結合でできるシステムを返します。
   * 
   * @param feedbackElement フィードバック結合するシステム
   * @param negative true: ネガティブフィードバック, false: ポジティブフィードバック
   * @return 閉ループシステム
   */
  public SystemBuilder<RS,RM,CS,CM> feedback(final SystemBuilder<RS,RM,CS,CM> feedbackElement, final boolean negative) {
    if (this.isAutoSize() && this.getInputSize() == -1) {
      this.setInputSize(feedbackElement.getOutputSize());
    }
    if (this.isAutoSize() && this.getOutputSize() == -1) {
      this.setOutputSize(feedbackElement.getInputSize());
    }
    if (feedbackElement.isAutoSize() && feedbackElement.getInputSize() == -1) {
      feedbackElement.setInputSize(this.getOutputSize());
    }
    if (feedbackElement.isAutoSize() && feedbackElement.getOutputSize() == -1) {
      feedbackElement.setOutputSize(this.getInputSize());
    }

    final AdjacencyMatrix<RS,RM,CS,CM> matrix1 = this.adjacencyMatrix;
    final int size1 = matrix1.length();
    final int inputSize1 = this.getInputSize();
    final int outputSize1 = this.getOutputSize();

    final AdjacencyMatrix<RS,RM,CS,CM> matrix2 = feedbackElement.adjacencyMatrix;
    int size2 = matrix2.length();

    // フィードバック結合によって生成される行列
    final AdjacencyMatrix<RS,RM,CS,CM> matrix = new AdjacencyMatrix<>(size1 + size2 + 2, size1 + size2 + 2, this.sunit);

    matrix.setElement(1, 2, new UnitSystem<>(inputSize1, this.sunit));
    matrix.setElement(size1 + 1, size1 + 2, new UnitSystem<>(outputSize1, this.sunit));
    matrix.setElement(size1 + 1, size1 + size2 + 2, new UnitSystem<>(outputSize1, this.sunit));

    if (negative) {
      matrix.setElement(size1 + size2 + 1, 2, new ConstantSystem<>(this.sunit.createUnitGrid(inputSize1, inputSize1).unaryMinus()));
    } else {
      matrix.setElement(size1 + size2 + 1, 2, new UnitSystem<>(inputSize1, this.sunit));
    }

    // フィードフォワード要素の隣接行列を代入
    matrix.setSubMatrix(2, 2 + size1 - 1, 2, 2 + size1 - 1, matrix1);

    // フォードバック要素の隣接行列を代入
    matrix.setSubMatrix(2 + size1, 2 + size1 + size2 - 1, 2 + size1, 2 + size1 + size2 - 1, matrix2);

    return (new SystemBuilder<>(matrix, this.sunit));
  }

  /**
   * システムを表す常微分方程式システム(状態方程式、入出力方程式)を返します。
   * 
   * @return システムを表す常微分方程式システム(状態方程式、入出力方程式)
   */
  public ExplicitDifferentialSystem<RS,RM,CS,CM> getDifferentialSystem() {
    if (isStatic()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.49")); //$NON-NLS-1$
    }

    if (!isContinuous()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.50")); //$NON-NLS-1$
    }

    return (ExplicitDifferentialSystem<RS,RM,CS,CM>)this.blockSystem;
  }

  /**
   * システムを表す連続時間代数方程式システム(入出力方程式)を返します。
   * 
   * @return システムを表す連続時間代数方程式システム(入出力方程式)
   */
  public ContinuousAlgebraicSystem<RS,RM,CS,CM> getContinuousAlgebraicEquation() {
    if (isDynamic()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.51")); //$NON-NLS-1$
    }

    if (!isContinuous()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.52")); //$NON-NLS-1$
    }

    return (ContinuousAlgebraicSystem<RS,RM,CS,CM>)this.blockSystem;
  }

  /**
   * システムを表す連続・離散時間代数方程式システム(入出力方程式)を返します。
   * 
   * @return システムを表す連続・離散時間代数方程式システム(入出力方程式)
   */
  public ContinuousDiscreteAlgebraicSystem<RS,RM,CS,CM> getContinuousDiscreteAlgebraicEquation() {
    if (isDynamic()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.53")); //$NON-NLS-1$
    }

    if (!isSampledData()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.54")); //$NON-NLS-1$
    }

    return (ContinuousDiscreteAlgebraicSystem<RS,RM,CS,CM>)this.blockSystem;
  }

  /**
   * システムを表す離散時間代数方程式システム(入出力方程式)を返します。
   * 
   * @return システムを表す離散時間代数方程式システム(入出力方程式)
   */
  public DiscreteAlgebraicSystem<RS,RM,CS,CM> getDiscreteAlgebraicEquation() {
    if (isDynamic()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.55")); //$NON-NLS-1$
    }

    if (!isDiscrete()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.56")); //$NON-NLS-1$
    }

    return (DiscreteAlgebraicSystem<RS,RM,CS,CM>)this.blockSystem;
  }

  /**
   * システムを表す差分方程式システム(状態方程式、入出力方程式)を返します。
   * 
   * @return システムを表す差分方程式システム(状態方程式、入出力方程式)
   */
  public DifferenceSystem<RS,RM,CS,CM> getDifferenceSystem() {
    if (isStatic()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.57")); //$NON-NLS-1$
    }

    if (!isDiscrete()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.58")); //$NON-NLS-1$
    }

    return (DifferenceSystem<RS,RM,CS,CM>)this.blockSystem;
  }

  /**
   * システムを表す微分差分方程式システム(状態方程式、入出力方程式)を返します。
   * 
   * @return システムを表す微分差分方程式システム(状態方程式、入出力方程式)
   */
  public DifferentialDifferenceSystem<RS,RM,CS,CM> getDifferentialDifferenceSystem() {
    if (isStatic()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.59")); //$NON-NLS-1$
    }

    if (!isSampledData()) {
      throw new IllegalStateException(Messages.getString("ControlSystem.60")); //$NON-NLS-1$
    }

    return (DifferentialDifferenceSystem<RS,RM,CS,CM>)this.blockSystem;
  }

  /**
   * 線形システムの式を返します。
   * 
   * @param requiringReachableSubSystem 可到達なサブシステム(入力ノードから出力ノードまでのパスに対応するシステム)を求めるならばtrue
   * @return 線形システムの式
   */
  public LinearSystem<RS,RM,CS,CM> getLinearSystem(final boolean requiringReachableSubSystem) {
    return this.adjacencyMatrix.getLinearSystem(requiringReachableSubSystem);
  }

  /**
   * 線形システムの式を返します。
   * 
   * @param requiringReachableSubSystem 可到達なサブシステム(入力ノードから出力ノードまでのパスに対応するシステム)を求めるならばtrue
   * @return 線形システムの式
   */
  public AdjacencyConstantMatrix<RS,RM,CS,CM> getSymbolicTransferFunction(final boolean requiringReachableSubSystem) {
    return this.adjacencyMatrix.getSymbolicTransferFunction(requiringReachableSubSystem);
  }

  /**
   * 線形システムの式を返します。
   * 
   * @param requiringReachableSubSystem 可到達なサブシステム(入力ノードから出力ノードまでのパスに対応するシステム)を求めるならばtrue
   * @param processor プロセッサ
   * @return 線形システムの式
   */
  public LinearSystem<RS,RM,CS,CM> getLinearSystemForMaxima(final boolean requiringReachableSubSystem, final ReversePolishNotationProcessor<RS,RM> processor) {
    return this.adjacencyMatrix.getLinearSystemForMaxima(requiringReachableSubSystem, processor);
  }

  /**
   * 線形システムの式(数式も含む)を返します。
   * 
   * @param requiringReachableSubSystem 可到達なサブシステム(入力ノードから出力ノードまでのパスに対応するシステム)を求めるならばtrue
   * @param processor 解釈するプロセッサー
   * @return 線形システムの式(数式も含む)
   */
  public LinearSystem<RS,RM,CS,CM> getLinearSystemWithExpression(final boolean requiringReachableSubSystem, final ReversePolishNotationProcessor<RS,RM> processor) {
    return this.adjacencyMatrix.getLinearSystemByProcessor(requiringReachableSubSystem, processor);
  }

  /**
   * 入力ノードから出力ノードまでの隣接行列を返します。
   * 
   * @param requiringReachableSubSystem 可到達なサブシステムを求めるならばtrue
   * @return 入力ノードから出力ノードまでの隣接行列
   */
  public AdjacencyMatrix<RS,RM,CS,CM> getAllSystem(final boolean requiringReachableSubSystem) {
    return this.adjacencyMatrix.getLinearSystemFromInputToOutput(requiringReachableSubSystem);
  }

  /**
   * システム<code>opponent</code>との和(並列結合)でできるシステムを返します。
   * 
   * @param opponent 結合するシステム(加えるシステム)
   * @return 結合システム
   */
  public SystemBuilder<RS,RM,CS,CM> add(final SystemBuilder<RS,RM,CS,CM> opponent) {
    if (this.isAutoSize() && this.getInputSize() == -1) {
      setInputSize(opponent.getInputSize());
    }
    if (this.isAutoSize() && this.getOutputSize() == -1) {
      setOutputSize(opponent.getOutputSize());
    }
    if (opponent.isAutoSize() && opponent.getInputSize() == -1) {
      opponent.setInputSize(getInputSize());
    }
    if (opponent.isAutoSize() && opponent.getOutputSize() == -1) {
      opponent.setOutputSize(getOutputSize());
    }

    final AdjacencyMatrix<RS,RM,CS,CM> matrix1 = this.adjacencyMatrix;
    final int size1 = matrix1.length();
    int inputSize1 = this.getInputSize();
    int outputSize1 = this.getOutputSize();

    final AdjacencyMatrix<RS,RM,CS,CM> matrix2 = opponent.adjacencyMatrix;
    final int size2 = matrix2.length();
    int inputSize2 = opponent.getInputSize();
    int outputSize2 = opponent.getOutputSize();

    // 新しい隣接行列
    final AdjacencyMatrix<RS,RM,CS,CM> matrix = new AdjacencyMatrix<>(size1 + size2 + 2, size1 + size2 + 2, this.sunit);

    matrix.setElement(1, 2, new UnitSystem<>(inputSize1, this.sunit));
    matrix.setElement(1, 2 + size1, new UnitSystem<>(inputSize2, this.sunit));
    matrix.setElement(1 + size1, 2 + size1 + size2, new UnitSystem<>(outputSize1, this.sunit));
    matrix.setElement(1 + size1 + size2, 2 + size1 + size2, new UnitSystem<>(outputSize2, this.sunit));

    // 隣接行列1を代入
    matrix.setSubMatrix(2, 2 + size1 - 1, 2, 2 + size1 - 1, matrix1);

    // 隣接行列2を代入
    matrix.setSubMatrix(2 + size1, 2 + size1 + size2 - 1, 2 + size1, 2 + size1 + size2 - 1, matrix2);

    return (new SystemBuilder<>(matrix, this.sunit));
  }

  /**
   * システムオペレータを返します。
   * 
   * @return システムオペレータ
   */
  public SystemOperator<RS,RM,CS,CM> getSystemOperator() {
    if (isSingleSystem()) {
      return this.adjacencyMatrix.getElement(1, 2);
    }
    return this.blockSystem;
  }

  /**
   * 入力器のリストを返します。
   * 
   * @return 入力器のリスト
   */
  public List<Importer> getImporters() {
    return this.adjacencyMatrix.getImporters();
  }

  /**
   * 出力器のリストを返します。
   * 
   * @return 出力器のリスト
   */
  public List<Exporter> getExporters() {
    return this.adjacencyMatrix.getExporters();
  }

  /**
   * 単一のシステム(結合システムでない)であるか判定します。
   * 
   * @return 単一システムならばtrue、そうでなければfalse
   */
  public boolean isSingleSystem() {
    return this.blockSystem.isSingleSystem();
  }

  /**
   * システムの隣接行列を返します。
   * 
   * @return システムの隣接行列
   */
  public AdjacencyMatrix<RS,RM,CS,CM> getAdjacencyMatrix() {
    return this.adjacencyMatrix;
  }

  /**
   * 自動的に入出力数を設定するか判定します。
   * 
   * @return 自動的に入出力数を設定するならばtrue、そうでなければfalse
   */
  public boolean isAutoSize() {
    return this.blockSystem.isAutoSize();
  }

  /**
   * 自動的に入出力数を設定するか設定します。
   * 
   * @param autoSize 自動的に入出力数を設定するならばtrue、そうでなければfalse
   */
  public void setAutoSize(boolean autoSize) {
    this.blockSystem.setAutoSize(autoSize);
  }

  /**
   * 自動的に入出力数を設定するシステムの入出力数をリセットします。
   */
  public void resetAutoSize() {
    if (isAutoSize() == false) {
      return;
    }

    this.blockSystem.resetAutoSize();
  }
}