Skip to content

Commit dd5d738

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 601b581 commit dd5d738

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
@@ -112,18 +112,32 @@ def place(A, B, p):
112112
return K
113113

114114

115-
def place_varga(A, B, p):
115+
def place_varga(A, B, p, DICO='C', alpha=None):
116116
"""Place closed loop eigenvalues
117-
K = place_varga(A, B, p)
117+
K = place_varga(A, B, p, DICO='C', alpha=None)
118118
119-
Parameters
119+
Required Parameters
120120
----------
121121
A : 2-d array
122122
Dynamics matrix
123123
B : 2-d array
124124
Input matrix
125125
p : 1-d list
126126
Desired eigenvalue locations
127+
128+
Optional Parameters
129+
---------------
130+
DICO : 'C' for continuous time pole placement or 'D' for discrete time.
131+
The default is DICO='C'.
132+
alpha: double scalar
133+
If DICO='C', then place_varga will leave the eigenvalues with real
134+
real part less than alpha untouched.
135+
If DICO='D', the place_varga will leave eigenvalues with modulus
136+
less than alpha untouched.
137+
138+
By default (alpha=None), place_varga computes alpha such that all
139+
poles will be placed.
140+
127141
Returns
128142
-------
129143
K : 2-d array
@@ -159,24 +173,39 @@ def place_varga(A, B, p):
159173
raise ControlSlycot("can't find slycot module 'sb01bd'")
160174

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

168182
# Compute the system eigenvalues and convert poles to numpy array
169183
system_eigs = np.linalg.eig(A_mat)[0]
170-
placed_eigs = np.array(p);
184+
placed_eigs = np.array(p)
171185

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

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

181210
# Return the gain matrix, with MATLAB gain convention
182211
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)