Skip to content

Commit ffc3041

Browse files
committed
add unsupervised calculation on gains for root locus plot.
1 parent 5ab74cf commit ffc3041

File tree

1 file changed

+104
-12
lines changed

1 file changed

+104
-12
lines changed

control/rlocus.py

Lines changed: 104 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656

5757
__all__ = ['root_locus', 'rlocus']
5858

59+
5960
# Main function: compute a root locus diagram
6061
def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
6162
PrintGain=True):
@@ -93,11 +94,10 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
9394
(nump, denp) = _systopoly1d(sys)
9495

9596
if kvect is None:
96-
kvect = _default_gains(sys)
97-
98-
# Compute out the loci
99-
mymat = _RLFindRoots(sys, kvect)
100-
mymat = _RLSortRoots(sys, mymat)
97+
kvect, mymat, xlim, ylim = _default_gains(nump, denp, xlim, ylim)
98+
else:
99+
mymat = _RLFindRoots(nump, denp, kvect)
100+
mymat = _RLSortRoots(mymat)
101101

102102
# Create the plot
103103
if (Plot):
@@ -130,11 +130,104 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
130130

131131
return mymat, kvect
132132

133-
def _default_gains(sys):
134-
# TODO: update with a smart calculation of the gains using sys poles/zeros
135-
return np.logspace(-3, 3)
136133

137-
# Utility function to extract numerator and denominator polynomials
134+
def _default_gains(num, den, xlim, ylim):
135+
"""Unsupervised gains calculation for root locus plot"""
136+
k_break, real_break = _break_points(num, den)
137+
kmax = _k_max(num, den, real_break, k_break)
138+
kvect = np.hstack((np.linspace(0, kmax, 50), np.real(k_break)))
139+
kvect.sort()
140+
mymat = _RLFindRoots(num, den, kvect)
141+
mymat = _RLSortRoots(mymat)
142+
open_loop_poles = den.roots
143+
open_loop_zeros = num.roots
144+
145+
if (open_loop_zeros.size != 0) & (open_loop_zeros.size < open_loop_poles.size):
146+
open_loop_zeros_xl = np.append(open_loop_zeros,
147+
np.ones(open_loop_poles.size - open_loop_zeros.size) * open_loop_zeros[-1])
148+
mymat_xl = np.append(mymat, open_loop_zeros_xl)
149+
else:
150+
mymat_xl = mymat
151+
152+
if xlim is None:
153+
x_tolerance = 0.05 * (np.max(np.max(np.real(mymat_xl))) - np.min(np.min(np.real(mymat_xl))))
154+
xlim = _ax_lim(mymat_xl)
155+
else:
156+
x_tolerance = 0.05 * (xlim[1] - xlim[0])
157+
if ylim is None:
158+
y_tolerance = 0.05 * (np.max(np.max(np.imag(mymat_xl))) - np.min(np.min(np.imag(mymat_xl))))
159+
ylim = _ax_lim(mymat_xl * 1j)
160+
else:
161+
y_tolerance = 0.05 * (ylim[1] - ylim[0])
162+
163+
tolerance = np.max([x_tolerance, y_tolerance])
164+
distance_points = np.abs(np.diff(mymat, axis=0))
165+
indexes_too_far = np.where(distance_points > tolerance)
166+
while (indexes_too_far[0].size > 0) & (kvect.size < 5000):
167+
for index in indexes_too_far[0]:
168+
new_gains = np.linspace(kvect[index], kvect[index+1], 5)
169+
new_points = _RLFindRoots(num, den, new_gains[1:4])
170+
kvect = np.insert(kvect, index+1, new_gains[1:4])
171+
mymat = np.insert(mymat, index+1, new_points, axis=0)
172+
mymat = _RLSortRoots(mymat)
173+
distance_points = np.abs(np.diff(mymat, axis=0))
174+
indexes_too_far = np.where(distance_points > tolerance)
175+
new_gains = np.hstack((np.logspace(np.log10(kvect[-1]), np.log10(kvect[-1]*200), 10)))
176+
new_points = _RLFindRoots(num, den, new_gains[1:10])
177+
kvect = np.append(kvect, new_gains[1:10])
178+
mymat = np.concatenate((mymat, new_points), axis=0)
179+
mymat = _RLSortRoots(mymat)
180+
return kvect, mymat, xlim, ylim
181+
182+
183+
def _break_points(num, den):
184+
"""Extract break points over real axis and the gains give these location"""
185+
# type: (np.poly1d, np.poly1d) -> (np.array, np.array)
186+
dnum = num.deriv(m=1)
187+
dden = den.deriv(m=1)
188+
polynom = den * dnum - num * dden
189+
real_break_pts = polynom.r
190+
real_break_pts = real_break_pts[num(real_break_pts) != 0] # don't care about infinite break points
191+
k_break = -den(real_break_pts) / num(real_break_pts)
192+
idx = k_break >= 0 # only positives gains
193+
k_break = k_break[idx]
194+
real_break_pts = real_break_pts[idx]
195+
return k_break, real_break_pts
196+
197+
198+
def _ax_lim(mymat):
199+
"""Utility to get the axis limits"""
200+
axmin = np.min(np.min(np.real(mymat)))
201+
axmax = np.max(np.max(np.real(mymat)))
202+
if axmax != axmin:
203+
deltax = (axmax - axmin) * 0.02
204+
else:
205+
deltax = np.max([1., axmax / 2])
206+
axlim = [axmin - deltax, axmax + deltax]
207+
return axlim
208+
209+
210+
def _k_max(num, den, real_break_points, k_break_points):
211+
"""" Calculation the maximum gain for the root locus shown in tne figure"""
212+
asymp_number = den.order - num.order
213+
singular_points = np.concatenate((num.roots, den.roots), axis=0)
214+
important_points = np.concatenate((singular_points, real_break_points), axis=0)
215+
216+
if asymp_number > 0:
217+
asymp_center = (np.sum(den.roots) - np.sum(num.roots))/asymp_number
218+
distance_max = 2 * np.max(np.abs(important_points - asymp_center))
219+
asymp_angles = (2 * np.arange(0, asymp_number)-1) * np.pi / asymp_number
220+
farthest_points = asymp_center + distance_max * np.exp(asymp_angles * 1j) # farthest points over asymptotes
221+
kmax_asymp = -den(farthest_points) / num(farthest_points)
222+
else:
223+
farthest_points = 2 * np.max(np.abs(important_points))
224+
kmax_asymp = -den(farthest_points) / num(farthest_points)
225+
if kmax_asymp == 0:
226+
kmax_asymp = -den(1) / num(1)
227+
kmax = np.max(np.concatenate((np.real(kmax_asymp), k_break_points), axis=0))
228+
return kmax
229+
230+
138231
def _systopoly1d(sys):
139232
"""Extract numerator and denominator polynomails for a system"""
140233
# Allow inputs from the signal processing toolbox
@@ -163,11 +256,10 @@ def _systopoly1d(sys):
163256
return (nump, denp)
164257

165258

166-
def _RLFindRoots(sys, kvect):
259+
def _RLFindRoots(nump, denp, kvect):
167260
"""Find the roots for the root locus."""
168261

169262
# Convert numerator and denominator to polynomials if they aren't
170-
(nump, denp) = _systopoly1d(sys)
171263

172264
roots = []
173265
for k in kvect:
@@ -179,7 +271,7 @@ def _RLFindRoots(sys, kvect):
179271
return mymat
180272

181273

182-
def _RLSortRoots(sys, mymat):
274+
def _RLSortRoots(mymat):
183275
"""Sort the roots from sys._RLFindRoots, so that the root
184276
locus doesn't show weird pseudo-branches as roots jump from
185277
one branch to another."""

0 commit comments

Comments
 (0)