Skip to content

Commit fbc58ec

Browse files
committed
add return_type keyword to functions that previously returned np.matrix
1 parent 08f1c51 commit fbc58ec

File tree

4 files changed

+127
-41
lines changed

4 files changed

+127
-41
lines changed

control/modelsimp.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@
4545

4646
# External packages and modules
4747
import numpy as np
48+
import warnings
4849
from .exception import ControlSlycot
4950
from .lti import isdtime, isctime
50-
from .statesp import StateSpace, ssmatrix
51+
from .statesp import StateSpace
5152
from .statefbk import gram
5253

5354
__all__ = ['hsvd', 'balred', 'modred', 'era', 'markov', 'minreal']
@@ -56,7 +57,7 @@
5657
# The following returns the Hankel singular values, which are singular values
5758
#of the matrix formed by multiplying the controllability and observability
5859
#grammians
59-
def hsvd(sys):
60+
def hsvd(sys, return_type=np.matrix):
6061
"""Calculate the Hankel singular values.
6162
6263
Parameters
@@ -66,9 +67,12 @@ def hsvd(sys):
6667
6768
Returns
6869
-------
69-
H : Matrix
70+
H : matrix
7071
A list of Hankel singular values
7172
73+
return_type: nparray subtype, optional (default = numpy.matrix)
74+
Set the ndarray subtype for the return value
75+
7276
See Also
7377
--------
7478
gram
@@ -86,6 +90,12 @@ def hsvd(sys):
8690
>>> H = hsvd(sys)
8791
8892
"""
93+
# If return_type is np.matrix, issue a pending deprecation warning
94+
if (return_type is np.matrix):
95+
warnings.warn("Returning numpy.matrix, soon to be deprecated; "
96+
"make sure calling code can handle nparray.",
97+
stacklevel=2)
98+
8999
# TODO: implement for discrete time systems
90100
if (isdtime(sys, strict=True)):
91101
raise NotImplementedError("Function not implemented in discrete time")
@@ -96,11 +106,12 @@ def hsvd(sys):
96106
w, v = np.linalg.eig(WoWc)
97107

98108
hsv = np.sqrt(w)
99-
hsv = np.array(hsv, ndmin=2) # was np.matrix(hsv)
109+
hsv = np.array(hsv, ndmin=2) # was np.matrix(hsv)
100110
hsv = np.sort(hsv)
101111
hsv = np.fliplr(hsv)
102-
# Return the Hankel singular values
103-
return hsv
112+
113+
# Return the Hankel singular values (casting type, if needed)
114+
return hsv.view(type=return_type)
104115

105116
def modred(sys, ELIM, method='matchdc'):
106117
"""

control/statefbk.py

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
# External packages and modules
4343
import numpy as np
4444
import scipy as sp
45+
import warnings
4546
from . import statesp
4647
from .exception import ControlSlycot, ControlArgument, ControlDimension
4748

@@ -219,7 +220,7 @@ def place_varga(A, B, p, dtime=False, alpha=None):
219220
return -F
220221

221222
# Contributed by Roberto Bucher <roberto.bucher@supsi.ch>
222-
def acker(A, B, poles):
223+
def acker(A, B, poles, return_type=np.matrix):
223224
"""Pole placement using Ackermann method
224225
225226
Call:
@@ -238,12 +239,18 @@ def acker(A, B, poles):
238239
Gains such that A - B K has given eigenvalues
239240
240241
"""
242+
# If return_type is np.matrix, issue a pending deprecation warning
243+
if (return_type is np.matrix):
244+
warnings.warn("Returning numpy.matrix, soon to be deprecated; "
245+
"make sure calling code can handle nparray.",
246+
stacklevel=2)
247+
241248
# Convert the inputs to matrices (arrays)
242-
a = np.array(A)
243-
b = np.array(B)
249+
a = statesp.ssmatrix(A)
250+
b = statesp.ssmatrix(B)
244251

245252
# Make sure the system is controllable
246-
ct = ctrb(A, B)
253+
ct = ctrb(A, B, return_type=np.ndarray)
247254
if np.linalg.matrix_rank(ct) != a.shape[0]:
248255
raise ValueError("System not reachable; pole placement invalid")
249256

@@ -258,7 +265,7 @@ def acker(A, B, poles):
258265
K = np.linalg.solve(ct, pmat)
259266

260267
K = K[-1][:] # Extract the last row
261-
return K
268+
return K.view(type=return_type)
262269

263270
def lqr(*args, **keywords):
264271
"""lqr(A, B, Q, R[, N])
@@ -368,14 +375,18 @@ def lqr(*args, **keywords):
368375

369376
return K, S, E
370377

371-
def ctrb(A,B):
378+
379+
def ctrb(A, B, return_type=np.matrix):
372380
"""Controllabilty matrix
373381
374382
Parameters
375383
----------
376384
A, B: array_like or string
377385
Dynamics and input matrix of the system
378386
387+
return_type: nparray subtype, optional (default = numpy.matrix)
388+
Set the ndarray subtype for the return value
389+
379390
Returns
380391
-------
381392
C: matrix
@@ -386,25 +397,36 @@ def ctrb(A,B):
386397
>>> C = ctrb(A, B)
387398
388399
"""
400+
# If return_type is np.matrix, issue a pending deprecation warning
401+
if (return_type is np.matrix):
402+
warnings.warn("Returning numpy.matrix, soon to be deprecated; "
403+
"make sure calling code can handle nparray.",
404+
stacklevel=2)
389405

