-
Notifications
You must be signed in to change notification settings - Fork 458
Expand file tree
/
Copy pathsisotool.py
More file actions
152 lines (128 loc) · 5.52 KB
/
Copy pathsisotool.py
File metadata and controls
152 lines (128 loc) · 5.52 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
__all__ = ['sisotool']
from .freqplot import bode_plot
from .timeresp import step_response
from .lti import issiso, isdtime
import matplotlib
import matplotlib.pyplot as plt
import warnings
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):
"""
Sisotool style collection of plots inspired by MATLAB's sisotool.
The left two plots contain the bode magnitude and phase diagrams.
The top right plot is a clickable root locus plot, clicking on the
root locus will change the gain of the system. The bottom left plot
shows a closed loop time response.
Parameters
----------
sys : LTI object
Linear input/output systems (SISO only)
kvect : list or ndarray, optional
List of gains to use for plotting root locus
xlim_rlocus : tuple or list, optional
control of x-axis range, normally with tuple
(see :doc:`matplotlib:api/axes_api`).
ylim_rlocus : tuple or list, optional
control of y-axis range
plotstr_rlocus : :func:`matplotlib.pyplot.plot` format string, optional
plotting style for the root locus plot(color, linestyle, etc)
rlocus_grid: boolean (default = False)
If True plot s-plane grid.
omega : freq_range
Range of frequencies in rad/sec for the bode plot
dB : boolean
If True, plot result in dB for the bode plot
Hz : boolean
If True, plot frequency in Hz for the bode plot (omega must be provided in rad/sec)
deg : boolean
If True, plot phase in degrees for the bode plot (else radians)
omega_limits: tuple, list, ... of two values
Limits of the to generate frequency vector.
If Hz=True the limits are in Hz otherwise in rad/s.
omega_num: int
number of samples
margins_bode : boolean
If True, plot gain and phase margin in the bode plot
tvect : list or ndarray, optional
List of timesteps to use for closed loop step response
Examples
--------
>>> sys = tf([1000], [1,25,100,0])
>>> sisotool(sys)
"""
from .rlocus import root_locus
# Check if it is a single SISO system
issiso(sys,strict=True)
# Setup sisotool figure or superimpose if one is already present
fig = plt.gcf()
if fig.canvas.get_window_title() != 'Sisotool':
plt.close(fig)
fig,axes = plt.subplots(2, 2)
fig.canvas.set_window_title('Sisotool')
# Extract bode plot parameters
bode_plot_params = {
'omega': omega,
'dB': dB,
'Hz': Hz,
'deg': deg,
'omega_limits': omega_limits,
'omega_num' : omega_num,
'sisotool': True,
'fig': fig,
'margins': margins_bode
}
# First time call to setup the bode and step response plots
_SisotoolUpdate(sys, fig,1 if kvect is None else kvect[0],bode_plot_params)
# Setup the root-locus plot window
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)
def _SisotoolUpdate(sys,fig,K,bode_plot_params,tvect=None):
if int(matplotlib.__version__[0]) == 1:
title_font_size = 12
label_font_size = 10
else:
title_font_size = 10
label_font_size = 8
# Get the subaxes and clear them
ax_mag,ax_rlocus,ax_phase,ax_step = fig.axes[0],fig.axes[1],fig.axes[2],fig.axes[3]
# Catch matplotlib 2.1.x and higher userwarnings when clearing a log axis
with warnings.catch_warnings():
warnings.simplefilter("ignore")
ax_step.clear(), ax_mag.clear(), ax_phase.clear()
# Update the bodeplot
bode_plot_params['syslist'] = sys*K.real
bode_plot(**bode_plot_params)
# Set the titles and labels
ax_mag.set_title('Bode magnitude',fontsize = title_font_size)
ax_mag.set_ylabel(ax_mag.get_ylabel(), fontsize=label_font_size)
ax_phase.set_title('Bode phase',fontsize=title_font_size)
ax_phase.set_xlabel(ax_phase.get_xlabel(),fontsize=label_font_size)
ax_phase.set_ylabel(ax_phase.get_ylabel(),fontsize=label_font_size)
ax_phase.get_xaxis().set_label_coords(0.5, -0.15)
ax_phase.get_shared_x_axes().join(ax_phase, ax_mag)
ax_step.set_title('Step response',fontsize = title_font_size)
ax_step.set_xlabel('Time (seconds)',fontsize=label_font_size)
ax_step.set_ylabel('Amplitude',fontsize=label_font_size)
ax_step.get_xaxis().set_label_coords(0.5, -0.15)
ax_step.get_yaxis().set_label_coords(-0.15, 0.5)
ax_rlocus.set_title('Root locus',fontsize = title_font_size)
ax_rlocus.set_ylabel('Imag', fontsize=label_font_size)
ax_rlocus.set_xlabel('Real', fontsize=label_font_size)
ax_rlocus.get_xaxis().set_label_coords(0.5, -0.15)
ax_rlocus.get_yaxis().set_label_coords(-0.15, 0.5)
# Generate the step response and plot it
sys_closed = (K*sys).feedback(1)
if tvect is None:
tvect, yout = step_response(sys_closed, T_num=100)
else:
tvect, yout = step_response(sys_closed,tvect)
if isdtime(sys_closed, strict=True):
ax_step.plot(tvect, yout, 'o')
else:
ax_step.plot(tvect, yout)
ax_step.axhline(1.,linestyle=':',color='k',zorder=-20)
# Manually adjust the spacing and draw the canvas
fig.subplots_adjust(top=0.9,wspace = 0.3,hspace=0.35)
fig.canvas.draw()