11__all__ = ['sisotool' ]
22
3+ from control .exception import ControlMIMONotImplemented
34from .freqplot import bode_plot
45from .timeresp import step_response
56from .lti import issiso , isdtime
7+ from .xferfcn import TransferFunction
8+ from .bdalg import append , connect
69import matplotlib
710import matplotlib .pyplot as plt
811import warnings
@@ -22,7 +25,11 @@ def sisotool(sys, kvect = None, xlim_rlocus = None, ylim_rlocus = None,
2225 Parameters
2326 ----------
2427 sys : LTI object
25- Linear input/output systems (SISO only)
28+ Linear input/output systems. If sys is SISO, use the same
29+ system for the root locus and step response. If sys is
30+ two-input, two-output, insert the selected gain between the
31+ first output and first input and use the second input and output
32+ for computing the step response.
2633 kvect : list or ndarray, optional
2734 List of gains to use for plotting root locus
2835 xlim_rlocus : tuple or list, optional
@@ -60,8 +67,14 @@ def sisotool(sys, kvect = None, xlim_rlocus = None, ylim_rlocus = None,
6067 """
6168 from .rlocus import root_locus
6269
63- # Check if it is a single SISO system
64- issiso (sys ,strict = True )
70+ # sys as loop transfer function if SISO
71+ if sys .issiso ():
72+ sys_loop = sys
73+ else :
74+ if not sys .ninputs == 2 and sys .noutputs == 2 :
75+ raise ControlMIMONotImplemented (
76+ 'sys must be SISO or 2-input, 2-output' )
77+ sys_loop = sys [0 ,0 ]
6578
6679 # Setup sisotool figure or superimpose if one is already present
6780 fig = plt .gcf ()
@@ -84,12 +97,15 @@ def sisotool(sys, kvect = None, xlim_rlocus = None, ylim_rlocus = None,
8497 }
8598
8699 # 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 )
100+ _SisotoolUpdate (sys , fig ,
101+ 1 if kvect is None else kvect [0 ], bode_plot_params )
88102
89103 # 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 )
104+ root_locus (sys_loop , kvect = kvect , xlim = xlim_rlocus ,
105+ ylim = ylim_rlocus , plotstr = plotstr_rlocus , grid = rlocus_grid ,
106+ fig = fig , bode_plot_params = bode_plot_params , tvect = tvect , sisotool = True )
91107
92- def _SisotoolUpdate (sys ,fig ,K , bode_plot_params ,tvect = None ):
108+ def _SisotoolUpdate (sys , fig , K , bode_plot_params , tvect = None ):
93109
94110 if int (matplotlib .__version__ [0 ]) == 1 :
95111 title_font_size = 12
@@ -99,49 +115,60 @@ def _SisotoolUpdate(sys,fig,K,bode_plot_params,tvect=None):
99115 label_font_size = 8
100116
101117 # 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 ]
118+ ax_mag , ax_rlocus , ax_phase , ax_step = \
119+ fig .axes [0 ], fig .axes [1 ], fig .axes [2 ], fig .axes [3 ]
103120
104121 # Catch matplotlib 2.1.x and higher userwarnings when clearing a log axis
105122 with warnings .catch_warnings ():
106123 warnings .simplefilter ("ignore" )
107124 ax_step .clear (), ax_mag .clear (), ax_phase .clear ()
108125
126+ sys_loop = sys if sys .issiso () else sys [0 ,0 ]
127+
109128 # Update the bodeplot
110- bode_plot_params ['syslist' ] = sys * K .real
129+ bode_plot_params ['syslist' ] = sys_loop * K .real
111130 bode_plot (** bode_plot_params )
112131
113132 # Set the titles and labels
114133 ax_mag .set_title ('Bode magnitude' ,fontsize = title_font_size )
115134 ax_mag .set_ylabel (ax_mag .get_ylabel (), fontsize = label_font_size )
135+ ax_mag .tick_params (axis = 'both' , which = 'major' , labelsize = label_font_size )
116136
117137 ax_phase .set_title ('Bode phase' ,fontsize = title_font_size )
118138 ax_phase .set_xlabel (ax_phase .get_xlabel (),fontsize = label_font_size )
119139 ax_phase .set_ylabel (ax_phase .get_ylabel (),fontsize = label_font_size )
120140 ax_phase .get_xaxis ().set_label_coords (0.5 , - 0.15 )
121141 ax_phase .get_shared_x_axes ().join (ax_phase , ax_mag )
142+ ax_phase .tick_params (axis = 'both' , which = 'major' , labelsize = label_font_size )
122143
123144 ax_step .set_title ('Step response' ,fontsize = title_font_size )
124145 ax_step .set_xlabel ('Time (seconds)' ,fontsize = label_font_size )
125146 ax_step .set_ylabel ('Amplitude' ,fontsize = label_font_size )
126147 ax_step .get_xaxis ().set_label_coords (0.5 , - 0.15 )
127148 ax_step .get_yaxis ().set_label_coords (- 0.15 , 0.5 )
149+ ax_step .tick_params (axis = 'both' , which = 'major' , labelsize = label_font_size )
128150
129151 ax_rlocus .set_title ('Root locus' ,fontsize = title_font_size )
130152 ax_rlocus .set_ylabel ('Imag' , fontsize = label_font_size )
131153 ax_rlocus .set_xlabel ('Real' , fontsize = label_font_size )
132154 ax_rlocus .get_xaxis ().set_label_coords (0.5 , - 0.15 )
133155 ax_rlocus .get_yaxis ().set_label_coords (- 0.15 , 0.5 )
134-
135-
156+ ax_rlocus .tick_params (axis = 'both' , which = 'major' ,labelsize = label_font_size )
136157
137158 # Generate the step response and plot it
138- sys_closed = (K * sys ).feedback (1 )
159+ if sys .issiso ():
160+ sys_closed = (K * sys ).feedback (1 )
161+ else :
162+ sys_closed = append (sys , K )
163+ connects = [[1 , 3 ],
164+ [3 , 1 ]]
165+ sys_closed = connect (sys_closed , connects , (2 ,), (2 ,))
139166 if tvect is None :
140167 tvect , yout = step_response (sys_closed , T_num = 100 )
141168 else :
142- tvect , yout = step_response (sys_closed ,tvect )
169+ tvect , yout = step_response (sys_closed , tvect )
143170 if isdtime (sys_closed , strict = True ):
144- ax_step .plot (tvect , yout , 'o ' )
171+ ax_step .plot (tvect , yout , '. ' )
145172 else :
146173 ax_step .plot (tvect , yout )
147174 ax_step .axhline (1. ,linestyle = ':' ,color = 'k' ,zorder = - 20 )
0 commit comments