390406
# Convert input parameters to matrices (if they aren't already)
391-
amat = np.array(A)
392-
bmat = np.array(B)
407+
amat = statesp.ssmatrix(A)
408+
bmat = statesp.ssmatrix(B)
393409
n = np.shape(amat)[0]
394410
# Construct the controllability matrix
395411
ctrb = bmat
396412
for i in range(1, n):
397413
ctrb = np.hstack((ctrb, np.dot(np.linalg.matrix_power(amat, i), bmat)))
398-
return ctrb
399414

400-
def obsv(A, C):
415+
# Return the observability matrix in the desired type
416+
return ctrb.view(type=return_type)
417+
418+
419+
def obsv(A, C, return_type=np.matrix):
401420
"""Observability matrix
402421
403422
Parameters
404423
----------
405424
A, C: array_like or string
406425
Dynamics and output matrix of the system
407426
427+
return_type: nparray subtype, optional (default = numpy.matrix)
428+
Set the ndarray subtype for the return value
429+
408430
Returns
409431
-------
410432
O: matrix
@@ -414,20 +436,28 @@ def obsv(A, C):
414436
--------
415437
>>> O = obsv(A, C)
416438
417-
"""
439+
"""
440+
# If return_type is np.matrix, issue a pending deprecation warning
441+
if (return_type is np.matrix):
442+
warnings.warn("Returning numpy.matrix, soon to be deprecated; "
443+
"make sure calling code can handle nparray.",
444+
stacklevel=2)
418445

419446
# Convert input parameters to matrices (if they aren't already)
420-
amat = np.array(A)
421-
cmat = np.array(C)
447+
amat = statesp.ssmatrix(A)
448+
cmat = statesp.ssmatrix(C)
422449
n = np.shape(amat)[0]
423450

424451
# Construct the controllability matrix
425452
obsv = cmat
426453
for i in range(1, n):
427454
obsv = np.vstack((obsv, np.dot(cmat, np.linalg.matrix_power(amat, i))))
428-
return obsv
429455

430-
def gram(sys,type):
456+
# Return the observability matrix in the desired type
457+
return obsv.view(type=return_type)
458+
459+
460+
def gram(sys, type, return_type=np.matrix):
431461
"""Gramian (controllability or observability)
432462
433463
Parameters
@@ -436,8 +466,9 @@ def gram(sys,type):
436466
State-space system to compute Gramian for
437467
type: String
438468
Type of desired computation.
439-
`type` is either 'c' (controllability) or 'o' (observability). To compute the
440-
Cholesky factors of gramians use 'cf' (controllability) or 'of' (observability)
469+
`type` is either 'c' (controllability) or 'o' (observability). To
470+
compute the Cholesky factors of gramians use 'cf' (controllability) or
471+
'of' (observability)
441472
442473
Returns
443474
-------
@@ -463,6 +494,11 @@ def gram(sys,type):
463494
>>> Ro = gram(sys,'of'), where Wo=Ro'*Ro
464495
465496
"""
497+
# If return_type is np.matrix, issue a pending deprecation warning
498+
if (return_type is np.matrix):
499+
warnings.warn("Returning numpy.matrix, soon to be deprecated; "
500+
"make sure calling code can handle nparray.",
501+
stacklevel=2)
466502

467503
#Check for ss system object
468504
if not isinstance(sys,statesp.StateSpace):
@@ -501,7 +537,7 @@ def gram(sys,type):
501537
A = np.array(sys.A) # convert to NumPy array for slycot
502538
X,scale,sep,ferr,w = sb03md(n, C, A, U, dico, job='X', fact='N', trana=tra)
503539
gram = X
504-
return gram
540+
return gram.view(type=return_type)
505541

506542
elif type=='cf' or type=='of':
507543
#Compute cholesky factored gramian from slycot routine sb03od
@@ -524,4 +560,4 @@ def gram(sys,type):
524560
C[0:n,0:m] = sys.C.transpose()
525561
X,scale,w = sb03od(n, m, A, Q, C.transpose(), dico, fact='N', trans=tra)
526562
gram = X
527-
return gram
563+
return gram.view(type=return_type)

control/tests/modelsimp_test.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import unittest
77
import numpy as np
8+
import warnings
89
from control.modelsimp import *
910
from control.matlab import *
1011
from control.exception import slycot_check
@@ -17,10 +18,20 @@ def testHSVD(self):
1718
C = np.array([[6., 8.]])
1819
D = np.array([[9.]])
1920
sys = ss(A,B,C,D)
20-
hsv = hsvd(sys)
21+
hsv = hsvd(sys, return_type=np.ndarray)
2122
hsvtrue = np.array([[24.42686, 0.5731395]]) # from MATLAB
2223
np.testing.assert_array_almost_equal(hsv, hsvtrue)
2324

25+
# Make sure default type values are correct
26+
self.assertTrue(isinstance(hsv, np.ndarray))
27+
28+
# Check that default type generates a warning
29+
# TODO: remove this check with matrix type is deprecated
30+
with warnings.catch_warnings(record=True) as w:
31+
hsv = hsvd(sys)
32+
self.assertTrue(issubclass(w[-1].category, UserWarning))
33+
self.assertTrue(isinstance(hsv, np.ndarray))
34+
2435
def testMarkov(self):
2536
U = np.array([[1.], [1.], [1.], [1.], [1.]])
2637
Y = U

0 commit comments

Comments
 (0)