1
1
__all__ = ['sisotool' ]
2
2
3
+ from control .exception import ControlMIMONotImplemented
3
4
from .freqplot import bode_plot
4
5
from .timeresp import step_response
5
6
from .lti import issiso , isdtime
7
+ from .xferfcn import TransferFunction
8
+ from .bdalg import append , connect
6
9
import matplotlib
7
10
import matplotlib .pyplot as plt
8
11
import warnings
9
12
10
- def sisotool (sys , kvect = None , xlim_rlocus = None , ylim_rlocus = None ,
11
- plotstr_rlocus = 'b' if int (matplotlib .__version__ [0 ]) == 1 else 'C0' ,
12
- rlocus_grid = False , omega = None , dB = None , Hz = None ,
13
- deg = None , omega_limits = None , omega_num = None ,
14
- margins_bode = True , tvect = None ):
13
+ def sisotool (sys , kvect = None , xlim_rlocus = None , ylim_rlocus = None ,
14
+ plotstr_rlocus = 'C0' , rlocus_grid = False , omega = None , dB = None ,
15
+ Hz = None , deg = None , omega_limits = None , omega_num = None ,
16
+ margins_bode = True , tvect = None ):
15
17
"""
16
18
Sisotool style collection of plots inspired by MATLAB's sisotool.
17
19
The left two plots contain the bode magnitude and phase diagrams.
@@ -22,7 +24,16 @@ def sisotool(sys, kvect = None, xlim_rlocus = None, ylim_rlocus = None,
22
24
Parameters
23
25
----------
24
26
sys : LTI object
25
- Linear input/output systems (SISO only)
27
+ Linear input/output systems. If sys is SISO, use the same
28
+ system for the root locus and step response. If it is desired to
29
+ see a different step response than feedback(K*loop,1), sys can be
30
+ provided as a two-input, two-output system (e.g. by using
31
+ :func:`bdgalg.connect' or :func:`iosys.interconnect`). Sisotool
32
+ inserts the negative of the selected gain K between the first output
33
+ and first input and uses the second input and output for computing
34
+ the step response. This allows you to see the step responses of more
35
+ complex systems, for example, systems with a feedforward path into the
36
+ plant or in which the gain appears in the feedback path.
26
37
kvect : list or ndarray, optional
27
38
List of gains to use for plotting root locus
28
39
xlim_rlocus : tuple or list, optional
@@ -32,21 +43,23 @@ def sisotool(sys, kvect = None, xlim_rlocus = None, ylim_rlocus = None,
32
43
control of y-axis range
33
44
plotstr_rlocus : :func:`matplotlib.pyplot.plot` format string, optional
34
45
plotting style for the root locus plot(color, linestyle, etc)
35
- rlocus_grid: boolean (default = False)
36
- If True plot s-plane grid.
37
- omega : freq_range
38
- Range of frequencies in rad/sec for the bode plot
46
+ rlocus_grid : boolean (default = False)
47
+ If True plot s- or z- plane grid.
48
+ omega : array_like
49
+ List of frequencies in rad/sec to be used for bode plot
39
50
dB : boolean
40
51
If True, plot result in dB for the bode plot
41
52
Hz : boolean
42
53
If True, plot frequency in Hz for the bode plot (omega must be provided in rad/sec)
43
54
deg : boolean
44
55
If True, plot phase in degrees for the bode plot (else radians)
45
- omega_limits: tuple, list, ... of two values
56
+ omega_limits : array_like of two values
46
57
Limits of the to generate frequency vector.
47
- If Hz=True the limits are in Hz otherwise in rad/s.
48
- omega_num: int
49
- number of samples
58
+ If Hz=True the limits are in Hz otherwise in rad/s. Ignored if omega
59
+ is provided, and auto-generated if omitted.
60
+ omega_num : int
61
+ Number of samples to plot. Defaults to
62
+ config.defaults['freqplot.number_of_samples'].
50
63
margins_bode : boolean
51
64
If True, plot gain and phase margin in the bode plot
52
65
tvect : list or ndarray, optional
@@ -60,8 +73,11 @@ def sisotool(sys, kvect = None, xlim_rlocus = None, ylim_rlocus = None,
60
73
"""
61
74
from .rlocus import root_locus
62
75
63
- # Check if it is a single SISO system
64
- issiso (sys ,strict = True )
76
+ # sys as loop transfer function if SISO
77
+ if not sys .issiso ():
78
+ if not (sys .ninputs == 2 and sys .noutputs == 2 ):
79
+ raise ControlMIMONotImplemented (
80
+ 'sys must be SISO or 2-input, 2-output' )
65
81
66
82
# Setup sisotool figure or superimpose if one is already present
67
83
fig = plt .gcf ()
@@ -84,64 +100,74 @@ def sisotool(sys, kvect = None, xlim_rlocus = None, ylim_rlocus = None,
84
100
}
85
101
86
102
# First time call to setup the bode and step response plots
87
- _SisotoolUpdate (sys , fig ,1 if kvect is None else kvect [0 ],bode_plot_params )
103
+ _SisotoolUpdate (sys , fig ,
104
+ 1 if kvect is None else kvect [0 ], bode_plot_params )
88
105
89
106
# Setup the root-locus plot window
90
- 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 )
107
+ root_locus (sys , kvect = kvect , xlim = xlim_rlocus ,
108
+ ylim = ylim_rlocus , plotstr = plotstr_rlocus , grid = rlocus_grid ,
109
+ fig = fig , bode_plot_params = bode_plot_params , tvect = tvect , sisotool = True )
91
110
92
- def _SisotoolUpdate (sys ,fig ,K , bode_plot_params ,tvect = None ):
111
+ def _SisotoolUpdate (sys , fig , K , bode_plot_params , tvect = None ):
93
112
94
- if int (matplotlib .__version__ [0 ]) == 1 :
95
- title_font_size = 12
96
- label_font_size = 10
97
- else :
98
- title_font_size = 10
99
- label_font_size = 8
113
+ title_font_size = 10
114
+ label_font_size = 8
100
115
101
116
# Get the subaxes and clear them
102
- ax_mag ,ax_rlocus ,ax_phase ,ax_step = fig .axes [0 ],fig .axes [1 ],fig .axes [2 ],fig .axes [3 ]
117
+ ax_mag , ax_rlocus , ax_phase , ax_step = \
118
+ fig .axes [0 ], fig .axes [1 ], fig .axes [2 ], fig .axes [3 ]
103
119
104
120
# Catch matplotlib 2.1.x and higher userwarnings when clearing a log axis
105
121
with warnings .catch_warnings ():
106
122
warnings .simplefilter ("ignore" )
107
123
ax_step .clear (), ax_mag .clear (), ax_phase .clear ()
108
124
125
+ sys_loop = sys if sys .issiso () else sys [0 ,0 ]
126
+
109
127
# Update the bodeplot
110
- bode_plot_params ['syslist' ] = sys * K .real
128
+ bode_plot_params ['syslist' ] = sys_loop * K .real
111
129
bode_plot (** bode_plot_params )
112
130
113
131
# Set the titles and labels
114
132
ax_mag .set_title ('Bode magnitude' ,fontsize = title_font_size )
115
133
ax_mag .set_ylabel (ax_mag .get_ylabel (), fontsize = label_font_size )
134
+ ax_mag .tick_params (axis = 'both' , which = 'major' , labelsize = label_font_size )
116
135
117
136
ax_phase .set_title ('Bode phase' ,fontsize = title_font_size )
118
137
ax_phase .set_xlabel (ax_phase .get_xlabel (),fontsize = label_font_size )
119
138
ax_phase .set_ylabel (ax_phase .get_ylabel (),fontsize = label_font_size )
120
139
ax_phase .get_xaxis ().set_label_coords (0.5 , - 0.15 )
121
140
ax_phase .get_shared_x_axes ().join (ax_phase , ax_mag )
141
+ ax_phase .tick_params (axis = 'both' , which = 'major' , labelsize = label_font_size )
122
142
123
143
ax_step .set_title ('Step response' ,fontsize = title_font_size )
124
144
ax_step .set_xlabel ('Time (seconds)' ,fontsize = label_font_size )
125
- ax_step .set_ylabel ('Amplitude ' ,fontsize = label_font_size )
145
+ ax_step .set_ylabel ('Output ' ,fontsize = label_font_size )
126
146
ax_step .get_xaxis ().set_label_coords (0.5 , - 0.15 )
127
147
ax_step .get_yaxis ().set_label_coords (- 0.15 , 0.5 )
148
+ ax_step .tick_params (axis = 'both' , which = 'major' , labelsize = label_font_size )
128
149
129
150
ax_rlocus .set_title ('Root locus' ,fontsize = title_font_size )
130
151
ax_rlocus .set_ylabel ('Imag' , fontsize = label_font_size )
131
152
ax_rlocus .set_xlabel ('Real' , fontsize = label_font_size )
132
153
ax_rlocus .get_xaxis ().set_label_coords (0.5 , - 0.15 )
133
154
ax_rlocus .get_yaxis ().set_label_coords (- 0.15 , 0.5 )
134
-
135
-
155
+ ax_rlocus .tick_params (axis = 'both' , which = 'major' ,labelsize = label_font_size )
136
156
137
157
# Generate the step response and plot it
138
- sys_closed = (K * sys ).feedback (1 )
158
+ if sys .issiso ():
159
+ sys_closed = (K * sys ).feedback (1 )
160
+ else :
161
+ sys_closed = append (sys , - K )
162
+ connects = [[1 , 3 ],
163
+ [3 , 1 ]]
164
+ sys_closed = connect (sys_closed , connects , 2 , 2 )
139
165
if tvect is None :
140
166
tvect , yout = step_response (sys_closed , T_num = 100 )
141
167
else :
142
- tvect , yout = step_response (sys_closed ,tvect )
168
+ tvect , yout = step_response (sys_closed , tvect )
143
169
if isdtime (sys_closed , strict = True ):
144
- ax_step .plot (tvect , yout , 'o ' )
170
+ ax_step .plot (tvect , yout , '. ' )
145
171
else :
146
172
ax_step .plot (tvect , yout )
147
173
ax_step .axhline (1. ,linestyle = ':' ,color = 'k' ,zorder = - 20 )
0 commit comments