Search code examples
pythonpython-2.7numpyscipyodeint

Integral control system does not behave properly


Yesterday I posted a question here: ValueError and odepack.error using integrate.odeint() which I thought had been successfully answered. However I have since noticed a couple of things.

  1. When running this program it does not tend towards the desired velocity vr
  2. When running the program and varying the angle (representing the pitch of the road) as the time changes it does not always return to desired velocity vr, or even to the previous steady state.

This program is intended to model an integral control system (specifically cruise control). It currently starts from velocity v0, runs at that speed for some time, and then has the cruise control engaged. At this point we should see a change in velocity (we do) that eventually stabilizes on the desired velocity vr. It doesn't. It reaches some other value for reasons unknown, and this value is different depending on the gradient of the rode. Regardless of the initial velocity it still fails to reach the desired velocity

I've played around with the different parameters and variables to no avail. I think the issue is that the controller is not being passed the correct current velocity, however I'm unsure of how to address that issue.

If you need additional information let me know. If I should have edited the previous question just let me know and I'll do that instead, sorry in advance.

Here is my code:

import matplotlib.pylab as plt
import numpy as np
import scipy.integrate as integrate

##Parameters
kp = .5 #proportional gain
ki = .1 #integral gain
vr = 25 #desired velocity in m/s
Tm = 190 #Max Torque in Nm
wm = 420 #engine speed
B = 0.4 #Beta
an = 12 #at gear 4
p = 1.3 #air density
Cd = 0.32 #Drag coefficient
Cr = .01 #Coefficient of rolling friction
A = 2.4 #frontal area

##Variables
m = 18000.0 #weight
v0 = 20. #starting velocity
t = np.linspace(61, 500, 5000) #time
theta = np.radians(4) #Theta

def torque(v):    
    return Tm * (1 - B*(an*v/wm - 1)**2)  

def vderivs(v, t):
    v1 = an * controller(v, t) * torque(v)
    v2 = m*Cr*np.sign(v)
    v3 = 0.5*p*Cd*A*v**2
    v4 = m*np.sin(theta)
    vtot = v1-v2-v3-v4*(t>=200)
    return vtot/m

def uderivs(v, t):
    return vr - v

def controller(currentV, time):
    z = integrate.odeint(uderivs, currentV, time)
    return kp*(vr-currentV) + ki*z.squeeze()

def velocity(desired, theta, time):
    return integrate.odeint(vderivs, desired, time)

t0l = [i for i in range(61)]
vf=[v0 for i in range(61)]+[v for v in velocity(v0,theta,t)]
tf=t0l+[time for time in t]

plt.plot(tf, vf, 'k-', label=('V(0) = '+str(v0)))

v0=35.
vf=[v0 for i in range(61)]+[v for v in velocity(v0,theta,t)]
plt.plot(tf, vf, 'b-', label=('V(0) = '+str(v0)))

v0=vr
vf=[v0 for i in range(61)]+[v for v in velocity(v0,theta,t)]
plt.plot(tf, vf, 'g-', label=('V(0) = Vr'))

plt.axhline(y=vr, xmin=0, xmax=1000, color='r', label='Desired Velocity')
plt.legend(loc = "upper right")
plt.axis([0,500,18,36])
plt.show()

This is what is plotted running the program as is, using a range of initial velocities Cruise Control

The first sharp change in velocity is when the cruise control is initiated, and the second is when the gradient of the rode changes


Solution

  • Your guess is correct. The target system and the PI controller is integrated, you can't separate it into two odeint. I modified your code, that the system has two status variables: one for the velocity of the system, one for the integration of the control error:

    import matplotlib.pylab as plt
    import numpy as np
    import scipy.integrate as integrate
    
    ##Parameters
    kp = .5 #proportional gain
    ki = .1 #integral gain
    vr = 25 #desired velocity in m/s
    Tm = 190 #Max Torque in Nm
    wm = 420 #engine speed
    B = 0.4 #Beta
    an = 12 #at gear 4
    p = 1.3 #air density
    Cd = 0.32 #Drag coefficient
    Cr = .01 #Coefficient of rolling friction
    A = 2.4 #frontal area
    
    ##Variables
    m = 18000.0 #weight
    v0 = 20. #starting velocity
    t = np.linspace(61, 500, 5000) #time
    theta = np.radians(4) #Theta
    
    def torque(v):    
        return Tm * (1 - B*(an*v/wm - 1)**2)  
    
    def vderivs(status, t):
        v, int_err = status
    
        err = vr - v
        control = kp * err + ki * int_err
    
        v1 = an * control * torque(v)
        v2 = m*Cr*np.sign(v)
        v3 = 0.5*p*Cd*A*v**2
        v4 = m*np.sin(theta)
        vtot = v1-v2-v3-v4*(t>=200)
        return vtot/m, err
    
    def velocity(desired, theta, time):
        return integrate.odeint(vderivs, [desired, 0], time)[:, 0]
    
    t0l = [i for i in range(61)]
    vf=[v0 for i in range(61)]+[v for v in velocity(v0,theta,t)]
    tf=t0l+[time for time in t]
    
    plt.plot(tf, vf, 'k-', label=('V(0) = '+str(v0)))
    
    v0=35.
    vf=[v0 for i in range(61)]+[v for v in velocity(v0,theta,t)]
    plt.plot(tf, vf, 'b-', label=('V(0) = '+str(v0)))
    
    v0=vr
    vf=[v0 for i in range(61)]+[v for v in velocity(v0,theta,t)]
    plt.plot(tf, vf, 'g-', label=('V(0) = Vr'))
    
    plt.axhline(y=vr, xmin=0, xmax=1000, color='r', label='Desired Velocity')
    plt.legend(loc = "upper right")
    plt.axis([0,500,18,36])
    plt.show()
    

    output:

    enter image description here