DoubleAdjacencyStringMatrix.java
/*
* Created on 2004/02/04 Copyright (C)
* 2004 Koga Laboratory. All rights
* reserved.
*/
package org.mklab.tool.control.system;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.mklab.tool.control.system.graph.SparceStringMatrix;
import org.mklab.tool.control.system.sink.DoubleContinuousSink;
import org.mklab.tool.control.system.sink.DoubleOutputPort;
import org.mklab.tool.control.system.sink.Exporter;
import org.mklab.tool.control.system.source.DoubleContinuousSource;
import org.mklab.tool.control.system.source.DoubleGround;
import org.mklab.tool.control.system.source.DoubleInputPort;
import org.mklab.tool.control.system.source.Importer;
/**
* ブロック線図(グラフ)の隣接関係を保持する文字列行列を表すクラスです。
*
* @author Yusuke Tsutsui
* @version $Revision: 1.52 $.
*/
public class DoubleAdjacencyStringMatrix {
/** システムのタグ文字列(G1,G2,…)と実際のシステム(ControlSystem)を関連付けるマップ */
private Map<String, DoubleSystemBuilder> csMap = new HashMap<>();
/** sourceシステムの番号とそれが接続するノードの番号のマップ */
private Map<Integer, Integer> sourceMap = new HashMap<>();
/** inputポートの番号とそれが接続するノードの番号のマップ */
private Map<Integer, Integer> inputMap = new HashMap<>();
/** sinkシステムの番号とそれが接続するノードの番号のマップ */
private Map<Integer, Integer> sinkMap = new HashMap<>();
/** outputポートの番号とそれが接続するノードの番号のマップ */
private Map<Integer, Integer> outputMap = new HashMap<>();
/** 多重器のマップ(出力ノード番号、入力ノード番号のリスト) */
private Map<Integer, List<Integer>> muxMap = new HashMap<>();
/** 分離器のマップ(入力ノード番号、出力ノード番号のリスト) */
private Map<Integer, List<Integer>> demuxMap = new HashMap<>();
/** 出力器のマップ(出力番号、出力器) */
private Map<Integer, Exporter> exporterMap = new TreeMap<>();
/** 入力器のマップ(入力番号、入力器) */
private Map<Integer, Importer> importerMap = new TreeMap<>();
// /** 乗除算器のマップ(出力ノード番号、入力ノード番号のリスト) */
// private Map<Integer, List<Integer>> productMap = new HashMap<Integer, List<Integer>>();
/** システムのID番号 */
private int systemId = 1;
/** 行列の成分を保持する文字列行列 */
private SparceStringMatrix elements;
/** ノードの次数 */
private int[] nodeDegrees;
/**
* 大きさ<code>size</code>の隣接行列を生成します。
*
* @param size 隣接行列のサイズ
*/
public DoubleAdjacencyStringMatrix(final int size) {
this.elements = new SparceStringMatrix(size, size);
this.nodeDegrees = new int[size];
for (int i = 0; i < size; i++) {
this.nodeDegrees[i] = -1;
}
}
/**
* <code>inputNode</code>ノードと<code>outputNode</code>ノードの間にエッジ(重み({@link DoubleSystemBuilder}))を追加します。
*
* <p>既に同じ位置にエッジが存在する場合は、重みが置き換えられます。
*
* @param inputNode 入力ノード
* @param outputNode 出力ノード
* @param system 入力ノードと出力ノードの間のエッジの重み({@link DoubleSystemBuilder})
*/
public void addEdge(final int inputNode, final int outputNode, final DoubleSystemBuilder system) {
system.resetAutoSize();
addEdge(inputNode, outputNode, "G" + this.systemId); //$NON-NLS-1$
setControlSystem("G" + this.systemId, system); //$NON-NLS-1$
this.systemId++;
if (system.isAutoSize() == false && system.getInputSize() != -1 && inputNode != 1) {
this.nodeDegrees[inputNode - 1] = system.getInputSize();
}
if (system.isAutoSize() == false && system.getOutputSize() != -1 && outputNode != this.nodeDegrees.length) {
this.nodeDegrees[outputNode - 1] = system.getOutputSize();
}
setupNodeDegree();
}
/**
* MOMOシステムを追加します。
*
* @param inputNodes 入力ノードのリスト
* @param outputNodes 出力ノードのリスト
* @param system MIMOシステム
*/
@SuppressWarnings("boxing")
public void addMIMO(final List<Integer> inputNodes, final List<Integer> outputNodes, final DoubleSystemBuilder system) {
for (final Integer inputNode : inputNodes) {
if (inputNode == null) {
continue;
}
for (final Integer outputNode : outputNodes) {
if (outputNode == null) {
continue;
}
if (hasEdge(inputNode, outputNode)) {
return;
}
system.resetAutoSize();
addEdge(inputNode, outputNode, "S" + this.systemId); //$NON-NLS-1$
setControlSystem("S" + this.systemId, system); //$NON-NLS-1$
}
}
this.systemId++;
}
/**
* 指定したノードとノードの間にエッジがあるか判定します。
*
* @param inputNode 入力ノード
* @param outputNode 出力ノード
* @return 指定したノードとノードの間にエッジがあればtrue、そうでなければfalse
*/
public boolean hasEdge(final int inputNode, final int outputNode) {
return this.elements.hasElement(inputNode, outputNode);
}
/**
* <code>inputNode</code>ノードから<code>outputNode</code>ノードに単位エッジ(重み=Iまたは-I)を追加します。
*
* @param inputNode 入力ノード
* @param outputNode 出力ノード
* @param positive 正入力かどうか(trueであれば正入力)
*/
public void addUnitEdge(final int inputNode, final int outputNode, final boolean positive) {
if (positive) {
addEdge(inputNode, outputNode, "P"); //$NON-NLS-1$
} else {
addEdge(inputNode, outputNode, "N"); //$NON-NLS-1$
}
if (this.nodeDegrees[inputNode - 1] == -1 && this.nodeDegrees[outputNode - 1] != -1) {
this.nodeDegrees[inputNode - 1] = this.nodeDegrees[outputNode - 1];
}
if (this.nodeDegrees[outputNode - 1] == -1 && this.nodeDegrees[inputNode - 1] != -1) {
this.nodeDegrees[outputNode - 1] = this.nodeDegrees[inputNode - 1];
}
setupNodeDegree();
}
/**
* <code>inputNodes</code>の各ノードから<code>outputNode</code>ノードへの多重器を追加します。
*
* @param inputNodes 入力ノードのリスト
* @param outputNode 出力ノード
*/
@SuppressWarnings("boxing")
public void addMultiplexer(final List<Integer> inputNodes, final int outputNode) {
this.muxMap.put(outputNode, inputNodes);
for (int i = 1; i <= inputNodes.size(); i++) {
final Integer node = inputNodes.get(i - 1);
if (node == null) {
continue;
}
addEdge(node, outputNode, "M" + i + "/" + inputNodes.size()); //$NON-NLS-1$ //$NON-NLS-2$
}
int outputSize = 0;
for (final Integer node : inputNodes) {
if (node == null) {
continue;
}
final int inputNode = node;
if (this.nodeDegrees[inputNode - 1] == -1) {
return;
}
outputSize += this.nodeDegrees[inputNode - 1];
}
this.nodeDegrees[outputNode - 1] = outputSize;
setupNodeDegree();
}
/**
* <code>inputNodes</code>の各ノードから<code>outputNode</code>ノードへの加算器を追加します。
*
* @param inputNodes 入力ノードのリスト
* @param outputNode 出力ノード
* @param positives 正入力かどうか(正入力ならばtrue)
*/
@SuppressWarnings("boxing")
public void addSum(final List<Integer> inputNodes, final int outputNode, final List<Boolean> positives) {
if (inputNodes.size() != positives.size()) {
throw new IllegalArgumentException(Messages.getString("AdjacencyStringMatrix.8")); //$NON-NLS-1$
}
for (int i = 0; i < inputNodes.size(); i++) {
final Integer node = inputNodes.get(i);
if (node == null) {
continue;
}
addUnitEdge(node, outputNode, positives.get(i));
}
}
/**
* 多重器の出力ノードの次数を設定します。
*
* @return 新たに多重器の出力ノードの次数を設定したならばtrue、そうでなければfalse
*/
@SuppressWarnings("boxing")
private boolean setupMultiplexerNodeDegree() {
boolean changed = false;
outer:for (Integer node : this.muxMap.keySet()) {
int outputNode = node;
if (this.nodeDegrees[outputNode - 1] != -1) {
continue;
}
final List<Integer> inputNodes = this.muxMap.get(outputNode);
int outputSize = 0;
for (final Integer inputNode : inputNodes) {
if (inputNode == null) {
continue;
}
if (this.nodeDegrees[inputNode - 1] == -1) {
continue outer;
}
outputSize += this.nodeDegrees[inputNode - 1];
}
this.nodeDegrees[outputNode - 1] = outputSize;
changed = true;
}
return changed;
}
/**
* <code>inputNode</code>ノードから<code>outputNodes</code>の各ノードへの分離器を追加します。
*
* @param inputNode 入力ノード
* @param outputNodes 出力ノードのリスト
*/
@SuppressWarnings("boxing")
public void addDeMultiplexer(final int inputNode, final List<Integer> outputNodes) {
this.demuxMap.put(inputNode, outputNodes);
for (int i = 1; i <= outputNodes.size(); i++) {
final Integer node = outputNodes.get(i - 1);
if (node == null) {
continue;
}
addEdge(inputNode, node, "D" + i + "/" + outputNodes.size()); //$NON-NLS-1$ //$NON-NLS-2$
}
int inputSize = 0;
for (final Integer node : outputNodes) {
if (node == null) {
continue;
}
final int outputNode = node;
if (this.nodeDegrees[outputNode - 1] == -1) {
return;
}
inputSize += this.nodeDegrees[outputNode - 1];
}
this.nodeDegrees[inputNode - 1] = inputSize;
setupNodeDegree();
}
/**
* 分離器の入力ノードの次数を設定します。
*
* @return 新たに分離器の入力ノードの次数を設定したならばtrue、そうでなければfalse
*/
@SuppressWarnings("boxing")
private boolean setupDeMultiplexerNodeDegree() {
boolean changed = false;
outer:for (final Integer node : this.demuxMap.keySet()) {
if (node == null) {
continue;
}
int inputNode = node;
if (this.nodeDegrees[inputNode - 1] != -1) {
continue;
}
final List<Integer> outputNodes = this.demuxMap.get(inputNode);
int inputSize = 0;
for (final Integer outputNode : outputNodes) {
if (outputNode == null) {
continue;
}
if (this.nodeDegrees[outputNode - 1] == -1) {
continue outer;
}
inputSize += this.nodeDegrees[outputNode - 1];
}
this.nodeDegrees[inputNode - 1] = inputSize;
changed = true;
}
return changed;
}
/**
* sourceシステムを追加します。
*
* @param inputNode sourceシステムを接続するノードの番号
* @param controlSystem sourceシステム
* @param sourceNumber sourceシステムの番号
*/
@SuppressWarnings("boxing")
public void addSource(final int inputNode, final DoubleSystemBuilder controlSystem, final int sourceNumber) {
controlSystem.resetAutoSize();
final DoubleSystemOperator system = controlSystem.getSystemOperator();
if (system instanceof Importer) {
this.importerMap.put(sourceNumber, (Importer)system);
}
if (system instanceof DoubleInputPort) {
if (this.inputMap.containsKey(sourceNumber)) {
throw new IllegalArgumentException(Messages.getString("AdjacencyStringMatrix.11")); //$NON-NLS-1$
}
this.inputMap.put(sourceNumber, inputNode);
final String tag = "I" + sourceNumber; //$NON-NLS-1$
addEdge(1, inputNode, tag);
setControlSystem(tag, controlSystem);
} else {
this.sourceMap.put(sourceNumber, inputNode);
addEdge(1, inputNode, controlSystem);
}
if (system instanceof DoubleContinuousSource) {
final int outputSize = controlSystem.getOutputSize();
if (((DoubleContinuousSource)system).isAutoSize() == false || outputSize != -1) {
this.nodeDegrees[inputNode - 1] = outputSize;
setupNodeDegree();
}
final int size = this.nodeDegrees[inputNode - 1];
if (size != -1) {
system.setOutputSize(size);
}
}
}
/**
* Sinkシステムを追加します
*
* @param outputNode Sinkシステムを接続するノードの番号
* @param controlSystem Sinkシステム
* @param sinkNumber Sinkの番号
*/
@SuppressWarnings("boxing")
public void addSink(final int outputNode, final DoubleSystemBuilder controlSystem, final int sinkNumber) {
controlSystem.resetAutoSize();
final DoubleSystemOperator system = controlSystem.getSystemOperator();
if (system instanceof Exporter) {
this.exporterMap.put(sinkNumber, (Exporter)system);
}
if (system instanceof DoubleOutputPort) {
if (this.outputMap.containsKey(sinkNumber)) {
throw new IllegalArgumentException(Messages.getString("AdjacencyStringMatrix.13")); //$NON-NLS-1$
}
this.outputMap.put(sinkNumber, outputNode);
final String tag = "O" + sinkNumber; //$NON-NLS-1$
addEdge(outputNode, this.size(), tag);
setControlSystem(tag, controlSystem);
} else {
this.sinkMap.put(sinkNumber, outputNode);
addEdge(outputNode, this.size(), controlSystem);
}
if (system instanceof DoubleContinuousSink) {
final int inputSize = controlSystem.getInputSize();
if (((DoubleContinuousSink)system).isAutoSize() == false || inputSize != -1) {
this.nodeDegrees[outputNode - 1] = inputSize;
setupNodeDegree();
}
final int size = this.nodeDegrees[outputNode - 1];
if (size != -1) {
system.setInputSize(size);
}
}
}
/**
* ポート番号順にソートしたinputポートとsourceシステムが接続されたノードのリストを返します。
*
* @return 「ポート番号順にソートしたinputポート」+「sourceシステム」とが接続されたノードのリスト
*/
@SuppressWarnings("boxing")
public List<Integer> getSortedInputSourceNodes() {
final List<Integer> inputSourceNodes = new ArrayList<>();
final SortedMap<Integer, Integer> sortedInputMap = new TreeMap<>();
for (final Integer key : this.inputMap.keySet()) {
sortedInputMap.put(key, this.inputMap.get(key));
}
/** inputポートが接続れているノードのリスト */
for (final Integer node : sortedInputMap.values()) {
if (getControlSystem(getWeightOfEdge(1, node)).getSystemOperator() instanceof DoubleInputPort) {
inputSourceNodes.add(node);
}
}
final SortedMap<Integer, Integer> sortedSourceMap = new TreeMap<>();
for (final Integer key : this.sourceMap.keySet()) {
sortedSourceMap.put(key, this.sourceMap.get(key));
}
/** sourceが接続れているノードのリスト */
for (final Integer node : sortedSourceMap.values()) {
if (getControlSystem(getWeightOfEdge(1, node)).getSystemOperator() instanceof DoubleInputPort == false) {
inputSourceNodes.add(node);
}
}
return inputSourceNodes;
}
/**
* ポート番号順にソートしたoutputポートとsinkシステムが接続されたノードのリストを返します。
*
* @return 「sinkシステム」+「ポート番号順にソートしたoutputポート」とが接続されたノードのリスト
*/
@SuppressWarnings("boxing")
public List<Integer> getSortedOutputSinkNodes() {
final List<Integer> outputSinkNodes = new ArrayList<>();
final int size = this.elements.getRowSize();
final SortedMap<Integer, Integer> sortedSinkMap = new TreeMap<>();
for (final Integer key : this.sinkMap.keySet()) {
sortedSinkMap.put(key, this.sinkMap.get(key));
}
/** sinkが接続されているノードのリスト */
for (final Integer node : sortedSinkMap.values()) {
if (getControlSystem(getWeightOfEdge(node, size)).getSystemOperator() instanceof DoubleOutputPort == false) {
outputSinkNodes.add(node);
}
}
final SortedMap<Integer, Integer> sortedOutputMap = new TreeMap<>();
for (final Integer key : this.outputMap.keySet()) {
sortedOutputMap.put(key, this.outputMap.get(key));
}
/** outputポートが接続されているノードのリスト */
for (final Integer node : sortedOutputMap.values()) {
if (getControlSystem(getWeightOfEdge(node, size)).getSystemOperator() instanceof DoubleOutputPort) {
outputSinkNodes.add(node);
}
}
return outputSinkNodes;
}
/**
* タグに対応するシステムを返します。
*
* @param tag システムのタグ(G1,G2,...)
* @return その文字列に対応する {@link DoubleSystemBuilder}
*/
public DoubleSystemBuilder getControlSystem(final String tag) {
return this.csMap.get(tag);
}
/**
* エッジを追加(隣接行列の成分を設定)します。
*
* @param inputNode 入力ノード
* @param outputNode 出力ノード
* @param weight エッジの重み
*/
private void addEdge(final int inputNode, final int outputNode, final String weight) {
this.elements.setElement(inputNode, outputNode, weight);
}
/**
* システムのタグとシステムの対を登録します。
*
* @param tag システムのタグ(G1,G2,...)
* @param system そのタグに対応するシステム
*/
private void setControlSystem(final String tag, final DoubleSystemBuilder system) {
this.csMap.put(tag, system);
}
/**
* エッジの重み(隣接行列の成分)を返します。
*
* @param inputNode 入力ノード
* @param outputNode 出力ノード
* @return システムを表す文字列
*/
public String getWeightOfEdge(final int inputNode, final int outputNode) {
return this.elements.getElement(inputNode, outputNode);
}
/**
* 隣接行列のサイズを返します。
*
* @return 隣接行列のサイズ
*/
public int size() {
return this.elements.getColumnSize();
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.elements.toString();
}
/**
* 行列を標準出力に出力します。
*/
public void print() {
this.elements.print();
}
/**
* ノードの次数を返します。
*
* @param nodeNumber ノードの番号
* @return ノードの次数
*/
public int getNodeDegree(final int nodeNumber) {
return this.nodeDegrees[nodeNumber - 1];
}
/**
* ノードの次数を設定します。
*
* @param nodeNumber ノードの番号
* @param degree ノードの次数
*/
public void setNodeDegree(int nodeNumber, int degree) {
this.nodeDegrees[nodeNumber - 1] = degree;
setupNodeDegree();
}
/**
* ノードの次数を設定します。
*/
void setupNodeDegree() {
boolean changed = false;
do {
changed = false;
for (int row = 1; row <= this.size(); row++) {
for (int column = 1; column <= this.size(); column++) {
final String weight = this.elements.getElement(row, column);
if (weight.length() == 0) {
continue;
}
final DoubleSystemBuilder controlSystem = getControlSystem(weight);
final DoubleSystemOperator system = controlSystem == null ? null : controlSystem.getSystemOperator();
if (system != null) {
if (this.nodeDegrees[row - 1] == -1 && this.nodeDegrees[column - 1] != -1) {
if (system instanceof DoubleBlockSystem == false || ((DoubleBlockSystem)system).getOutputNodeSize() == 1) {
system.setOutputSize(this.nodeDegrees[column - 1]);
if (row != 1 && this.nodeDegrees[row - 1] == -1) {
final int inputSize = system.getInputSize();
if (inputSize != -1) {
changed = true;
this.nodeDegrees[row - 1] = inputSize;
}
}
}
}
if (this.nodeDegrees[column - 1] == -1 && this.nodeDegrees[row - 1] != -1) {
if (system instanceof DoubleBlockSystem == false || ((DoubleBlockSystem)system).getInputNodeSize() == 1) {
system.setInputSize(this.nodeDegrees[row - 1]);
if (column != this.nodeDegrees.length && this.nodeDegrees[column - 1] == -1) {
final int outputSize = system.getOutputSize();
if (outputSize != -1) {
changed = true;
this.nodeDegrees[column - 1] = outputSize;
}
}
}
}
if (system instanceof DoubleGround && isConnectedToMultiplexer(column - 1)) {
final int outputSize = system.getOutputSize();
if (outputSize == -1) {
changed = true;
this.nodeDegrees[column - 1] = 0;
system.setOutputSize(0);
}
}
} else if (weight.equals("P") || weight.equals("N")) { //$NON-NLS-1$ //$NON-NLS-2$
if (this.nodeDegrees[row - 1] == -1 && this.nodeDegrees[column - 1] != -1) {
changed = true;
this.nodeDegrees[row - 1] = this.nodeDegrees[column - 1];
}
if (this.nodeDegrees[column - 1] == -1 && this.nodeDegrees[row - 1] != -1) {
changed = true;
this.nodeDegrees[column - 1] = this.nodeDegrees[row - 1];
}
}
}
}
if (setupMultiplexerNodeDegree()) {
changed = true;
}
if (setupDeMultiplexerNodeDegree()) {
changed = true;
}
} while (changed);
setInputSizeAndOutputSize();
}
/**
* 接続先が多重器であるか判定します。
*
* @param node 接続先をしらべるノード
* @return 接続先が多重器ならばtrue、そうでなければfalse
*/
private boolean isConnectedToMultiplexer(final int node) {
for (int column = 1; column <= this.size(); column++) {
final String weight = this.elements.getElement(node + 1, column);
if (weight.length() == 0) {
continue;
}
if (weight.startsWith("M")) { //$NON-NLS-1$
return true;
}
}
return false;
}
/**
* 入力数と出力数が未定のシステムの入出力数を設定します。
*/
private void setInputSizeAndOutputSize() {
final int size = this.size();
for (int row = 1; row <= size; row++) {
for (int column = 1; column <= size; column++) {
final String weight = this.elements.getElement(row, column);
if (weight.length() == 0) {
continue;
}
final DoubleSystemBuilder controlSystem = getControlSystem(weight);
final DoubleSystemOperator system = controlSystem == null ? null : controlSystem.getSystemOperator();
if (system == null) {
continue;
}
final int inputNodeSize = this.nodeDegrees[row - 1];
if (inputNodeSize != -1 && system.getInputSize() == -1) {
system.setInputSize(inputNodeSize);
final int outputSize = system.getOutputSize();
if (outputSize != -1) {
this.nodeDegrees[column - 1] = outputSize;
}
}
final int outputNodeSize = this.nodeDegrees[column - 1];
if (outputNodeSize != -1 && system.getOutputSize() == -1) {
system.setOutputSize(outputNodeSize);
final int inputSize = system.getInputSize();
if (inputSize != -1) {
this.nodeDegrees[row - 1] = inputSize;
}
}
}
}
}
}