10
10
from control .tests .conftest import slycotonly
11
11
pytestmark = pytest .mark .usefixtures ("mplcleanup" )
12
12
13
+ #
14
+ # Define a system for testing out different sharing options
15
+ #
16
+
17
+ omega = np .logspace (- 2 , 2 , 5 )
18
+ fresp1 = np .array ([10 + 0j , 5 - 5j , 1 - 1j , 0.5 - 1j , - .1j ])
19
+ fresp2 = np .array ([1j , 0.5 - 0.5j , - 0.5 , 0.1 - 0.1j , - .05j ]) * 0.1
20
+ fresp3 = np .array ([10 + 0j , - 20j , - 10 , 2j , 1 ])
21
+ fresp4 = np .array ([10 + 0j , 5 - 5j , 1 - 1j , 0.5 - 1j , - .1j ]) * 0.01
22
+
23
+ fresp = np .empty ((2 , 2 , omega .size ), dtype = complex )
24
+ fresp [0 , 0 ] = fresp1
25
+ fresp [0 , 1 ] = fresp2
26
+ fresp [1 , 0 ] = fresp3
27
+ fresp [1 , 1 ] = fresp4
28
+ manual_response = ct .FrequencyResponseData (
29
+ fresp , omega , sysname = "Manual Response" )
30
+
31
+ @pytest .mark .parametrize (
32
+ "sys" , [
33
+ ct .tf ([1 ], [1 , 2 , 1 ], name = 'System 1' ), # SISO
34
+ manual_response , # simple MIMO
35
+ ])
36
+ # @pytest.mark.parametrize("pltmag", [True, False])
37
+ # @pytest.mark.parametrize("pltphs", [True, False])
38
+ # @pytest.mark.parametrize("shrmag", ['row', 'all', False, None])
39
+ # @pytest.mark.parametrize("shrphs", ['row', 'all', False, None])
40
+ # @pytest.mark.parametrize("shrfrq", ['col', 'all', False, None])
41
+ # @pytest.mark.parametrize("secsys", [False, True])
42
+ @pytest .mark .parametrize ( # combinatorial-style test (faster)
43
+ "pltmag, pltphs, shrmag, shrphs, shrfrq, secsys" ,
44
+ [(True , True , None , None , None , False ),
45
+ (True , False , None , None , None , False ),
46
+ (False , True , None , None , None , False ),
47
+ (True , True , None , None , None , True ),
48
+ (True , True , 'row' , 'row' , 'col' , False ),
49
+ (True , True , 'row' , 'row' , 'all' , True ),
50
+ (True , True , 'all' , 'row' , None , False ),
51
+ (True , True , 'row' , 'all' , None , True ),
52
+ (True , True , 'none' , 'none' , None , True ),
53
+ (True , False , 'all' , 'row' , None , False ),
54
+ (True , True , True , 'row' , None , True ),
55
+ (True , True , None , 'row' , True , False ),
56
+ (True , True , 'row' , None , None , True ),
57
+ ])
58
+ def test_response_plots (
59
+ sys , pltmag , pltphs , shrmag , shrphs , shrfrq , secsys , clear = True ):
60
+
61
+ # Save up the keyword arguments
62
+ kwargs = dict (
63
+ plot_magnitude = pltmag , plot_phase = pltphs ,
64
+ share_magnitude = shrmag , share_phase = shrphs , share_frequency = shrfrq ,
65
+ # overlay_outputs=ovlout, overlay_inputs=ovlinp
66
+ )
67
+
68
+ # Create the response
69
+ if isinstance (sys , ct .FrequencyResponseData ):
70
+ response = sys
71
+ else :
72
+ response = ct .frequency_response (sys )
73
+
74
+ # Look for cases where there are no data to plot
75
+ if not pltmag and not pltphs :
76
+ return None
77
+
78
+ # Plot the frequency response
79
+ plt .figure ()
80
+ out = response .plot (** kwargs )
81
+
82
+ # Make sure all of the outputs are of the right type
83
+ nlines_plotted = 0
84
+ for ax_lines in np .nditer (out , flags = ["refs_ok" ]):
85
+ for line in ax_lines .item ():
86
+ assert isinstance (line , mpl .lines .Line2D )
87
+ nlines_plotted += 1
88
+
89
+ # Make sure number of plots is correct
90
+ nlines_expected = response .ninputs * response .noutputs * \
91
+ (2 if pltmag and pltphs else 1 )
92
+ assert nlines_plotted == nlines_expected
93
+
94
+ # Save the old axes to compare later
95
+ old_axes = plt .gcf ().get_axes ()
96
+
97
+ # Add additional data (and provide info in the title)
98
+ if secsys :
99
+ newsys = ct .rss (
100
+ 4 , sys .noutputs , sys .ninputs , strictly_proper = True )
101
+ ct .frequency_response (newsys ).plot (** kwargs )
102
+
103
+ # Make sure we have the same axes
104
+ new_axes = plt .gcf ().get_axes ()
105
+ assert new_axes == old_axes
106
+
107
+ # Make sure every axes has multiple lines
108
+ for ax in new_axes :
109
+ assert len (ax .get_lines ()) > 1
110
+
111
+ # Update the title so we can see what is going on
112
+ fig = out [0 , 0 ][0 ].axes .figure
113
+ fig .suptitle (
114
+ fig ._suptitle ._text +
115
+ f" [{ sys .noutputs } x{ sys .ninputs } , pm={ pltmag } , pp={ pltphs } ,"
116
+ f" sm={ shrmag } , sp={ shrphs } , sf={ shrfrq } ]" , # TODO: ", "
117
+ # f"oo={ovlout}, oi={ovlinp}, ss={secsys}]", # TODO: add back
118
+ fontsize = 'small' )
119
+
120
+ # Get rid of the figure to free up memory
121
+ if clear :
122
+ plt .close ('.Figure' )
123
+
124
+
125
+ # Use the manaul response to verify that different settings are working
126
+ def test_manual_response_limits ():
127
+ # Default response: limits should be the same across rows
128
+ out = manual_response .plot ()
129
+ axs = ct .get_plot_axes (out )
130
+ for i in range (manual_response .noutputs ):
131
+ for j in range (1 , manual_response .ninputs ):
132
+ # Everything in the same row should have the same limits
133
+ assert axs [i * 2 , 0 ].get_ylim () == axs [i * 2 , j ].get_ylim ()
134
+ assert axs [i * 2 + 1 , 0 ].get_ylim () == axs [i * 2 + 1 , j ].get_ylim ()
135
+ # Different rows have different limits
136
+ assert axs [0 , 0 ].get_ylim () != axs [2 , 0 ].get_ylim ()
137
+ assert axs [1 , 0 ].get_ylim () != axs [3 , 0 ].get_ylim ()
138
+
139
+ # TODO: finish writing tests
140
+
13
141
def test_basic_freq_plots (savefigs = False ):
14
142
# Basic SISO Bode plot
15
143
plt .figure ()
@@ -23,7 +151,7 @@ def test_basic_freq_plots(savefigs=False):
23
151
24
152
# Basic MIMO Bode plot
25
153
plt .figure ()
26
- sys_mimo = ct .tf2ss (
154
+ sys_mimo = ct .tf (
27
155
[[[1 ], [0.1 ]], [[0.2 ], [1 ]]],
28
156
[[[1 , 0.6 , 1 ], [1 , 1 , 1 ]], [[1 , 0.4 , 1 ], [1 , 2 , 1 ]]], name = "MIMO" )
29
157
ct .frequency_response (sys_mimo ).plot ()
@@ -41,6 +169,17 @@ def test_basic_freq_plots(savefigs=False):
41
169
ct .frequency_response (sys_mimo ).plot (plot_magnitude = False )
42
170
43
171
172
+ def test_gangof4_plots (savefigs = False ):
173
+ proc = ct .tf ([1 ], [1 , 1 , 1 ], name = "process" )
174
+ ctrl = ct .tf ([100 ], [1 , 5 ], name = "control" )
175
+
176
+ plt .figure ()
177
+ ct .gangof4_plot (proc , ctrl )
178
+
179
+ if savefigs :
180
+ plt .savefig ('freqplot-gangof4.png' )
181
+
182
+
44
183
if __name__ == "__main__" :
45
184
#
46
185
# Interactive mode: generate plots for manual viewing
@@ -55,10 +194,36 @@ def test_basic_freq_plots(savefigs=False):
55
194
# Start by clearing existing figures
56
195
plt .close ('all' )
57
196
197
+ # Define a set of systems to test
198
+ sys_siso = ct .tf ([1 ], [1 , 2 , 1 ], name = "SISO" )
199
+ sys_mimo = ct .tf (
200
+ [[[1 ], [0.1 ]], [[0.2 ], [1 ]]],
201
+ [[[1 , 0.6 , 1 ], [1 , 1 , 1 ]], [[1 , 0.4 , 1 ], [1 , 2 , 1 ]]], name = "MIMO" )
202
+ sys_test = manual_response
203
+
204
+ # Run through a large number of test cases
205
+ test_cases = [
206
+ # sys pltmag pltphs shrmag shrphs shrfrq secsys
207
+ (sys_siso , True , True , None , None , None , False ),
208
+ (sys_siso , True , True , None , None , None , True ),
209
+ (sys_mimo , True , True , 'row' , 'row' , 'col' , False ),
210
+ (sys_mimo , True , True , 'row' , 'row' , 'col' , True ),
211
+ (sys_test , True , True , 'row' , 'row' , 'col' , False ),
212
+ (sys_test , True , True , 'row' , 'row' , 'col' , True ),
213
+ (sys_test , True , True , 'none' , 'none' , 'col' , True ),
214
+ (sys_test , True , True , 'all' , 'row' , 'col' , False ),
215
+ (sys_test , True , True , 'row' , 'all' , 'col' , True ),
216
+ (sys_test , True , True , None , 'row' , 'col' , False ),
217
+ (sys_test , True , True , 'row' , None , 'col' , True ),
218
+ ]
219
+ for args in test_cases :
220
+ test_response_plots (* args , clear = False )
<
663D
tr class="diff-line-row">
221
+
58
222
# Define and run a selected set of interesting tests
59
223
# TODO: TBD (see timeplot_test.py for format)
60
224
61
225
test_basic_freq_plots (savefigs = True )
226
+ test_gangof4_plots (savefigs = True )
62
227
63
228
#
64
229
# Run a few more special cases to show off capabilities (and save some
0 commit comments