pythonでPID制御

PID制御とは何か、pythonでシミュレーションをしながら解説します。シミュレーションについては以下の記事で紹介しています。

まず、質量のある物体( 制御対象 )に(制御入力)を加え、ある値に収束させることを考えます。

下図は縦軸が位置、横軸が時間を表しています。このようにxの値が0に収束するような制御入力の与え方をいろいろと試していきます。

pd制御

ON-OFF制御

xの値を0に収束させたい。そんなとき、どうすれば良いか。一番に思い付くのは、xが大きければ負の方向に力を加え、xが小さければ正の方向に力を加えることでしょう。

import matplotlib.pyplot as plt
x_list = []
t_list = []
dt = 0.1
m = 1.0		#質量[kg]
x = 1		#初期位置[m]
v = 0		#初速度[m/s]
f = 1		#入力[N]
def F(e):       #制御入力
	if x>0.8:
		u = -f
	elif x<-0.8:
		u = f
	else:
		u = 0
	return u

for i in range(int(50/dt)):
	t = i*dt
	
	x += dt*v
	v += dt*F(x)/m
	
	x_list.append(x)
	t_list.append(t)

plt.plot(t_list,x_list,color ='b')
plt.title('ON-OFF')
plt.xlabel('t[s]')
plt.ylabel('x[m]')

plt.show()

だめだ、収束しない

P制御( Proportional Controller )

目標値と現在値の差に応じて、力の入力を変えればうまくいくのではないか。つまり、目標値との差に比例した制御入力を与える。

import matplotlib.pyplot as plt
x_list = []
t_list = []
dt = 0.1
m = 1.0			#質量[kg]
x = 1			#初期位置[m]
v = 0			#初速度[m/s]
Kp = 0.3		#比例ゲイン
for i in range(int(50/dt)):
	t = i*dt
	
	x += dt*v
	F = -x*Kp
	v += dt*F/m
	
	x_list.append(x)
	t_list.append(t)

plt.plot(t_list,x_list,color ='b')
plt.title('Proportional-Controller')
plt.xlabel('t[s]')
plt.ylabel('x[m]')

plt.show()
p制御

これは単振動。"位置に比例した力"はバネと同じなので、収束しない。(抵抗とかあったら収束する)

PD制御( Proportional-Differential Controller )

速度(位置の微分値)に比例した制御入力を追加する。変化を抑制する。

import matplotlib.pyplot as plt
x_list = []
t_list = []
dt = 0.1
m = 1.0			#質量[kg]
x = 1			#初期位置[m]
v = 0			#初速度[m/s]
Kp = 0.3		#比例ゲイン
Kd = 0.3		#微分ゲイン
for i in range(int(50/dt)):
	t = i*dt
	
	x += dt*v
	F = -x*Kp -v*Kd
	v += dt*F/m
	
	x_list.append(x)
	t_list.append(t)

plt.plot(t_list,x_list,color ='b')
plt.title('Proportional-Differential-Controller')
plt.xlabel('t[s]')
plt.ylabel('x[m]')

plt.show()
pd制御

収束した。

ここではノイズを考慮しないが、ノイズがある場合は微分値にローパスフィルタをかける必要がある。 (ノイズでギザギザの値は微分できないから)ローパスフィルタをかけた微分を不完全微分という。ローパスフィルタについては以下の記事で紹介しています。

PID制御(Proportional-Integral-Differential Controller)

PD制御だと、目標値付近ではほとんど入力が無くなる→無限時間経たないと完全には0に収束しない。そこで、積分成分を追加する。いつまでも正の位置にあれば負の方向へ、負の位置に居れば正の方向へ制御入力が増えていく。

import matplotlib.pyplot as plt
x_list = []
t_list = []
dt = 0.1
m = 1.0			#質量[kg]
x = 1			#初期位置[m]
v = 0			#初速度[m/s]
w = 0
Kp = 0.2		#比例ゲイン
Kd = 0.5		#微分ゲイン
Ki = 0.01		#積分ゲイン
for i in range(int(50/dt)):
	t = i*dt
	
	x += dt*v
	w += dt*x
	F = -x*Kp -v*Kd -w*Ki
	v += dt*F/m
	
	x_list.append(x)
	t_list.append(t)

plt.plot(t_list,x_list,color ='b')
plt.title('Proportional-Differential-Controller')
plt.xlabel('t[s]')
plt.ylabel('x[m]')

plt.show()
pid制御

あとは、ゲインKp,Ki,Kdをうまく調節すればきれいに収束するようになる。

アニメーションにするとこんな感じ

pidのアニメーション

アニメーションはArtistAnimationを使って作成

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
fig, ax = plt.subplots()
ims = []
dt = 0.01
m = 1.0				#質量[kg]
Kp = 0.0			
Kd = 0.0
Ki = 0.0	
for j in range(100):
	x_list = []
	t_list = []
	if Kp < 0.2:		#比例ゲイン
		Kp += 0.01
	elif Ki < 0.001:	#積分ゲイン
		Ki += 0.001
	elif Kd < 0.5:		#微分ゲイン
		Kd += 0.01
	x = 1				#初期位置[m]
	v = 0				#初速度[m/s]
	w = 0
	for i in range(int(50/dt)):
		t = i*dt
		x += dt*v
		w += dt*x
		F = -x*Kp -v*Kd -w*Ki
		v += dt*F/m
		
		x_list.append(x)
		t_list.append(t)
	
	tx1 = "Kp=" + str(round(Kp,3))
	tx2 = "Ki=" + str(round(Ki,3))
	tx3 = "Kd=" + str(round(Kd,3))
	title1 = ax.text(0,-0.3,tx1)
	title2 = ax.text(0,-0.45,tx2)
	title3 = ax.text(0,-0.6,tx3)
	
	im = plt.plot([0,50],[0,0],color='r')
	im += plt.plot(t_list,x_list,color ='b')
	ims.append(im + [title1] + [title2] + [title3])
	
ani = animation.ArtistAnimation(fig,ims,interval=100)
plt.ylim(-1,1)
plt.xlim(0,50)
plt.xlabel('t[s]')
plt.ylabel('x[m]')

plt.show()

コメント