DoubleBode.java

/*
 * $Id: Bode.java,v 1.85 2008/04/10 03:34:27 koga Exp $
 *
 * Copyright (C) 2004 Koga Laboratory. All rights reserved.
 */
package org.mklab.tool.control;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.mklab.nfc.matrix.DoubleComplexMatrix;
import org.mklab.nfc.matrix.DoubleMatrix;
import org.mklab.nfc.matrix.misc.LogarithmicallySpacedVector;
import org.mklab.nfc.scalar.DoubleComplexNumber;
import org.mklab.nfc.scalar.DoubleNumber;
import org.mklab.nfc.scalar.DoublePolynomial;
import org.mklab.nfc.scalar.DoubleRationalPolynomial;
import org.mklab.nfc.util.Pause;
import org.mklab.tool.graph.gnuplot.Gnuplot;
import org.mklab.tool.matrix.Unwrap;


/**
 * ボード線図を描画するためのデータ(ゲイン、位相)を求めるクラスです。
 * 
 * <p>Bode response
 * 
 * @author koga
 * @version $Revision: 1.85 $
 */
public class DoubleBode {

  /** システム */
  private DoubleLinearSystem system;
  /** サンプリング周期 */
  private double samplingInterval;

  /**
   * 新しく生成された<code>Bode</code>オブジェクトを初期化します。
   * 
   * @param system 線形システム
   */
  public DoubleBode(final DoubleLinearSystem system) {
    this.system = (DoubleLinearSystem)system.clone();
    this.system.setTimeDomainType(TimeDomainType.CONTINUOUS);
  }

  /**
   * 新しく生成された<code>Bode</code>オブジェクトを初期化します。
   * 
   * @param system 線形システム
   * @param samplingInterval サンプリング周期
   */
  public DoubleBode(final DoubleLinearSystem system, final double samplingInterval) {
    this.system = (DoubleLinearSystem)system.clone();
    this.samplingInterval = samplingInterval;
    this.system.setTimeDomainType(TimeDomainType.DISCRETE);
  }

  /**
   * システムの周波数応答(大きさ(絶対値),偏角(度),評価周波数)を返します。
   * 
   * @param angularFrequencies 評価する角周波数(radian/s)
   * @return 周波数応答(大きさ(絶対値),偏角(度),評価周波数)
   */
  public List<List<DoubleMatrix>> getGainAndPhase(final DoubleMatrix angularFrequencies) {
    final List<List<DoubleMatrix>> magnitudesPhases = new ArrayList<>();

    if (this.system.isContinuous()) {
      for (int inputNumber = 1; inputNumber <= this.system.getInputSize(); inputNumber++) {
        final List<DoubleMatrix> magnitudePhase = getGainAndPhaseForContinuous(angularFrequencies, inputNumber);
        magnitudesPhases.add(magnitudePhase);
      }
    } else {
      for (int inputNumber = 1; inputNumber <= this.system.getInputSize(); inputNumber++) {
        final List<DoubleMatrix> magnitudePhase = getGainAndPhaseForDiscrete(angularFrequencies, inputNumber);
        magnitudesPhases.add(magnitudePhase);
      }
    }

    return magnitudesPhases;
  }

  /**
   * システムの周波数応答(大きさ(絶対値)、偏角(度)、評価周波数)を返します。
   * 
   * @param angularFrequencies 評価する角周波数(radian/s)
   * @param inputNumber 入力番号
   * @return 周波数応答(大きさ(絶対値),偏角(度),評価周波数)
   */
  private List<DoubleMatrix> getGainAndPhaseForContinuous(final DoubleMatrix angularFrequencies, final int inputNumber) {
    final DoubleNumber unit = angularFrequencies.getElement(1, 1);
    final DoubleComplexNumber j = new DoubleComplexNumber(0,1);
    final DoubleComplexMatrix jw = new DoubleComplexMatrix(angularFrequencies).multiply(j);
    final DoubleComplexMatrix gjw = new DoubleFrequencyResponse(this.system).getResponse(jw, inputNumber);

    final DoubleMatrix magnitude = gjw.absElementWise().getRealPart();

    final DoubleMatrix phase = gjw.logElementWise().getImaginaryPart();
    final DoubleNumber unwarpTolerance = unit.createPI().multiply(170).divide(180);
    final DoubleMatrix unwrapedPhase = Unwrap.unwrapRowWise(phase, unwarpTolerance);
    final DoubleNumber phaseDeviationTolerance = unit.createUnit().divide(10000);
    final DoubleMatrix compensatedPhase = compensateForIntegratorAndDerivative(magnitude, unwrapedPhase, angularFrequencies, phaseDeviationTolerance);
    final DoubleNumber pi = unit.createPI();
    final DoubleMatrix phaseOfDegree = compensatedPhase.multiply(pi.inverse().multiply(180));

    return new ArrayList<>(Arrays.asList(new DoubleMatrix[] {magnitude, phaseOfDegree, angularFrequencies}));
  }

