Skip to content

Commit 9a42fc0

Browse files
committed
Add discrete time support to place_varga(). Also, fix issue #177 by changing how the alpha parameter is computed. In additional, expose alpha to the user an optional parameter
1 parent 4b0101c commit 9a42fc0

2 files changed

Lines changed: 112 additions & 12 deletions

File tree

control/statefbk.py

Lines changed: 39 additions & 10 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, DICO='C', alpha=None):
117117
"""Place closed loop eigenvalues
118-
K = place_varga(A, B, p)
118+
K = place_varga(A, B, p, DICO='C', 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+
DICO : 'C' for continuous time pole placement or 'D' for discrete time.
132+
The default is DICO='C'.
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
@@ -160,24 +174,39 @@ 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)
172186

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);
187+
if alpha is None:
188+
# SB01BD ignores eigenvalues with real part less than alpha
189+
# (if DICO='C') or with modulus less than alpha
190+
# (if DICO = 'D').
191+
if DICO == 'C':
192+
# Choosing alpha=min_eig is insufficient and can lead to an
193+
# error or not having all the eigenvalues placed that we wanted.
194+
# Evidently, what python thinks are the eigs is not precisely
195+
# the same as what slicot thinks are the eigs. So we need some
196+
# numerical breathing room. The following is pretty heuristic,
197+
# but does the trick
198+
alpha = -2*abs(min(system_eigs.real))
199+
elif DICO == 'D':
200+
# For discrete time, slycot only cares about modulus, so just make
201+
# alpha the smallest it can be.
202+
alpha = 0.0
203+
elif DICO == 'D' and alpha < 0.0:
204+
raise ValueError("Need alpha > 0 when DICO='D'")
176205

177206
# Call SLICOT routine to place the eigenvalues
178207
A_z,w,nfp,nap,nup,F,Z = \
179208
sb01bd(B_mat.shape[0], B_mat.shape[1], len(placed_eigs), alpha,
180-
A_mat, B_mat, placed_eigs, 'C');
209+
A_mat, B_mat, placed_eigs, DICO)
181210

182211
# Return the gain matrix, with MATLAB gain convention
183212
return -F

control/tests/statefbk_test.py

Lines changed: 73 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 DICO='C'
192+
"""
190193
A = np.array([[1., -2.], [3., -4.]])
191194
B = np.array([[5.], [7.]])
192195

@@ -202,6 +205,74 @@ 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+
def testPlace_varga_continuous_partial_eigs(self):
222+
"""
223+
Check that we are able to use the alpha parameter to only place
224+
a subset of the eigenvalues, for the continous time case.
225+
"""
226+
# A matrix has eigenvalues at s=-1, and s=-2. Choose alpha = -1.5
227+
# and check that eigenvalue at s=-2 stays put.
228+
A = np.array([[1., -2.], [3., -4.]])
229+
B = np.array([[5.], [7.]])
230+
231+
P = np.array([-3.])
232+
P_expected = np.array([-2.0, -3.0])
233+
alpha = -1.5
234+
K = place_varga(A, B, P, alpha=alpha)
235+
236+
P_placed = np.linalg.eigvals(A - B.dot(K))
237+
# No guarantee of the ordering, so sort them
238+
P_expected.sort()
239+
P_placed.sort()
240+
np.testing.assert_array_almost_equal(P_expected, P_placed)
241+
242+
def testPlace_varga_discrete(self):
243+
"""
244+
Check that we can place poles using DICO='D' (discrete time)
245+
"""
246+
A = np.array([[1., 0], [0, 0.5]])
247+
B = np.array([[5.], [7.]])
248+
249+
P = np.array([0.5, 0.5])
250+
K = place_varga(A, B, P, DICO='D')
251+
P_placed = np.linalg.eigvals(A - B.dot(K))
252+
# No guarantee of the ordering, so sort them
253+
P.sort()
254+
P_placed.sort()
255+
np.testing.assert_array_almost_equal(P, P_placed)
256+
257+
def testPlace_varga_discrete_partial_eigs(self):
258+
""""
259+
Check that we can only assign a single eigenvalue in the discrete
260+
time case.
261+
"""
262+
# A matrix has eigenvalues at 1.0 and 0.5. Set alpha = 0.51, and
263+
# check that the eigenvalue at 0.5 is not moved.
264+
A = np.array([[1., 0], [0, 0.5]])
265+
B = np.array([[5.], [7.]])
266+
P = np.array([0.2, 0.6])
267+
P_expected = np.array([0.5, 0.6])
268+
alpha = 0.51
269+
K = place_varga(A, B, P, DICO='D', alpha=alpha)
270+
P_placed = np.linalg.eigvals(A - B.dot(K))
271+
P_expected.sort()
272+
P_placed.sort()
273+
np.testing.assert_array_almost_equal(P_expected, P_placed)
274+
275+
205276
def check_LQR(self, K, S, poles, Q, R):
206277
S_expected = np.array(np.sqrt(Q * R))
207278
K_expected = S_expected / R

0 commit comments

Comments
 (0)