Pythonによる制御工学入門 第6回
今回は、状態フィードバック制御について取り扱う
参考書では、
5.6 「状態フィードバック制御」
7.1 「オブサーバを用いた出力フィードバック制御」
に該当する
状態フィードバック
極配置
下記のモデルで、極配置による状態フィードバック制御を行う
import matplotlib.pyplot as plt import numpy as np import pandas as pd import control.matlab as pyctrl A = '0 1; -4 5' B = '0; 1' C = '1 0 ; 0 1' D = '0; 0' P = pyctrl.ss(A, B, C, D)
このままだと、固有値は下記のように、[1, 4] になる
np.linalg.eigvals(P.A)
array([1., 4.])
固有値が [-1, -1] になるように、フィードバックゲインを求める
下記のように求めると [3, -7] が得られる
Pole = [-1, -1] F_pole = -pyctrl.acker(P.A, P.B, Pole) F_pole
matrix([[ 3., -7.]])
このゲインで状態フィードバックを実施した際の固有値を確認すると [-1, -1] となっている
Acl = P.A + P.B * F_pole np.linalg.eigvals(Acl)
array([-1., -1.])
ゼロ入力応答をプロットすると下図になる
Pfb = pyctrl.ss(Acl, P.B, P.C, P.D) x_pole, t = pyctrl.initial(Pfb, np.arange(0, 5, 0.01), [-0.3, 0.4]) #ゼロ入力応答 fig = plt.figure(figsize=(6, 5)) ax = fig.add_subplot(111) ax.plot(t, x_pole[:,0], label = '$x_1$') ax.plot(t, x_pole[:,1], ls = '-.', label = '$x_2$') ax.set_xlabel('t'); ax.set_ylabel('x') ax.grid(); ax.legend() fig.tight_layout()
数値シミュレーション版も確認
dt = 0.001 t_s = [0.0] x1_s = [-0.3] x2_s = [0.4] F_s = [0.0] for i in range(5000): x1_tmp = x1_s[-1] x2_tmp = x2_s[-1] F = 3.0 * x1_tmp + -7.0 * x2_tmp x1_s += [x1_tmp + ( 0.0 * x1_tmp + 1.0 * x2_tmp + 0.0 * F) * dt] x2_s += [x2_tmp + (-4.0 * x1_tmp + 5.0 * x2_tmp + 1.0 * F) * dt] t_s += [t_s[-1] + dt] F_s += [F] df = pd.DataFrame() df['x1'] = x1_s[::10] df['x2'] = x2_s[::10] df['u'] = F_s[::10] df.index = t_s[::10]
fig = plt.figure(figsize=(8, 4)) ax1 = fig.add_subplot(121) ax2 = fig.add_subplot(122) ax1.plot(df.index, df['x1'], ls='-.', label='sim') ax2.plot(df.index, df['x2'], ls='-.', label='sim') # モジュールの結果と比較 ax1.plot(t, x_pole[:,0], label='pyctrl') ax2.plot(t, x_pole[:,1], label='pyctrl') ax1.set_xlabel('t'); ax1.set_ylabel('$x_1$') ax1.grid(); ax1.legend() ax2.set_xlabel('t'); ax2.set_ylabel('$x_2$') ax2.grid(); ax2.legend() fig.tight_layout()
結果は一致している
最適レギュレータ
極配置法で、任意の固有値を設定できる
理論的には、負の大きな値に設定すれば、収束は改善するが、
実際は、動力を伝えるモータなどのアクチュエータの出力の制限がある
最適レギュレータを設定することで、収束性と出力制限のバランスを取りつつ設計できる
フィードバックゲインの設計
下記のように、Q, r のパラメータを与えることで、フィードバックゲインを設計できる
Q = [[100, 0], [0, 1]] R = 1 F_lqr, PX, E = pyctrl.lqr(P.A, P.B, Q, R) F_lqr = -F_lqr print('--- フィードバックゲイン ---') print(F_lqr) print(-(1/R) * P.B.T * PX) print('--- 閉ループ極 ---') print(E) print(np.linalg.eigvals(P.A + P.B * F_lqr))
--- フィードバックゲイン --- [[ -6.77032961 -11.28813639]] [[ -6.77032961 -11.28813639]] --- 閉ループ極 --- [-3.14406819+0.94083198j -3.14406819-0.94083198j] [-3.14406819+0.94083198j -3.14406819-0.94083198j]
Acl = P.A + P.B * F_lqr Pfb = pyctrl.ss(Acl, P.B, P.C, P.D) x_lqr, t = pyctrl.initial(Pfb, np.arange(0, 5, 0.01), [-0.3, 0.4]) #ゼロ入力応答 fig = plt.figure(figsize=(6, 5)) ax = fig.add_subplot(111) ax.plot(t, x_lqr[:,0], label = '$x_1$') ax.plot(t, x_lqr[:,1], ls = '-.', label = '$x_2$') ax.set_xlabel('t'); ax.set_ylabel('x') ax.grid(); ax.legend() fig.tight_layout()
fig = plt.figure(figsize=(6, 5)) ax = fig.add_subplot(111) ax.plot(t, np.array(F_lqr)[0][0] * x_lqr[:,0] + np.array(F_lqr)[0][1] * x_lqr[:,1], label = 'LQR') ax.plot(t, np.array(F_pole)[0][0] * x_pole[:,0] + np.array(F_pole)[0][1] * x_pole[:,1], label = 'Pole') ax.plot(t_s, F_s, '--', label='sim') ax.set_xlabel('t'); ax.set_ylabel('u') ax.grid(); ax.legend() fig.tight_layout()
最適レギュレータで設計した方が、最初の立ち上がりが緩やかになる
ハミルトン行列
計算で求める場合は、下記のようにハミルトン行列の固有値を求め、極配置法で設計できる
H1 = np.c_[P.A, -P.B*(1/R)*P.B.T] H2 = np.c_[Q, P.A.T] H = np.r_[H1, -H2] eigH = np.linalg.eigvals(H) # print(eigH) print('--- ハミルトン行列の安定固有値 ---') eigH_stable = [i for i in eigH if i < 0] print(eigH_stable) F = -pyctrl.acker(P.A, P.B, eigH_stable) print('--- フィードバックゲイン ---') print(F)
--- ハミルトン行列の安定固有値 --- [(-3.1440681937792796+0.9408319760374402j), (-3.1440681937792796-0.9408319760374402j)] --- フィードバックゲイン --- [[ -6.77032961 -11.28813639]]
積分サーボ
先の極配置法でのモデルに、一定の外力が加わった状態をシミュレートする
入力がある場合は、lsim
を使用する
Acl = P.A + P.B * F_pole Pfb = pyctrl.ss(Acl, P.B, P.C, P.D) Td = np.arange(0, 8, 0.01) Ud = 0.2 * (Td>0) x, t, _ = pyctrl.lsim(Pfb, Ud, Td, [-0.3, 0.4]) fig = plt.figure(figsize=(6, 5)) ax = fig.add_subplot(111) ax.plot(t, x[:,0], label = '$x_1$') ax.plot(t, x[:,1], ls = '-.', label = '$x_2$') ax.set_xlabel('t'); ax.set_ylabel('x') ax.grid(); ax.legend() fig.tight_layout()
外力によって、0.2に整定してしまう
外力を打ち消す積分項を考慮した、拡大状態変数を考え、2行2列から、3行3列に拡大する
A_integ = '0 1 0; -4 5 0; -1, 0, 0' B_integ = '0; 1; 0' C_integ = '1 0 0; 0 1 0; 0 0 1' D_integ = '0; 0; 0' P_integ = pyctrl.ss(A_integ, B_integ, C_integ, D_integ) Pole = [-1, -1, -5] F_integ = -pyctrl.acker(P_integ.A, P_integ.B, Pole) F_integ
matrix([[ -7., -12., 5.]])
Acl = P_integ.A + P_integ.B * F_integ Pfb = pyctrl.ss(Acl, P_integ.B, P_integ.C, P_integ.D) Td = np.arange(0, 8, 0.01) Ud = 0.2 * (Td>0) x, t, _ = pyctrl.lsim(Pfb, Ud, Td, [-0.3, 0.4, 0]) fig = plt.figure(figsize=(6, 5)) ax = fig.add_subplot(111) ax.plot(t, x[:,0], label = '$x_1$') ax.plot(t, x[:,1], ls = '-.',label = '$x_2$') # ax.plot(t, Ud, c='k') ax.set_xlabel('t'); ax.set_ylabel('x') ax.grid(); ax.legend() fig.tight_layout()
定常偏差を無くすことができている
オブザーバ
ここまでは、状態変数が観測可能(Cが単位行列)であるとしたが、
実際は、一部の状態変数しか観測できない
その場合は、オブザーバ
を設計して、状態変数を推定する
この推定においても、制御と同じようにフィードバックをすることができる
A = '0 1; -4 5' B = '0; 1' C = '1 0' D = '0' P = pyctrl.ss(A, B, C, D) A = '0 1; -4 5' B = '0; 1' C = '1 0; 0 1' D = '0; 0' Ps = pyctrl.ss(A, B, C, D)
オブザーバゲインの設計
オブザーバの極は、フィードバック制御の極より、
収束を早くする(負に大きくする)
# オブザーバ極 observer_poles = [-15 + 5j, -15 - 5j] # オブザーバゲインの設計(状態フィードバックの双対) L = -pyctrl.acker(P.A.T, P.C.T, observer_poles).T print(L)
[[ -35.] [-421.]]
# レギュレータ極 regulator_poles = [-5 + 5j, -5 - 5j] # 極配置 F = -pyctrl.acker(P.A, P.B, regulator_poles) print(F)
[[-46. -15.]]
Gsf = pyctrl.ss(P.A + P.B * F, P.B, np.eye(2), [[0],[0]]) Obs = pyctrl.ss(P.A + L * P.C, np.c_[P.B, -L], np.eye(2), [[0,0],[0,0]] ) fig = plt.figure(figsize=(8, 4)) ax1 = fig.add_subplot(1, 2, 1) ax2 = fig.add_subplot(1, 2, 2) T = np.arange(0, 3, 0.01) x, t = pyctrl.initial(Gsf, T, X0=[-1, 0.5]) ax1.plot(t, x[:, 0], ls='-.', label='${x}_1$') ax2.plot(t, x[:, 1], ls='-.', label='${x}_2$') # 入力 u = Fx u = [[F[0, 0] * x[i, 0] + F[0, 1] * x[i, 1]] for i in range(len(x))] # 出力 y = Cx y = x[:, 0] # オブザーバで推定した状態の振る舞い xhat, t, x0 = pyctrl.lsim(Obs, np.c_[u, y], T, [0, 0]) ax1.plot(t, xhat[:, 0], label='$\hat{x}_1$') ax2.plot(t, xhat[:, 1], label='$\hat{x}_2$') ax1.set_xlim([0, 3]); ax1.set_ylim([-2, 2]) ax1.set_xlabel('t'); ax1.set_ylabel('$x_1, \hat{x}_1$') ax1.grid(); ax1.legend() ax2.set_xlim([0, 3]) ax2.set_xlabel('t'); ax2.set_ylabel('$x_2, \hat{x}_2$') ax2.grid(); ax2.legend() fig.tight_layout()
最初は誤差があるが、最終的には推定値と実現値が一致している
オブザーバによる出力フィードバック
オブザーバの推定値を使って、状態フィードバックを実行する
# 出力フィードバック(オブザーバ+状態フィードバック) K = pyctrl.ss(P.A + P.B * F + L * P.C, -L, F, 0) # フィードバック系 Gfb = pyctrl.feedback(P, K, sign=1)
fig = plt.figure(figsize=(6, 5)) ax = fig.add_subplot(1, 1, 1) Td = np.arange(0, 3, 0.01) y, t = pyctrl.initial(Gfb, Td, [-1, 0.5, 0, 0]) ax.plot(t, y, label='Obs FB') ax.set_xlim([0, 3]); ax.set_ylim([-1.5, 3.5]) ax.set_xlabel('t'); ax.set_ylabel('y') ax.grid(); ax.legend() fig.tight_layout()
後半、かなり飛ばしてやっています
詳細を知りたい方は、参考書の購入をご検討ください
Pythonを使った制御設計の良い参考書になっています
参考文献
この記事は以下の書籍を参考にしましたが、
私の拙い知識で書いておりますので、誤り等ありましたらご指摘ください