2222from .freqplot import _freqplot_defaults , _get_line_labels
2323from . import config
2424
25- __all__ = ['pzmap_response ' , 'pzmap_plot ' , 'pzmap' ]
25+ __all__ = ['pole_zero_map ' , 'root_locus_map' , 'pole_zero_plot ' , 'pzmap' ]
2626
2727
2828# Define default parameter values for this module
3434
3535
3636# Classes for keeping track of pzmap plots
37- class PoleZeroResponseList (list ):
37+ class RootLocusList (list ):
3838 def plot (self , * args , ** kwargs ):
39- return pzmap_plot (self , * args , ** kwargs )
39+ return pole_zero_plot (self , * args , ** kwargs )
4040
4141
42- class PoleZeroResponseData :
42+ class RootLocusData :
4343 def __init__ (
44- self , poles , zeros , gains = None , loci = None , dt = None , sysname = None ):
44+ self , poles , zeros , gains = None , loci = None , xlim = None , ylim = None ,
45+ dt = None , sysname = None ):
4546 self .poles = poles
4647 self .zeros = zeros
4748 self .gains = gains
4849 self .loci = loci
50+ self .xlim = xlim
51+ self .ylim = ylim
4952 self .dt = dt
5053 self .sysname = sysname
5154
@@ -54,38 +57,78 @@ def __iter__(self):
5457 return iter ((self .poles , self .zeros ))
5558
5659 def plot (self , * args , ** kwargs ):
57- return pzmap_plot (self , * args , ** kwargs )
60+ return pole_zero_plot (self , * args , ** kwargs )
5861
5962
60- # pzmap response funciton
61- def pzmap_response (sysdata ):
63+ # Pole/zero map
64+ def pole_zero_map (sysdata ):
6265 # Convert the first argument to a list
6366 syslist = sysdata if isinstance (sysdata , (list , tuple )) else [sysdata ]
6467
6568 responses = []
6669 for idx , sys in enumerate (syslist ):
6770 responses .append (
68- PoleZeroResponseData (
71+ RootLocusData (
6972 sys .poles (), sys .zeros (), dt = sys .dt , sysname = sys .name ))
7073
7174 if isinstance (sysdata , (list , tuple )):
72- return PoleZeroResponseList (responses )
75+ return RootLocusList (responses )
76+ else :
77+ return responses [0 ]
78+
79+
80+ # Root locus map
81+ def root_locus_map (sysdata , gains = None , xlim = None , ylim = None ):
82+ # Convert the first argument to a list
83+ syslist = sysdata if isinstance (sysdata , (list , tuple )) else [sysdata ]
84+
85+ responses = []
86+ for idx , sys in enumerate (syslist ):
87+ from .rlocus import _systopoly1d , _default_gains
88+ from .rlocus import _RLFindRoots , _RLSortRoots
89+
90+ if not sys .issiso ():
91+ raise ControlMIMONotImplemented (
92+ "sys must be single-input single-output (SISO)" )
93+
94+ # Convert numerator and denominator to polynomials if they aren't
95+ nump , denp = _systopoly1d (sys [0 , 0 ])
96+
97+ if xlim is None and sys .isdtime (strict = True ):
98+ xlim = (- 1.2 , 1.2 )
99+ if ylim is None and sys .isdtime (strict = True ):
100+ xlim = (- 1.3 , 1.3 )
101+
102+ if gains is None :
103+ kvect , root_array , xlim , ylim = _default_gains (
104+ nump , denp , xlim , ylim )
105+ else :
106+ kvect = np .atleast_1d (gains )
107+ root_array = _RLFindRoots (nump , denp , kvect )
108+ root_array = _RLSortRoots (root_array )
109+
110+ responses .append (RootLocusData (
111+ sys .poles (), sys .zeros (), kvect , root_array ,
112+ dt = sys .dt , sysname = sys .name , xlim = xlim , ylim = ylim ))
113+
114+ if isinstance (sysdata , (list , tuple )):
115+ return RootLocusList (responses )
73116 else :
74117 return responses [0 ]
75118
76119
77120# TODO: Implement more elegant cross-style axes. See:
78121# https://matplotlib.org/2.0.2/examples/axes_grid/demo_axisline_style.html
79122# https://matplotlib.org/2.0.2/examples/axes_grid/demo_curvelinear_grid.html
80- def pzmap_plot (
123+ def pole_zero_plot (
81124 data , plot = None , grid = None , title = None , marker_color = None ,
82125 marker_size = None , marker_width = None , legend_loc = 'upper right' ,
83- ** kwargs ):
126+ xlim = None , ylim = None , ** kwargs ):
84127 """Plot a pole/zero map for a linear system.
85128
86129 Parameters
87130 ----------
88- sysdata: List of PoleZeroResponseData objects or LTI systems
131+ sysdata: List of RootLocusData objects or LTI systems
89132 List of pole/zero response data objects generated by pzmap_response
90133 or rootlocus_response() that are to be plotted. If a list of systems
91134 is given, the poles and zeros of those systems will be plotted.
@@ -124,6 +167,7 @@ def pzmap_plot(
124167 grid = config ._get_param ('pzmap' , 'grid' , grid , False )
125168 marker_size = config ._get_param ('pzmap' , 'marker_size' , marker_size , 6 )
126169 marker_width = config ._get_param ('pzmap' , 'marker_width' , marker_width , 1.5 )
170+ xlim_user , ylim_user = xlim , ylim
127171 freqplot_rcParams = config ._get_param (
128172 'freqplot' , 'rcParams' , kwargs , _freqplot_defaults ,
129173 pop = True , last = True )
@@ -136,17 +180,17 @@ def pzmap_plot(
136180 if all ([isinstance (
137181 sys , (StateSpace , TransferFunction )) for sys in data ]):
138182 # Get the response, popping off keywords used there
139- pzmap_responses = pzmap_response (data )
140- elif all ([isinstance (d , PoleZeroResponseData ) for d in data ]):
183+ pzmap_responses = pole_zero_map (data )
184+ elif all ([isinstance (d , RootLocusData ) for d in data ]):
141185 pzmap_responses = data
142186 else :
143187 raise TypeError ("unknown system data type" )
144188
145189 # Legacy return value processing
146190 if plot is not None :
147191 warnings .warn (
148- "`pzmap_plot ` return values of poles, zeros is deprecated; "
149- "use pzmap_response ()" , DeprecationWarning )
192+ "`pole_zero_plot ` return values of poles, zeros is deprecated; "
193+ "use pole_zero_map ()" , DeprecationWarning )
150194
151195 # Extract out the values that we will eventually return
152196 poles = [response .poles for response in pzmap_responses ]
@@ -169,9 +213,9 @@ def pzmap_plot(
169213 with plt .rc_context (freqplot_rcParams ):
170214 if grid :
171215 plt .clf ()
172- if all ([response .isctime () for response in data ]):
216+ if all ([response .dt in [ 0 , None ] for response in data ]):
173217 ax , fig = sgrid ()
174- elif all ([response .isdtime () for response in data ]):
218+ elif all ([response .dt > 0 for response in data ]):
175219 ax , fig = zgrid ()
176220 else :
177221 ValueError (
@@ -192,10 +236,11 @@ def pzmap_plot(
192236 color_offset = color_cycle .index (last_color ) + 1
193237
194238 # Create a list of lines for the output
195- out = np .empty ((len (pzmap_responses ), 2 ), dtype = object )
239+ out = np .empty ((len (pzmap_responses ), 3 ), dtype = object )
196240 for i , j in itertools .product (range (out .shape [0 ]), range (out .shape [1 ])):
197241 out [i , j ] = [] # unique list in each element
198242
243+ xlim , ylim = ax .get_xlim (), ax .get_ylim ()
199244 for idx , response in enumerate (pzmap_responses ):
200245 poles = response .poles
201246 zeros = response .zeros
@@ -208,44 +253,65 @@ def pzmap_plot(
208253
209254 # Plot the locations of the poles and zeros
210255 if len (poles ) > 0 :
256+ label = response .sysname if response .loci is None else None
211257 out [idx , 0 ] = ax .plot (
212258 real (poles ), imag (poles ), marker = 'x' , linestyle = '' ,
213259 markeredgecolor = color , markerfacecolor = color ,
214260 markersize = marker_size , markeredgewidth = marker_width ,
215- label = response . sysname )
261+ label = label )
216262 if len (zeros ) > 0 :
217263 out [idx , 1 ] = ax .plot (
218264 real (zeros ), imag (zeros ), marker = 'o' , linestyle = '' ,
219265 markeredgecolor = color , markerfacecolor = 'none' ,
220266 markersize = marker_size , markeredgewidth = marker_width )
221267
268+ # Plot the loci, if present
269+ if response .loci is not None :
270+ for locus in response .loci .transpose ():
271+ out [idx , 2 ] += ax .plot (
272+ real (locus ), imag (locus ), color = color ,
273+ label = response .sysname )
274+
275+ # Compute the axis limits to use
276+ xlim = (min (xlim [0 ], response .xlim [0 ]), max (xlim [1 ], response .xlim [1 ]))
277+ ylim = (min (ylim [0 ], response .ylim [0 ]), max (ylim [1 ], response .ylim [1 ]))
278+
279+ # Set up the limits for the plot
280+ ax .set_xlim (xlim if xlim_user is None else xlim_user )
281+ ax .set_ylim (ylim if ylim_user is None else ylim_user )
282+
222283 # List of systems that are included in this plot
223284 lines , labels = _get_line_labels (ax )
224285
225- # Update the lines to use tuples for poles and zeros
226- from matplotlib .lines import Line2D
227- from matplotlib .legend_handler import HandlerTuple
228- line_tuples = []
229- for pole_line in lines :
230- zero_line = Line2D (
231- [0 ], [0 ], marker = 'o' , linestyle = '' ,
232- markeredgecolor = pole_line .get_markerfacecolor (),
233- markerfacecolor = 'none' , markersize = marker_size ,
234- markeredgewidth = marker_width )
235- handle = (pole_line , zero_line )
236- line_tuples .append (handle )
237- print (line_tuples )
238-
239286 # Add legend if there is more than one system plotted
240287 if len (labels ) > 1 and legend_loc is not False :
241- with plt .rc_context (freqplot_rcParams ):
242- ax .legend (
243- line_tuples , labels , loc = legend_loc ,
244- handler_map = {tuple : HandlerTuple (ndivide = None )})
288+ if response .loci is None :
289+ # Use "x o" for the system label, via matplotlib tuple handler
290+ from matplotlib .lines import Line2D
291+ from matplotlib .legend_handler import HandlerTuple
292+
293+ line_tuples = []
294+ for pole_line in lines :
295+ zero_line = Line2D (
296+ [0 ], [0 ], marker = 'o' , linestyle = '' ,
297+ markeredgecolor = pole_line .get_markerfacecolor (),
298+ markerfacecolor = 'none' , markersize = marker_size ,
299+ markeredgewidth = marker_width )
300+ handle = (pole_line , zero_line )
301+ line_tuples .append (handle )
302+
303+ with plt .rc_context (freqplot_rcParams ):
304+ ax .legend (
305+ line_tuples , labels , loc = legend_loc ,
306+ handler_map = {tuple : HandlerTuple (ndivide = None )})
307+ else :
308+ # Regular legend, with lines
309+ with plt .rc_context (freqplot_rcParams ):
310+ ax .legend (lines , labels , loc = legend_loc )
245311
246312 # Add the title
247313 if title is None :
248- title = "Pole/zero map for " + ", " .join (labels )
314+ title = "Pole/zero plot for " + ", " .join (labels )
249315 with plt .rc_context (freqplot_rcParams ):
250316 fig .suptitle (title )
251317
@@ -259,4 +325,4 @@ def pzmap_plot(
259325 return out
260326
261327
262- pzmap = pzmap_plot
328+ pzmap = pole_zero_plot
0 commit comments