|
111 | 111 | 'freqplot.share_frequency': 'col', |
112 | 112 | } |
113 | 113 |
|
| 114 | +# |
| 115 | +# Frequency response data list class |
| 116 | +# |
| 117 | +# This class is a subclass of list that adds a plot() method, enabling |
| 118 | +# direct plotting from routines returning a list of FrequencyResponseData |
| 119 | +# objects. |
| 120 | +# |
| 121 | + |
| 122 | +class FrequencyResponseList(list): |
| 123 | + def plot(self, *args, plot_type=None, **kwargs): |
| 124 | + if plot_type == None: |
| 125 | + for response in self: |
| 126 | + if plot_type is not None and response.plot_type != plot_type: |
| 127 | + raise TypeError( |
| 128 | + "inconsistent plot_types in data; set plot_type " |
| 129 | + "to 'bode', 'svplot', or 'nyquist'") |
| 130 | + plot_type = response.plot_type |
| 131 | + |
| 132 | + if plot_type == 'bode': |
| 133 | + bode_plot(self, *args, **kwargs) |
| 134 | + elif plot_type == 'svplot': |
| 135 | + singular_values_plot(self, *args, **kwargs) |
| 136 | + elif plot_type == 'nyquist': |
| 137 | + # nyquist_plot(self, *args, **kwargs) |
| 138 | + raise NotImplementedError("Nyquist plots not yet supported") |
| 139 | + else: |
| 140 | + raise ValueError(f"unknown plot type '{plot_type}'") |
| 141 | + |
114 | 142 | # |
115 | 143 | # Bode plot |
116 | 144 | # |
| 145 | +# This is the default method for plotting frequency responses. There are |
| 146 | +# lots of options available for tuning the format of the plot, (hopefully) |
| 147 | +# covering most of the common use cases. |
| 148 | +# |
117 | 149 |
|
118 | 150 | def bode_plot( |
119 | 151 | data, omega=None, *fmt, ax=None, omega_limits=None, omega_num=None, |
@@ -653,7 +685,6 @@ def _make_line_label(response, output_index, input_index): |
653 | 685 | label += ", " if label != "" else "" |
654 | 686 | label += f"{response.sysname}" |
655 | 687 |
|
656 | | - print(label) |
657 | 688 | return label |
658 | 689 |
|
659 | 690 | for index, response in enumerate(data): |
@@ -1927,14 +1958,14 @@ def singular_values_response( |
1927 | 1958 | svd_responses.append( |
1928 | 1959 | FrequencyResponseData( |
1929 | 1960 | sigma_fresp, response.omega, _return_singvals=True, |
1930 | | - outputs=[f'$\\sigma_{k}$' for k in range(sigma.shape[0])], |
| 1961 | + outputs=[f'$\\sigma_{{{k+1}}}$' for k in range(sigma.shape[0])], |
1931 | 1962 | inputs='inputs', dt=response.dt, plot_phase=False, |
1932 | | - sysname=response.sysname, |
| 1963 | + sysname=response.sysname, plot_type='svplot', |
1933 | 1964 | title=f"Singular values for {response.sysname}")) |
1934 | 1965 |
|
1935 | 1966 | # Return the responses in the same form that we received the systems |
1936 | 1967 | if isinstance(sys, (list, tuple)): |
1937 | | - return svd_responses |
| 1968 | + return FrequencyResponseList(svd_responses) |
1938 | 1969 | else: |
1939 | 1970 | return svd_responses[0] |
1940 | 1971 |
|
@@ -2017,6 +2048,11 @@ def singular_values_plot( |
2017 | 2048 | else: |
2018 | 2049 | plot = True |
2019 | 2050 |
|
| 2051 | + # Warn the user if we got past something that is not real-valued |
| 2052 | + if any([not np.allclose(np.imag(response.fresp[:, 0, :]), 0) |
| 2053 | + for response in responses]): |
| 2054 | + warnings.warn("data has non-zero imaginary component") |
| 2055 | + |
2020 | 2056 | # Extract the data we need for plotting |
2021 | 2057 | sigmas = [np.real(response.fresp[:, 0, :]) for response in responses] |
2022 | 2058 | omegas = [response.omega for response in responses] |
|
0 commit comments