def sv_dir_plot(G, plot_type, w_start=-2, w_end=2, axlim=None, points=1000): """ Plot the input and output singular vectors associated with the minimum and maximum singular values. Parameters ---------- G : matrix Plant model or sensitivity function. plot_type : string Type of plot. ========= ============================ plot_type Type of plot ========= ============================ input Plots input vectors output Plots output vectors ========= ============================ Returns ------- Plot : matplotlib figure Note ---- Can be used with the plant matrix G and the sensitivity function S for controlability analysis """ s, w, axlim = df.frequency_plot_setup(axlim, w_start, w_end, points) freqresp = [G(si) for si in s] if plot_type == 'input': vec = numpy.array([V for _, _, V in map(utils.SVD, freqresp)]) d = 'v' elif plot_type == 'output': vec = numpy.array([U for U, _, _ in map(utils.SVD, freqresp)]) d = 'u' else: raise ValueError('Invalid plot_type parameter.') dim = numpy.shape(vec)[1] for i in range(dim): plt.subplot(dim*2, 1, 2*i + 1) plt.semilogx(w, abs(vec[:, i, 0]), label='$%s_{max}$' % d, lw=2) plt.semilogx(w, abs(vec[:, i, -1]), label='$%s_{min}$' % d, lw=2) plt.axhline(0, color='red', ls=':') plt.axis(axlim) plt.ylabel('$|{0}_{1}|$'.format(d, i + 1)) plt.subplot(dim*2, 1, 2*i + 2) plt.semilogx(w, utils.phase(vec[:, i, 0], deg=True), label='$%s_{max}$' % d, lw=2) plt.semilogx(w, utils.phase(vec[:, i, -1], deg=True), label='$%s_{min}$' % d, lw=2) plt.axhline(0, color='red', ls=':') plt.axis(axlim) plt.ylabel(r'$\angle {0}_{1}$'.format(d, i + 1)) plt.xlabel('Frequency [rad/unit time]')
def bodeclosedloop(G, K, w_start=-2, w_end=2, axlim=None, points=1000, margin=False): """ Shows the bode plot for a controller model Parameters ---------- G : tf Plant transfer function. K : tf Controller transfer function. margin : boolean Show the cross over frequencies on the plot (optional). """ if axlim is None: axlim = [None, None, None, None] plt.gcf().set_facecolor('white') w = numpy.logspace(w_start, w_end, points) L = G(1j * w) * K(1j * w) S = utils.feedback(1, L) T = utils.feedback(L, 1) plt.subplot(2, 1, 1) plt.loglog(w, abs(L)) plt.loglog(w, abs(S)) plt.loglog(w, abs(T)) plt.axis(axlim) plt.grid() plt.ylabel("Magnitude") plt.legend(["L", "S", "T"]) if margin: plt.plot(w, 1 / numpy.sqrt(2) * numpy.ones(len(w)), linestyle='dotted') plt.subplot(2, 1, 2) plt.semilogx(w, utils.phase(L, deg=True)) plt.semilogx(w, utils.phase(S, deg=True)) plt.semilogx(w, utils.phase(T, deg=True)) plt.axis(axlim) plt.grid() plt.ylabel("Phase") plt.xlabel("Frequency [rad/s]")
def Bode(G): """give the Bode plot along with GM and PM""" GM, PM, wc, w_180 = margins(G) # plotting of Bode plot and with corresponding freqeuncies for PM and GM w = np.logspace(-5, np.log(w_180), 1000) s = 1j*w plt.subplot(211) gains = np.abs(G(s)) plt.loglog(w, gains) plt.loglog(wc*np.ones(2), [np.max(gains), np.min(gains)]) plt.text(w_180, np.average([np.max(gains), np.min(gains)]), '<G(jw) = -180 Deg') plt.loglog(w_180*np.ones(2), [np.max(gains), np.min(gains)]) plt.loglog(w, 1*np.ones(len(w))) # argument of G plt.subplot(212) phaseangle = phase(G(s), deg=True) plt.semilogx(w, phaseangle) plt.semilogx(wc*np.ones(2), [np.max(phaseangle), np.min(phaseangle)]) plt.semilogx(w_180*np.ones(2), [-180, 0]) plt.show() 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 """ 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 bodeclosedloop(G, K, w_start=-2, w_end=2, axlim=None, points=1000, margin=False): """ Shows the bode plot for a controller model Parameters ---------- G : tf Plant transfer function. K : tf Controller transfer function. margin : boolean Show the cross over frequencies on the plot (optional). """ if axlim is None: axlim = [None, None, None, None] plt.gcf().set_facecolor('white') w = numpy.logspace(w_start, w_end, points) L = G(1j*w) * K(1j*w) S = utils.feedback(1, L) T = utils.feedback(L, 1) plt.subplot(2, 1, 1) plt.loglog(w, abs(L)) plt.loglog(w, abs(S)) plt.loglog(w, abs(T)) plt.axis(axlim) plt.grid() plt.ylabel("Magnitude") plt.legend(["L", "S", "T"]) if margin: plt.plot(w, 1/numpy.sqrt(2) * numpy.ones(len(w)), linestyle='dotted') plt.subplot(2, 1, 2) plt.semilogx(w, utils.phase(L, deg=True)) plt.semilogx(w, utils.phase(S, deg=True)) plt.semilogx(w, utils.phase(T, deg=True)) plt.axis(axlim) plt.grid() plt.ylabel("Phase") plt.xlabel("Frequency [rad/s]")
def Rule_7(w_start, w_end): # Rule 7 determining the phase of GGm at -180 deg. # This is solved visually from a plot. w = np.logspace(w_start, w_end, 1000) Pz = np.polymul(G()[0], Gm()[0]) Pp = np.polymul(G()[1], Gm()[1]) [w, h] = scs.freqs(Pz, Pp, w) plt.semilogx(w, (180 / np.pi) * (phase(h) + w * Time_Delay())) plt.show()
def Rule_7(w_start, w_end): # Rule 7 determining the phase of GGm at -180deg # this is solved visually from a plot w = np.logspace(w_start, w_end, 1000) Pz = np.polymul(G()[0], Gm()[0]) Pp = np.polymul(G()[1], Gm()[1]) [w, h] = scs.freqs(Pz, Pp, w) plt.semilogx(w, (180 / np.pi) * (phase(h) + w * Time_Delay())) plt.show()
def Bode_Parametrized(f="8/((s+8)*(s+1))", startBaseFreq=-5, endBaseFreq=37): """Give the Bode plot for a given plant f in string format (e.g. 8/((s+8)*(s+1))). If no argument is provided the example is the default value of f. The starting frequency and ending frequencies for the plot are provided as minFreq and maxFreq.""" def mod(x): """to give the function to calculate |G(jw)| = 1""" return np.abs(G(f, x)) - 1 # how to calculate the frequency at which |G(jw)| = 1 wc = sc.optimize.fsolve(mod, 0.1) def arg(w): """function to calculate the phase angle at -180 deg""" return np.angle(G(f, w)) + np.pi # where the frequency is calculated where arg G(jw) = -180 deg w_180 = sc.optimize.fsolve(arg, -1) # Plotting of gain Bode plot w = np.logspace(startBaseFreq, endBaseFreq, num=1000) #Set up the plot fig = plt.figure() plt.subplot(211) plt.loglog(w, np.abs(G(f, w))) #Plot the line for the unit gain plt.loglog(w, np.ones(1000), 'r--') plt.ylabel("Magnitude") plt.title("Figure 2.2: Frequency response (Bode plots) of G(s) = " + f + "\n ", fontsize=12) # \n for a newline so that it doesn't hog the top of the graph. #Set the axis to the w range - adds in some x-space otherwise, sucks... plt.xlim(10.0**startBaseFreq, 10.0**endBaseFreq) # Plotting of phase Bode plot plt.subplot(212) phaseangle = phase(G(f, w), deg=True) plt.semilogx(w, phaseangle) plt.semilogx(w, -180 * np.ones(1000), 'r--') #Set the axis to the w range - adds in some x-space otherwise, sucks... plt.xlim(10.0**startBaseFreq, 10.0**endBaseFreq) plt.ylabel("Phase") plt.xlabel("Frequency [rad/s]") plt.show() return fig
def Bode(): """give the Bode plot along with GM and PM""" def mod(x): """to give the function to calculate |G(jw)| = 1""" return np.abs(G(x)) - 1 # how to calculate the freqeuncy at which |G(jw)| = 1 wc = sc.optimize.fsolve(mod, 0.1) def arg(w): """function to calculate the phase angle at -180 deg""" return np.angle(G(w)) + np.pi # where the freqeuncy is calculated where arg G(jw) = -180 deg w_180 = sc.optimize.fsolve(arg, -1) PM = np.angle(G(wc), deg=True) + 180 GM = 1/(np.abs(G(w_180))) # plotting of Bode plot and with corresponding freqeuncies for PM and GM w = np.logspace(-5, np.log(w_180), 1000) plt.subplot(211) plt.loglog(w, np.abs(G(w))) plt.loglog(wc*np.ones(2), [np.max(np.abs(G(w))), np.min(np.abs(G(w)))]) plt.text(w_180, np.average([np.max(np.abs(G(w))), np.min(np.abs(G(w)))]), '<G(jw) = -180 Deg') plt.loglog(w_180*np.ones(2), [np.max(np.abs(G(w))), np.min(np.abs(G(w)))]) plt.loglog(w, 1*np.ones(len(w))) # argument of G plt.subplot(212) phaseangle = phase(G(w), deg=True) plt.semilogx(w, phaseangle) plt.semilogx(wc*np.ones(2), [np.max(phaseangle), np.min(phaseangle)]) plt.semilogx(w_180*np.ones(2), [-180, 0]) plt.show() return GM, PM
Kc = 0.05 #plant model G = 3*(-2*s+1)/((10*s+1)*(5*s+1)) #Controller model K = Kc*(10*s+1)*(5*s+1)/(s*(2*s+1)*(0.33*s+1)) #closed-loop transfer function L = G*K #magnitude and phase plt.subplot(2, 1, 1) plt.loglog(w, abs(L(wi))) plt.loglog(w, np.ones_like(w)) plt.ylabel('Magnitude') plt.subplot(2, 1, 2) plt.semilogx(w, phase(L(wi), deg=True)) plt.semilogx(w, -180*np.ones_like(w)) plt.ylabel('Phase') plt.xlabel('frequency (rad/s)') plt.figure() # From the figure we can calculate GM and PM, # cross-over frequency wc and w180 # results:GM = 1/0.354 = 2.82 # PM = -125.3 + 180 = 54 degC # w180 = 0.44 # wc = 0.15 #Response to step in reference for loop shaping design #y = Tr, r(t) = 1 for t > 0
s = w*1j K = Kc*(1+1/(Tauc*s)) G = 3*(-2*(s)+1)/((10*s+1)*(5*s+1)) L = G*K S = feedback(1, L) T = feedback(L, 1) plt.subplot(2, 1, 1) plt.loglog(w, abs(L)) plt.loglog(w, abs(S)) plt.loglog(w, abs(T)) plt.ylabel("Magnitude") plt.legend(["L", "S", "T"], bbox_to_anchor=(0, 1.01, 1, 0), loc=3, ncol=3) plt.subplot(2, 1, 2) plt.semilogx(w, phase(L, deg=True)) plt.semilogx(w, phase(S, deg=True)) plt.semilogx(w, phase(T, deg=True)) plt.ylabel("Phase") plt.xlabel("Frequency [rad/s]") # Calculate the frequency at which |L(jw)| = 1 wc = w[np.flatnonzero(np.abs(L) < 1)[0]] # Calculate the frequency at which Angle[L(jw)] = -180 def Lu_180(w): s = w*1j G = 3*(-2*(s)+1)/((10*s+1)*(5*s+1))
w = np.logspace(-2, 1, 1000) s = w*1j kc = 0.05 #plant model G = 3*(-2*s+1)/((10*s+1)*(5*s+1)) #Controller model K = kc*(10*s+1)*(5*s+1)/(s*(2*s+1)*(0.33*s+1)) #closed-loop transfer function L = G*K #magnitude and phase plt.subplot(2, 1, 1) plt.loglog(w, abs(L)) plt.loglog(w, 1*np.ones(len(w))) plt.ylabel('Magnitude') plt.subplot(2, 1, 2) plt.semilogx(w, phase(L, deg=True)) plt.semilogx(w, (-180)*np.ones(len(w))) plt.ylabel('Phase') plt.xlabel('frequency (rad/s)') plt.show() # From the figure we can calculate GM and PM, # cross-over frequency wc and w180 # results:GM = 1/0.354 = 2.82 # PM = -125.3 + 180 = 54 degC # w180 = 0.44 # wc = 0.15
Kc = 1.25 tau1 = 1.5 K = Kc * (1 + 1 / (tau1 * s)) L = K * G S = feedback(1, L) T = feedback(L, 1) #Bode magnitude and phase plot - Figure 2.15 plt.subplot(2, 1, 1) plt.loglog(w, abs(L)) plt.loglog(w, abs(S)) plt.loglog(w, abs(T)) plt.ylabel("Magnitude") plt.legend(["L", "S", "T"], bbox_to_anchor=(0, 1.01, 1, 0), loc=3, ncol=3) plt.subplot(2, 1, 2) plt.semilogx(w, phase(L, deg=True)) plt.semilogx(w, phase(S, deg=True)) plt.semilogx(w, phase(T, deg=True)) plt.semilogx(w, (-180) * np.ones(len(w))) plt.ylabel("Phase") plt.xlabel("Frequency [rad/s]") # Calculate the frequency at which |L(jw)| = 1 wc = w[np.flatnonzero(np.abs(L) < 1)[0]] # Calculate the frequency at which Angle[L(jw)] = -180 def Lu_180(w): s = w * 1j G = 4 / ((s - 1) * (0.02 * s + 1)**2) Kc = 1.25
Kc = 0.05 #plant model G = 3 * (-2 * s + 1) / ((10 * s + 1) * (5 * s + 1)) #Controller model K = Kc * (10 * s + 1) * (5 * s + 1) / (s * (2 * s + 1) * (0.33 * s + 1)) #closed-loop transfer function L = G * K #magnitude and phase plt.subplot(2, 1, 1) plt.loglog(w, abs(L(wi))) plt.axhline(1) plt.ylabel('Magnitude') plt.subplot(2, 1, 2) plt.semilogx(w, phase(L(wi), deg=True)) plt.axhline(-180) plt.ylabel('Phase') plt.xlabel('frequency (rad/s)') plt.figure() # From the figure we can calculate GM and PM, # cross-over frequency wc and w180 # results:GM = 1/0.354 = 2.82 # PM = -125.3 + 180 = 54 degC # w180 = 0.44 # wc = 0.15 #Response to step in reference for loop shaping design #y = Tr, r(t) = 1 for t > 0
def G_GM(w): Pz = np.polymul(G()[0], Gm()[0]) Pp = np.polymul(G()[1], Gm()[1]) G_w = scs.freqs(Pz, Pp, w)[1] return np.abs(phase(G_w))-np.pi
def RULES(R, wr): # rule 1 wc>wd def Gd_mod_1(w): return np.abs(scs.freqs(Gd()[0], Gd()[1], w)[1])-1 wd = sc.optimize.fsolve(Gd_mod_1, 10) wc_min_1 = wd # rule 2 # rule 3 # for perfect control def G_Gd_1(w): f = scs.freqs(G()[0], G()[1], w)[1] g = scs.freqs(Gd()[0], Gd()[1], w)[1] return np.abs(f)-np.abs(g) w_G_Gd = sc.optimize.fsolve(G_Gd_1, 0.001) plt.figure(1) if np.abs(scs.freqs(G()[0], G()[1], [w_G_Gd+0.0001])[1])>np.abs(scs.freqs(Gd()[0], Gd()[1], [w_G_Gd+0.0001])[1]): print "Acceptable control" print "control only at high frequencies", w_G_Gd, "< w < inf" w = np.logspace(-3, np.log10(w_G_Gd), 100) plt.loglog(w, np.abs(scs.freqs(G()[0], G()[1], w)[1]), 'r') plt.loglog(w, np.abs(scs.freqs(Gd()[0], Gd()[1], w)[1]), 'r.') max_p = np.max([np.abs(scs.freqs(G()[0], G()[1], w)[1]), np.abs(scs.freqs(Gd()[0], Gd()[1], w)[1])]) w = np.logspace(np.log10(w_G_Gd), 5, 100) plt.loglog(w, np.abs(scs.freqs(G()[0], G()[1], w)[1]), 'b') plt.loglog(w, np.abs(scs.freqs(Gd()[0], Gd()[1], w)[1]), 'b.') min_p = np.min([np.abs(scs.freqs(G()[0], G()[1], w)[1]), np.abs(scs.freqs(Gd()[0], Gd()[1], w)[1])]) if np.abs(scs.freqs(G()[0], G()[1], [w_G_Gd-0.0001])[1])>= np.abs(scs.freqs(Gd()[0], Gd()[1], [w_G_Gd-0.0001])[1]): print "Acceptable control" print "control up to frequency 0 < w < ", w_G_Gd w = np.logspace(-3, np.log10(w_G_Gd), 100) plt.loglog(w, np.abs(scs.freqs(G()[0], G()[1], w)[1]), 'b') plt.loglog(w, np.abs(scs.freqs(Gd()[0], Gd()[1], w)[1]), 'b.') max_p = np.max([np.abs(scs.freqs(G()[0], G()[1], w)[1]), np.abs(scs.freqs(Gd()[0], Gd()[1], w)[1])]) w = np.logspace(np.log10(w_G_Gd), 5, 100) plt.loglog(w, np.abs(scs.freqs(G()[0], G()[1], w)[1]), 'r') plt.loglog(w, np.abs(scs.freqs(Gd()[0], Gd()[1], w)[1]), 'r.') min_p = np.min([np.abs(scs.freqs(G()[0], G()[1], w)[1]), np.abs(scs.freqs(Gd()[0], Gd()[1], w)[1])]) plt.loglog(w_G_Gd*np.ones(2), [max_p, min_p], 'g') # rule 4 # rule 5 # critical freqeuncy of controller needs to smaller than wc_5 = Time_Delay()[0]/2.0000 # rule 6 # control over RHP zeros Pz_G_Gm = np.polymul(G()[0], Gm()[0]) if len(Pz_G_Gm) == 1: wc_6 = wc_5 else: Pz_roots = np.roots(Pz_G_Gm) print Pz_roots if np.real(np.max(Pz_roots))>0: if np.imag(np.min(Pz_roots)) == 0: # it the roots aren't imagenary # looking for the minimum values of the zeros = > results in the tightest control wc_6 = (np.min(np.abs(Pz_roots)))/2.000 else: wc_6 = 0.8600*np.abs(np.min(Pz_roots)) else: wc_6 = wc_5 # rule 7 def G_GM(w): Pz = np.polymul(G()[0], Gm()[0]) Pp = np.polymul(G()[1], Gm()[1]) G_w = scs.freqs(Pz, Pp, w)[1] return np.abs(phase(G_w))-np.pi w = np.logspace(-3, 3, 100) plt.figure(2) Pz = np.polymul(G()[0], Gm()[0]) Pp = np.polymul(G()[1], Gm()[1]) [w, h] = scs.freqs(Pz, Pp, w) plt.subplot(211) plt.loglog(w, np.abs(h)) plt.subplot(212) plt.semilogx(w, phase(h)) wc_7 = np.abs(sc.optimize.fsolve(G_GM, 10)) w_vec = [wc_5, wc_6, wc_7] wc_min_everything = np.min(w_vec) print " " print "maximum value of wc < ", wc_min_everything # rule 8 # unstable RHP poles Poles_p = np.roots(G()[1]) vec_p = [wc_min_1] for p in Poles_p: if np.real(p) > 0: vec_p.append(2*np.abs(p)) wc_min_everything = np.max(vec_p) print "minimum value of wc > ", wc_min_everything plt.show()
def phase(self): """Returns the current phase of the cursor """ return utils.phase(self.angle)