前言
PID控制器不仅仅用于智能硬件的控制,只要是有用到简单的反馈,那就一定用到过比例、积分、微分来对反馈量计算。一个PID反馈函数会涉及这样三个操作,一般用拉布拉斯变换后的结果表示。想到函数可能进行泰勒展开,我猜测比PID更简单的操作就是用多项式来表示反馈函数。
"准备·缓存"系统
给出这样一个例子
- 我们负责管理一个系统,它把item发送到下游的一个处理步骤(就是另一个模块)
- 下游系统有一个存放item的缓存区
- 在每一次操作中,下游的系统要从缓存区里拿一些item进行处理,被处理过的item将被移出缓存区(移出后的这里不考虑)
- 我们不能直接把item放到下游的缓存区,只能把其放入“准备区”中,这些item最终会被运输到下游缓存区
- item一旦被放进准备区,我们对其就不能任何控制:它们会被运输到下游缓存区,受到其他设备的控制
- 下游系统完成item处理的数量(第3步)以及从准备区移动到下游缓存区(第5步)的item数量都会随机波动
- 每个步骤,我们都需要决定放多少item到准备区,以保证下游缓存区能满负荷但又不会溢出——事实上,下游系统的管理者更希望我们保证缓存区的item数量不变
模拟缓存系统
1,首先,我们放入准备区的item数量只有小于准备区长度(max_wip)或者等于准备区长度(max_wip)——小于零的情况可以转换成这两种。
2,然后再从这样的长度里随机(这里用高斯分布随机)一小节长度,准备区减少这节长度,缓存区加上这节长度。
3,以缓存区长度(max_flow)为最大随机一节长度,用这节长度与缓存区已有的item数量相比,用原来的缓存区长度(queued)减去这两个比较后的最小的长度/数量就是这个缓存区的长度。
输入:情况A或情况B的长度(数量)
输出:最后情况C或情况D的长度(数量)
'''
buffer region and ready region
wip : the number of item of ready region
flow : the number of item of buffer region
'''
class Buffer:
def __init__(self,max_wip,max_flow):
self.queued=0
self.wip=0 #work-in-progress("ready pool")
self.max_wip=max_wip
self.max_flow=max_flow #avg outflow is max_flow/2
def work(self,num):
#add to ready pool
num=max(0,int(round(num)))
num=min(num,self.max_wip)
self.wip+=num
#transfer from ready pool to queue
r=int(round(random.uniform(0,self.wip)))
self.wip-=r
self.queued+=r
#release from queue to downstream process
r=int(round(random.uniform(0,self.max_flow)))
r=min(r,self.queued)
self.queued-=r
return self.queued #return the value of y
不加控制器的模拟。看看自由状态下缓存系统会如何运作。
import random
import numpy as np
import matplotlib.pyplot as plt
class Buffer:
...
def open_loop(buffer,tm=5000):
y=0
global yDir #ignore the function of shadowing
for t in range(tm):
yDir.append(y) #save the number which out of buffer
r=5
y=buffer.work(r)
print("t=%d,r=%d,error=%d,num=%d,y=%d"%(t,r,r-y,r,y))
yDir=[]
b=Buffer(20,10)
open_loop(b,1000)
#paint the carve
#实际的输出数量的曲线
xS=np.arange(0,1000,1)
yS=yDir
plt.plot(xS,yS,color="g",linestyle="-",linewidth=1.0,label="queue's length")
plt.xlabel("time step")
plt.ylabel("number of item")
plt.show()
模拟控制器
中间item数量随机的,我们无法控制,最后缓存区出去的item数量也是随机的,我们也无法控制。但是有了两次随机的数值,我们可以用反馈来控制输入从而间接达到控制输出的目的。当然,一个合理的黑箱只有一个输出,所以这里我们就只以最后随机的来构造反馈函数。
所谓的PID,就是一个反馈函数里拥有比例运算,积分运算和微分运算(用拉布拉斯转换后就是乘法和除法)而已。PID不过是对这类函数的说法。
设定一个目标,减去缓存区长度,得到的就是偏差error或bias。再用偏差和充当积分项,这样得到的输入item数量公式为
'''
control system
kp : proportionality coefficient
ki : integral coefficient
self.i : cumulative deviation
'''
class Controller:
def __init__(self,kp,ki):
self.kp,self.ki=kp,ki
self.i =0 #cumulative error ("integral")
def work(self,error):
self.i+=error
return self.kp*error + self.ki*self.i
加入控制器,设定目标曲线之后。
import random
import numpy as np
import matplotlib.pyplot as plt
class Buffer:
...
class Controller:
...
def closed_loop(buffer,controller,tm=5000):
def target(t):
if t<100:return 0
if t<300:return 50
return 10
y=0
global yDir #ignore the function of shadowing
for t in range(tm):
yDir.append(y) #save the number which out of buffer
r=target(t)
error = r-y
num=controller.work(error) #num=kp*error + ki*i
y=buffer.work(num)
print("t=%d,r=%d,error=%d,num=%d,y=%d"%(t,r,error,num,y)) #大概满足情况就不需要在浪费性能了
yDir=[]
b=Buffer(20,10)
c=Controller(1.25,0.01) #比例系数1.25 积分系数0.01
closed_loop(b,c,1000)
#paint the carve
#实际的输出数量的曲线
xS=np.arange(0,1000,1)
yS=yDir
#目标曲线
xTarget=[0,100,100,300,300,999]
yTarget=[0,0,50,50,10,10]
plt.plot(xS,yS,color="g",linestyle="-",linewidth=1.0,label="queue's length")
plt.plot(xTarget,yTarget,color="b",linestyle="-",linewidth=1.0,label="wanted length")
plt.legend(loc="upper right",bbox_to_anchor=(0.9,0.95))
plt.xlabel("time step")
plt.ylabel("number of item")
plt.show()
在这个系统中,可以设定的是准备区和缓存区的长度,目标曲线和积分系数与比例系数。上面放的两张图算是其中很优秀的结果了,两个区的长度到50左右控制器就不起什么作用了。
缓存系统长度越短,越容易稳定。越长控制器的作用就越小。
还算是有点反馈控制的意思了。
来自《企业级编程与控制理论》