Main

## Stability Analysis

Stability analysis is finding the range of controller gains that lead to a stabilizing controller. There are multiple methods to compute this range between a lower limit K_{cL} and an upper limit K_{cL}.

$$K_{cL} \le K_c \le K_{cU}$$

This range is important to know the range of tuning values that will not lead to a destabilizing controller.

#### Exercise

Consider a feedback control system that has the following open loop transfer function.

$$G(s) = \frac{4K_c}{(s+1)(s+2)(s+3)}$$

Determine the values of K_c that keep the closed loop system response stable.

#### Solution

Root Locus Plot

Determine where the real portion of the roots crosses to the right hand side of the plane. In this case, the real part of two roots becomes positive at K_c=15.

Bode Plot

Determine the gain margin at -180o phase. The magnitude at -180o phase is about -23 dB. With -23 = 20 log_{10} (AR), the gain margin is 1/{AR} and approximately equal to 15. This is the upper bound on the controller gain to keep the system stable. This answer agrees with the root locus plot solution.

import numpy as np
from scipy import signal
import matplotlib.pyplot as plt

# open loop
num = [4.0]
den = [1.0,6.0,11.0,6.0]
sys = signal.TransferFunction(num, den)
t1,y1 = signal.step(sys)

# closed loop
Kc = 1.0
num = [4.0*Kc]
den = [1.0,6.0,11.0,4.0*Kc+6.0]
sys2 = signal.TransferFunction(num, den)
t2,y2 = signal.step(sys2)

plt.figure(1)
plt.subplot(2,1,1)
plt.plot(t1,y1,'k-')
plt.legend(['Open Loop'],loc='best')

plt.subplot(2,1,2)
plt.plot(t2,y2,'r--')
plt.legend(['Closed Loop'],loc='best')
plt.xlabel('Time')

# root locus plot
import numpy.polynomial.polynomial as poly
n = 1000  # number of points to plot
nr = 3    # number of roots
rs = np.zeros((n,2*nr))   # store results
Kc = np.logspace(-2,2,n)  # Kc values
for i in range(n):        # cycle through n times
den = [1.0,6.0,11.0,4.0*Kc[i]+6.0] # polynomial
roots = poly.polyroots(den) # find roots
for j in range(nr):   # store roots
rs[i,j] = roots[j].real # store real
rs[i,j+nr] = roots[j].imag # store imaginary
plt.figure(2)
plt.subplot(2,1,1)
plt.xlabel('Root (real)')
plt.ylabel('Root (imag)')
plt.grid(b=True, which='major', color='b', linestyle='-')
plt.grid(b=True, which='minor', color='r', linestyle='--')
for i in range(nr):
plt.plot(rs[:,i],rs[:,i+nr],'.')
plt.subplot(2,1,2)
plt.plot([Kc[0],Kc[-1]],[0,0],'k-')
for i in range(3):
plt.plot(Kc,rs[:,i],'.')
plt.ylabel('Root (real part)')
plt.xlabel('Controller Gain (Kc)')

# bode plot
w,mag,phase = signal.bode(sys)
plt.figure(3)
plt.subplot(2,1,1)
plt.semilogx(w,mag,'k-',linewidth=3)
plt.grid(b=True, which='major', color='b', linestyle='-')
plt.grid(b=True, which='minor', color='r', linestyle='--')
plt.ylabel('Magnitude')

plt.subplot(2,1,2)
plt.semilogx(w,phase,'k-',linewidth=3)
plt.grid(b=True, which='major', color='b', linestyle='-')
plt.grid(b=True, which='minor', color='r', linestyle='--')
plt.ylabel('Phase')
plt.xlabel('Frequency')

plt.show()