サイトマップ

Pythonによる制御工学入門 第3回

Pythonによる制御工学入門 第3回

前回と同様に、参考書の5章「閉ループ系に注目した制御設計」の
5.2「PID制御」のボード線図を自作の数値シミュレーションで再現する

前回は、制御対象の"生"のモデルの周波数特性を取得したが、
今回は、PID制御でフィードバック制御が働いている状態の周波数特性を取得する

P制御

垂直駆動アームのモデルに対して、制御を行っていく

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import control.matlab as pyctrl

g  = 9.81                # 重力加速度[m/s^2]
l  = 0.2                 # アームの長さ[m]
M  = 0.5                 # アームの質量[kg]
mu = 1.5e-2              # 粘性摩擦係数[kg*m^2/s]
J  = 1.0e-2              # 慣性モーメント[kg*m^2]

target = 30 # 目標角度 [deg]
kp = 2.0

ステップ応答

下記のスクリプトで数値シミュレーションを行う

dt = 0.0001

t_s = [0.0]
x_s = [0.0]
v_s = [0.0]
a_s = [0.0]
F_s = [0.0]
ref_s = [0.0]

for i in range(20000):
    ref = target
    ref_s += [ref]
    F_s += [(ref - x_s[-1]) * kp]
    a_s += [(- mu * v_s[-1] - M * g * l * x_s[-1] + F_s[-1]) / J]
    v_s += [v_s[-1] + a_s[-1] * dt]
    x_s += [x_s[-1] + v_s[-1] * dt]
    t_s += [t_s[-1] + dt]

df = pd.DataFrame()
df['in']  = F_s[::100]
df['out'] = x_s[::100]
df.index  = t_s[::100]

結果をプロットし、controlモジュールの結果と比較する

fig = plt.figure(figsize=(6, 5))
ax1 = fig.add_subplot(1, 1, 1)

ax1.plot(df.index, df['out'], '.-', label='sim')

# controlモジュールによるステップ応答と比較
P = pyctrl.tf([0, 1], [J, mu, M *g *l])
K = pyctrl.tf([0, kp], [0, 1])
Gyr = pyctrl.feedback(P * K, 1)
y,t = pyctrl.step(Gyr, np.arange(0, 2, 0.01))
ax1.plot(t, y*target, '-', label='pyctrl')

ax1.axhline(target, color="k", linewidth=0.5)
ax1.set_xlim(0, 2); ax1.set_ylim(0, 50)
ax1.legend(); ax1.grid()
ax1.set_ylabel('y'); ax1.set_xlabel('t')
fig.tight_layout()

周波数特性

0.01Hz ~ 10Hz までを等比で100分割する

hz_s = []
hz = 0.01
while True:
    if hz > 10 * 10 ** (3 / 100):
        break
    hz_s.append(hz)
    hz = hz * 10 ** (3 / 100)

各周波数の正弦波を目標に入れて周波数特性を取得する

dt = 0.0001
df_list = []
for hz in hz_s:
    t_s = [0.0]
    x_s = [0.0]
    v_s = [0.0]
    a_s = [0.0]
    F_s = [0.0]
    ref_s = [0.0]

    for i in range(50000):
        ref = target * np.sin(2.0 * np.pi * hz * t_s[-1])
        ref_s += [ref]
        F_s += [(ref - x_s[-1]) * kp]
        a_s += [(- mu * v_s[-1] - M * g * l * x_s[-1] + F_s[-1]) / J]
        v_s += [v_s[-1] + a_s[-1] * dt]
        x_s += [x_s[-1] + v_s[-1] * dt]
        t_s += [t_s[-1] + dt]

    df = pd.DataFrame()
    df['in']  = ref_s[30000:]
    df['out'] = x_s[30000:]
    df.index  = t_s[30000:]
    df_list += [df]

時系列波形からゲインと位相変化を取得する関数

from scipy.optimize import curve_fit

def sin_func(x, p0, p1, p2):
    return p0 * np.sin(2.0 * np.pi * p1 * x + p2)

