|
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.
|