  /**
   * 積分器と微分器のための補正をした位相を返します。
   * 
   * @param magnitudes 大きさ
   * @param phases 位相
   * @param angularFrequencies 評価する角周波数
   * @param phaseDeviationTolerance 許容誤差
   * @return 補正後の位相
   */
  private DoubleMatrix compensateForIntegratorAndDerivative(final DoubleMatrix magnitudes, final DoubleMatrix phases, final DoubleMatrix angularFrequencies,
      final DoubleNumber phaseDeviationTolerance) {
    final DoubleMatrix modifiedPhases = phases.createClone();
    final int size = angularFrequencies.length();

    final DoubleNumber pi = magnitudes.getElement(1, 1).createPI();

    for (int outputNumber = 1; outputNumber <= magnitudes.getRowSize(); outputNumber++) {
      final DoubleMatrix phase = phases.getRowVector(outputNumber);
      if (phase.max().subtract(phase.min()).isGreaterThan(phaseDeviationTolerance)) {
        continue;
      }

      final DoubleMatrix gain = magnitudes.getRowVector(outputNumber).log10ElementWise().multiply(20);
      final DoubleMatrix gainDeviation = gain.getSubVector(2, size).subtract(gain.getSubVector(1, size - 1));
      final DoubleMatrix wLog10 = angularFrequencies.log10ElementWise();
      final DoubleMatrix wLog10Deviation = wLog10.getSubVector(2, size).subtract(wLog10.getSubVector(1, size - 1));
      final DoubleMatrix rates = gainDeviation.divideElementWise(wLog10Deviation);
      final DoubleNumber degree = rates.mean().divide(20).round();
     modifiedPhases.setRowVector(outputNumber, magnitudes.createOnes(1, size).multiply(degree.multiply(pi.divide(2))));
    }

    return modifiedPhases;
  }

  /**
   * ゲイン線図と位相線図を描画します。
   * 
   * @param gnuplot Gnuplot
   * @param angularFrequencies 評価する角周波数(radian/s)
   */
  public void plot(final Gnuplot gnuplot, final DoubleMatrix angularFrequencies) {
    new BodePlotter(gnuplot).plot(getGainAndPhase(angularFrequencies), angularFrequencies);
  }

  /**
   * ゲイン線図と位相線図を描画します。
   * 
   * @param gnuplot Gnuplot
   */
  public void plot(final Gnuplot gnuplot) {
    final DoubleMatrix angularFrequencies;
    if (this.system.isContinuous()) {
      angularFrequencies = LogarithmicallySpacedVector.create(-2, 2);
    } else {
      final double period = 2 * Math.PI / this.samplingInterval;
      angularFrequencies = LogarithmicallySpacedVector.create(-1, Math.log10(period));
    }
    plot(gnuplot, angularFrequencies);
  }

  /**
   * システムの<code>inputNumber</code>番目の入力からの周波数応答(絶対値,位相[度],評価周波数)を返します。
   * 
   * @param angularFrequencies 伝達関数を評価する角周波数 [rad/sec]
   * @param inputNumber 入力番号
   * @return {magnitude, phase}
   * 
   *         <p>周波数応答(絶対値,位相[度],評価周波数)
   * 
   *         <p>(magnitude(absolute value), phase(in degrees))
   */
  private List<DoubleMatrix> getGainAndPhaseForDiscrete(DoubleMatrix angularFrequencies, int inputNumber) {
    final DoubleNumber unit = angularFrequencies.getElement(1, 1);

    final DoubleComplexNumber j = new DoubleComplexNumber(0,1);
    final DoubleComplexMatrix exp_jwT = new DoubleComplexMatrix(angularFrequencies).multiply(j.multiply(this.samplingInterval)).expElementWise();
    final DoubleComplexMatrix gjw = new DoubleFrequencyResponse(this.system).getResponse(exp_jwT, inputNumber);

    final DoubleMatrix magnitude = gjw.absElementWise().getRealPart();

    final DoubleMatrix phase = gjw.logElementWise().getImaginaryPart();
    final DoubleMatrix unwrapedPhase = Unwrap.unwrapRowWise(phase);
    final DoubleNumber pi = unit.createPI();
    final DoubleMatrix phaseOfDegree = unwrapedPhase.multiply(pi.inverse().multiply(180));

    return new ArrayList<>(Arrays.asList(new DoubleMatrix[] {magnitude, phaseOfDegree, angularFrequencies}));
  }

  /**
   * メインメソッド
   * 
   * @param args コマンドライン引数
   * @throws IOException キーボードから入力できない場合
   */
  public static void main(String[] args) throws IOException {
    final DoublePolynomial s = new DoublePolynomial("s"); //$NON-NLS-1$
    final DoubleRationalPolynomial gc = new DoubleRationalPolynomial(new DoublePolynomial(1), s.add(1));
    final Gnuplot gnuplot1 = new Gnuplot();
    final DoubleMatrix w = LogarithmicallySpacedVector.create(-2, 2);
    new DoubleBode(DoubleLinearSystemFactory.createLinearSystem(gc)).plot(gnuplot1, w);

    final DoublePolynomial z = new DoublePolynomial("z"); //$NON-NLS-1$
    final DoubleRationalPolynomial gz = new DoubleRationalPolynomial(new DoublePolynomial(0.5), z.subtract(0.5));
    final Gnuplot gnuplot2 = new Gnuplot();
    new DoubleBode(DoubleLinearSystemFactory.createLinearSystem(gz), 0.1).plot(gnuplot2);

    try {
      Pause.pause();
    } finally {
      gnuplot1.close();
      gnuplot2.close();
    }
  }
}