59
59
# Default values for module parameter variables
60
60
_freqplot_defaults = {
61
61
'freqplot.feature_periphery_decades' : 1 ,
62
- 'freqplot.number_of_samples' : None ,
62
+ 'freqplot.number_of_samples' : 1000 ,
63
63
}
64
64
65
65
#
@@ -94,7 +94,7 @@ def bode_plot(syslist, omega=None,
94
94
----------
95
95
syslist : linsys
96
96
List of linear input/output systems (single system is OK)
97
- omega : list
97
+ omega : array_like
98
98
List of frequencies in rad/sec to be used for frequency response
99
99
dB : bool
100
100
If True, plot result in dB. Default is false.
@@ -106,10 +106,10 @@ def bode_plot(syslist, omega=None,
106
106
config.defaults['bode.deg']
107
107
plot : bool
108
108
If True (default), plot magnitude and phase
109
- omega_limits: tuple, list, ... of two values
109
+ omega_limits : array_like of two values
110
110
Limits of the to generate frequency vector.
111
111
If Hz=True the limits are in Hz otherwise in rad/s.
112
- omega_num: int
112
+ omega_num : int
113
113
Number of samples to plot. Defaults to
114
114
config.defaults['freqplot.number_of_samples'].
115
115
margins : bool
@@ -200,18 +200,18 @@ def bode_plot(syslist, omega=None,
200
200
omega = default_frequency_range (syslist , Hz = Hz ,
201
201
number_of_samples = omega_num )
202
202
else :
203
- omega_limits = np .array (omega_limits )
203
+ omega_limits = np .asarray (omega_limits )
204
+ if len (omega_limits ) != 2 :
205
+ raise ValueError ("len(omega_limits) must be 2" )
204
206
if Hz :
205
207
omega_limits *= 2. * math .pi
206
208
if omega_num :
207
- omega = np .logspace (np .log10 (omega_limits [0 ]),
208
- np .log10 (omega_limits [1 ]),
209
- num = omega_num ,
210
- endpoint = True )
209
+ num = omega_num
211
210
else :
212
- omega = np .logspace (np .log10 (omega_limits [0 ]),
213
- np .log10 (omega_limits [1 ]),
214
- endpoint = True )
211
+ num = config .defaults ['freqplot.number_of_samples' ]
212
+ omega = np .logspace (np .log10 (omega_limits [0 ]),
213
+ np .log10 (omega_limits [1 ]), num = num ,
214
+ endpoint = True )
215
215
216
216
mags , phases , omegas , nyquistfrqs = [], [], [], []
217
217
for sys in syslist :
@@ -507,9 +507,9 @@ def gen_zero_centered_series(val_min, val_max, period):
507
507
# Nyquist plot
508
508
#
509
509
510
- def nyquist_plot (syslist , omega = None , plot = True , label_freq = 0 ,
511
- arrowhead_length = 0.1 , arrowhead_width = 0.1 ,
512
- color = None , * args , ** kwargs ):
510
+ def nyquist_plot (syslist , omega = None , plot = True , omega_limits = None ,
511
+ omega_num = None , label_freq = 0 , arrowhead_length = 0.1 ,
512
+ arrowhead_width = 0.1 , color = None , * args , ** kwargs ):
513
513
"""
514
514
Nyquist plot for a system
515
515
@@ -519,29 +519,36 @@ def nyquist_plot(syslist, omega=None, plot=True, label_freq=0,
519
519
----------
520
520
syslist : list of LTI
521
521
List of linear input/output systems (single system is OK)
522
- omega : freq_range
523
- Range of frequencies (list or bounds) in rad/sec
524
- Plot : boolean
522
+ plot : boolean
525
523
If True, plot magnitude
524
+ omega : array_like
525
+ Range of frequencies in rad/sec
526
+ omega_limits : array_like of two values
527
+ Limits of the to generate frequency vector.
528
+ omega_num : int
529
+ Number of samples to plot. Defaults to
530
+ config.defaults['freqplot.number_of_samples'].
526
531
color : string
527
- Used to specify the color of the plot
532
+ Used to specify the color of the line and arrowhead
528
533
label_freq : int
529
534
Label every nth frequency on the plot
530
- arrowhead_width : arrow head width
531
- arrowhead_length : arrow head length
535
+ arrowhead_width : float
536
+ Arrow head width
537
+ arrowhead_length : float
538
+ Arrow head length
532
539
*args : :func:`matplotlib.pyplot.plot` positional properties, optional
533
540
Additional arguments for `matplotlib` plots (color, linestyle, etc)
534
541
**kwargs : :func:`matplotlib.pyplot.plot` keyword properties, optional
535
542
Additional keywords (passed to `matplotlib`)
536
543
537
544
Returns
538
545
-------
539
- real : array
546
+ real : ndarray
540
547
real part of the frequency response array
541
- imag : array
548
+ imag : ndarray
542
549
imaginary part of the frequency response array
543
- freq : array
544
- frequencies
550
+ freq : ndarray
551
+ frequencies in rad/s
545
552
546
553
Examples
547
554
--------
@@ -571,7 +578,21 @@ def nyquist_plot(syslist, omega=None, plot=True, label_freq=0,
571
578
572
579
# Select a default range if none is provided
573
580
if omega is None :
574
- omega = default_frequency_range (syslist )
581
+ if omega_limits is None :
582
+ # Select a default range if none is provided
583
+ omega = default_frequency_range (syslist , Hz = False ,
584
+ number_of_samples = omega_num )
585
+ else :
586
+ omega_limits = np .asarray (omega_limits )
587
+ if len (omega_limits ) != 2 :
588
+ raise ValueError ("len(omega_limits) must be 2" )
589
+ if omega_num :
590
+ num = omega_num
591
+ else :
592
+ num = config .defaults ['freqplot.number_of_samples' ]
593
+ omega = np .logspace (np .log10 (omega_limits [0 ]),
594
+ np .log10 (omega_limits [1 ]), num = num ,
595
+ endpoint = True )
575
596
576
597
# Interpolate between wmin and wmax if a tuple or list are provided
577
598
elif isinstance (omega , list ) or isinstance (omega , tuple ):
@@ -580,65 +601,61 @@ def nyquist_plot(syslist, omega=None, plot=True, label_freq=0,
580
601
raise ValueError ("Supported frequency arguments are (wmin,wmax)"
581
602
"tuple or list, or frequency vector. " )
582
603
omega = np .logspace (np .log10 (omega [0 ]), np .log10 (omega [1 ]),
583
- num = 50 , endpoint = True , base = 10.0 )
604
+ num = 500 , endpoint = True , base = 10.0 )
584
605
585
606
for sys in syslist :
586
607
if not sys .issiso ():
587
608
# TODO: Add MIMO nyquist plots.
588
609
raise ControlMIMONotImplemented (
589
610
"Nyquist is currently only implemented for SISO systems." )
590
- else :
591
- # Get the magnitude and phase of the system
592
- mag_tmp , phase_tmp , omega = sys .frequency_response (omega )
593
- mag = np .squeeze (mag_tmp )
594
- phase = np .squeeze (phase_tmp )
595
-
596
- # Compute the primary curve
597
- x = np .multiply (mag , np .cos (phase ))
598
- y = np .multiply (mag , np .sin (phase ))
599
611
600
- if plot :
601
- # Plot the primary curve and mirror image
602
- p = plt .plot (x , y , '-' , color = color , * args , ** kwargs )
603
- c = p [0 ].get_color ()
604
- ax = plt .gca ()
605
- # Plot arrow to indicate Nyquist encirclement orientation
606
- ax .arrow (x [0 ], y [0 ], (x [1 ]- x [0 ])/ 2 , (y [1 ]- y [0 ])/ 2 , fc = c , ec = c ,
607
- head_width = arrowhead_width ,
608
- head_length = arrowhead_length )
609
-
610
- plt .plot (x , - y , '-' , color = c , * args , ** kwargs )
611
- ax .arrow (
612
- x [- 1 ], - y [- 1 ], (x [- 1 ]- x [- 2 ])/ 2 , (y [- 1 ]- y [- 2 ])/ 2 ,
613
- fc = c , ec = c , head_width = arrowhead_width ,
614
- head_length = arrowhead_length )
615
-
616
- # Mark the -1 point
617
- plt .plot ([- 1 ], [0 ], 'r+' )
618
-
619
- # Label the frequencies of the points
620
- if label_freq :
621
- ind = slice (None , None , label_freq )
622
- for xpt , ypt , omegapt in zip (x [ind ], y [ind ], omega [ind ]):
623
- # Convert to Hz
624
- f = omegapt / (2 * np .pi )
625
-
626
- # Factor out multiples of 1000 and limit the
627
- # result to the range [-8, 8].
628
- pow1000 = max (min (get_pow1000 (f ), 8 ), - 8 )
629
-
630
- # Get the SI prefix.
631
- prefix = gen_prefix (pow1000 )
632
-
633
- # Apply the text. (Use a space before the text to
634
- # prevent overlap with the data.)
635
- #
636
- # np.round() is used because 0.99... appears
637
- # instead of 1.0, and this would otherwise be
638
- # truncated to 0.
639
- plt .text (xpt , ypt , ' ' +
640
- str (int (np .round (f / 1000 ** pow1000 , 0 ))) + ' ' +
641
- prefix + 'Hz' )
612
+ # Get the magnitude and phase of the system
613
+ mag , phase , omega = sys .frequency_response (omega )
614
+
615
+ # Compute the primary curve
616
+ x = mag * np .cos (phase )
617
+ y = mag * np .sin (phase )
618
+
619
+ if plot :
620
+ # Plot the primary curve and mirror image
621
+ p = plt .plot (np .hstack ((x ,x )), np .hstack ((y ,- y )),
622
+ '-' , color = color , * args , ** kwargs )
623
+ c = p [0 ].get_color ()
624
+ ax = plt .gca ()
625
+ # Plot arrow to indicate Nyquist encirclement orientation
626
+ ax .arrow (x [0 ], y [0 ], (x [1 ]- x [0 ])/ 2 , (y [1 ]- y [0 ])/ 2 ,
627
+ fc = c , ec = c , head_width = arrowhead_width ,
628
+ head_length = arrowhead_length , label = None )
629
+ ax .arrow (x [- 1 ], - y [- 1 ], (x [- 1 ]- x [- 2 ])/ 2 , (y [- 1 ]- y [- 2 ])/ 2 ,
630
+ fc = c , ec = c , head_width = arrowhead_width ,
631
+ head_length = arrowhead_length , label = None )
632
+
633
+ # Mark the -1 point
634
+ plt .plot ([- 1 ], [0 ], 'r+' )
635
+
636
+ # Label the frequencies of the points
637
+ if label_freq :
638
+ ind = slice (None , None , label_freq )
639
+ for xpt , ypt , omegapt in zip (x [ind ], y [ind ], omega [ind ]):
640
+ # Convert to Hz
641
+ f = omegapt / (2 * np .pi )
642
+
643
+ # Factor out multiples of 1000 and limit the
644
+ # result to the range [-8, 8].
645
+ pow1000 = max (min (get_pow1000 (f ), 8 ), - 8 )
646
+
647
+ # Get the SI prefix.
648
+ prefix = gen_prefix (pow1000 )
649
+
650
+ # Apply the text. (Use a space before the text to
651
+ # prevent overlap with the data.)
652
+ #
653
+ # np.round() is used because 0.99... appears
654
+ # instead of 1.0, and this would otherwise be
655
+ # truncated to 0.
656
+ plt .text (xpt , ypt , ' ' +
657
+ str (int (np .round (f / 1000 ** pow1000 , 0 ))) + ' ' +
658
+ prefix + 'Hz' )
642
659
643
660
if plot :
644
661
ax = plt .gca ()
0 commit comments