def bode(G, w_start=-2, w_end=2, axlim=None, points=1000, margin=False): """ Shows the bode plot for a plant model Parameters ---------- G : tf Plant transfer function. margin : boolean Show the cross over frequencies on the plot (optional). Returns ------- GM : array containing a real number Gain margin. PM : array containing a real number Phase margin. Plot : matplotlib figure """ if axlim is None: axlim = [None, None, None, None] plt.clf() plt.gcf().set_facecolor('white') GM, PM, wc, w_180 = utils.margins(G) # plotting of Bode plot and with corresponding frequencies for PM and GM # if ((w2 < numpy.log(w_180)) and margin): # w2 = numpy.log(w_180) w = numpy.logspace(w_start, w_end, points) s = 1j*w # Magnitude of G(jw) plt.subplot(2, 1, 1) gains = numpy.abs(G(s)) plt.loglog(w, gains) if margin: plt.axvline(w_180, color='black') plt.text(w_180, numpy.average([numpy.max(gains), numpy.min(gains)]), r'$\angle$G(jw) = -180$\degree$') plt.axhline(1., color='red', linestyle='--') plt.axis(axlim) plt.grid() plt.ylabel('Magnitude') # Phase of G(jw) plt.subplot(2, 1, 2) phaseangle = utils.phase(G(s), deg=True) plt.semilogx(w, phaseangle) if margin: plt.axvline(wc, color='black') plt.text(wc, numpy.average([numpy.max(phaseangle), numpy.min(phaseangle)]), '|G(jw)| = 1') plt.axhline(-180., color='red', linestyle='--') plt.axis(axlim) plt.grid() plt.ylabel('Phase') plt.xlabel('Frequency [rad/unit time]') return GM, PM
def bode(G, w_start=-2, w_end=2, axlim=None, points=1000, margin=False): """ Shows the bode plot for a plant model Parameters ---------- G : tf Plant transfer function. margin : boolean Show the cross over frequencies on the plot (optional). Returns ------- GM : array containing a real number Gain margin. PM : array containing a real number Phase margin. Plot : matplotlib figure """ s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points) plt.clf() GM, PM, wc, w_180 = utils.margins(G) # plotting of Bode plot and with corresponding frequencies for PM and GM # if ((w2 < numpy.log(w_180)) and margin): # w2 = numpy.log(w_180) # Magnitude of G(jw) plt.subplot(2, 1, 1) gains = numpy.abs(G(s)) plt.loglog(w, gains) if margin: plt.axvline(w_180, color='black') plt.text(w_180, numpy.average([numpy.max(gains), numpy.min(gains)]), r'$\angle$G(jw) = -180$\degree$') plt.axhline(1., color='red', linestyle='--') plt.axis(axlim) plt.grid() plt.ylabel('Magnitude') # Phase of G(jw) plt.subplot(2, 1, 2) phaseangle = utils.phase(G(s), deg=True) plt.semilogx(w, phaseangle) if margin: plt.axvline(wc, color='black') plt.text(wc, numpy.average([numpy.max(phaseangle), numpy.min(phaseangle)]), '|G(jw)| = 1') plt.axhline(-180., color='red', linestyle='--') plt.axis(axlim) plt.grid() plt.ylabel('Phase') plt.xlabel('Frequency [rad/unit time]') return GM, PM
def rule6(G, Gm, message=False): """ This is rule six of chapter five Calculates if tight control at low frequencies with RHP-zeros is possible Parameters ---------- G : tf plant model Gm : tf measurement model message : boolean show the rule message (optional) Returns ------- valid6 : boolean value if rule conditions was met wc : crossover frequency where | G(jwc) | = 1 wd : crossover frequency where | Gd(jwd) | = 1 """ GGm = G * Gm zeros = GGm.zeros() _, _, wc, _ = margins(GGm) wz = 0 if len(zeros) > 0: if np.imag(np.min(zeros)) == 0: # If the roots aren't imaginary. # Looking for the minimum values of the zeros => # results in the tightest control. wz = (np.min(np.abs(zeros))) / 2 valid6 = wc < 0.86 * np.abs(wz) else: wz = 0.86 * np.abs(np.min(zeros)) valid6 = wc < wz / 2 else: valid6 = False if message: print('Rule 6:') if wz != 0: print('These are the roots of the transfer function ' 'matrix GGm', zeros) if valid6: print( 'The critical frequency of S for the system to be ' 'controllable is', wz) else: print('No zeros in the system to evaluate') return valid6, wz
def rule6(G, Gm, message=False): """ This is rule six of chapter five Calculates if tight control at low frequencies with RHP-zeros is possible Parameters ---------- G : tf plant model Gm : tf measurement model message : boolean show the rule message (optional) Returns ------- valid6 : boolean value if rule conditions was met wc : crossover frequency where | G(jwc) | = 1 wd : crossover frequency where | Gd(jwd) | = 1 """ GGm = G * Gm zeros = GGm.zeros() _, _, wc, _ = margins(GGm) wz = 0 if len(zeros) > 0: if np.imag(np.min(zeros)) == 0: # If the roots aren't imaginary. # Looking for the minimum values of the zeros => # results in the tightest control. wz = (np.min(np.abs(zeros)))/2 valid6 = wc < 0.86*np.abs(wz) else: wz = 0.86*np.abs(np.min(zeros)) valid6 = wc < wz/2 else: valid6 = False if message: print('Rule 6:') if wz != 0: print('These are the roots of the transfer function ' 'matrix GGm', zeros) if valid6: print('The critical frequency of S for the system to be ' 'controllable is', wz) else: print('No zeros in the system to evaluate') return valid6, wz
def rule5(G, Gm=1, message=False): """ This is rule five of chapter five Calculates constraints for time delay, wc < 1 / theta Parameters ---------- G : tf plant model Gm : tf measurement model message : boolean show the rule message (optional) Returns ------- valid5 : boolean value if rule conditions was met wtd: real time delay frequency """ GGm = G * Gm TimeDelay = GGm.deadtime _, _, wc, _ = margins(GGm) valid5 = False if TimeDelay == 0: wtd = 0 else: wtd = 1 / TimeDelay valid5 = wc < wtd if message: print('Rule 5:') if TimeDelay == 0: print("There isn't any deadtime in the system") if valid5: print('wc < 1 / theta :', np.round(wc, 2), '<', np.round(wtd, 2)) else: print('wc > 1 / theta :', np.round(wc, 2), '>', np.round(wtd, 2)) return valid5, wtd
def rule7(G, Gm, message=False): """ This is rule seven of chapter five Calculates the phase lag constraints Parameters ---------- G : tf plant model Gm : tf measurement model message : boolean show the rule message (optional) Returns ------- valid7 : boolean value if rule conditions was met wc : real crossover frequency where | G(jwc) | = 1 wd : real crossover frequency where | Gd(jwd) | = 1 """ # Rule 7 determining the phase of GGm at -180 deg. # This is solved visually from a plot. GGm = G*Gm _, _, wc, wu = margins(GGm) valid7 = wc < wu if message: print('Rule 7:') if valid7: print('wc < wu :', wc, '<', wu) else: print('wc > wu :', wc, '>', wu) return valid7, wu
def rule7(G, Gm, message=False): """ This is rule seven of chapter five Calculates the phase lag constraints Parameters ---------- G : tf plant model Gm : tf measurement model message : boolean show the rule message (optional) Returns ------- valid1 : boolean value if rule conditions was met wc : real crossover frequency where | G(jwc) | = 1 wd : real crossover frequency where | Gd(jwd) | = 1 """ # Rule 7 determining the phase of GGm at -180 deg. # This is solved visually from a plot. GGm = G * Gm _, _, wc, wu = margins(GGm) valid7 = wc < wu if message: print('Rule 7:') if valid7: print('wc < wu :', wc, '<', wu) else: print('wc > wu :', wc, '>', wu) return valid7, wu
def rule8(G, message=False): ''' This is rule one of chapter five This function determines if the plant is open-loop stable at its poles Parameters ---------- G : tf plant model Gd : tf plant distrubance model message : boolean show the rule message (optional) Returns ------- valid1 : boolean value if rule conditions was met wc : real crossover frequency where | G(jwc) | = 1 ''' #Rule 8 for critical frequency min value due to poles poles = np.roots(G.denominator) _,_,wc,_ = margins(G) wp = 0 if np.max(poles) < 0: wp = 2 * np.max(np.abs(poles)) valid8 = wc > wp else: valid8 = False if message: print 'Rule 8:' if valid8: print 'wc > 2p :', wc , '>' , wp else: print 'wc < 2p :', wc , '<' , wp return valid8, wp
def rule8(G, message=False): """ This is rule eight of chapter five This function determines if the plant is open-loop stable at its poles Parameters ---------- G : tf plant model message : boolean show the rule message (optional) Returns ------- valid1 : boolean value if rule conditions was met wc : real crossover frequency where | G(jwc) | = 1 """ # Rule 8 for critical frequency min value due to poles poles = G.poles() _, _, wc, _ = margins(G) wp = 0 if np.max(poles) < 0: wp = 2 * np.max(np.abs(poles)) valid8 = wc > wp else: valid8 = False if message: print('Rule 8:') if valid8: print('wc > 2p :', wc, '>', wp) else: print('wc < 2p :', wc, '<', wp) return valid8, wp
def rule8(G, message=False): """ This is rule eight of chapter five This function determines if the plant is open-loop stable at its poles Parameters ---------- G : tf plant model message : boolean show the rule message (optional) Returns ------- valid8 : boolean value if rule conditions were met wc : real crossover frequency where | G(jwc) | = 1 """ # Rule 8 for critical frequency min value due to poles poles = G.poles() _, _, wc, _ = margins(G) wp = 0 if np.max(poles) < 0: wp = 2*np.max(np.abs(poles)) valid8 = wc > wp else: valid8 = False if message: print('Rule 8:') if valid8: print('wc > 2p :', wc, '>', wp) else: print('wc < 2p :', wc, '<', wp) return valid8, wp
import matplotlib.pyplot as plt import utilsplot import numpy as np s = tf([1,0], 1) z = 0.1 tau = 1 L = (-s + z)/(s*(tau*s + tau*z + 2)) T = (-s + z)/((s + z)*(tau*s + 1)) S = 1/(1 + L) mT = maxpeak(T) mS = maxpeak(S) GM, PM, wc, wb, wbt, valid = marginsclosedloop(L) GM, PM, wc, w_180 = margins(L) print('GM = ', np.round(GM, 1)) print('PM = ', np.round(PM, 1), 'rad') print('wB = ', np.round(wb, 3)) print('wC = ', np.round(wc, 3)) print('wBT = ', wbt) print('w180 = ', np.round(w_180, 3)) print('Ms = ', np.round(mS, 2)) print('Mt = ', np.round(mT, 1)) [t, y] = tf_step(T, 50) plt.figure('Figure 2.17') plt.title('Step response for system T') plt.plot(t, y) plt.xlabel('time (s)') plt.ylabel('y(t)')
from utils import tf, margins import Chapter_05 as ch5 s = tf([1, 0], 1) # Example plant based on Example 2.9 and Example 2.16 G = (s + 200) / ((10 * s + 1) * (0.05 * s + 1)**2) G.deadtime = 0.002 Gd = 33 / (10 * s + 1) K = 0.4 * ((s + 2) / s) * (0.075 * s + 1) L = G * K w = np.logspace(-3, 3) GM, PM, wc, wu = margins(G) GM, PM, wd, w_180 = margins(Gd) [valid6, wz] = ch5.rule6(G, 1) [valid5, wtd] = ch5.rule5(G) [valid8, wp] = ch5.rule8(G) print 'wc: ', np.round(wc, 3) print 'wd: ', np.round(wd, 3) print 'wu: ', np.round(wu, 3) wuy = abs(G((1j * wu))) wzy = abs(G((1j * wz))) wpy = abs(G((1j * wp))) wtdy = abs(G((1j * wtd)))
def rule1(G, Gd, K=1, message=False, plot=False, w1=-4, w2=2): """ This is rule one of chapter five Calculates the speed of response to reject distrurbances. Condition require |S(jw)| <= |1/Gd(jw)| Parameters ---------- G : tf plant model Gd : tf plant distrubance model K : tf control model message : boolean show the rule message (optional) plot : boolean show the bode plot with constraints (optional) w1 : integer start frequency, 10^w1 (optional) w2 : integer end frequency, 10^w2 (optional) Returns ------- valid1 : boolean value if rule conditions was met wc : real crossover frequency where | G(jwc) | = 1 wd : real crossover frequency where | Gd(jwd) | = 1 """ _, _, wc, _ = margins(G) _, _, wd, _ = margins(Gd) valid1 = wc > wd if message: print('Rule 1: Speed of response to reject distrubances') if valid1: print('First condition met, wc > wd') else: print('First condition no met, wc < wd') print('Seconds conditions requires |S(jw)| <= |1/Gd(jw)|') if plot: plt.figure('Rule 1') w, mag_s = df.setup_plot(['|S|', '1/R', '$w_r$'], w1, w2, G, K, wr) inv_gd = 1 / Gd mag_i = np.abs(inv_gd(s)) plt.loglog(w, mag_s) plt.loglog(w, mag_i, ls='--') df.setup_plot(['|S|', '1/|Gd|']) return valid1, wc, wd
def rule1(G, Gd, K=1, message=False, plot=False, w1=-4, w2=2): """ This is rule one of chapter five Calculates the speed of response to reject disturbances. Condition require |S(jw)| <= |1/Gd(jw)| Parameters ---------- G : tf plant model Gd : tf plant distrubance model K : tf control model message : boolean show the rule message (optional) plot : boolean show the bode plot with constraints (optional) w1 : integer start frequency, 10^w1 (optional) w2 : integer end frequency, 10^w2 (optional) Returns ------- valid1 : boolean value if rule conditions was met wc : real crossover frequency where | G(jwc) | = 1 wd : real crossover frequency where | Gd(jwd) | = 1 """ _, _, wc, _ = margins(G) _, _, wd, _ = margins(Gd) valid1 = wc > wd if message: print('Rule 1: Speed of response to reject distrubances') if valid1: print('First condition met, wc > wd') else: print('First condition not met, wc < wd') print('Second condition requires |S(jw)| <= |1/Gd(jw)|') if plot: w = np.logspace(w1, w2, 1000) s = 1j * w S = 1 / (1 + G * K) gain = np.abs(S(s)) inv_gd = 1 / Gd mag_i = np.abs(inv_gd(s)) plt.figure('Rule 1') plt.loglog(w, gain, label='|S|') plt.loglog(w, mag_i, ls='--', label='|1/G_d |') plt.xlabel('Frequency [rad/s]') plt.ylabel('Magnitude') plt.legend(bbox_to_anchor=(0, 1.01, 1, 0), loc=3, ncol=3) plt.show() return valid1, wc, wd
import utilsplot import numpy as np s = tf([1, 0], 1) z = 0.1 tau = 1 L = (-s+z)/(s*(tau*s + tau*z + 2)) T = (-s+z)/((s+z)*(tau*s+1)) S = 1/(1+L) mT = maxpeak(T) mS = maxpeak(S) GM, PM, wc, wb, wbt, valid = marginsclosedloop(L) GM, PM, wc, w_180 = margins(L) print('GM = ', np.round(GM, 1)) print('PM = ', np.round(PM, 1), 'rad') print('wB = ', np.round(wb, 3)) print('wC = ', np.round(wc, 3)) print('wBT = ', wbt) print('w180 = ', np.round(w_180, 3)) print('Ms = ', np.round(mS, 2)) print('Mt = ', np.round(mT, 1)) [t, y] = tf_step(T, 50) plt.figure('Figure 2.17') plt.plot(t, y) plt.xlabel('time (s)') plt.ylabel('y(t)') plt.show()
import utils import numpy as np import sympy as sp from scipy.optimize import fsolve s = utils.tf([1,0],[1]) L = (-s + 2)/(s*(s + 2)) _,_,_,w180 = utils.margins(L) GM = 1/np.abs(L(1j*w180)) print('w_180',w180) print('GM = 1/|L(w180)|',GM) print('From 7.55, kmax = GM = ',GM) omega = np.logspace(-2,2,1000) def rk(kmax): return (kmax - 1)/(kmax + 1) def kbar(kmax): return (kmax + 1)/2 def RScondition(kmax): abs_ineq = [abs(rk(kmax) * kbar(kmax) * L(1j*s)/(1 + kbar(kmax)*L(1j*s))) for s in omega] max_ineq = max(abs_ineq) - 1 return max_ineq kcal = fsolve(RScondition,1) print('From 7.58, kmax = ',np.round(kcal,2))
def rule1(G, Gd, K=1, message=False, plot=False, w1=-4, w2=2): """ This is rule one of chapter five Calculates the speed of response to reject disturbances. Condition require |S(jw)| <= |1/Gd(jw)| Parameters ---------- G : tf plant model Gd : tf plant disturbance model K : tf control model message : boolean show the rule message (optional) plot : boolean show the bode plot with constraints (optional) w1 : integer start frequency, 10^w1 (optional) w2 : integer end frequency, 10^w2 (optional) Returns ------- valid1 : boolean value if rule conditions were met wc : real crossover frequency where | G(jwc) | = 1 wd : real crossover frequency where | Gd(jwd) | = 1 """ _, _, wc, _ = margins(G) _, _, wd, _ = margins(Gd) valid1 = wc > wd if message: print('Rule 1: Speed of response to reject disturbances') if valid1: print('First condition met, wc > wd') else: print('First condition not met, wc < wd') print('Second condition requires |S(jw)| <= |1/Gd(jw)|') if plot: w = np.logspace(w1, w2, 1000) s = 1j*w S = 1/(1+G*K) gain = np.abs(S(s)) inv_gd = 1/Gd mag_i = np.abs(inv_gd(s)) plt.figure('Rule 1') plt.loglog(w, gain, label='|S|') plt.loglog(w, mag_i, ls='--', label='|1/G_d |') plt.xlabel('Frequency [rad/s]') plt.ylabel('Magnitude') plt.legend(bbox_to_anchor=(0, 1.01, 1, 0), loc=3, ncol=3) plt.show() return valid1, wc, wd
import Chapter_05 as ch5 s = tf([1, 0], 1) # Example plant based on Example 2.9 and Example 2.16 G = (s + 200) / ((10 * s + 1) * (0.05 * s + 1)**2) G.deadtime = 0.002 Gd = 33 / (10 * s + 1) K = 0.4 * ((s + 2) / s) * (0.075 * s + 1) L = G * K w = np.logspace(-3,3) GM, PM, wc, wu = margins(G) GM, PM, wd, w_180 = margins(Gd) [valid6, wz] = ch5.rule6(G, 1) [valid5, wtd] = ch5.rule5(G) [valid8, wp] = ch5.rule8(G) print 'wc: ' , np.round(wc, 3) print 'wd: ' , np.round(wd, 3) print 'wu: ' , np.round(wu, 3) wuy = abs(G((1j*wu))) wzy = abs(G((1j*wz))) wpy = abs(G((1j*wp))) wtdy = abs(G((1j*wtd)))
def rule1(G, Gd, K=1, message=False, plot=False, w1=-4, w2=2): """ This is rule one of chapter five Calculates the speed of response to reject distrurbances. Condition require |S(jw)| <= |1/Gd(jw)| Parameters ---------- G : tf plant model Gd : tf plant distrubance model K : tf control model message : boolean show the rule message (optional) plot : boolean show the bode plot with constraints (optional) w1 : integer start frequency, 10^w1 (optional) w2 : integer end frequency, 10^w2 (optional) Returns ------- valid1 : boolean value if rule conditions was met wc : real crossover frequency where | G(jwc) | = 1 wd : real crossover frequency where | Gd(jwd) | = 1 """ _,_,wc,_ = margins(G) _,_,wd,_ = margins(Gd) valid1 = wc > wd if message: print('Rule 1: Speed of response to reject distrubances') if valid1: print('First condition met, wc > wd') else: print('First condition no met, wc < wd') print('Seconds conditions requires |S(jw)| <= |1/Gd(jw)|') if plot: plt.figure('Rule 1') w, mag_s = df.setup_plot(['|S|', '1/R', '$w_r$'], w1, w2, G, K, wr) inv_gd = 1 / Gd mag_i = np.abs(inv_gd(s)) plt.loglog(w, mag_s) plt.loglog(w, mag_i, ls = '--') df.setup_plot(['|S|', '1/|Gd|']) return valid1, wc, wd