Skip to content

Commit 2e1bf7d

Browse files
committed
second sisotool checkpoint
1 parent 54583d5 commit 2e1bf7d

4 files changed

Lines changed: 121 additions & 37 deletions

File tree

control/freqplot.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -177,24 +177,32 @@ def bode_plot(syslist, omega=None, dB=None, Hz=None, deg=None,
177177
# https://github.com/matplotlib/matplotlib/issues/9024).
178178
# The code below should work on all cases.
179179

180-
# Get the current figure
181-
fig = plt.gcf()
182-
ax_mag = None
183-
ax_phase = None
184-
185-
# Get the current axes if they already exist
186-
for ax in fig.axes:
187-
if ax.get_label() == 'control-bode-magnitude':
188-
ax_mag = ax
189-
elif ax.get_label() == 'control-bode-phase':
190-
ax_phase = ax
191-
192-
# If no axes present, create them from scratch
193-
if ax_mag is None or ax_phase is None:
194-
plt.clf()
195-
ax_mag = plt.subplot(211, label = 'control-bode-magnitude')
196-
ax_phase = plt.subplot(212, label = 'control-bode-phase',
197-
sharex=ax_mag)
180+
# Get the current figure
181+
if 'sisotool' in kwargs:
182+
fig = kwargs['fig']
183+
ax_mag = fig.axes[0]
184+
ax_phase = fig.axes[2]
185+
sisotool = kwargs['sisotool']
186+
del kwargs['fig']
187+
del kwargs['sisotool']
188+
else:
189+
fig = plt.gcf()
190+
ax_mag = None
191+
ax_phase = None
192+
193+
# Get the current axes if they already exist
194+
for ax in fig.axes:
195+
if ax.get_label() == 'control-bode-magnitude':
196+
ax_mag = ax
197+
elif ax.get_label() == 'control-bode-phase':
198+
ax_phase = ax
199+
200+
# If no axes present, create them from scratch
201+
if ax_mag is None or ax_phase is None:
202+
plt.clf()
203+
ax_mag = plt.subplot(211, label = 'control-bode-magnitude')
204+
ax_phase = plt.subplot(212, label = 'control-bode-phase',
205+
sharex=ax_mag)
198206

199207
# Magnitude plot
200208
if dB:

control/rlocus.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,16 @@
5252
import pylab # plotting routines
5353
from .xferfcn import _convertToTransferFunction
5454
from .exception import ControlMIMONotImplemented
55+
from .sisotool import _SisotoolUpdate
5556
from functools import partial
5657

5758
__all__ = ['root_locus', 'rlocus']
5859

5960

6061
# Main function: compute a root locus diagram
6162
def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
62-
PrintGain=True, grid=False, sisotool = False, f=None, ax =None):
63+
PrintGain=True, grid=False, **kwargs):
64+
6365
"""Root locus plot
6466
6567
Calculate the root locus by finding the roots of 1+k*TF(s)
@@ -96,14 +98,16 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
9698
(nump, denp) = _systopoly1d(sys)
9799

98100
if kvect is None:
101+
start_mat = _RLFindRoots(nump, denp, [1])
99102
kvect, mymat, xlim, ylim = _default_gains(nump, denp, xlim, ylim)
100103
else:
104+
start_mat = _RLFindRoots(nump, denp, kvect[0])
101105
mymat = _RLFindRoots(nump, denp, kvect)
102106
mymat = _RLSortRoots(mymat)
103107

104108
# Create the Plot
105109
if Plot:
106-
if sisotool == False:
110+
if 'sisotool' not in kwargs:
107111
figure_number = pylab.get_fignums()
108112
figure_title = [pylab.figure(numb).canvas.get_window_title() for numb in figure_number]
109113
new_figure_name = "Root Locus"
@@ -113,11 +117,19 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
113117
rloc_num += 1
114118
f = pylab.figure(new_figure_name)
115119
ax = pylab.axes()
116-
120+
else:
121+
sisotool = kwargs['sisotool']
122+
f = kwargs['fig']
123+
ax = f.axes[1]
124+
bode_plot_params = kwargs['bode_plot_params']
125+
tvect = kwargs['tvect']
126+
event = type('event', (object,), {'xdata': start_mat[0][0].real,'ydata':start_mat[0][0].imag})()
127+
_RLFeedbackClicks(event, sys, f, sisotool,bode_plot_params,tvect)
117128

118129
if PrintGain:
130+
119131
f.canvas.mpl_connect(
120-
'button_release_event', partial(_RLFeedbackClicks,sys=sys,fig=f,sisotool=sisotool))
132+
'button_release_event', partial(_RLFeedbackClicks,sys=sys,fig=f,sisotool=sisotool,bode_plot_params=bode_plot_params,tvect=tvect))
121133

122134
# plot open loop poles
123135
poles = array(denp.r)
@@ -139,7 +151,9 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
139151
ax.set_ylim(ylim)
140152
ax.set_xlabel('Real')
141153
ax.set_ylabel('Imaginary')
142-
if grid:
154+
if grid and sisotool:
155+
_sgrid_func(f)
156+
elif grid:
143157
_sgrid_func()
144158
return mymat, kvect
145159

@@ -344,7 +358,7 @@ def _RLSortRoots(mymat):
344358
return sorted
345359

346360

347-
def _RLFeedbackClicks(event,sys,fig,sisotool):
361+
def _RLFeedbackClicks(event,sys,fig,sisotool,bode_plot_params,tvect):
348362
"""Print root-locus gain feedback for clicks on the root-locus plot
349363
"""
350364
(nump, denp) = _systopoly1d(sys)
@@ -356,26 +370,33 @@ def _RLFeedbackClicks(event,sys,fig,sisotool):
356370
fig.suptitle("Clicked at: %10.4g%+10.4gj gain: %10.4g damp: %10.4g" %
357371
(s.real, s.imag, K.real, -1 * s.real / abs(s)))
358372

359-
ax = fig.axes[0]
360-
for line in reversed(ax.lines):
373+
if sisotool:
374+
ax_rlocus = fig.axes[1]
375+
else:
376+
ax_rlocus = fig.axes[0]
377+
378+
for line in reversed(ax_rlocus.lines):
361379
if len(line.get_xdata()) == 1:
362380
line.remove()
363381
del line
364-
else:
365-
break
382+
#else:
383+
# break
366384

367385
if sisotool:
368386
mymat = _RLFindRoots(nump, denp, K.real)
369-
ax.plot([root.real for root in mymat],[root.imag for root in mymat],'m.',marker='s',markersize=8, zorder=20)
387+
ax_rlocus.plot([root.real for root in mymat],[root.imag for root in mymat],'m.',marker='s',markersize=8, zorder=20)
388+
_SisotoolUpdate(sys,fig,K.real[0][0],bode_plot_params,tvect)
370389
else:
371-
ax.plot(s.real, s.imag, 'k.', marker='s', markersize=8, zorder=20)
390+
ax_rlocus.plot(s.real, s.imag, 'k.', marker='s', markersize=8, zorder=20)
372391

373392
fig.canvas.draw()
374393

375394
def _sgrid_func(fig=None, zeta=None, wn=None):
376395
if fig is None:
377396
fig = pylab.gcf()
378-
ax = fig.gca()
397+
ax = fig.gca()
398+
else:
399+
ax = fig.axes[1]
379400
xlocator = ax.get_xaxis().get_major_locator()
380401

381402
ylim = ax.get_ylim()

control/sisotool.py

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,65 @@
11
__all__ = ['sisotool']
22

33
from .freqplot import bode_plot
4-
from .rlocus import root_locus
4+
from .timeresp import step_response
5+
from .lti import issiso
56
import matplotlib.pyplot as plt
67

7-
def sisotool(sys, kvect=None,PrintGain=True,grid=False,dB=None,Hz=None,deg=None):
8-
f, (ax1, ax2) = plt.subplots(1, 2)
9-
root_locus(sys,ax=ax1,f=f,sisotool=True)
10-
#bode_plot(sys,ax=ax2)
8+
def sisotool(sys, kvect = None, xlim = None, ylim = None, plotstr_rlocus = '-',rlocus_grid = False, omega = None, dB = None, Hz = None, deg = None, omega_limits = None, omega_num = None, tvect=None):
9+
10+
from .rlocus import root_locus
11+
12+
# Check if it is a single SISO system
13+
issiso(sys,strict=True)
14+
15+
# Setup sisotool figure or superimpose if one is already present
16+
fig = plt.gcf()
17+
if fig.canvas.get_window_title() != 'Sisotool':
18+
plt.close(fig)
19+
fig,axes = plt.subplots(2, 2)
20+
fig.canvas.set_window_title('Sisotool')
21+
22+
# Extract bode plot parameters
23+
bode_plot_params = {
24+
'omega': omega,
25+
'dB': dB,
26+
'Hz': Hz,
27+
'deg': deg,
28+
'omega_limits': omega_limits,
29+
'omega_num' : omega_num,
30+
'sisotool': True,
31+
'fig': fig,
32+
}
33+
34+
#To-do find out clever way to pass correct settings to other plots
35+
_SisotoolUpdate(sys, fig, 1,bode_plot_params,tvect)
36+
37+
# Setup the root-locus plot window
38+
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)
39+
40+
def _SisotoolUpdate(sys,fig,K,bode_plot_params,tvect):
41+
42+
# Get the subaxes and clear them
43+
ax_mag,ax_rlocus,ax_phase,ax_step = fig.axes[0],fig.axes[1],fig.axes[2],fig.axes[3]
44+
ax_mag.cla(),ax_phase.cla(),ax_step.cla()
45+
46+
# Set the titles and labels
47+
ax_mag.set_title('Bode magnitude')
48+
ax_phase.set_title('Bode phase')
49+
ax_rlocus.set_title('Root locus')
50+
ax_step.set_title('Step response')
51+
ax_step.set_xlabel('Time (seconds)')
52+
ax_step.set_ylabel('Amplitude')
53+
54+
# Update the bodeplot
55+
bode_plot_params['syslist'] = sys*K.real
56+
bode_plot(**bode_plot_params)
57+
58+
# Generate the step response
59+
sys_closed = (K*sys).feedback(1)
60+
if tvect is None:
61+
tvect, yout = step_response(sys_closed)
62+
else:
63+
tvect, yout = step_response(sys_closed,tvect)
64+
ax_step.plot(tvect, yout)
65+

control/timeresp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False):
339339
def _get_ss_simo(sys, input=None, output=None):
340340
"""Return a SISO or SIMO state-space version of sys
341341
342-
If input is not specified, select first input and issue warning
342+
If input is not sfpecified, select first input and issue warning
343343
"""
344344
sys_ss = _convertToStateSpace(sys)
345345
if sys_ss.issiso():

0 commit comments

Comments
 (0)