Skip to content

Commit 7c5f28d

Browse files
authored
Merge pull request #176 from rabraker/update_place
Update place to use scipy.signal.place_poles
2 parents df4ee82 + 70ecf75 commit 7c5f28d

File tree

3 files changed

+137
-5
lines changed

3 files changed

+137
-5
lines changed

control/statefbk.py

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,13 @@
4545
from . import statesp
4646
from .exception import ControlSlycot, ControlArgument, ControlDimension
4747

48-
__all__ = ['ctrb', 'obsv', 'gram', 'place', 'lqr', 'acker']
48+
__all__ = ['ctrb', 'obsv', 'gram', 'place', 'place_varga', 'lqr', 'acker']
49+
4950

5051
# Pole placement
5152
def place(A, B, p):
5253
"""Place closed loop eigenvalues
53-
54+
K = place(A, B, p)
5455
Parameters
5556
----------
5657
A : 2-d array
@@ -63,13 +64,92 @@ def place(A, B, p):
6364
Returns
6465
-------
6566
K : 2-d array
66-
Gains such that A - B K has given eigenvalues
67+
Gain such that A - B K has eigenvalues given in p
68+
69+
Algorithm
70+
---------
71+
This is a wrapper function for scipy.signal.place_poles, which
72+
implements the Tits and Yang algorithm [1]. It will handle SISO,
73+
MISO, and MIMO systems. If you want more control over the algorithm,
74+
use scipy.signal.place_poles directly.
75+
76+
[1] A.L. Tits and Y. Yang, "Globally convergent algorithms for robust
77+
pole assignment by state feedback, IEEE Transactions on Automatic
78+
Control, Vol. 41, pp. 1432-1452, 1996.
79+
80+
Limitations
81+
-----------
82+
The algorithm will not place poles at the same location more
83+
than rank(B) times.
6784
6885
Examples
6986
--------
7087
>>> A = [[-1, -1], [0, 1]]
7188
>>> B = [[0], [1]]
7289
>>> K = place(A, B, [-2, -5])
90+
91+
See Also
92+
--------
93+
place_varga, acker
94+
"""
95+
from scipy.signal import place_poles
96+
97+
# Convert the system inputs to NumPy arrays
98+
A_mat = np.array(A)
99+
B_mat = np.array(B)
100+
if (A_mat.shape[0] != A_mat.shape[1]):
101+
raise ControlDimension("A must be a square matrix")
102+
103+
if (A_mat.shape[0] != B_mat.shape[0]):
104+
err_str = "The number of rows of A must equal the number of rows in B"
105+
raise ControlDimension(err_str)
106+
107+
# Convert desired poles to numpy array
108+
placed_eigs = np.array(p)
109+
110+
result = place_poles(A_mat, B_mat, placed_eigs, method='YT')
111+
K = result.gain_matrix
112+
return K
113+
114+
115+
def place_varga(A, B, p):
116+
"""Place closed loop eigenvalues
117+
K = place_varga(A, B, p)
118+
119+
Parameters
120+
----------
121+
A : 2-d array
122+
Dynamics matrix
123+
B : 2-d array
124+
Input matrix
125+
p : 1-d list
126+
Desired eigenvalue locations
127+
Returns
128+
-------
129+
K : 2-d array
130+
Gain such that A - B K has eigenvalues given in p.
131+
132+
133+
Algorithm
134+
---------
135+
This function is a wrapper for the slycot function sb01bd, which
136+
implements the pole placement algorithm of Varga [1]. In contrast to
137+
the algorithm used by place(), the Varga algorithm can place
138+
multiple poles at the same location. The placement, however, may not
139+
be as robust.
140+
141+
[1] Varga A. "A Schur method for pole assignment."
142+
IEEE Trans. Automatic Control, Vol. AC-26, pp. 517-519, 1981.
143+
144+
Examples
145+
--------
146+
>>> A = [[-1, -1], [0, 1]]
147+
>>> B = [[0], [1]]
148+
>>> K = place(A, B, [-2, -5])
149+
150+
See Also:
151+
--------
152+
place, acker
73153
"""
74154

