Repository navigation
Expand file tree
/
Copy pathctrlplot_test.py
More file actions
176 lines (146 loc) · 6.47 KB
/
Copy pathctrlplot_test.py
File metadata and controls
176 lines (146 loc) · 6.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# ctrlplot_test.py - test out control plotting utilities
# RMM, 27 Jun 2024
import inspect
import warnings
import matplotlib.pyplot as plt
import numpy as np
import pytest
import control as ct
# List of all plotting functions
resp_plot_fcns = [
# response function plotting function
(ct.frequency_response, ct.bode_plot),
(ct.frequency_response, ct.nichols_plot),
(ct.singular_values_response, ct.singular_values_plot),
(ct.gangof4_response, ct.gangof4_plot),
(ct.describing_function_response, ct.describing_function_plot),
(None, ct.phase_plane_plot),
(ct.pole_zero_map, ct.pole_zero_plot),
(ct.nyquist_response, ct.nyquist_plot),
(ct.root_locus_map, ct.root_locus_plot),
(ct.initial_response, ct.time_response_plot),
(ct.step_response, ct.time_response_plot),
(ct.impulse_response, ct.time_response_plot),
(ct.forced_response, ct.time_response_plot),
(ct.input_output_response, ct.time_response_plot),
]
deprecated_fcns = [ct.phase_plot]
# Make sure we didn't miss any plotting functions
def test_find_respplot_functions():
# Get the list of plotting functions
plot_fcns = {respplot[1] for respplot in resp_plot_fcns}
# Look through every object in the package
found = 0
for name, obj in inspect.getmembers(ct):
# Skip anything that is outside of this module
if inspect.getmodule(obj) is not None and \
not inspect.getmodule(obj).__name__.startswith('control'):
# Skip anything that isn't part of the control package
continue
# Only look for non-deprecated functions ending in 'plot'
if not inspect.isfunction(obj) or name[-4:] != 'plot' or \
obj in deprecated_fcns:
continue
# Make sure that we have this on our list of functions
assert obj in plot_fcns
found += 1
assert found == len(plot_fcns)
@pytest.mark.parametrize("resp_fcn, plot_fcn", resp_plot_fcns)
@pytest.mark.usefixtures('mplcleanup')
def test_plot_ax_processing(resp_fcn, plot_fcn):
# Create some systems to use
sys1 = ct.rss(2, 1, 1, strictly_proper=True)
sys2 = ct.rss(4, 1, 1, strictly_proper=True)
# Set up arguments
kwargs = meth_kwargs = plot_fcn_kwargs = {}
get_line_color = lambda cplt: cplt.lines.reshape(-1)[0][0].get_color()
match resp_fcn, plot_fcn:
case ct.describing_function_response, _:
F = ct.descfcn.saturation_nonlinearity(1)
amp = np.linspace(1, 4, 10)
args = (sys1, F, amp)
case ct.gangof4_response, _:
args = (sys1, sys2)
case ct.frequency_response, ct.nichols_plot:
args = (sys1, )
meth_kwargs = {'plot_type': 'nichols'}
case ct.root_locus_map, ct.root_locus_plot:
args = (sys1, )
meth_kwargs = plot_fcn_kwargs = {'interactive': False}
case (ct.forced_response | ct.input_output_response, _):
timepts = np.linspace(1, 10)
U = np.sin(timepts)
args = (sys1, timepts, U)
case None, ct.phase_plane_plot:
args = (sys1, )
get_line_color = None
warnings.warn("ct.phase_plane_plot returns nonstandard lines")
case _, _:
args = (sys1, )
# Call the plot through the response function
if resp_fcn is not None:
resp = resp_fcn(*args, **kwargs)
cplt1 = resp.plot(**kwargs, **meth_kwargs)
else:
# No response function available; just plot the data
cplt1 = plot_fcn(*args, **kwargs, **meth_kwargs)
assert isinstance(cplt1, ct.ControlPlot)
# Call the plot directly, plotting on top of previous plot
if plot_fcn == ct.time_response_plot:
# Can't call the time_response_plot() with system => reuse data
cplt2 = plot_fcn(resp, **kwargs, **plot_fcn_kwargs)
else:
cplt2 = plot_fcn(*args, **kwargs, **plot_fcn_kwargs)
assert isinstance(cplt2, ct.ControlPlot)
# Plot should have landed on top of previous plot, in different colors
assert cplt2.figure == cplt1.figure
assert np.all(cplt2.axes == cplt1.axes)
assert len(cplt2.lines[0]) == len(cplt1.lines[0])
if get_line_color is not None:
assert get_line_color(cplt2) != get_line_color(cplt1)
# Pass axes explicitly
if resp_fcn is not None:
cplt3 = resp.plot(**kwargs, **meth_kwargs, ax=cplt1.axes)
else:
cplt3 = plot_fcn(*args, **kwargs, **meth_kwargs)
assert cplt3.figure == cplt1.figure
# Plot should have landed on top of previous plot, in different colors
assert np.all(cplt3.axes == cplt1.axes)
assert len(cplt3.lines[0]) == len(cplt1.lines[0])
if get_line_color is not None:
assert get_line_color(cplt3) != get_line_color(cplt1)
assert get_line_color(cplt3) != get_line_color(cplt2)
@pytest.mark.usefixtures('mplcleanup')
def test_rcParams():
sys = ct.rss(2, 2, 2)
# Create new set of rcParams
my_rcParams = {}
for key in [
'axes.labelsize', 'axes.titlesize', 'figure.titlesize',
'legend.fontsize', 'xtick.labelsize', 'ytick.labelsize']:
match plt.rcParams[key]:
case 8 | 9 | 10:
my_rcParams[key] = plt.rcParams[key] + 1
case 'medium':
my_rcParams[key] = 11.5
case 'large':
my_rcParams[key] = 9.5
case _:
raise ValueError(f"unknown rcParam type for {key}")
# Generate a figure with the new rcParams
out = ct.step_response(sys).plot(rcParams=my_rcParams)
ax, fig = out.axes[0, 0], out.figure
# Check to make sure new settings were used
assert ax.xaxis.get_label().get_fontsize() == my_rcParams['axes.labelsize']
assert ax.yaxis.get_label().get_fontsize() == my_rcParams['axes.labelsize']
assert ax.title.get_fontsize() == my_rcParams['axes.titlesize']
assert ax.get_xticklabels()[0].get_fontsize() == \
my_rcParams['xtick.labelsize']
assert ax.get_yticklabels()[0].get_fontsize() == \
my_rcParams['ytick.labelsize']
assert fig._suptitle.get_fontsize() == my_rcParams['figure.titlesize']
def test_deprecation_warning():
sys = ct.rss(2, 2, 2)
lines = ct.step_response(sys).plot(overlay_traces=True)
with pytest.warns(FutureWarning, match="deprecated"):
assert len(lines[0, 0]) == 2