22
22
from .freqplot import _freqplot_defaults , _get_line_labels
23
23
from . import config
24
24
25
- __all__ = ['pzmap_response ' , 'pzmap_plot ' , 'pzmap' ]
25
+ __all__ = ['pole_zero_map ' , 'root_locus_map' , 'pole_zero_plot ' , 'pzmap' ]
26
26
27
27
28
28
# Define default parameter values for this module
34
34
35
35
36
36
# Classes for keeping track of pzmap plots
37
- class PoleZeroResponseList (list ):
37
+ class RootLocusList (list ):
38
38
def plot (self , * args , ** kwargs ):
39
- return pzmap_plot (self , * args , ** kwargs )
39
+ return pole_zero_plot (self , * args , ** kwargs )
40
40
41
41
42
- class PoleZeroResponseData :
42
+ class RootLocusData :
43
43
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 ):
45
46
self .poles = poles
46
47
self .zeros = zeros
47
48
self .gains = gains
48
49
self .loci = loci
50
+ self .xlim = xlim
51
+ self .ylim = ylim
49
52
self .dt = dt
50
53
self .sysname = sysname
51
54
@@ -54,38 +57,78 @@ def __iter__(self):
54
57
return iter ((self .poles , self .zeros ))
55
58
56
59
def plot (self , * args , ** kwargs ):
57
- return pzmap_plot (self , * args , ** kwargs )
60
+ return pole_zero_plot (self , * args , ** kwargs )
58
61
59
62
60
- # pzmap response funciton
61
- def pzmap_response (sysdata ):
63
+ # Pole/zero map
64
+ def pole_zero_map (sysdata ):
62
65
# Convert the first argument to a list
63
66
syslist = sysdata if isinstance (sysdata , (list , tuple )) else [sysdata ]
64
67
65
68
responses = []
66
69
for idx , sys in enumerate (syslist ):
67
70
responses .append (
68
- PoleZeroResponseData (
71
+ RootLocusData (
69
72
sys .poles (), sys .zeros (), dt = sys .dt , sysname = sys .name ))
70
73
71
74
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 )
73
116
else :
74
117
return responses [0 ]
75
118
76
119
77
120
# TODO: Implement more elegant cross-style axes. See:
78
121
# https://matplotlib.org/2.0.2/examples/axes_grid/demo_axisline_style.html
79
122
# https://matplotlib.org/2.0.2/examples/axes_grid/demo_curvelinear_grid.html
80
- def pzmap_plot (
123
+ def pole_zero_plot (
81
124
data , plot = None , grid = None , title = None , marker_color = None ,
82
125
marker_size = None , marker_width = None , legend_loc = 'upper right' ,
83
- ** kwargs ):
126
+ xlim = None , ylim = None , ** kwargs ):
84
127
"""Plot a pole/zero map for a linear system.
85
128
86
129
Parameters
87
130
----------
88
- sysdata: List of PoleZeroResponseData objects or LTI systems
131
+ sysdata: List of RootLocusData objects or LTI systems
89
132
List of pole/zero response data objects generated by pzmap_response
90
133
or rootlocus_response() that are to be plotted. If a list of systems
91
134
is given, the poles and zeros of those systems will be plotted.
@@ -124,6 +167,7 @@ def pzmap_plot(
124
167
grid = config ._get_param ('pzmap' , 'grid' , grid , False )
125
168
marker_size = config ._get_param ('pzmap' , 'marker_size' , marker_size , 6 )
126
169
marker_width = config ._get_param ('pzmap' , 'marker_width' , marker_width , 1.5 )
170
+ xlim_user , ylim_user = xlim , ylim
127
171
freqplot_rcParams = config ._get_param (
128
172
'freqplot' , 'rcParams' , kwargs , _freqplot_defaults ,
129
173
pop = True , last = True )
@@ -136,17 +180,17 @@ def pzmap_plot(
136
180
if all ([isinstance (
137
181
sys , (StateSpace , TransferFunction )) for sys in data ]):
138
182
# 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 ]):
141
185
pzmap_responses = data
142
186
else :
143
187
raise TypeError ("unknown system data type" )
144
188
145
189
# Legacy return value processing
146
190
if plot is not None :
147
191
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 )
150
194
151
195
# Extract out the values that we will eventually return
152
196
poles = [response .poles for response in pzmap_responses ]
@@ -169,9 +213,9 @@ def pzmap_plot(
169
213
with plt .rc_context (freqplot_rcParams ):
170
214
if grid :
171
215
plt .clf ()
172
- if all ([response .isctime () for response in data ]):
216
+ if all ([response .dt in [ 0 , None ] for response in data ]):
173
217
ax , fig = sgrid ()
174
- elif all ([response .isdtime () for response in data ]):
218
+ elif all ([response .dt > 0 for response in data ]):
175
219
ax , fig = zgrid ()
176
220
else :
177
221
ValueError (
@@ -192,10 +236,11 @@ def pzmap_plot(
192
236
color_offset = color_cycle .index (last_color ) + 1
193
237
194
238
# 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 )
196
240
for i , j in itertools .product (range (out .shape [0 ]), range (out .shape [1 ])):
197
241
out [i , j ] = [] # unique list in each element
198
242
243
+ xlim , ylim = ax .get_xlim (), ax .get_ylim ()
199
244
for idx , response in enumerate (pzmap_responses ):
200
245
poles = response .poles
201
246
zeros = response .zeros
@@ -208,44 +253,65 @@ def pzmap_plot(
208
253
209
254
# Plot the locations of the poles and zeros
210
255
if len (poles ) > 0 :
256
+ label = response .sysname if response .loci is None else None
211
257
out [idx , 0 ] = ax .plot (
212
258
real (poles ), imag (poles ), marker = 'x' , linestyle = '' ,
213
259
markeredgecolor = color , markerfacecolor = color ,
214
260
markersize = marker_size , markeredgewidth = marker_width ,
215
- label = response . sysname )
261
+ label = label )
216
262
if len (zeros ) > 0 :
217
263
out [idx , 1 ] = ax .plot (
218
264
real (zeros ), imag (zeros ), marker = 'o' , linestyle = '' ,
219
265
markeredgecolor = color , markerfacecolor = 'none' ,
220
266
markersize = marker_size , markeredgewidth = marker_width )
221
267
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
+
222
283
# List of systems that are included in this plot
223
284
lines , labels = _get_line_labels (ax )
224
285
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
-
239
286
# Add legend if there is more than one system plotted
240
287
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 )
245
311
246
312
# Add the title
247
313
if title is None :
248
- title = "Pole/zero map for " + ", " .join (labels )
314
+ title = "Pole/zero plot for " + ", " .join (labels )
249
315
with plt .rc_context (freqplot_rcParams ):
250
316
fig .suptitle (title )
251
317
@@ -259,4 +325,4 @@ def pzmap_plot(
259
325
return out
260
326
261
327
262
- pzmap = pzmap_plot
328
+ pzmap = pole_zero_plot
0 commit comments