From: <all...@us...> - 2011-02-13 13:21:35
|
Revision: 133 http://python-control.svn.sourceforge.net/python-control/?rev=133&view=rev Author: allanmcinnes Date: 2011-02-13 13:21:29 +0000 (Sun, 13 Feb 2011) Log Message: ----------- Split out common code from M-circle and N-circle utility functions. Make variable naming through nichols_grid and utility functions more consistent, and provide more explanation of what's going on. Modified Paths: -------------- trunk/src/freqplot.py Modified: trunk/src/freqplot.py =================================================================== --- trunk/src/freqplot.py 2011-02-13 03:35:38 UTC (rev 132) +++ trunk/src/freqplot.py 2011-02-13 13:21:29 UTC (rev 133) @@ -247,55 +247,69 @@ ------------- None """ - gmin_default = -40.0 # dB - gain_step = 20.0 # dB + mag_min_default = -40.0 # dB + mag_step = 20.0 # dB + # Chart defaults + phase_min, phase_max, mag_min, mag_max = -360.0, 0.0, mag_min_default, 40.0 + + # Set actual chart bounds based on current plot if plt.gcf().gca().has_data(): - pmin, pmax, gmin, gmax = plt.axis() + phase_min, phase_max, mag_min, mag_max = plt.axis() + + # M-circle magnitudes. + # The "fixed" set are always generated, since this guarantees a recognizable + # Nichols chart grid. + mags_fixed = np.array([-40.0, -20.0, -12.0, -6.0, -3.0, -1.0, -0.5, 0.0, + 0.25, 0.5, 1.0, 3.0, 6.0, 12.0]) + + if mag_min < mag_min_default: + # Outside the "fixed" set of magnitudes, the generated M-circles + # are extended in steps of 'mag_step' dB to cover anything made + # visible by the range of the existing plot + mags_adjust = np.arange(mag_step*np.floor(mag_min/mag_step), + mag_min_default, mag_step) + mags = np.concatenate((mags_adjust, mags_fixed)) else: - pmin, pmax = -360.0, 0.0 - gmin, gmax = gmin_default, 40.0 - - # Determine the bounds of the chart - mags_low_end = np.min([gmin_default, gain_step*np.floor(gmin/gain_step)]) - phases_low_end = 360.0*np.ceil(pmin/360.0) - phases_high_end = 360.0*(1.0 + np.ceil(pmax/360.0)) - - # M-circle magnitudes - we adjust the lower end of the range to match - # any existing data - mags_fixed = np.array([-20.0, -12.0, -6.0, -3.0, -1.0, -0.5, 0.0, - 0.25, 0.5, 1.0, 3.0, 6.0, 12.0]) - mags_adjustable = np.arange(mags_low_end, np.min(mags_fixed), gain_step) - mags = np.concatenate((mags_adjustable, mags_fixed)) + mags = mags_fixed # N-circle phases (should be in the range -360 to 0) phases = np.array([-0.25, -10.0, -20.0, -30.0, -45.0, -60.0, -90.0, -120.0, -150.0, -180.0, -210.0, -240.0, -270.0, -310.0, -325.0, -340.0, -350.0, -359.75]) + # Find the M-contours - mcs = m_circles(mags, pmin=np.min(phases), pmax=np.max(phases)) - mag_m = 20*sp.log10(np.abs(mcs)) - phase_m = sp.mod(sp.degrees(sp.angle(mcs)), -360.0) # Unwrap + m = m_circles(mags, phase_min=np.min(phases), phase_max=np.max(phases)) + m_mag = 20*sp.log10(np.abs(m)) + m_phase = sp.mod(sp.degrees(sp.angle(m)), -360.0) # Unwrap # Find the N-contours - ncs = n_circles(phases, gmin=np.min(mags), gmax=np.max(mags)) - mag_n = 20*sp.log10(np.abs(ncs)) - phase_n = sp.mod(sp.degrees(sp.angle(ncs)), -360.0) # Unwrap + n = n_circles(phases, mag_min=np.min(mags), mag_max=np.max(mags)) + n_mag = 20*sp.log10(np.abs(n)) + n_phase = sp.mod(sp.degrees(sp.angle(n)), -360.0) # Unwrap - # Plot the contours - for phase_offset in np.arange(phases_low_end, phases_high_end, 360.0): - plt.plot(phase_m + phase_offset, mag_m, color='gray', + # Plot the contours behind other plot elements. + # The "phase offset" is used to produce copies of the chart that cover + # the entire range of the plotted data, starting from a base chart computed + # over the range -360 < phase < 0 (see above). Given the range + # the base chart is computed over, the phase offset should be 0 + # for -360 < phase_min < 0. + phase_offset_min = 360.0*np.ceil(phase_min/360.0) + phase_offset_max = 360.0*np.ceil(phase_max/360.0) + 360.0 + phase_offsets = np.arange(phase_offset_min, phase_offset_max, 360.0) + for phase_offset in phase_offsets: + plt.plot(m_phase + phase_offset, m_mag, color='gray', linestyle='dashed', zorder=0) - plt.plot(phase_n + phase_offset, mag_n, color='gray', + plt.plot(n_phase + phase_offset, n_mag, color='gray', linestyle='dashed', zorder=0) # Add magnitude labels - for x, y, m in zip(phase_m[:][-1], mag_m[:][-1], mags): + for x, y, m in zip(m_phase[:][-1], m_mag[:][-1], mags): align = 'right' if m < 0.0 else 'left' plt.text(x, y, str(m) + ' dB', size='small', ha=align) # Make sure axes conform to any pre-existing plot. - plt.axis([pmin, pmax, gmin, gmax]) + plt.axis([phase_min, phase_max, mag_min, mag_max]) # Gang of Four #! TODO: think about how (and whether) to handle lists of systems @@ -402,54 +416,84 @@ return omega +# Compute contours of a closed-loop transfer function +def closed_loop_contours(Hmag, Hphase): + """Contours of the function H = G/(1+G). + + Usage + ===== + contours = closed_loop_contours(mags, phases) + + Parameters + ---------- + mags : array-like + Meshgrid array of magnitudes of the contours + phases : array-like + Meshgrid array of phases in radians of the contours + + Return values + ------------- + contours : complex array + Array of complex numbers corresponding to the contours. + """ + # Compute the contours in H-space + H = Hmag*sp.exp(1.j*Hphase) + + # Invert H = G/(1+G) to get an expression for the contours in G-space + return H/(1.0 - H) + # M-circle -def m_circles(mags, pmin=-359.75, pmax=-0.25): +def m_circles(mags, phase_min=-359.75, phase_max=-0.25): """Constant-magnitude contours of the function H = G/(1+G). Usage ===== - contour = m_circle(mags) + contours = m_circles(mags) Parameters ---------- mags : array-like Array of magnitudes in dB of the M-circles + phase_min : degrees + Minimum phase in degrees of the N-circles + phase_max : degrees + Maximum phase in degrees of the N-circles Return values ------------- - contour : complex array - Array of complex numbers corresponding to the contour. + contours : complex array + Array of complex numbers corresponding to the contours. """ - # Compute the contours in H-space - phases = sp.radians(sp.linspace(pmin, pmax, 500)) + # Convert magnitudes and phase range into a grid suitable for + # building contours + phases = sp.radians(sp.linspace(phase_min, phase_max, 500)) Hmag, Hphase = sp.meshgrid(10.0**(mags/20.0), phases) - H = Hmag*sp.exp(1.j*Hphase) - - # Invert H = G/(1+G) to get an expression for the contour in G-space - return H/(1.0 - H) + return closed_loop_contours(Hmag, Hphase) # N-circle -def n_circles(phases, gmin=-40.0, gmax=12.0): +def n_circles(phases, mag_min=-40.0, mag_max=12.0): """Constant-phase contours of the function H = G/(1+G). Usage ===== - contour = n_circle(angles) + contour = n_circles(angles) Parameters ---------- phases : array-like - Array of phases in degrees of the N-circle + Array of phases in degrees of the N-circles + mag_min : dB + Minimum magnitude in dB of the N-circles + mag_max : dB + Maximum magnitude in dB of the N-circles Return values ------------- - contour : complex array + contours : complex array Array of complex numbers corresponding to the contours. """ - # Compute the contours in H-space - mags = sp.linspace(10**(gmin/20.0), 10**(gmax/20.0), 2000) + # Convert phases and magnitude range into a grid suitable for + # building contours + mags = sp.linspace(10**(mag_min/20.0), 10**(mag_max/20.0), 2000) Hphase, Hmag = sp.meshgrid(sp.radians(phases), mags) - H = Hmag*sp.exp(1.j*Hphase) - - # Invert H = G/(1+G) to get an expression for the contours in G-space - return H/(1.0 - H) + return closed_loop_contours(Hmag, Hphase) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <all...@us...> - 2011-02-14 07:49:12
|
Revision: 135 http://python-control.svn.sourceforge.net/python-control/?rev=135&view=rev Author: allanmcinnes Date: 2011-02-14 07:49:05 +0000 (Mon, 14 Feb 2011) Log Message: ----------- Add the ability to define custom Nichols charts by passing magnitude and/or phase arrays to nichols_grid. Also some tweaks to the chart grid generation and axis configuration, and further clean-up of documentation and naming. Modified Paths: -------------- trunk/src/freqplot.py Modified: trunk/src/freqplot.py =================================================================== --- trunk/src/freqplot.py 2011-02-13 16:49:58 UTC (rev 134) +++ trunk/src/freqplot.py 2011-02-14 07:49:05 UTC (rev 135) @@ -181,7 +181,7 @@ # Nichols plot # Contributed by Allan McInnes <All...@ca...> #! TODO: need unit test code -def nichols(syslist, omega=None): +def nichols(syslist, omega=None, grid=True): """Nichols plot for a system Usage @@ -196,6 +196,8 @@ List of linear input/output systems (single system is OK) omega : freq_range Range of frequencies (list or bounds) in rad/sec + grid : boolean + True if the plot should include a Nichols-chart grid Return values ------------- @@ -228,88 +230,114 @@ # Mark the -180 point plt.plot([-180], [0], 'r+') + + # Add grid + if grid: + nichols_grid() # Nichols grid -def nichols_grid(): +def nichols_grid(**kwargs): """Nichols chart grid Usage ===== nichols_grid() - Plots a Nichols chart grid on the current axis. + Plots a Nichols chart grid on the current axis, or creates a new chart + if no plot already exists. Parameters ---------- - None + cl_mags : array-like (dB) + Array of closed-loop magnitudes defining the iso-gain lines on a + custom Nichols chart. + cl_phases : array-like (degrees) + Array of closed-loop phases defining the iso-phase lines on a custom + Nichols chart. Must be in the range -360 < cl_phases < 0 Return values ------------- None """ - mag_min_default = -40.0 # dB - mag_step = 20.0 # dB + # Default chart size + ol_phase_min = -359.99 + ol_phase_max = 0.0 + ol_mag_min = -40.0 + ol_mag_max = default_ol_mag_max = 50.0 + + # Find bounds of the current dataset, if there is one. + if plt.gcf().gca().has_data(): + ol_phase_min, ol_phase_max, ol_mag_min, ol_mag_max = plt.axis() - # Chart defaults - phase_min, phase_max, mag_min, mag_max = -360.0, 0.0, mag_min_default, 40.0 - - # Set actual chart bounds based on current plot - if plt.gcf().gca().has_data(): - phase_min, phase_max, mag_min, mag_max = plt.axis() - # M-circle magnitudes. - # The "fixed" set are always generated, since this guarantees a recognizable - # Nichols chart grid. - mags_fixed = np.array([-40.0, -20.0, -12.0, -6.0, -3.0, -1.0, -0.5, 0.0, - 0.25, 0.5, 1.0, 3.0, 6.0, 12.0]) - - if mag_min < mag_min_default: - # Outside the "fixed" set of magnitudes, the generated M-circles - # are extended in steps of 'mag_step' dB to cover anything made - # visible by the range of the existing plot - mags_adjust = np.arange(mag_step*np.floor(mag_min/mag_step), - mag_min_default, mag_step) - mags = np.concatenate((mags_adjust, mags_fixed)) + if kwargs.has_key('cl_mags'): + # Custom chart + cl_mags = kwargs['cl_mags'] else: - mags = mags_fixed + # Default chart magnitudes + # The key set of magnitudes are always generated, since this + # guarantees a recognizable Nichols chart grid. + key_cl_mags = np.array([-40.0, -20.0, -12.0, -6.0, -3.0, -1.0, -0.5, 0.0, + 0.25, 0.5, 1.0, 3.0, 6.0, 12.0]) + # Extend the range of magnitudes if necessary. The extended arange + # will end up empty if no extension is required. Assumes that closed-loop + # magnitudes are approximately aligned with open-loop magnitudes beyond + # the value of np.min(key_cl_mags) + cl_mag_step = -20.0 # dB + extended_cl_mags = np.arange(np.min(key_cl_mags), + ol_mag_min + cl_mag_step, cl_mag_step) + cl_mags = np.concatenate((extended_cl_mags, key_cl_mags)) # N-circle phases (should be in the range -360 to 0) - phases = np.array([-0.25, -10.0, -20.0, -30.0, -45.0, -60.0, -90.0, - -120.0, -150.0, -180.0, -210.0, -240.0, -270.0, - -310.0, -325.0, -340.0, -350.0, -359.75]) + if kwargs.has_key('cl_phases'): + # Custom chart + cl_phases = kwargs['cl_phases'] + assert ((-360.0 < np.min(cl_phases)) and (np.max(cl_phases) < 0.0)) + else: + # Choose a reasonable set of default phases (denser if the open-loop + # data is restricted to a relatively small range of phases). + key_cl_phases = np.array([-0.25, -45.0, -90.0, -180.0, -270.0, -325.0, -359.75]) + if np.abs(ol_phase_max - ol_phase_min) < 90.0: + other_cl_phases = np.arange(-10.0, -360.0, -10.0) + else: + other_cl_phases = np.arange(-10.0, -360.0, -20.0) + cl_phases = np.concatenate((key_cl_phases, other_cl_phases)) # Find the M-contours - m = m_circles(mags, phase_min=np.min(phases), phase_max=np.max(phases)) + m = m_circles(cl_mags, phase_min=np.min(cl_phases), phase_max=np.max(cl_phases)) m_mag = 20*sp.log10(np.abs(m)) m_phase = sp.mod(sp.degrees(sp.angle(m)), -360.0) # Unwrap # Find the N-contours - n = n_circles(phases, mag_min=np.min(mags), mag_max=np.max(mags)) + n = n_circles(cl_phases, mag_min=np.min(cl_mags), mag_max=np.max(cl_mags)) n_mag = 20*sp.log10(np.abs(n)) n_phase = sp.mod(sp.degrees(sp.angle(n)), -360.0) # Unwrap # Plot the contours behind other plot elements. # The "phase offset" is used to produce copies of the chart that cover # the entire range of the plotted data, starting from a base chart computed - # over the range -360 < phase < 0 (see above). Given the range + # over the range -360 < phase < 0. Given the range # the base chart is computed over, the phase offset should be 0 - # for -360 < phase_min < 0. - phase_offset_min = 360.0*np.ceil(phase_min/360.0) - phase_offset_max = 360.0*np.ceil(phase_max/360.0) + 360.0 + # for -360 < ol_phase_min < 0. + phase_offset_min = 360.0*np.ceil(ol_phase_min/360.0) + phase_offset_max = 360.0*np.ceil(ol_phase_max/360.0) + 360.0 phase_offsets = np.arange(phase_offset_min, phase_offset_max, 360.0) + for phase_offset in phase_offsets: + # Draw M and N contours plt.plot(m_phase + phase_offset, m_mag, color='gray', linestyle='dashed', zorder=0) plt.plot(n_phase + phase_offset, n_mag, color='gray', linestyle='dashed', zorder=0) - # Add magnitude labels - for x, y, m in zip(m_phase[:][-1], m_mag[:][-1], mags): - align = 'right' if m < 0.0 else 'left' - plt.text(x, y, str(m) + ' dB', size='small', ha=align) + # Add magnitude labels + for x, y, m in zip(m_phase[:][-1] + phase_offset, m_mag[:][-1], cl_mags): + align = 'right' if m < 0.0 else 'left' + plt.text(x, y, str(m) + ' dB', size='small', ha=align) - # Make sure axes conform to any pre-existing plot. - plt.axis([phase_min, phase_max, mag_min, mag_max]) + # Fit axes to generated chart + plt.axis([phase_offset_min - 360.0, phase_offset_max - 360.0, + np.min(cl_mags), np.max([ol_mag_max, default_ol_mag_max])]) # Gang of Four #! TODO: think about how (and whether) to handle lists of systems @@ -417,38 +445,44 @@ return omega # Compute contours of a closed-loop transfer function -def closed_loop_contours(Hmag, Hphase): - """Contours of the function H = G/(1+G). +def closed_loop_contours(Gcl_mags, Gcl_phases): + """Contours of the function Gcl = Gol/(1+Gol), where + Gol is an open-loop transfer function, and Gcl is a corresponding + closed-loop transfer function. Usage ===== - contours = closed_loop_contours(mags, phases) + contours = closed_loop_contours(Gcl_mags, Gcl_phases) Parameters ---------- - mags : array-like - Meshgrid array of magnitudes of the contours - phases : array-like - Meshgrid array of phases in radians of the contours + Gcl_mags : array-like + Array of magnitudes of the contours + Gcl_phases : array-like + Array of phases in radians of the contours Return values ------------- contours : complex array Array of complex numbers corresponding to the contours. """ - # Compute the contours in H-space - H = Hmag*sp.exp(1.j*Hphase) + # Compute the contours in Gcl-space. Since we're given closed-loop + # magnitudes and phases, this is just a case of converting them into + # a complex number. + Gcl = Gcl_mags*sp.exp(1.j*Gcl_phases) - # Invert H = G/(1+G) to get an expression for the contours in G-space - return H/(1.0 - H) + # Invert Gcl = Gol/(1+Gol) to map the contours into the open-loop space + return Gcl/(1.0 - Gcl) # M-circle def m_circles(mags, phase_min=-359.75, phase_max=-0.25): - """Constant-magnitude contours of the function H = G/(1+G). + """Constant-magnitude contours of the function Gcl = Gol/(1+Gol), where + Gol is an open-loop transfer function, and Gcl is a corresponding + closed-loop transfer function. Usage ===== - contours = m_circles(mags) + contours = m_circles(mags, phase_min, phase_max) Parameters ---------- @@ -467,16 +501,18 @@ # Convert magnitudes and phase range into a grid suitable for # building contours phases = sp.radians(sp.linspace(phase_min, phase_max, 500)) - Hmag, Hphase = sp.meshgrid(10.0**(mags/20.0), phases) - return closed_loop_contours(Hmag, Hphase) + Gcl_mags, Gcl_phases = sp.meshgrid(10.0**(mags/20.0), phases) + return closed_loop_contours(Gcl_mags, Gcl_phases) # N-circle def n_circles(phases, mag_min=-40.0, mag_max=12.0): - """Constant-phase contours of the function H = G/(1+G). + """Constant-phase contours of the function Gcl = Gol/(1+Gol), where + Gol is an open-loop transfer function, and Gcl is a corresponding + closed-loop transfer function. Usage ===== - contour = n_circles(angles) + contours = n_circles(phases, mag_min, mag_max) Parameters ---------- @@ -495,5 +531,5 @@ # Convert phases and magnitude range into a grid suitable for # building contours mags = sp.linspace(10**(mag_min/20.0), 10**(mag_max/20.0), 2000) - Hphase, Hmag = sp.meshgrid(sp.radians(phases), mags) - return closed_loop_contours(Hmag, Hphase) + Gcl_phases, Gcl_mags = sp.meshgrid(sp.radians(phases), mags) + return closed_loop_contours(Gcl_mags, Gcl_phases) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <all...@us...> - 2011-02-19 03:02:13
|
Revision: 136 http://python-control.svn.sourceforge.net/python-control/?rev=136&view=rev Author: allanmcinnes Date: 2011-02-19 03:02:07 +0000 (Sat, 19 Feb 2011) Log Message: ----------- Add a Nyquist grid (aka Hall chart) showing M-circles and N-circles on the complex plane. Tweak Nichols grid to eliminate unnecessary use of kwargs. Modified Paths: -------------- trunk/src/freqplot.py Modified: trunk/src/freqplot.py =================================================================== --- trunk/src/freqplot.py 2011-02-14 07:49:05 UTC (rev 135) +++ trunk/src/freqplot.py 2011-02-19 03:02:07 UTC (rev 136) @@ -178,6 +178,83 @@ # Mark the -1 point plt.plot([-1], [0], 'r+') +# Nyquist grid +#! TODO: Consider making linestyle configurable +def nyquist_grid(cl_mags=None, cl_phases=None): + """Nyquist plot grid of M-circles and N-circles (aka "Hall chart") + + Usage + ===== + nyquist_grid() + + Plots a grid of M-circles and N-circles on the current axis, or + creates a default grid if no plot already exists. + + Parameters + ---------- + cl_mags : array-like (dB) + Array of closed-loop magnitudes defining a custom set of + M-circle iso-gain lines. + cl_phases : array-like (degrees) + Array of closed-loop phases defining a custom set of + N-circle iso-phase lines. Must be in the range -180.0 < cl_phases < 180.0 + + Return values + ------------- + None + """ + # Default chart size + re_min = -4.0 + re_max = 3.0 + im_min = -2.0 + im_max = 2.0 + + # Find bounds of the current dataset, if there is one. + if plt.gcf().gca().has_data(): + re_min, re_max, im_min, im_max = plt.axis() + + # M-circle magnitudes. + if cl_mags is None: + cl_mags = np.array([-20.0, -10.0, -6.0, -4.0, -2.0, 0.0, + 2.0, 4.0, 6.0, 10.0, 20.0]) + + # N-circle phases (should be in the range -180.0 to 180.0) + if cl_phases is None: + cl_phases = np.array([-90.0, -60.0, -45.0, -30.0, -15.0, + 15.0, 30.0, 45.0, 60.0, 90.0]) + else: + assert ((-180.0 < np.min(cl_phases)) and (np.max(cl_phases) < 180.0)) + + # Find the M-contours and N-contours + m = m_circles(cl_mags, phase_min=0.0, phase_max=359.99) + n = n_circles(cl_phases, mag_min=-40.0, mag_max=40.0) + + # Draw contours + plt.plot(np.real(m), np.imag(m), color='gray', linestyle='dotted', zorder=0) + plt.plot(np.real(n), np.imag(n), color='gray', linestyle='dotted', zorder=0) + + # Add magnitude labels + for i in range(0, len(cl_mags)): + if not cl_mags[i] == 0.0: + mag = 10.0**(cl_mags[i]/20.0) + x = -mag**2.0/(mag**2.0 - 1.0) # Center of the M-circle + y = np.abs(mag/(mag**2.0 - 1.0)) # Maximum point + else: + x, y = -0.5, im_max + plt.text(x, y, str(cl_mags[i]) + ' dB', size='small', color='gray') + + # Add phase labels + for i in range(0, len(cl_phases)): + y = np.sign(cl_phases[i])*np.max(np.abs(np.imag(n)[:,i])) + p = str(cl_phases[i]) + plt.text(-0.5, y, p + '$^\circ$', size='small', color='gray') + + # Fit axes to original plot + plt.axis([re_min, re_max, im_min, im_max]) + +# Make an alias +hall_grid = nyquist_grid + # Nichols plot # Contributed by Allan McInnes <All...@ca...> #! TODO: need unit test code @@ -209,7 +286,7 @@ syslist = (syslist,) # Select a default range if none is provided - if (omega == None): + if omega is None: omega = default_frequency_range(syslist) for sys in syslist: @@ -236,7 +313,8 @@ nichols_grid() # Nichols grid -def nichols_grid(**kwargs): +#! TODO: Consider making linestyle configurable +def nichols_grid(cl_mags=None, cl_phases=None): """Nichols chart grid Usage @@ -270,10 +348,7 @@ ol_phase_min, ol_phase_max, ol_mag_min, ol_mag_max = plt.axis() # M-circle magnitudes. - if kwargs.has_key('cl_mags'): - # Custom chart - cl_mags = kwargs['cl_mags'] - else: + if cl_mags is None: # Default chart magnitudes # The key set of magnitudes are always generated, since this # guarantees a recognizable Nichols chart grid. @@ -289,11 +364,7 @@ cl_mags = np.concatenate((extended_cl_mags, key_cl_mags)) # N-circle phases (should be in the range -360 to 0) - if kwargs.has_key('cl_phases'): - # Custom chart - cl_phases = kwargs['cl_phases'] - assert ((-360.0 < np.min(cl_phases)) and (np.max(cl_phases) < 0.0)) - else: + if cl_phases is None: # Choose a reasonable set of default phases (denser if the open-loop # data is restricted to a relatively small range of phases). key_cl_phases = np.array([-0.25, -45.0, -90.0, -180.0, -270.0, -325.0, -359.75]) @@ -302,6 +373,8 @@ else: other_cl_phases = np.arange(-10.0, -360.0, -20.0) cl_phases = np.concatenate((key_cl_phases, other_cl_phases)) + else: + assert ((-360.0 < np.min(cl_phases)) and (np.max(cl_phases) < 0.0)) # Find the M-contours m = m_circles(cl_mags, phase_min=np.min(cl_phases), phase_max=np.max(cl_phases)) @@ -326,14 +399,14 @@ for phase_offset in phase_offsets: # Draw M and N contours plt.plot(m_phase + phase_offset, m_mag, color='gray', - linestyle='dashed', zorder=0) + linestyle='dotted', zorder=0) plt.plot(n_phase + phase_offset, n_mag, color='gray', - linestyle='dashed', zorder=0) + linestyle='dotted', zorder=0) # Add magnitude labels for x, y, m in zip(m_phase[:][-1] + phase_offset, m_mag[:][-1], cl_mags): align = 'right' if m < 0.0 else 'left' - plt.text(x, y, str(m) + ' dB', size='small', ha=align) + plt.text(x, y, str(m) + ' dB', size='small', ha=align, color='gray') # Fit axes to generated chart plt.axis([phase_offset_min - 360.0, phase_offset_max - 360.0, @@ -500,7 +573,7 @@ """ # Convert magnitudes and phase range into a grid suitable for # building contours - phases = sp.radians(sp.linspace(phase_min, phase_max, 500)) + phases = sp.radians(sp.linspace(phase_min, phase_max, 2000)) Gcl_mags, Gcl_phases = sp.meshgrid(10.0**(mags/20.0), phases) return closed_loop_contours(Gcl_mags, Gcl_phases) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <all...@us...> - 2011-02-19 03:15:24
|
Revision: 137 http://python-control.svn.sourceforge.net/python-control/?rev=137&view=rev Author: allanmcinnes Date: 2011-02-19 03:15:18 +0000 (Sat, 19 Feb 2011) Log Message: ----------- Bugfix freqplot.default_frequency_range(), which failed to deal properly with single systems. Also add an option to control the number of points. Set default to a larger number of points than the logspace default, so that we're more likely to get a smooth curve on bode, nyquist, and nichols plots. Modified Paths: -------------- trunk/src/freqplot.py Modified: trunk/src/freqplot.py =================================================================== --- trunk/src/freqplot.py 2011-02-19 03:02:07 UTC (rev 136) +++ trunk/src/freqplot.py 2011-02-19 03:15:18 UTC (rev 137) @@ -192,10 +192,10 @@ Parameters ---------- - cl_mags : array-like (dB) + cl_mags : array-like (dB), optional Array of closed-loop magnitudes defining a custom set of M-circle iso-gain lines. - cl_phases : array-like (degrees) + cl_phases : array-like (degrees), optional Array of closed-loop phases defining a custom set of N-circle iso-phase lines. Must be in the range -180.0 < cl_phases < 180.0 @@ -273,8 +273,8 @@ List of linear input/output systems (single system is OK) omega : freq_range Range of frequencies (list or bounds) in rad/sec - grid : boolean - True if the plot should include a Nichols-chart grid + grid : boolean, optional + True if the plot should include a Nichols-chart grid. Default is True. Return values ------------- @@ -326,10 +326,10 @@ Parameters ---------- - cl_mags : array-like (dB) + cl_mags : array-like (dB), optional Array of closed-loop magnitudes defining the iso-gain lines on a custom Nichols chart. - cl_phases : array-like (degrees) + cl_phases : array-like (degrees), optional Array of closed-loop phases defining the iso-phase lines on a custom Nichols chart. Must be in the range -360 < cl_phases < 0 @@ -468,7 +468,7 @@ # # Compute reasonable defaults for axes -def default_frequency_range(syslist): +def default_frequency_range(syslist, num=1000): """Compute a reasonable default frequency range for frequency domain plots. @@ -483,6 +483,8 @@ ---------- syslist : linsys List of linear input/output systems (single system is OK) + num : integer, optional + Number of samples to generate. Default is 50. Return values ------------- @@ -495,6 +497,10 @@ # integer. It excludes poles and zeros at the origin. If no features # are found, it turns logspace(-1, 1) + # If argument was a singleton, turn it into a list + if (not getattr(syslist, '__iter__', False)): + syslist = (syslist,) + # Find the list of all poles and zeros in the systems features = np.array(()) for sys in syslist: @@ -513,7 +519,7 @@ # Set the range to be an order of magnitude beyond any features omega = sp.logspace(np.floor(np.min(features))-1, - np.ceil(np.max(features))+1) + np.ceil(np.max(features))+1, num) return omega This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mur...@us...> - 2012-01-08 02:56:30
|
Revision: 179 http://python-control.svn.sourceforge.net/python-control/?rev=179&view=rev Author: murrayrm Date: 2012-01-08 02:56:24 +0000 (Sun, 08 Jan 2012) Log Message: ----------- small doc fix Modified Paths: -------------- trunk/src/freqplot.py Modified: trunk/src/freqplot.py =================================================================== --- trunk/src/freqplot.py 2012-01-08 02:55:55 UTC (rev 178) +++ trunk/src/freqplot.py 2012-01-08 02:56:24 UTC (rev 179) @@ -319,8 +319,8 @@ syslist : list of Lti List of linear input/output systems (single system is OK) - Return - ------ + Returns + ------- omega : array Range of frequencies in rad/sec This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |