Skip to content

Commit 1e3a925

Browse files
committed
Added docstring and unittest
1 parent bc74a0a commit 1e3a925

4 files changed

Lines changed: 138 additions & 8 deletions

File tree

control/freqplot.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,10 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
213213
pltline = ax_mag.semilogx(omega_plot, 20 * np.log10(mag),
214214
*args, **kwargs)
215215
else:
216+
216217
pltline = ax_mag.loglog(omega_plot, mag, *args, **kwargs)
217218

219+
218220
if nyquistfrq_plot:
219221
ax_mag.axvline(nyquistfrq_plot,
220222
color=pltline[0].get_color())

control/rlocus.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ def _RLFeedbackClicksSisotool(event,sys,fig,bode_plot_params,tvect):
380380
def _RLFeedbackClicksPoint(event,sys,fig,ax_rlocus=None,sisotool=False):
381381
"""Display root-locus gain feedback point for clicks on the root-locus plot
382382
"""
383+
print(event)
383384
if sisotool == False:
384385
ax_rlocus = fig.axes[0]
385386

@@ -413,8 +414,6 @@ def _RLFeedbackClicksPoint(event,sys,fig,ax_rlocus=None,sisotool=False):
413414

414415
return True
415416

416-
417-
418417
def _sgrid_func(fig=None, zeta=None, wn=None):
419418
if fig is None:
420419
fig = pylab.gcf()

control/sisotool.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,56 @@
55
from .lti import issiso
66
import matplotlib
77
import matplotlib.pyplot as plt
8-
9-
def sisotool(sys, kvect = None, xlim = None, ylim = None, plotstr_rlocus = 'b' if int(matplotlib.__version__[0]) == 1 else 'C0',rlocus_grid = False, omega = None, dB = None, Hz = None, deg = None, omega_limits = None, omega_num = None,margins = True, tvect=None):
10-
8+
import warnings
9+
10+
def sisotool(sys, kvect = None, xlim_rlocus = None, ylim_rlocus = None, plotstr_rlocus = 'b' if int(matplotlib.__version__[0]) == 1 else 'C0',rlocus_grid = False, omega = None, dB = None, Hz = None, deg = None, omega_limits = None, omega_num = None,margins_bode = True, tvect=None):
11+
12+
"""Sisotool
13+
14+
Sisotool style collection of plots inspired by the matlab sisotool.
15+
The left two plots contain the bode magnitude and phase diagrams.
16+
The top right plot is a clickable root locus plot, clicking on the
17+
root locus will change the gain of the system. The bottom left plot
18+
shows a closed loop time response.
19+
20+
Parameters
21+
----------
22+
sys : LTI object
23+
Linear input/output systems (SISO only)
24+
kvect : list or ndarray, optional
25+
List of gains to use for plotting root locus
26+
xlim_rlocus : tuple or list, optional
27+
control of x-axis range, normally with tuple (see matplotlib.axes)
28+
ylim_rlocus : tuple or list, optional
29+
control of y-axis range
30+
plotstr_rlocus : Additional options to matplotlib
31+
plotting style for the root locus plot(color, linestyle, etc)
32+
rlocus_grid: boolean (default = False)
33+
If True plot s-plane grid.
34+
omega : freq_range
35+
Range of frequencies in rad/sec for the bode plot
36+
dB : boolean
37+
If True, plot result in dB for the bode plot
38+
Hz : boolean
39+
If True, plot frequency in Hz for the bode plot (omega must be provided in rad/sec)
40+
deg : boolean
41+
If True, plot phase in degrees for the bode plot (else radians)
42+
omega_limits: tuple, list, ... of two values
43+
Limits of the to generate frequency vector.
44+
If Hz=True the limits are in Hz otherwise in rad/s.
45+
omega_num: int
46+
number of samples
47+
margins_bode : boolean
48+
If True, plot gain and phase margin in the bode plot
49+
tvect : list or ndarray, optional
50+
List of timesteps to use for closed loop step response
51+
52+
Examples
53+
--------
54+
>>> sys = tf([1000], [1,25,100,0])
55+
>>> sisotool(sys)
56+
57+
"""
1158
from .rlocus import root_locus
1259

1360
# Check if it is a single SISO system
@@ -30,14 +77,14 @@ def sisotool(sys, kvect = None, xlim = None, ylim = None, plotstr_rlocus = 'b' i
3077
'omega_num' : omega_num,
3178
'sisotool': True,
3279
'fig': fig,
33-
'margins': margins
80+
'margins': margins_bode
3481
}
3582

3683
# First time call to setup the bode and step response plots
3784
_SisotoolUpdate(sys, fig,1 if kvect is None else kvect[0],bode_plot_params)
3885

3986
# Setup the root-locus plot window
40-
root_locus(sys,kvect=kvect,xlim=xlim,ylim = ylim,plotstr=plotstr_rlocus,grid = rlocus_grid,fig=fig,bode_plot_params=bode_plot_params,tvect=tvect,sisotool=True)
87+
root_locus(sys,kvect=kvect,xlim=xlim_rlocus,ylim = ylim_rlocus,plotstr=plotstr_rlocus,grid = rlocus_grid,fig=fig,bode_plot_params=bode_plot_params,tvect=tvect,sisotool=True)
4188

4289
def _SisotoolUpdate(sys,fig,K,bode_plot_params,tvect=None):
4390