def _convert_phase(phase):
    phase += 180
    phase %= 360
    if phase < 0:
        phase += 180
    else:
        phase -= 180
    return phase

def _convert_param(param):
    param = param.copy()
    param[2] = param[2] / np.pi * 180
    if param[0] < 0:
        param[0] = -param[0]
        param[2] += 180

    param[2] = _convert_phase(param[2])

    return param

def calc_f_func(param1, param2):
    param1 = _convert_param(param1)
    param2 = _convert_param(param2)

    freq = param1[1]
    gain = param2[0] / param1[0]
    phase = param2[2] - param1[2]
    phase = _convert_phase(phase)

    return param1, param2, freq, gain, phase

周波数解析を実施

data_s = []
for hz, df in zip(hz_s, df_list):
    p0 = [target, hz, 0.0]
    p1, conv = curve_fit(sin_func, np.array(df.index), np.array(df['in']), p0=p0)

    p0 = [target, hz, 0.0]
    p2, conv = curve_fit(sin_func, np.array(df.index), np.array(df['out']), p0=p0)

    param1, param2, freq, gain, phase = calc_f_func(p1, p2)

    data_s.append([freq, gain, phase])

df_res = pd.DataFrame(data_s)
df_res.columns = ['freq', 'gain', 'phase']

結果を表示する
これもcontrolモジュールと比較する

fig = plt.figure(figsize=(6, 5))
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)

ax1.plot(df_res['freq'], np.log10(df_res['gain'])*20.0, 'o-', label='sim')
ax2.plot(df_res['freq'], df_res['phase'], 'o-', label='sim')

K = pyctrl.tf([0, kp], [0, 1])
Gyr = pyctrl.feedback(P*K, 1)
gain, phase, w = pyctrl.bode(Gyr, pyctrl.logspace(-1,2), plot=False)
ax1.plot(w/(2*np.pi), 20*np.log10(gain), label='pyctrl')
ax2.plot(w/(2*np.pi), phase*180/np.pi, label='pyctrl')

ax2.set_ylim(-190, 10); ax2.set_yticks([-180, -90, 0])
ax1.legend(); ax1.grid(); ax1.set_xscale('log')
ax1.set_ylabel('Gain [dB]')
ax2.legend(); ax2.grid(); ax2.set_xscale('log')
ax2.set_xlabel('[Hz]')
ax2.set_ylabel('Phase [deg]')
fig.tight_layout()

PD制御

D制御を加えることで、振動を抑えることができる

kp = 2
kd = 0.2

ステップ応答

目標値と現在値の差分(偏差)を演算し、
前回の偏差との差分からD成分を算出する

dt = 0.0001

t_s = [0.0]
x_s = [0.0]
v_s = [0.0]
a_s = [0.0]
F_s = [0.0]
ref_s = [0.0]
pre_err = 0.0

for i in range(20000):
    ref = target
    ref_s += [ref]
    err = ref - x_s[-1]
    F_s += [ err * kp + (err - pre_err) * kd / dt]
    a_s += [(- mu * v_s[-1] - M * g * l * x_s[-1] + F_s[-1]) / J]
    v_s += [v_s[-1] + a_s[-1] * dt]
    x_s += [x_s[-1] + v_s[-1] * dt]
    t_s += [t_s[-1] + dt]
    pre_err = err

df = pd.DataFrame()
df['in']  = F_s[::100]
df['out'] = x_s[::100]
df.index  = t_s[::100]
fig = plt.figure(figsize=(6, 5))
ax1 = fig.add_subplot(1, 1, 1)

ax1.plot(df.index, df['out'], '.-', label='sim')

# controlモジュールによるステップ応答と比較
P = pyctrl.tf([0, 1], [J, mu, M *g *l])
K = pyctrl.tf([kd, kp], [0, 1])
Gyr = pyctrl.feedback(P * K, 1)
y,t = pyctrl.step(Gyr, np.arange(0, 2, 0.01))
ax1.plot(t, y*target, '-', label='pyctrl')

