10000 changes to rlocus to be compatible with discrete-time systems (#410) · python-control/python-control@d3142ff · GitHub
[go: up one dir, main page]

Skip to content

Commit d3142ff

Browse files
changes to rlocus to be compatible with discrete-time systems (#410)
* give correct z-plane damping ratio on mouse click * auto zoom into unit circle * show zgrid with lines of constant damping ratio and natural frequency if desired * sisotool now plots dots instead of a continuous line for discrete-time systems * fixed spelling of variables in a couple of places
1 parent 0160990 commit d3142ff

File tree

3 files changed

+65
-29
lines changed

3 files changed

+65
-29
lines changed

control/grid.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,12 @@ def nogrid():
136136
return ax, f
137137

138138

139-
def zgrid(zetas=None, wns=None):
139+
def zgrid(zetas=None, wns=None, ax=None):
140140
'''Draws discrete damping and frequency grid'''
141141

142142
fig = plt.gcf()
143-
ax = fig.gca()
143+
if ax is None:
144+
ax = fig.gca()
144145

145146
# Constant damping lines
146147
if zetas is None:
@@ -154,11 +155,11 @@ def zgrid(zetas=None, wns=None):
154155
# Draw upper part in retangular coordinates
155156
xret = mag*cos(ang)
156157
yret = mag*sin(ang)
157-
ax.plot(xret, yret, 'k:', lw=1)
158+
ax.plot(xret, yret, ':', color='grey', lw=0.75)
158159
# Draw lower part in retangular coordinates
159160
xret = mag*cos(-ang)
160161
yret = mag*sin(-ang)
161-
ax.plot(xret, yret, 'k:', lw=1)
162+
ax.plot(xret, yret, ':', color='grey', lw=0.75)
162163
# Annotation
163164
an_i = int(len(xret)/2.5)
164165
an_x = xret[an_i]
@@ -177,7 +178,7 @@ def zgrid(zetas=None, wns=None):
177178
# Draw in retangular coordinates
178179
xret = mag*cos(ang)
179180
yret = mag*sin(ang)
180-
ax.plot(xret, yret, 'k:', lw=1)
181+
ax.plot(xret, yret, ':', color='grey', lw=0.75)
181182
# Annotation
182183
an_i = -1
183184
an_x = xret[an_i]

control/rlocus.py

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
# RMM, 2 April 2011: modified to work with new LTI structure (see ChangeLog)
4444
# * Not tested: should still work on signal.ltisys objects
4545
#
46+
# Sawyer B. Fuller (minster@uw.edu) 21 May 2020:
47+
# * added compatibility with discrete-time systems.
48+
#
4649
# $Id$
4750

4851
# Packages used by this module
@@ -52,9 +55,11 @@
5255
import matplotlib.pyplot as plt
5356
from numpy import array, poly1d, row_stack, zeros_like, real, imag
5457
import scipy.signal # signal processing toolbox
58+
from .lti import isdtime
5559
from .xferfcn import _convert_to_transfer_function
5660
from .exception import ControlMIMONotImplemented
5761
from .sisotool import _SisotoolUpdate
62+
from .grid import sgrid, zgrid
5863
from . import config
5964

6065
__all__ = ['root_locus', 'rlocus']
@@ -131,6 +136,13 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
131136
# Convert numerator and denominator to polynomials if they aren't
132137
(nump, denp) = _systopoly1d(sys)
133138

139+
# if discrete-time system and if xlim and ylim are not given,
140+
# that we a view of the unit circle
141+
if xlim is None and isdtime(sys, strict=True):
142+
xlim = (-1.2, 1.2)
143+
if ylim is None and isdtime(sys, strict=True):
144+
xlim = (-1.3, 1.3)
145+
134146
if kvect is None:
135147
start_mat = _RLFindRoots(nump, denp, [1])
136148
kvect, mymat, xlim, ylim = _default_gains(nump, denp, xlim, ylim)
@@ -163,10 +175,14 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
163175
[root.real for root in start_mat],
164176
[root.imag for root in start_mat],
165177
'm.', marker='s', markersize=8, zorder=20, label='gain_point')
178+
s = start_mat[0][0]
179+
if isdtime(sys, strict=True):
180+
zeta = -np.cos(np.angle(np.log(s)))
181+
else:
182+
zeta = -1 * s.real / abs(s)
166183
fig.suptitle(
167184
"Clicked at: %10.4g%+10.4gj gain: %10.4g damp: %10.4g" %
168-
(start_mat[0][0].real, start_mat[0][0].imag,
169-
1, -1 * start_mat[0][0].real / abs(start_mat[0][0])),
185+
(s.real, s.imag, 1, zeta),
170186
fontsize=12 if int(mpl.__version__[0]) == 1 else 10)
171187
fig.canvas.mpl_connect(
172188
'button_release_event',
@@ -199,20 +215,31 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
199215
ax.plot(real(col), imag(col), plotstr, label='rootlocus')
200216

201217
# Set up plot axes and labels
202-
if xlim:
203-
ax.set_xlim(xlim)
204-
if ylim:
205-
ax.set_ylim(ylim)
206-
207218
ax.set_xlabel('Real')
208219
ax.set_ylabel('Imaginary')
220+
209221
if grid and sisotool:
210-
_sgrid_func(f)
222+
if isdtime(sys, strict=True):
223+
zgrid(ax=ax)
224+
else:
225+
_sgrid_func(f)
211226
elif grid:
212-
_sgrid_func()
227+
if isdtime(sys, strict=True):
228+
zgrid(ax=ax)
229+
else:
230+
_sgrid_func()
213231
else:
214232
ax.axhline(0., linestyle=':', color='k', zorder=-20)
215-
ax.axvline(0., linestyle=':', color='k')
233+
ax.axvline(0., linestyle=':', color='k', zorder=-20)
234+
if isdtime(sys, strict=True):
235+
ax.add_patch(plt.Circle((0,0), radius=1.0,
236+
linestyle=':', edgecolor='k', linewidth=1.5,
237+
fill=False, zorder=-20))
238+
239+
if xlim:
240+
ax.set_xlim(xlim)
241+
if ylim:
242+
ax.set_ylim(ylim)
216243

217244
return mymat, kvect
218245

@@ -567,12 +594,17 @@ def _RLFeedbackClicksPoint(event, sys, fig, ax_rlocus, sisotool=False):
567594
if abs(K.real) > 1e-8 and abs(K.imag / K.real) < gain_tolerance and \
568595
event.inaxes == ax_rlocus.axes and K.real > 0.:
569596

597+
if isdtime(sys, strict=True):
598+
zeta = -np.cos(np.angle(np.log(s)))
599+
else:
600+
zeta = -1 * s.real / abs(s)
601+
570602
# Display the parameters in the output window and figure
571603
print("Clicked at %10.4g%+10.4gj gain %10.4g damp %10.4g" %
572-
(s.real, s.imag, K.real, -1 * s.real / abs(s)))
604+
(s.real, s.imag, K.real, zeta))
573605
fig.suptitle(
574606
"Clicked at: %10.4g%+10.4gj gain: %10.4g damp: %10.4g" %
575-
(s.real, s.imag, K.real, -1 * s.real / abs(s)),
607+
(s.real, s.imag, K.real, zeta),
576608
fontsize=12 if int(mpl.__version__[0]) == 1 else 10)
577609

578610
# Remove the previous line
@@ -616,13 +648,13 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
616648
if zeta is None:
617649
zeta = _default_zetas(xlim, ylim)
618650

619-
angules = []
651+
angles = []
620652
for z in zeta:
621653
if (z >= 1e-4) and (z <= 1):
622-
angules.append(np.pi/2 + np.arcsin(z))
654+
angles.append(np.pi/2 + np.arcsin(z))
623655
else:
624656
zeta.remove(z)
625-
y_over_x = np.tan(angules)
657+
y_over_x = np.tan(angles)
626658

627659
# zeta-constant lines
628660

@@ -647,30 +679,30 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
647679
ax.plot([0, 0], [ylim[0], ylim[1]],
648680
color='gray', linestyle='dashed', linewidth=0.5)
649681

650-
angules = np.linspace(-90, 90, 20)*np.pi/180
682+
angles = np.linspace(-90, 90, 20)*np.pi/180
651683
if wn is None:
652684
wn = _default_wn(xlocator(), ylim)
653685

654686
for om in wn:
655687
if om < 0:
656-
yp = np.sin(angules)*np.abs(om)
657-
xp = -np.cos(angules)*np.abs(om)
688+
yp = np.sin(angles)*np.abs(om)
689+
xp = -np.cos(angles)*np.abs(om)
658690
ax.plot(xp, yp, color='gray',
659691
linestyle='dashed', linewidth=0.5)
660692
an = "%.2f" % -om
661693
ax.annotate(an, textcoords='data', xy=[om, 0], fontsize=8)
662694

663695

664696
def _default_zetas(xlim, ylim):
665-
"""Return default list of dumps coefficients"""
697+
"""Return default list of damping coefficients"""
666698
sep1 = -xlim[0]/4
667699
ang1 = [np.arctan((sep1*i)/ylim[1]) for i in np.arange(1, 4, 1)]
668700
sep2 = ylim[1] / 3
669701
ang2 = [np.arctan(-xlim[0]/(ylim[1]-sep2*i)) for i in np.arange(1, 3, 1)]
670702

671-
angules = np.concatenate((ang1, ang2))
672-
angules = np.insert(angules, len(angules), np.pi/2)
673-
zeta = np.sin(angules)
703+
angles = np.concatenate((ang1, ang2))
704+
angles = np.insert(angles, len(angles), np.pi/2)
705+
zeta = np.sin(angles)
674706
return zeta.tolist()
675707

676708

control/sisotool.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from .freqplot import bode_plot
44
from .timeresp import step_response
5-
from .lti import issiso
5+
from .lti import issiso, isdtime
66
import matplotlib
77
import matplotlib.pyplot as plt
88
import warnings
@@ -139,7 +139,10 @@ def _SisotoolUpdate(sys,fig,K,bode_plot_params,tvect=None):
139139
tvect, yout = step_response(sys_closed, T_num=100)
140140
else:
141141
tvect, yout = step_response(sys_closed,tvect)
142-
ax_step.plot(tvect, yout)
142+
if isdtime(sys_closed, strict=True):
143+
ax_step.plot(tvect, yout, 'o')
144+
else:
145+
ax_step.plot(tvect, yout)
143146
ax_step.axhline(1.,linestyle=':',color='k',zorder=-20)
144147

145148
# Manually adjust the spacing and draw the canvas

0 commit comments

Comments
 (0)
0