55#
66# Functionality to add
77# [ ] Get rid of this long header (need some common, documented convention)
8- # [ ] Add mechanisms for storing/plotting margins? (currently forces FRD)
9- # [ ] Allow line colors/styles to be set in plot() command (also time plots)
10- # [ ] Allow bode or nyquist style plots from plot()
11- # [ ] Allow nyquist_response() to generate the response curve (?)
8+ # [x ] Add mechanisms for storing/plotting margins? (currently forces FRD)
9+ # [? ] Allow line colors/styles to be set in plot() command (also time plots)
10+ # [x ] Allow bode or nyquist style plots from plot()
11+ # [i ] Allow nyquist_response() to generate the response curve (?)
1212# [i] Allow MIMO frequency plots (w/ mag/phase subplots a la MATLAB)
1313# [i] Update sisotool to use ax=
1414# [i] Create __main__ in freqplot_test to view results (a la timeplot_test)
1717# [i] Re-implement including of gain/phase margin in the title (?)
1818# [i] Change gangof4 to use bode_plot(plot_phase=False) w/ proper labels
1919# [ ] Allow use of subplot labels instead of output/input subtitles
20- # [ ] Add line labels to gangof4
21- # [ ] Update FRD to allow nyquist_response contours
22- # [ ] Allow frequency range to be overridden in bode_plot
23- # [ ] Unit tests for discrete time systems with different sample times
24- # [ ] Check examples/bode-and-nyquist-plots.ipynb for differences
20+ # [i ] Add line labels to gangof4 [done by via bode_plot()]
21+ # [i] Allow frequency range to be overridden in bode_plot
22+ # [i] Unit tests for discrete time systems with different sample times
23+ # [c] Check examples/bode-and-nyquist-plots.ipynb for differences
24+ # [ ] Add unit tests for ct.config.defaults['freqplot_number_of_samples']
2525
2626#
2727# This file contains some standard control system plots: Bode plots,
@@ -704,7 +704,7 @@ def _make_line_label(response, output_index, input_index):
704704 # Get the frequencies and convert to Hz, if needed
705705 omega_plot = omega_sys / (2 * math .pi ) if Hz else omega_sys
706706 if response .isdtime (strict = True ):
707- nyq_freq = 0.5 / response .dt if Hz else math .pi / response .dt
707+ nyq_freq = ( 0.5 / response .dt ) if Hz else ( math .pi / response .dt )
708708
709709 # Save the magnitude and phase to plot
710710 mag_plot = 20 * np .log10 (mag [i , j ]) if dB else mag [i , j ]
@@ -1163,7 +1163,7 @@ def plot(self, *args, **kwargs):
11631163
11641164
11651165def nyquist_response (
1166- syslist , omega = None , plot = None , omega_limits = None , omega_num = None ,
1166+ sysdata , omega = None , plot = None , omega_limits = None , omega_num = None ,
11671167 label_freq = 0 , color = None , return_contour = False , check_kwargs = True ,
11681168 warn_encirclements = True , warn_nyquist = True , ** kwargs ):
11691169 """Nyquist response for a system.
@@ -1179,7 +1179,7 @@ def nyquist_response(
11791179
11801180 Parameters
11811181 ----------
1182- syslist : list of LTI
1182+ sysdata : LTI or list of LTI
11831183 List of linear input/output systems (single system is OK). Nyquist
11841184 curves for each system are plotted on the same graph.
11851185
@@ -1283,9 +1283,8 @@ def nyquist_response(
12831283 if check_kwargs and kwargs :
12841284 raise TypeError ("unrecognized keywords: " , str (kwargs ))
12851285
1286- # If argument was a singleton, turn it into a tuple
1287- if not isinstance (syslist , (list , tuple )):
1288- syslist = (syslist ,)
1286+ # Convert the first argument to a list
1287+ syslist = sysdata if isinstance (sysdata , (list , tuple )) else [sysdata ]
12891288
12901289 # Determine the range of frequencies to use, based on args/features
12911290 omega , omega_range_given = _determine_omega_vector (
@@ -1499,17 +1498,15 @@ def nyquist_response(
14991498 count , contour , resp , sys .dt , sysname = sysname ,
15001499 return_contour = return_contour ))
15011500
1502- # Return response
1503- if len (responses ) == 1 : # TODO: update to match input type
1504- return responses [0 ]
1505- else :
1501+ if isinstance (sysdata , (list , tuple )):
15061502 return NyquistResponseList (responses )
1503+ else :
1504+ return responses [0 ]
15071505
15081506
15091507def nyquist_plot (
1510- data , omega = None , plot = None , omega_limits = None , omega_num = None ,
1511- label_freq = 0 , color = None , return_contour = None , title = None ,
1512- legend_loc = 'upper right' , ** kwargs ):
1508+ data , omega = None , plot = None , label_freq = 0 , color = None ,
1509+ return_contour = None , title = None , legend_loc = 'upper right' , ** kwargs ):
15131510 """Nyquist plot for a system.
15141511
15151512 Generates a Nyquist plot for the system over a (optional) frequency
@@ -1677,8 +1674,6 @@ def nyquist_plot(
16771674
16781675 """
16791676 # Get values for params (and pop from list to allow keyword use in plot)
1680- omega_num_given = omega_num is not None
1681- omega_num = config ._get_param ('freqplot' , 'number_of_samples' , omega_num )
16821677 arrows = config ._get_param (
16831678 'nyquist' , 'arrows' , kwargs , _nyquist_defaults , pop = True )
16841679 arrow_size = config ._get_param (
@@ -1724,18 +1719,21 @@ def _parse_linestyle(style_name, allow_false=False):
17241719 else :
17251720 raise ValueError ("unknown or unsupported arrow location" )
17261721
1722+ # Set the arrow style
1723+ if arrow_style is None :
1724+ arrow_style = mpl .patches .ArrowStyle (
1725+ 'simple' , head_width = arrow_size , head_length = arrow_size )
1726+
17271727 # If argument was a singleton, turn it into a tuple
17281728 if not isinstance (data , (list , tuple )):
17291729 data = (data ,)
17301730
17311731 # If we are passed a list of systems, compute response first
1732- # If we were passed a list of systems, convert to data
17331732 if all ([isinstance (
17341733 sys , (StateSpace , TransferFunction , FrequencyResponseData ))
17351734 for sys in data ]):
17361735 nyquist_responses = nyquist_response (
1737- data , omega = omega , omega_limits = omega_limits , omega_num = omega_num ,
1738- check_kwargs = False , ** kwargs )
1736+ data , omega = omega , check_kwargs = False , ** kwargs )
17391737 if not isinstance (nyquist_responses , list ):
17401738 nyquist_responses = [nyquist_responses ]
17411739 else :
@@ -1763,11 +1761,6 @@ def _parse_linestyle(style_name, allow_false=False):
17631761 for i in range (out .shape [0 ]):
17641762 out [i ] = [] # unique list in each element
17651763
1766- # Set the arrow style
1767- if arrow_style is None :
1768- arrow_style = mpl .patches .ArrowStyle (
1769- 'simple' , head_width = arrow_size , head_length = arrow_size )
1770-
17711764 for idx , response in enumerate (nyquist_responses ):
17721765 resp = response .response
17731766 if response .dt in [0 , None ]:
@@ -1919,6 +1912,7 @@ def _parse_linestyle(style_name, allow_false=False):
19191912 title = "Nyquist plot for " + ", " .join (labels )
19201913 fig .suptitle (title )
19211914
1915+ # Legacy return pocessing
19221916 if plot is True or return_contour is not None :
19231917 if len (data ) == 1 :
19241918 counts , contours = counts [0 ], contours [0 ]
@@ -2134,7 +2128,7 @@ def gangof4_plot(P, C, omega=None, **kwargs):
21342128# Singular values plot
21352129#
21362130def singular_values_response (
2137- sys , omega = None , omega_limits = None , omega_num = None , Hz = False ):
2131+ sysdata , omega = None , omega_limits = None , omega_num = None , Hz = False ):
21382132 """Singular value response for a system.
21392133
21402134 Computes the singular values for a system or list of systems over
@@ -2173,8 +2167,8 @@ def singular_values_response(
21732167 >>> response = ct.singular_values_response(G, omega=omegas)
21742168
21752169 """
2176- # If argument was a singleton, turn it into a tuple
2177- syslist = sys if isinstance (sys , (list , tuple )) else ( sys ,)
2170+ # Convert the first argument to a list
2171+ syslist = sysdata if isinstance (sysdata , (list , tuple )) else [ sysdata ]
21782172
21792173 if any ([not isinstance (sys , LTI ) for sys in syslist ]):
21802174 ValueError ("singular values can only be computed for LTI systems" )
@@ -2201,8 +2195,7 @@ def singular_values_response(
22012195 sysname = response .sysname , plot_type = 'svplot' ,
22022196 title = f"Singular values for { response .sysname } " ))
22032197
2204- # Return the responses in the same form that we received the systems
2205- if isinstance (sys , (list , tuple )):
2198+ if isinstance (sysdata , (list , tuple )):
22062199 return FrequencyResponseList (svd_responses )
22072200 else :
22082201 return svd_responses [0 ]
@@ -2554,20 +2547,20 @@ def _default_frequency_range(syslist, Hz=None, number_of_samples=None,
25542547 if np .any (toreplace ):
25552548 features_ = features_ [~ toreplace ]
25562549 elif sys .isdtime (strict = True ):
2557- fn = math .pi * 1. / sys .dt
2550+ fn = math .pi / sys .dt
25582551 # TODO: What distance to the Nyquist frequency is appropriate?
25592552 freq_interesting .append (fn * 0.9 )
25602553
25612554 features_ = np .concatenate ((sys .poles (), sys .zeros ()))
25622555 # Get rid of poles and zeros on the real axis (imag==0)
2563- # * origin and real < 0
2556+ # * origin and real < 0
25642557 # * at 1.: would result in omega=0. (logaritmic plot!)
25652558 toreplace = np .isclose (features_ .imag , 0.0 ) & (
25662559 (features_ .real <= 0. ) |
25672560 (np .abs (features_ .real - 1.0 ) < 1.e-10 ))
25682561 if np .any (toreplace ):
25692562 features_ = features_ [~ toreplace ]
2570- # TODO: improve
2563+ # TODO: improve (mapping pack to continuous time)
25712564 features_ = np .abs (np .log (features_ ) / (1.j * sys .dt ))
25722565 else :
25732566 # TODO
0 commit comments