Skip to content

Commit c008e22

Browse files
authored
Merge 33c59a8 into 4b0101c
2 parents 4b0101c + 33c59a8 commit c008e22

File tree

2 files changed

+123
-13
lines changed

2 files changed

+123
-13
lines changed

control/statefbk.py

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,32 @@ def place(A, B, p):
113113
return K
114114

115115

116-
def place_varga(A, B, p):
116+
def place_varga(A, B, p, dtime=False, alpha=None):
117117
"""Place closed loop eigenvalues
118-
K = place_varga(A, B, p)
118+
K = place_varga(A, B, p, dtime=False, alpha=None)
119119
120-
Parameters
120+
Required Parameters
121121
----------
122122
A : 2-d array
123123
Dynamics matrix
124124
B : 2-d array
125125
Input matrix
126126
p : 1-d list
127127
Desired eigenvalue locations
128+
129+
Optional Parameters
130+
---------------
131+
dtime: False for continuous time pole placement or True for discrete time.
132+
The default is dtime=False.
133+
alpha: double scalar
134+
If DICO='C', then place_varga will leave the eigenvalues with real
135+
real part less than alpha untouched.
136+
If DICO='D', the place_varga will leave eigenvalues with modulus
137+
less than alpha untouched.
138+
139+
By default (alpha=None), place_varga computes alpha such that all
140+
poles will be placed.
141+
128142
Returns
129143
-------
130144
K : 2-d array
@@ -146,7 +160,7 @@ def place_varga(A, B, p):
146160
--------
147161
>>> A = [[-1, -1], [0, 1]]
148162
>>> B = [[0], [1]]
149-
>>> K = place(A, B, [-2, -5])
163+
>>> K = place_varga(A, B, [-2, -5])
150164
151165
See Also:
152166
--------
@@ -160,24 +174,46 @@ def place_varga(A, B, p):
160174
raise ControlSlycot("can't find slycot module 'sb01bd'")
161175

162176
# Convert the system inputs to NumPy arrays
163-
A_mat = np.array(A);
164-
B_mat = np.array(B);
177+
A_mat = np.array(A)
178+
B_mat = np.array(B)
165179
if (A_mat.shape[0] != A_mat.shape[1] or
166180
A_mat.shape[0] != B_mat.shape[0]):
167181
raise ControlDimension("matrix dimensions are incorrect")
168182

169183
# Compute the system eigenvalues and convert poles to numpy array
170184
system_eigs = np.linalg.eig(A_mat)[0]
171-
placed_eigs = np.array(p);
185+
placed_eigs = np.array(p)
186+
187+
# Need a character parameter for SB01BD
188+
if dtime:
189+
DICO = 'D'
190+
else:
191+
DICO = 'C'
192+
193+
if alpha is None:
194+
# SB01BD ignores eigenvalues with real part less than alpha
195+
# (if DICO='C') or with modulus less than alpha
196+
# (if DICO = 'D').
197+
if dtime:
198+
# For discrete time, slycot only cares about modulus, so just make
199+
# alpha the smallest it can be.
200+
alpha = 0.0
201+
else:
202+
# Choosing alpha=min_eig is insufficient and can lead to an
203+
# error or not having all the eigenvalues placed that we wanted.
204+
# Evidently, what python thinks are the eigs is not precisely
205+
# the same as what slicot thinks are the eigs. So we need some
206+
# numerical breathing room. The following is pretty heuristic,
207+
# but does the trick
208+
alpha = -2*abs(min(system_eigs.real))
209+
elif dtime and alpha < 0.0:
210+
raise ValueError("Need alpha > 0 when DICO='D'")
172211

173-
# SB01BD sets eigenvalues with real part less than alpha
174-
# We want to place all poles of the system => set alpha to minimum
175-
alpha = min(system_eigs.real);
176212

177213
# Call SLICOT routine to place the eigenvalues
178214
A_z,w,nfp,nap,nup,F,Z = \
179215
sb01bd(B_mat.shape[0], B_mat.shape[1], len(placed_eigs), alpha,
180-
A_mat, B_mat, placed_eigs, 'C');
216+
A_mat, B_mat, placed_eigs, DICO)
181217

182218
# Return the gain matrix, with MATLAB gain convention
183219
return -F

control/tests/statefbk_test.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from __future__ import print_function
77
import unittest
88
import numpy as np
9-
from control.statefbk import ctrb, obsv, place, lqr, gram, acker
9+
from control.statefbk import ctrb, obsv, place, place_varga, lqr, gram, acker
1010
from control.matlab import *
1111
from control.exception import slycot_check, ControlDimension
1212

@@ -186,7 +186,10 @@ def testPlace(self):
186186
np.testing.assert_raises(ValueError, place, A, B, P_repeated)
187187

188188
@unittest.skipIf(not slycot_check(), "slycot not installed")
189-
def testPlace_varga(self):
189+
def testPlace_varga_continuous(self):
190+
"""
191+
Check that we can place eigenvalues for dtime=False
192+
"""
190193
A = np.array([[1., -2.], [3., -4.]])
191194
B = np.array([[5.], [7.]])
192195

@@ -202,6 +205,77 @@ def testPlace_varga(self):
202205
np.testing.assert_raises(ControlDimension, place, A[1:, :], B, P)
203206
np.testing.assert_raises(ControlDimension, place, A, B[1:, :], P)
204207

208+
# Regression test against bug #177
209+
# https://github.com/python-control/python-control/issues/177
210+
A = np.array([[0, 1], [100, 0]])
211+
B = np.array([[0], [1]])
212+
P = np.array([-20 + 10*1j, -20 - 10*1j])
213+
K = place_varga(A, B, P)
214+
P_placed = np.linalg.eigvals(A - B.dot(K))
215+
216+
# No guarantee of the ordering, so sort them
217+
P.sort()
218+
P_placed.sort()
219+
np.testing.assert_array_almost_equal(P, P_placed)
220+
221+
@unittest.skipIf(not slycot_check(), "slycot not installed")
222+
def testPlace_varga_continuous_partial_eigs(self):
223+
"""
224+
Check that we are able to use the alpha parameter to only place
225+
a subset of the eigenvalues, for the continous time case.
226+
"""
227+
# A matrix has eigenvalues at s=-1, and s=-2. Choose alpha = -1.5
228+
# and check that eigenvalue at s=-2 stays put.
229+
A = np.array([[1., -2.], [3., -4.]])
230+
B = np.array([[5.], [7.]])
231+
232+
P = np.array([-3.])
233+
P_expected = np.array([-2.0, -3.0])
234+
alpha = -1.5
235+
K = place_varga(A, B, P, alpha=alpha)
236+
237+
P_placed = np.linalg.eigvals(A - B.dot(K))
238+
# No guarantee of the ordering, so sort them
239+
P_expected.sort()
240+
P_placed.sort()
241+
np.testing.assert_array_almost_equal(P_expected, P_placed)
242+
243+
@unittest.skipIf(not slycot_check(), "slycot not installed")
244+
def testPlace_varga_discrete(self):
245+
"""
246+
Check that we can place poles using dtime=True (discrete time)
247+
"""
248+
A = np.array([[1., 0], [0, 0.5]])
249+
B = np.array([[5.], [7.]])
250+
251+
P = np.array([0.5, 0.5])
252+
K = place_varga(A, B, P, dtime=True)
253+
P_placed = np.linalg.eigvals(A - B.dot(K))
254+
# No guarantee of the ordering, so sort them
255+
P.sort()
256+
P_placed.sort()
257+
np.testing.assert_array_almost_equal(P, P_placed)
258+
259+
@unittest.skipIf(not slycot_check(), "slycot not installed")
260+
def testPlace_varga_discrete_partial_eigs(self):
261+
""""
262+
Check that we can only assign a single eigenvalue in the discrete
263+
time case.
264+
"""
265+
# A matrix has eigenvalues at 1.0 and 0.5. Set alpha = 0.51, and
266+
# check that the eigenvalue at 0.5 is not moved.
267+
A = np.array([[1., 0], [0, 0.5]])
268+
B = np.array([[5.], [7.]])
269+
P = np.array([0.2, 0.6])
270+
P_expected = np.array([0.5, 0.6])
271+
alpha = 0.51
272+
K = place_varga(A, B, P, dtime=True, alpha=alpha)
273+
P_placed = np.linalg.eigvals(A - B.dot(K))
274+
P_expected.sort()
275+
P_placed.sort()
276+
np.testing.assert_array_almost_equal(P_expected, P_placed)
277+
278+
205279
def check_LQR(self, K, S, poles, Q, R):
206280
S_expected = np.array(np.sqrt(Q * R))
207281
K_expected = S_expected / R

0 commit comments

Comments
 (0)