イメージで理解するPID(pythonアニメーション)
PIDって?
フィードバック制御の一つで、比例要素、微分要素、積分要素を使用し、目標値に制御するものです。また、PIDのゲイン調整は経験則的に調整を行うことが多いので自由度の高いフィードバック制御として重宝されています。
イメージ的に理解しよう
今回は、pythonのmatplotlibを使って視覚的に理解することを目標とします。
モデルケースとしてPIDを使って車輪付きの台車を目標の位置(100m先など)に持っていくことを考えます。また、台車には摩擦が働くものとして考えます。
PIDではどの程度台車を押すかどうかを逐次的に計算することで目標位置に近づけていきます。
このとき押す力の強さを
と表すことができます。
このとき、
P要素
となります。とりあえず近づくことのみを考える要素です。目標値が遠ければ遠いほど強く台車を押そうというものです。このとき
pythonでシミュレーションしてみるとこんな感じ...
力が足りずこれ以上進みません... しかし、このままではこれはPゲインが小さいだけでは? となってしまうので、Pゲインを高くしてみると...
右に行ったり左に行ったりしています(振動している)。
また、このPゲインのみの制御では摩擦の大きさが少し変わるだけで大きく挙動が変わってしまいます。 そのため、
I要素
となります。本当に徐々に
実際に強くなっていることが分かります。(吹き飛んでしまいますが...)
先ほどの
少し行き過ぎてしまっていますが、
この制御方法をPI制御といいます。この制御では行き過ぎてしまう(オーバーシュート)ため制御としては微妙です。
D要素
PI制御の行き過ぎてしまうのは台車が加速しすぎてしまい、目標地点に到達しても止まれないということが発生します(オーバーシュート)。 そのため、加速しすぎを防ぐために目標値への近づき方を使って摩擦のようなブレーキをかけます。
今回の
理論どうりなら少しづつ振動が小さくなっていくはずです。
だんだん小さくなっていくことが分かります。確かに加速を抑制しています。 これをPD制御といいます。
これまでの三つの要素を合わせてPID制御を行うと...
綺麗に目標地点に行っていることが分かります。しかし、これでは時間がかかりすぎてるので、ゲインを調整すると...
最後に
このようにPIDを使うことで目標に瞬時に近づけることができます。
また、PIDは数値を更新する頻度を増やすとPIDの精度を高めることができます。
今回のPIDのアニメーションのpythonのコードはこのようになっています。
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import matplotlib.patches as pat
from matplotlib.animation import FuncAnimation
class controlObject:
def __init__(self,time):
self.g = 9.8
self.m = 300
self.x = 0
self.v = 0
self.k = 0.05
self.Time = time.copy()
self.fig,[self.ax,self.ax1,self.ax2] = plt.subplots(3)
self.logData = {"x":[],"v":[],"F":[]}
def move(self,dt):
self.x += self.v*dt
self.Friction(dt)
self.logData["x"].append(self.x)
self.logData["v"].append(self.v)
def Accel(self,F,dt):
self.v += F/self.m*dt
self.logData["F"].append(F)
def Friction(self,dt):
frictionForce = self.k*self.m*self.g
v = self.v
if(abs(self.v) > np.sign(self.v)*frictionForce/self.m*dt):
self.v -= np.sign(self.v)*frictionForce/self.m*dt
else:
self.v = 0.0
def animFunc(self,i):
self.ax.cla()
self.ax.set(xlim = (-10,400),ylim = (-10,30),aspect=1,title = "time : %5.3lf" % self.Time[i])
self.ax.yaxis.set_visible(False)
self.ax.set_xticks([0,200])
self.ax.set_xticklabels(["初期値","目標値"])
objRect = pat.Rectangle((self.logData["x"][i],0),10,10)
Rect = self.ax.add_patch(objRect)
self.ax1.cla()
self.ax1.set_title(r"$e_{(t)}$")
self.ax1.set(xlim = (0,self.Time[-1]),ylim = (-200,200),aspect = 0.05)
self.ax1.plot(self.Time[:i],np.array(self.logData["x"][:i])-200,c='b')
self.ax2.cla()
self.ax2.set_title(r"$U_{(t)}$")
self.ax2.set(xlim = (0,self.Time[-1]),ylim = (-10000,10000),aspect = 0.001)
self.ax2.plot(self.Time[:i],self.logData["F"][:i],c='b')
def main():
deltaT = 1
Time = np.arange(0,100,deltaT)
obj = controlObject(Time)
Target = 200
Integral = 0
Pro = 0
Gains = {"P":1.5,"I":0.00001,"D":2.0}
for i in Time:
Diff = ((Target - obj.x) - Pro)/deltaT
Integral += (Pro + (Target - obj.x))*deltaT/2.0
Pro = Target - obj.x
output = Gains["P"]*(Target - obj.x) + Gains["I"]*Integral + Gains["D"]*Diff
obj.Accel(output,deltaT)
obj.move(deltaT)
anim = FuncAnimation(obj.fig,obj.animFunc, np.arange(Time.shape[0]),interval= 1,repeat = False)
plt.show()
if __name__ == "__main__":
main()
Discussion