42
42
'pzmap.grid' : False , # Plot omega-damping grid
43
43
'pzmap.marker_size' : 6 , # Size of the markers
44
44
'pzmap.marker_width' : 1.5 , # Width of the markers
45
- 'pzmap.expansion_factor' : 2 , # Amount to scale plots beyond features
45
+ 'pzmap.expansion_factor' : 1.8 , # Amount to scale plots beyond features
46
+ 'pzmap.buffer_factor' : 1.05 , # Buffer to leave around plot peaks
46
47
}
47
48
48
49
#
@@ -110,7 +111,7 @@ def pole_zero_map(sysdata):
110
111
def pole_zero_plot (
111
112
data , plot = None , grid = None , title = None , marker_color = None ,
112
113
marker_size = None , marker_width = None , legend_loc = 'upper right' ,
113
- xlim = None , ylim = None , interactive = False , ax = None ,
114
+ xlim = None , ylim = None , interactive = False , ax = None , scaling = None ,
114
115
initial_gain = None , ** kwargs ):
115
116
# TODO: update docstring (see other response/plot functions for style)
116
117
"""Plot a pole/zero map for a linear system.
@@ -144,7 +145,7 @@ def pole_zero_plot(
144
145
(legacy) If the `plot` keyword is given, the system poles and zeros
145
146
are returned.
146
147
147
- Notes (TODO: update)
148
+ Notes (TODO: update, including scaling )
148
149
-----
149
150
The pzmap function calls matplotlib.pyplot.axis('equal'), which means
150
151
that trying to reset the axis limits may not behave as expected. To
@@ -209,14 +210,15 @@ def pole_zero_plot(
209
210
if grid :
210
211
plt .clf ()
211
212
if all ([isctime (dt = response .dt ) for response in data ]):
212
- ax , fig = sgrid ()
213
+ ax , fig = sgrid (scaling = scaling )
213
214
elif all ([isdtime (dt = response .dt ) for response in data ]):
214
- ax , fig = zgrid ()
215
+ ax , fig = zgrid (scaling = scaling )
215
216
else :
216
217
ValueError (
217
218
"incompatible time responses; don't know how to grid" )
218
219
elif len (axs ) == 0 :
219
- ax , fig = nogrid (data [0 ].dt ) # use first response timebase
220
+ # use first response timebase
221
+ ax , fig = nogrid (data [0 ].dt , scaling = scaling )
220
222
else :
221
223
# Use the existing axes and any grid that is there
222
224
# TODO: allow axis to be overriden via parameter
@@ -270,7 +272,7 @@ def pole_zero_plot(
270
272
label = response .sysname )
271
273
272
274
# Compute the axis limits to use based on the response
273
- resp_xlim , resp_ylim = _compute_root_locus_limits (response . loci )
275
+ resp_xlim , resp_ylim = _compute_root_locus_limits (response )
274
276
275
277
# Keep track of the current limits
276
278
xlim = [min (xlim [0 ], resp_xlim [0 ]), max (xlim [1 ], resp_xlim [1 ])]
@@ -433,11 +435,22 @@ def _create_root_locus_label(sys, K, s):
433
435
434
436
435
437
# Utility function to compute limits for root loci
436
- # TODO: compare to old code and recapture functionality (especially asymptotes)
437
438
# TODO: (note that sys is now available => code here may not be needed)
438
- def _compute_root_locus_limits (loci ):
439
- # Go through each locus
440
- xlim , ylim = [0 , 0 ], 0
439
+ def _compute_root_locus_limits (response ):
440
+ loci = response .loci
441
+
442
+ # Start with information about zeros, if present
443
+ if response .sys is not None and response .sys .zeros ().size > 0 :
444
+ xlim = [
445
+ min (0 , np .min (response .sys .zeros ().real )),
446
+ max (0 , np .max (response .sys .zeros ().real ))
447
+ ]
448
+ ylim = max (0 , np .max (response .sys .zeros ().imag ))
449
+ else :
450
+ xlim , ylim = [0 , 0 ], 0
451
+
452
+ # Go through each locus and look for features
453
+ rho = config ._get_param ('pzmap' , 'buffer_factor' )
441
454
for locus in loci .transpose ():
442
455
# Include all starting points
443
456
xlim = [min (xlim [0 ], locus [0 ].real ), max (xlim [1 ], locus [0 ].real )]
@@ -446,18 +459,22 @@ def _compute_root_locus_limits(loci):
446
459
# Find the local maxima of root locus curve
447
460
xpeaks = np .where (
448
461
np .diff (np .abs (locus .real )) < 0 , locus .real [0 :- 1 ], 0 )
449
- xlim = [min (xlim [0 ], np .min (xpeaks )), max (xlim [1 ], np .max (xpeaks ))]
462
+ xlim = [
463
+ min (xlim [0 ], np .min (xpeaks ) * rho ),
464
+ max (xlim [1 ], np .max (xpeaks ) * rho )
465
+ ]
450
466
451
467
ypeaks = np .where (
452
468
np .diff (np .abs (locus .imag )) < 0 , locus .imag [0 :- 1 ], 0 )
453
- ylim = max (ylim , np .max (ypeaks ))
454
-
455
- # Adjust the limits to include some space around features
456
- # TODO: use _k_max and project out to max k for all value?
457
- rho = config ._get_param ('pzmap' , 'expansion_factor' )
458
- xlim [0 ] = rho * xlim [0 ] if xlim [0 ] < 0 else 0
459
- xlim [1 ] = rho * xlim [1 ] if xlim [1 ] > 0 else 0
460
- ylim = rho * ylim if ylim > 0 else np .max (np .abs (xlim ))
469
+ ylim = max (ylim , np .max (ypeaks ) * rho )
470
+
471
+ if isctime (dt = response .dt ):
472
+ # Adjust the limits to include some space around features
473
+ # TODO: use _k_max and project out to max k for all value?
474
+ rho = config ._get_param ('pzmap' , 'expansion_factor' )
475
+ xlim [0 ] = rho * xlim [0 ] if xlim [0 ] < 0 else 0
476
+ xlim [1 ] = rho * xlim [1 ] if xlim [1 ] > 0 else 0
477
+ ylim = rho * ylim if ylim > 0 else np .max (np .abs (xlim ))
461
478
462
479
return xlim , [- ylim , ylim ]
463
480
0 commit comments