ax1.axhline(target, color="k", linewidth=0.5)
ax1.set_xlim(0, 2); ax1.set_ylim(0, 50)
ax1.legend(); ax1.grid()
ax1.set_ylabel('y'); ax1.set_xlabel('t')
fig.tight_layout()

周波数特性

dt = 0.0001
df_list = []
for hz in hz_s:
    t_s = [0.0]
    x_s = [0.0]
    v_s = [0.0]
    a_s = [0.0]
    F_s = [0.0]
    ref_s = [0.0]
    pre_err = 0.0

    for i in range(50000):
        ref = target * np.sin(2.0 * np.pi * hz * t_s[-1])
        ref_s += [ref]
        err = ref - x_s[-1]
        F_s += [ err * kp + (err - pre_err) * kd / dt]
        a_s += [(- mu * v_s[-1] - M * g * l * x_s[-1] + F_s[-1]) / J]
        v_s += [v_s[-1] + a_s[-1] * dt]
        x_s += [x_s[-1] + v_s[-1] * dt]
        t_s += [t_s[-1] + dt]
        pre_err = err

    df = pd.DataFrame()
    df['in']  = ref_s[30000:]
    df['out'] = x_s[30000:]
    df.index  = t_s[30000:]
    df_list += [df]
data_s = []
for hz, df in zip(hz_s, df_list):
    p0 = [target, hz, 0.0]
    p1, conv = curve_fit(sin_func, np.array(df.index), np.array(df['in']), p0=p0)

    p0 = [target, hz, 0.0]
    p2, conv = curve_fit(sin_func, np.array(df.index), np.array(df['out']), p0=p0)

    param1, param2, freq, gain, phase = calc_f_func(p1, p2)

    data_s.append([freq, gain, phase])

df_res = pd.DataFrame(data_s)
df_res.columns = ['freq', 'gain', 'phase']
fig = plt.figure(figsize=(6, 5))
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)

ax1.plot(df_res['freq'], np.log10(df_res['gain'])*20.0, 'o-', label='sim')
ax2.plot(df_res['freq'], df_res['phase'], 'o-', label='sim')

K = pyctrl.tf([kd, kp], [0, 1])
Gyr = pyctrl.feedback(P*K, 1)
gain, phase, w = pyctrl.bode(Gyr, pyctrl.logspace(-1,2), plot=False)
ax1.plot(w/(2*np.pi), 20*np.log10(gain), label='pyctrl')
ax2.plot(w/(2*np.pi), phase*180/np.pi, label='pyctrl')

ax2.set_ylim(-190, 10); ax2.set_yticks([-180, -90, 0])
ax1.legend(); ax1.grid(); ax1.set_xscale('log')
ax1.set_ylabel('Gain [dB]')
ax2.legend(); ax2.grid(); ax2.set_xscale('log')
ax2.set_xlabel('[Hz]')
ax2.set_ylabel('Phase [deg]')
fig.tight_layout()

D制御で、ピークゲインが抑えられ、振動が小さくなる

PID制御

I制御を加えて、定常偏差を押さえる

kp = 2
kd = 0.1
ki = 10

ステップ応答

偏差を積算し、I成分として使用する

dt = 0.0001

t_s = [0.0]
x_s = [0.0]
v_s = [0.0]
a_s = [0.0]
F_s = [0.0]
ref_s = [0.0]
pre_err = 0.0
integ = 0.0

for i in range(20000):
    ref = target
    ref_s += [ref]
    err = ref - x_s[-1]
    integ += err
    F_s += [ err * kp + (err - pre_err) * kd / dt + integ * ki * dt]
    a_s += [(- mu * v_s[-1] - M * g * l * x_s[-1] + F_s[-1]) / J]
    v_s += [v_s[-1] + a_s[-1] * dt]
    x_s += [x_s[-1] + v_s[-1] * dt]
    t_s += [t_s[-1] + dt]
    pre_err = err

