-
Notifications
You must be signed in to change notification settings - Fork 458
Expand file tree
/
Copy pathsisotool_test.py
More file actions
221 lines (192 loc) · 8.94 KB
/
Copy pathsisotool_test.py
File metadata and controls
221 lines (192 loc) · 8.94 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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
"""sisotool_test.py"""
from control.exception import ControlMIMONotImplemented
import matplotlib.pyplot as plt
import numpy as np
from numpy.testing import assert_array_almost_equal
import pytest
from control.sisotool import sisotool, rootlocus_pid_designer
from control.sisotool import _click_dispatcher
from control.xferfcn import TransferFunction
from control.statesp import StateSpace
from control import c2d
@pytest.mark.usefixtures("mplcleanup")
class TestSisotool:
"""These are tests for the sisotool in sisotool.py."""
@pytest.fixture
def tsys(self, request):
"""Return a generic SISO transfer function"""
dt = getattr(request, 'param', 0)
return TransferFunction([1000], [1, 25, 100, 0], dt)
@pytest.fixture
def sys222(self):
"""2-states square system (2 inputs x 2 outputs)"""
A222 = [[4., 1.],
[2., -3]]
B222 = [[5., 2.],
[-3., -3.]]
C222 = [[2., -4],
[0., 1.]]
D222 = [[3., 2.],
[1., -1.]]
return StateSpace(A222, B222, C222, D222)
@pytest.fixture
def sys221(self):
"""2-states, 2 inputs x 1 output"""
A222 = [[4., 1.],
[2., -3]]
B222 = [[5., 2.],
[-3., -3.]]
C221 = [[0., 1.]]
D221 = [[1., -1.]]
return StateSpace(A222, B222, C221, D221)
@pytest.mark.skipif(plt.get_current_fig_manager().toolbar is None,
reason="Requires the zoom toolbar")
def test_sisotool(self, tsys):
sisotool(tsys, Hz=False)
fig = plt.gcf()
ax_mag, ax_rlocus, ax_phase, ax_step = fig.axes[:4]
# Check the initial root locus plot points
initial_point_0 = (np.array([-22.53155977]), np.array([0.]))
initial_point_1 = (np.array([-1.23422011]), np.array([-6.54667031]))
initial_point_2 = (np.array([-1.23422011]), np.array([6.54667031]))
assert_array_almost_equal(ax_rlocus.lines[4].get_data(),
initial_point_0, 4)
assert_array_almost_equal(ax_rlocus.lines[5].get_data(),
initial_point_1, 4)
assert_array_almost_equal(ax_rlocus.lines[6].get_data(),
initial_point_2, 4)
# Check the step response before moving the point
step_response_original = np.array(
[0. , 0.0216, 0.1271, 0.3215, 0.5762, 0.8522, 1.1114, 1.3221,
1.4633, 1.5254])
assert_array_almost_equal(
ax_step.lines[0].get_data()[1][:10], step_response_original, 4)
bode_plot_params = {
'omega': None,
'dB': False,
'Hz': False,
'deg': True,
'omega_limits': None,
'omega_num': None,
'ax': np.array([[ax_mag], [ax_phase]]),
'display_margins': 'overlay',
}
# Check that the xaxes of the bode plot are shared before the rlocus click
assert ax_mag.get_xlim() == ax_phase.get_xlim()
ax_mag.set_xlim(2, 12)
assert ax_mag.get_xlim() == (2, 12)
assert ax_phase.get_xlim() == (2, 12)
# Move the rootlocus to another point
event = type('test', (object,), {'xdata': 2.31206868287,
'ydata': 15.5983051046,
'inaxes': ax_rlocus.axes})()
_click_dispatcher(event=event, sys=tsys, ax=ax_rlocus,
bode_plot_params=bode_plot_params, tvect=None)
# Check the moved root locus plot points
moved_point_0 = (np.array([-29.91742755]), np.array([0.]))
moved_point_1 = (np.array([2.45871378]), np.array([-15.52647768]))
moved_point_2 = (np.array([2.45871378]), np.array([15.52647768]))
assert_array_almost_equal(ax_rlocus.lines[-3].get_data(),
moved_point_0, 4)
assert_array_almost_equal(ax_rlocus.lines[-2].get_data(),
moved_point_1, 4)
assert_array_almost_equal(ax_rlocus.lines[-1].get_data(),
moved_point_2, 4)
# Check if the bode_mag line has moved
bode_mag_moved = np.array(
[69.0065, 68.6749, 68.3448, 68.0161, 67.6889, 67.3631, 67.0388,
66.7159, 66.3944, 66.0743])
assert_array_almost_equal(ax_mag.lines[0].get_data()[1][10:20],
bode_mag_moved, 4)
# Check if the step response has changed
step_response_moved = np.array(
[0. , 0.0237, 0.1596, 0.4511, 0.884 , 1.3985, 1.9031, 2.2922,
2.4676, 2.3606])
assert_array_almost_equal(
ax_step.lines[0].get_data()[1][:10], step_response_moved, 4)
# Check that the xaxes of the bode plot are still shared after the rlocus click
assert ax_mag.get_xlim() == ax_phase.get_xlim()
ax_mag.set_xlim(3, 13)
assert ax_mag.get_xlim() == (3, 13)
assert ax_phase.get_xlim() == (3, 13)
@pytest.mark.skipif(plt.get_current_fig_manager().toolbar is None,
reason="Requires the zoom toolbar")
@pytest.mark.parametrize('tsys', [0, True],
indirect=True, ids=['ctime', 'dtime'])
def test_sisotool_tvect(self, tsys):
# test supply tvect
tvect = np.linspace(0, 1, 10)
sisotool(tsys, tvect=tvect)
fig = plt.gcf()
ax_rlocus, ax_step = fig.axes[1], fig.axes[3]
# Move the rootlocus to another point and confirm same tvect
event = type('test', (object,), {'xdata': 2.31206868287,
'ydata': 15.5983051046,
'inaxes': ax_rlocus.axes})()
_click_dispatcher(event=event, sys=tsys, ax=ax_rlocus,
bode_plot_params=dict(), tvect=tvect)
assert_array_almost_equal(tvect, ax_step.lines[0].get_data()[0])
@pytest.mark.skipif(plt.get_current_fig_manager().toolbar is None,
reason="Requires the zoom toolbar")
def test_sisotool_initial_gain(self, tsys):
sisotool(tsys, initial_gain=1.2)
# kvect keyword should give deprecation warning
with pytest.warns(FutureWarning):
sisotool(tsys, kvect=1.2)
@pytest.mark.filterwarnings("ignore:connect:FutureWarning")
def test_sisotool_mimo(self, sys222, sys221):
# a 2x2 should not raise an error:
sisotool(sys222)
# but 2 input, 1 output should
with pytest.raises(ControlMIMONotImplemented):
sisotool(sys221)
@pytest.mark.usefixtures("mplcleanup")
class TestPidDesigner:
@pytest.fixture
def plant(self, request):
plants = {
'syscont':TransferFunction(1,[1, 3, 0]),
'sysdisc1':c2d(TransferFunction(1,[1, 3, 0]), .1),
'syscont221':StateSpace([[-.3, 0],[1,0]],[[-1,],[.1,]], [0, -.3], 0)}
return plants[request.param]
# test permutations of system construction without plotting
@pytest.mark.parametrize('plant', ('syscont', 'sysdisc1', 'syscont221'), indirect=True)
@pytest.mark.parametrize('gain', ('P', 'I', 'D'))
@pytest.mark.parametrize('sign', (1,))
@pytest.mark.parametrize('input_signal', ('r', 'd'))
@pytest.mark.parametrize('Kp0', (0,))
@pytest.mark.parametrize('Ki0', (1.,))
@pytest.mark.parametrize('Kd0', (0.1,))
@pytest.mark.parametrize('deltaK', (1.,))
@pytest.mark.parametrize('tau', (0.01,))
@pytest.mark.parametrize('C_ff', (0, 1,))
@pytest.mark.parametrize('derivative_in_feedback_path', (True, False,))
@pytest.mark.parametrize("kwargs", [{'plot':False},])
def test_pid_designer_1(self, plant, gain, sign, input_signal, Kp0, Ki0, Kd0, deltaK, tau, C_ff,
derivative_in_feedback_path, kwargs):
rootlocus_pid_designer(plant, gain, sign, input_signal, Kp0, Ki0, Kd0, deltaK, tau, C_ff,
derivative_in_feedback_path, **kwargs)
# test creation of sisotool plot
# input from reference or disturbance
@pytest.mark.parametrize('plant', ('syscont', 'syscont221'), indirect=True)
@pytest.mark.parametrize("kwargs", [
{'input_signal':'r', 'Kp0':0.01, 'derivative_in_feedback_path':True},
{'input_signal':'d', 'Kp0':0.01, 'derivative_in_feedback_path':True},
{'input_signal':'r', 'Kd0':0.01, 'derivative_in_feedback_path':True}])
@pytest.mark.filterwarnings("ignore:connect:FutureWarning")
def test_pid_designer_2(self, plant, kwargs):
rootlocus_pid_designer(plant, **kwargs)
if __name__ == "__main__":
#
# Interactive mode: generate plots for manual viewing
#
# Running this script in python (or better ipython) will show a
# collection of figures that should all look OK on the screeen.
#
import control as ct
# In interactive mode, turn on ipython interactive graphics
plt.ion()
# Start by clearing existing figures
plt.close('all')
tsys = ct.tf([1000], [1, 25, 100, 0])
ct.sisotool(tsys)