@@ -50,7 +97,11 @@ def _SisotoolUpdate(sys,fig,K,bode_plot_params,tvect=None):
5097

5198
# Get the subaxes and clear them
5299
ax_mag,ax_rlocus,ax_phase,ax_step = fig.axes[0],fig.axes[1],fig.axes[2],fig.axes[3]
53-
ax_mag.cla(),ax_phase.cla(),ax_step.cla()
100+
101+
# Catch matplotlib 2.1.x and higher userwarnings when clearing a log axis
102+
with warnings.catch_warnings():
103+
warnings.simplefilter("ignore")
104+
ax_step.clear(), ax_mag.clear(), ax_phase.clear()
54105

55106
# Update the bodeplot
56107
bode_plot_params['syslist'] = sys*K.real

control/tests/sisotool_test.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import unittest
2+
import numpy as np
3+
from control.sisotool import sisotool
4+
from control.tests.margin_test import assert_array_almost_equal
5+
from control.rlocus import _RLFeedbackClicksSisotool
6+
from control.xferfcn import TransferFunction
7+
import matplotlib.pyplot as plt
8+
9+
class TestSisotool(unittest.TestCase):
10+
"""These are tests for the sisotool in sisotool.py."""
11+
12+
def setUp(self):
13+
"""This contains some random LTI systems and scalars for testing."""
14+
15+
# Two random SISO systems.
16+
sys1 = TransferFunction([1000],[1,25,100,0])
17+
sys2 = TransferFunction([1,1,1],[1,1])
18+
self.systems = (sys1, sys2)
19+
20+
def test_sisotool(self):
21+
sisotool(self.systems[0])
22+
fig = plt.gcf()
23+
ax_mag,ax_rlocus,ax_phase,ax_step = fig.axes[0],fig.axes[1],fig.axes[2],fig.axes[3]
24+
25+
# Check the initial root locus plot points
26+
initial_point_0 = (np.array([-22.53155977]),np.array([0.]))
27+
initial_point_1 = (np.array([-1.23422011]), np.array([-6.54667031]))
28+
initial_point_2 = (np.array([-1.23422011]), np.array([06.54667031]))
29+
assert_array_almost_equal(ax_rlocus.lines[0].get_data(),initial_point_0)
30+
assert_array_almost_equal(ax_rlocus.lines[1].get_data(),initial_point_1)
31+
assert_array_almost_equal(ax_rlocus.lines[2].get_data(),initial_point_2)
32+
33+
# Check the bode plot magnitude line
34+
bode_mag_original = np.array([ 15.12670407, 12.48358602, 10.28367521, 8.44961576, 6.91740918, 5.63436301, 4.55750665, 3.65237575, 2.89200358, 2.25587634])
35+
assert_array_almost_equal(ax_mag.lines[0].get_data()[1][10:20],bode_mag_original)
36+
37+
# Check the step response before moving the point
38+
print(ax_step.lines[0].get_data()[1][:10])
39+
step_response_original = np.array([ 0., 0.02233651, 0.13118374, 0.33078542, 0.5907113, 0.87041549, 1.13038536, 1.33851053, 1.47374666, 1.52757114])
40+
assert_array_almost_equal(ax_step.lines[0].get_data()[1][:10],step_response_original)
41+
42+
bode_plot_params = {
43+
'omega': None,
44+
'dB': False,
45+
'Hz': False,
46+
'deg': True,
47+
'omega_limits': None,
48+
'omega_num': None,
49+
'sisotool': True,
50+
'fig': fig,
51+
'margins': True
52+
}
53+
54+
# Move the rootlocus to another point
55+
event = type('test', (object,), {'xdata': 2.31206868287,'ydata':15.5983051046, 'inaxes':ax_rlocus.axes})()
56+
_RLFeedbackClicksSisotool(event=event, sys=self.systems[0], fig=fig, bode_plot_params=bode_plot_params, tvect=None)
57+
58+
# Check the moved root locus plot points
59+
moved_point_0 = (np.array([-29.91742755]), np.array([0.]))
60+
moved_point_1 = (np.array([2.45871378]), np.array([-15.52647768]))
61+
moved_point_2 = (np.array([2.45871378]), np.array([15.52647768]))
62+
assert_array_almost_equal(ax_rlocus.lines[-3].get_data(),moved_point_0)
63+
assert_array_almost_equal(ax_rlocus.lines[-2].get_data(),moved_point_1)
64+
assert_array_almost_equal(ax_rlocus.lines[-1].get_data(),moved_point_2)
65+
66+
# Check if the bode_mag line has moved
67+
bode_mag_moved = np.array([ 111.83321224, 92.29238035, 76.02822315, 62.46884113, 51.14108703, 41.6554004, 33.69409534, 27.00237344, 21.38086717, 16.67791585])
68+
assert_array_almost_equal(ax_mag.lines[0].get_data()[1][10:20],bode_mag_moved)
69+
70+
# Check if the step response has changed
71+
step_response_moved = np.array([[ 0., 0.02458187, 0.16529784 , 0.46602716 , 0.91012035 , 1.43364313, 1.93996334 , 2.3190105 , 2.47041552 , 2.32724853] ])
72+
assert_array_almost_equal(ax_step.lines[0].get_data()[1][:10],step_response_moved)
73+
74+
def test_suite():
75+
return unittest.TestLoader().loadTestsFromTestCase(TestSisotool)
76+
77+
if __name__ == "__main__":
78+
unittest.main()

0 commit comments

Comments
 (0)