df = pd.DataFrame()
df['in']  = F_s[::100]
df['out'] = x_s[::100]
df.index  = t_s[::100]
fig = plt.figure(figsize=(6, 5))
ax1 = fig.add_subplot(1, 1, 1)

ax1.plot(df.index, df['out'], '.-', label='sim')

# controlモジュールによるステップ応答と比較
P = pyctrl.tf([0, 1], [J, mu, M *g *l])
K = pyctrl.tf([kd, kp, ki], [1, 0])
Gyr = pyctrl.feedback(P * K, 1)
y,t = pyctrl.step(Gyr, np.arange(0, 2, 0.01))
ax1.plot(t, y*target, '-', label='pyctrl')

ax1.axhline(target, color="k", linewidth=0.5)
ax1.set_xlim(0, 2); ax1.set_ylim(0, 50)
ax1.legend(); ax1.grid()
ax1.set_ylabel('y'); ax1.set_xlabel('t')
fig.tight_layout()

周波数特性

dt = 0.0001
df_list = []
for hz in hz_s:
    t_s = [0.0]
    x_s = [0.0]
    v_s = [0.0]
    a_s = [0.0]
    F_s = [0.0]
    ref_s = [0.0]
    pre_err = 0.0
    integ = 0.0

    for i in range(50000):
        ref = target * np.sin(2.0 * np.pi * hz * t_s[-1])
        ref_s += [ref]
        err = ref - x_s[-1]
        integ += err
        F_s += [ err * kp + (err - pre_err) * kd / dt + integ * ki * dt]
        a_s += [(- mu * v_s[-1] - M * g * l * x_s[-1] + F_s[-1]) / J]
        v_s += [v_s[-1] + a_s[-1] * dt]
        x_s += [x_s[-1] + v_s[-1] * dt]
        t_s += [t_s[-1] + dt]
        pre_err = err

    df = pd.DataFrame()
    df['in']  = ref_s[30000:]
    df['out'] = x_s[30000:]
    df.index  = t_s[30000:]
    df_list += [df]
data_s = []
for hz, df in zip(hz_s, df_list):
    p0 = [target, hz, 0.0]
    p1, conv = curve_fit(sin_func, np.array(df.index), np.array(df['in']), p0=p0)

    p0 = [target, hz, 0.0]
    p2, conv = curve_fit(sin_func, np.array(df.index), np.array(df['out']), p0=p0)

    param1, param2, freq, gain, phase = calc_f_func(p1, p2)

    data_s.append([freq, gain, phase])

df_res = pd.DataFrame(data_s)
df_res.columns = ['freq', 'gain', 'phase']
fig = plt.figure(figsize=(6, 5))
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2)

ax1.plot(df_res['freq'], np.log10(df_res['gain'])*20.0, 'o-', label='sim')
ax2.plot(df_res['freq'], df_res['phase'], 'o-', label='sim')

K = pyctrl.tf([kd, kp, ki], [1, 0])
Gyr = pyctrl.feedback(P*K, 1)
gain, phase, w = pyctrl.bode(Gyr, pyctrl.logspace(-1,2), plot=False)
ax1.plot(w/(2*np.pi), 20*np.log10(gain), label='pyctrl')
ax2.plot(w/(2*np.pi), phase*180/np.pi, label='pyctrl')

ax2.set_ylim(-190, 10); ax2.set_yticks([-180, -90, 0])
ax1.legend(); ax1.grid(); ax1.set_xscale('log')
ax1.set_ylabel('Gain [dB]')
ax2.legend(); ax2.grid(); ax2.set_xscale('log')
ax2.set_xlabel('[Hz]')
ax2.set_ylabel('Phase [deg]')
fig.tight_layout()

I制御により、低域のゲインが上がり、定常偏差がなくなる

参考文献

この記事は以下の書籍を参考にしましたが、
私の拙い知識で書いておりますので、誤り等ありましたらご指摘ください