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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy