Skip to content

Commit 24eb89b

Browse files
committed
Fix python-control#45 - make gain vector optional in root_locus
1 parent b7e7ae9 commit 24eb89b

File tree

4 files changed

+51
-33
lines changed

4 files changed

+51
-33
lines changed

control/matlab.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,11 +1110,8 @@ def rlocus(sys, klist = None, **keywords):
11101110
----------
11111111
sys: StateSpace or TransferFunction
11121112
Linear system
1113-
klist:
1113+
klist: iterable, optional
11141114
optional list of gains
1115-
1116-
Keyword parameters
1117-
------------------
11181115
xlim : control of x-axis range, normally with tuple, for
11191116
other options, see matplotlib.axes
11201117
ylim : control of y-axis range
@@ -1132,12 +1129,8 @@ def rlocus(sys, klist = None, **keywords):
11321129
list of gains used to compute roots
11331130
"""
11341131
from .rlocus import root_locus
1135-
#! TODO: update with a smart calculation of the gains using sys poles/zeros
1136-
if klist == None:
1137-
klist = logspace(-3, 3)
11381132

1139-
rlist = root_locus(sys, klist, **keywords)
1140-
return rlist, klist
1133+
return root_locus(sys, klist, **keywords)
11411134

11421135
def margin(*args):
11431136
"""Calculate gain and phase margins and associated crossover frequencies

control/rlocus.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,43 +46,51 @@
4646
# $Id$
4747

4848
# Packages used by this module
49+
import numpy as np
4950
from scipy import array, poly1d, row_stack, zeros_like, real, imag
5051
import scipy.signal # signal processing toolbox
5152
import pylab # plotting routines
5253
from . import xferfcn
5354
from .exception import ControlMIMONotImplemented
5455
from functools import partial
5556

56-
5757
# Main function: compute a root locus diagram
58-
def root_locus(sys, kvect, xlim=None, ylim=None, plotstr='-', Plot=True,
58+
def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
5959
PrintGain=True):
6060
"""Calculate the root locus by finding the roots of 1+k*TF(s)
6161
where TF is self.num(s)/self.den(s) and each k is an element
6262
of kvect.
6363
6464
Parameters
6565
----------
66-
sys : linsys
66+
sys : LTI object
6767
Linear input/output systems (SISO only, for now)
68-
kvect : gain_range (default = None)
68+
kvect : list or ndarray, optional
6969
List of gains to use in computing diagram
70-
xlim : control of x-axis range, normally with tuple, for
71-
other options, see matplotlib.axes
72-
ylim : control of y-axis range
73-
Plot : boolean (default = True)
70+
xlim : tuple or list, optional
71+
control of x-axis range, normally with tuple (see matplotlib.axes)
72+
ylim : tuple or list, optional
73+
control of y-axis range
74+
Plot : boolean, optional (default = True)
7475
If True, plot magnitude and phase
7576
PrintGain: boolean (default = True)
7677
If True, report mouse clicks when close to the root-locus branches,
7778
calculate gain, damping and print
78-
Return values
79-
-------------
80-
rlist : list of computed root locations
79+
80+
Returns
81+
-------
82+
rlist : ndarray
83+
Computed root locations, given as a 2d array
84+
klist : ndarray or list
85+
Gains used. Same as klist keyword argument if provided.
8186
"""
8287

8388
# Convert numerator and denominator to polynomials if they aren't
8489
(nump, denp) = _systopoly1d(sys)
8590

91+
if kvect is None:
92+
kvect = _default_gains(sys)
93+
8694
# Compute out the loci
8795
mymat = _RLFindRoots(sys, kvect)
8896
mymat = _RLSortRoots(sys, mymat)
@@ -116,8 +124,11 @@ def root_locus(sys, kvect, xlim=None, ylim=None, plotstr='-', Plot=True,
116124
ax.set_xlabel('Real')
117125
ax.set_ylabel('Imaginary')
118126

119-
return mymat
127+
return mymat, kvect
120128

129+
def _default_gains(sys):
130+
# TODO: update with a smart calculation of the gains using sys poles/zeros
131+
return np.logspace(-3, 3)
121132

122133
# Utility function to extract numerator and denominator polynomials
123134
def _systopoly1d(sys):

control/tests/matlab_test.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,10 @@ def testRlocus(self):
309309
rlocus(self.siso_ss1)
310310
rlocus(self.siso_tf1)
311311
rlocus(self.siso_tf2)
312-
rlist, klist = rlocus(self.siso_tf2, klist=[1, 10, 100], Plot=False)
312+
klist = [1, 10, 100]
313+
rlist, klist_out = rlocus(self.siso_tf2, klist=klist, Plot=False)
314+
np.testing.assert_equal(len(rlist), len(klist))
315+
np.testing.assert_array_equal(klist, klist_out)
313316

314317
def testNyquist(self):
315318
nyquist(self.siso_ss1)
@@ -618,7 +621,7 @@ def testCombi01(self):
618621
# for i in range(len(tfdata)):
619622
# np.testing.assert_array_almost_equal(tfdata_1[i], tfdata_2[i])
620623

621-
def suite():
624+
def test_suite():
622625
return unittest.TestLoader().loadTestsFromTestCase(TestMatlab)
623626

624627
if __name__ == '__main__':

control/tests/rlocus_test.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,32 @@ def setUp(self):
1717
"""This contains some random LTI systems and scalars for testing."""
1818

1919
# Two random SISO systems.
20-
self.sys1 = TransferFunction([1, 2], [1, 2, 3])
21-
self.sys2 = StateSpace([[1., 4.], [3., 2.]], [[1.], [-4.]],
20+
sys1 = TransferFunction([1, 2], [1, 2, 3])
21+
sys2 = StateSpace([[1., 4.], [3., 2.]], [[1.], [-4.]],
2222
[[1., 0.]], [[0.]])
23+
self.systems = (sys1, sys2)
24+
25+
def check_cl_poles(self, sys, pole_list, k_list):
26+
for k, poles in zip(k_list, pole_list):
27+
poles_expected = np.sort(feedback(sys, k).pole())
28+
poles = np.sort(poles)
29+
np.testing.assert_array_almost_equal(poles, poles_expected)
2330

2431
def testRootLocus(self):
2532
"""Basic root locus plot"""
2633
klist = [-1, 0, 1]
27-
rlist = root_locus(self.sys1, [-1, 0, 1], Plot=False)
28-
29-
for k in klist:
30-
np.testing.assert_array_almost_equal(
31-
np.sort(rlist[k]),
32-
np.sort(feedback(self.sys1, klist[k]).pole()))
33-
34-
def suite():
34+
for sys in self.systems:
35+
roots, k_out = root_locus(sys, klist, Plot=False)
36+
np.testing.assert_equal(len(roots), len(klist))
37+
np.testing.assert_array_equal(klist, k_out)
38+
self.check_cl_poles(sys, roots, klist)
39+
40+
def test_without_gains(self):
41+
for sys in self.systems:
42+
roots, kvect = root_locus(sys, Plot=False)
43+
self.check_cl_poles(sys, roots, kvect)
44+
45+
def test_suite():
3546
return unittest.TestLoader().loadTestsFromTestCase(TestRootLocus)
3647

3748
if __name__ == "__main__":

0 commit comments

Comments
 (0)