75155
# Make sure that SLICOT is installed

control/tests/matlab_test.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,15 @@ def testModred(self):
401401
modred(self.siso_ss3, [1], 'truncate')
402402

403403
@unittest.skipIf(not slycot_check(), "slycot not installed")
404+
def testPlace_varga(self):
405+
place_varga(self.siso_ss1.A, self.siso_ss1.B, [-2, -2])
406+
404407
def testPlace(self):
405-
place(self.siso_ss1.A, self.siso_ss1.B, [-2, -2])
408+
place(self.siso_ss1.A, self.siso_ss1.B, [-2, -2.5])
409+
410+
def testAcker(self):
411+
acker(self.siso_ss1.A, self.siso_ss1.B, [-2, -2.5])
412+
406413

407414
@unittest.skipIf(not slycot_check(), "slycot not installed")
408415
def testLQR(self):

control/tests/statefbk_test.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import numpy as np
99
from control.statefbk import ctrb, obsv, place, lqr, gram, acker
1010
from control.matlab import *
11-
from control.exception import slycot_check
11+
from control.exception import slycot_check, ControlDimension
1212

1313
class TestStatefbk(unittest.TestCase):
1414
"""Test state feedback functions"""
@@ -157,6 +157,51 @@ def testAcker(self):
157157
np.testing.assert_array_almost_equal(np.sort(poles),
158158
np.sort(placed), decimal=4)
159159

160+
def testPlace(self):
161+
# Matrices shamelessly stolen from scipy example code.
162+
A = np.array([[1.380, -0.2077, 6.715, -5.676],
163+
[-0.5814, -4.290, 0, 0.6750],
164+
[1.067, 4.273, -6.654, 5.893],
165+
[0.0480, 4.273, 1.343, -2.104]])
166+
167+
B = np.array([[0, 5.679],
168+
[1.136, 1.136],
169+
[0, 0,],
170+
[-3.146, 0]])
171+
P = np.array([-0.5+1j, -0.5-1j, -5.0566, -8.6659])
172+
K = place(A, B, P)
173+
P_placed = np.linalg.eigvals(A - B.dot(K))
174+
# No guarantee of the ordering, so sort them
175+
P.sort()
176+
P_placed.sort()
177+
np.testing.assert_array_almost_equal(P, P_placed)
178+
179+
# Test that the dimension checks work.
180+
np.testing.assert_raises(ControlDimension, place, A[1:, :], B, P)
181+
np.testing.assert_raises(ControlDimension, place, A, B[1:, :], P)
182+
183+
# Check that we get an error if we ask for too many poles in the same
184+
# location. Here, rank(B) = 2, so lets place three at the same spot.
185+
P_repeated = np.array([-0.5, -0.5, -0.5, -8.6659])
186+
np.testing.assert_raises(ValueError, place, A, B, P_repeated)
187+
188+
@unittest.skipIf(not slycot_check(), "slycot not installed")
189+
def testPlace_varga(self):
190+
A = np.array([[1., -2.], [3., -4.]])
191+
B = np.array([[5.], [7.]])
192+
193+
P = np.array([-2., -2.])
194+
K = place_varga(A, B, P)
195+
P_placed = np.linalg.eigvals(A - B.dot(K))
196+
# No guarantee of the ordering, so sort them
197+
P.sort()
198+
P_placed.sort()
199+
np.testing.assert_array_almost_equal(P, P_placed)
200+
201+
# Test that the dimension checks work.
202+
np.testing.assert_raises(ControlDimension, place, A[1:, :], B, P)
203+
np.testing.assert_raises(ControlDimension, place, A, B[1:, :], P)
204+
160205
def check_LQR(self, K, S, poles, Q, R):
161206
S_expected = np.array(np.sqrt(Q * R))
162207
K_expected = S_expected / R

0 commit comments

Comments
 (0)