Skip to content

Commit dc333a6

Browse files
committed
Create config options for using numpy.matrix as state space return type
1 parent d93ea5e commit dc333a6

4 files changed

Lines changed: 82 additions & 46 deletions

File tree

control/config.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@
77
# files. For now, you can just choose between MATLAB and FBS default
88
# values.
99

10+
import warnings
11+
import numpy as np
12+
from .statesp import StateSpaceMatrix
13+
1014
# Bode plot defaults
1115
bode_dB = False # Bode plot magnitude units
1216
bode_deg = True # Bode Plot phase units
1317
bode_Hz = False # Bode plot frequency units
1418
bode_number_of_samples = None # Bode plot number of samples
1519
bode_feature_periphery_decade = 1.0 # Bode plot feature periphery in decades
1620

21+
# State space return type (change to StateSpaceMatrix at next major revision)
22+
ss_return_type = np.matrix
23+
1724

1825
def reset_defaults():
1926
"""Reset configuration values to their default values."""
@@ -22,6 +29,7 @@ def reset_defaults():
2229
global bode_Hz; bode_Hz = False
2330
global bode_number_of_samples; bode_number_of_samples = None
2431
global bode_feature_periphery_decade; bode_feature_periphery_decade = 1.0
32+
global ss_return_type; ss_return_type = np.matrix # TODO: update
2533

2634

2735
# Set defaults to match MATLAB
@@ -52,3 +60,49 @@ def use_fbs_defaults():
5260
global bode_deg; bode_deg = True
5361
global bode_Hz; bode_Hz = False
5462

63+
#
64+
# State space function return type
65+
#
66+
# These functions are used to set the return type for state space functions
67+
# that return a matrix. In the original version of python-control, these
68+
# functions returned a numpy.matrix object. To handle the eventual
69+
# deprecation of the numpy.matrix type, the StateSpaceMatrix object type,
70+
# which implements matrix multiplications and related numpy.matrix operations
71+
# was created. To avoid breaking existing code, the return type from
72+
# functions that used to return an np.matrix object now call the
73+
# get_ss_return_type function to obtain the return type. The return type can
74+
# also be set using the `return_type` keyword, overriding the default state
75+
# space matrix return type.
76+
#
77+
78+
79+
# Set state space return type
80+
def set_ss_return_type(subtype, warn=True):
81+
global ss_return_type
82+
# If return_type is np.matrix, issue a pending deprecation warning
83+
if (subtype is np.matrix and warn):
84+
warnings.warn("Return type numpy.matrix is soon to be deprecated.",
85+
stacklevel=2)
86+
ss_return_type = subtype
87+
88+
89+
# Get the state space return type
90+
def get_ss_return_type(subtype=None, warn=True):
91+
global ss_return_type
92+
return_type = ss_return_type if subtype is None else subtype
93+
# If return_type is np.matrix, issue a pending deprecation warning
94+
if (return_type is np.matrix and warn):
95+
warnings.warn("Returning numpy.matrix, soon to be deprecated; "
96+
"make sure calling code can handle nparray.",
97+
stacklevel=2)
98+
return return_type
99+
100+
101+
# Function to turn on/off use of np.matrix type
102+
def use_numpy_matrix(flag=True, warn=True):
103+
if flag and warn:
104+
warnings.warn("Return type numpy.matrix is soon to be deprecated.",
105+
stacklevel=2)
106+
set_ss_return_type(np.matrix, warn=False)
107+
else:
108+
set_ss_return_type(StateSpaceMatrix)

control/modelsimp.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,19 @@
4545

4646
# External packages and modules
4747
import numpy as np
48-
import warnings
4948
from .exception import ControlSlycot
5049
from .lti import isdtime, isctime
5150
from .statesp import StateSpace
5251
from .statefbk import gram
52+
from .config import get_ss_return_type
5353

5454
__all__ = ['hsvd', 'balred', 'modred', 'era', 'markov', 'minreal']
5555

5656
# Hankel Singular Value Decomposition
5757
# The following returns the Hankel singular values, which are singular values
5858
#of the matrix formed by multiplying the controllability and observability
5959
#grammians
60-
def hsvd(sys, return_type=np.matrix):
60+
def hsvd(sys, return_type=None):
6161
"""Calculate the Hankel singular values.
6262
6363
Parameters
@@ -90,12 +90,6 @@ def hsvd(sys, return_type=np.matrix):
9090
>>> H = hsvd(sys)
9191
9292
"""
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-
9993
# TODO: implement for discrete time systems
10094
if (isdtime(sys, strict=True)):
10195
raise NotImplementedError("Function not implemented in discrete time")
@@ -111,7 +105,7 @@ def hsvd(sys, return_type=np.matrix):
111105
hsv = np.fliplr(hsv)
112106

113107
# Return the Hankel singular values (casting type, if needed)
114-
return hsv.view(type=return_type)
108+
return hsv.view(type=get_ss_return_type(return_type))
115109

116110
def modred(sys, ELIM, method='matchdc'):
117111
"""

control/statefbk.py

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
# External packages and modules
4343
import numpy as np
4444
import scipy as sp
45-
import warnings
4645
from . import statesp
46+
from .config import get_ss_return_type
4747
from .exception import ControlSlycot, ControlArgument, ControlDimension
4848

4949
__all__ = ['ctrb', 'obsv', 'gram', 'place', 'place_varga', 'lqr', 'acker']
@@ -220,7 +220,7 @@ def place_varga(A, B, p, dtime=False, alpha=None):
220220
return -F
221221

222222
# Contributed by Roberto Bucher <roberto.bucher@supsi.ch>
223-
def acker(A, B, poles, return_type=np.matrix):
223+
def acker(A, B, poles, return_type=None):
224224
"""Pole placement using Ackermann method
225225
226226
Call:
@@ -239,12 +239,6 @@ def acker(A, B, poles, return_type=np.matrix):
239239
Gains such that A - B K has given eigenvalues
240240
241241
"""
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-
248242
# Convert the inputs to matrices (arrays)
249243
a = statesp.ssmatrix(A)
250244
b = statesp.ssmatrix(B)
@@ -265,7 +259,7 @@ def acker(A, B, poles, return_type=np.matrix):
265259
K = np.linalg.solve(ct, pmat)
266260

267261
K = K[-1][:] # Extract the last row
268-
return K.view(type=return_type)
262+
return K.view(type=get_ss_return_type(return_type))
269263

270264
def lqr(*args, **keywords):
271265
"""lqr(A, B, Q, R[, N])
@@ -375,7 +369,7 @@ def lqr(*args, **keywords):
375369

376370
return K, S, E
377371

378-
def ctrb(A, B, return_type=np.matrix):
372+
def ctrb(A, B, return_type=None):
379373
"""Controllabilty matrix
380374
381375
Parameters
@@ -396,12 +390,6 @@ def ctrb(A, B, return_type=np.matrix):
396390
>>> C = ctrb(A, B)
397391
398392
"""
399-
# If return_type is np.matrix, issue a pending deprecation warning
400-
if (return_type is np.matrix):
401-
warnings.warn("Returning numpy.matrix, soon to be deprecated; "
402-
"make sure calling code can handle nparray.",
403-
stacklevel=2)
404-
405393
# Convert input parameters to matrices (if they aren't already)
406394
amat = statesp.ssmatrix(A)
407395
bmat = statesp.ssmatrix(B)
@@ -412,10 +400,10 @@ def ctrb(A, B, return_type=np.matrix):
412400
for i in range(1, n)])
413401

414402
# Return the observability matrix in the desired type
415-
return ctrb.view(type=return_type)
403+
return ctrb.view(type=get_ss_return_type(return_type))
416404

417405

418-
def obsv(A, C, return_type=np.matrix):
406+
def obsv(A, C, return_type=None):
419407
"""Observability matrix
420408
421409
Parameters
@@ -436,12 +424,6 @@ def obsv(A, C, return_type=np.matrix):
436424
>>> O = obsv(A, C)
437425
438426
"""
439-
# If return_type is np.matrix, issue a pending deprecation warning
440-
if (return_type is np.matrix):
441-
warnings.warn("Returning numpy.matrix, soon to be deprecated; "
442-
"make sure calling code can handle nparray.",
443-
stacklevel=2)
444-
445427
# Convert input parameters to matrices (if they aren't already)
446428
amat = statesp.ssmatrix(A)
447429
cmat = statesp.ssmatrix(C)
@@ -452,10 +434,10 @@ def obsv(A, C, return_type=np.matrix):
452434
for i in range(1, n)])
453435

454436
# Return the observability matrix in the desired type
455-
return obsv.view(type=return_type)
437+
return obsv.view(type=get_ss_return_type(return_type))
456438

457439

458-
def gram(sys, type, return_type=np.matrix):
440+
def gram(sys, type, return_type=None):
459441
"""Gramian (controllability or observability)
460442
461443
Parameters
@@ -492,12 +474,6 @@ def gram(sys, type, return_type=np.matrix):
492474
>>> Ro = gram(sys,'of'), where Wo=Ro'*Ro
493475
494476
"""
495-
# If return_type is np.matrix, issue a pending deprecation warning
496-
if (return_type is np.matrix):
497-
warnings.warn("Returning numpy.matrix, soon to be deprecated; "
498-
"make sure calling code can handle nparray.",
499-
stacklevel=2)
500-
501477
#Check for ss system object
502478
if not isinstance(sys,statesp.StateSpace):
503479
raise ValueError("System must be StateSpace!")
@@ -535,7 +511,7 @@ def gram(sys, type, return_type=np.matrix):
535511
A = np.array(sys.A) # convert to NumPy array for slycot
536512
X,scale,sep,ferr,w = sb03md(n, C, A, U, dico, job='X', fact='N', trana=tra)
537513
gram = X
538-
return gram.view(type=return_type)
514+
return gram.view(type=get_ss_return_type(return_type))
539515

540516
elif type=='cf' or type=='of':
541517
#Compute cholesky factored gramian from slycot routine sb03od
@@ -558,4 +534,4 @@ def gram(sys, type, return_type=np.matrix):
558534
C[0:n,0:m] = sys.C.transpose()
559535
X,scale,w = sb03od(n, m, A, Q, C.transpose(), dico, fact='N', trans=tra)
560536
gram = X
561-
return gram.view(type=return_type)
537+
return gram.view(type=get_ss_return_type(return_type))

control/tests/statefbk_test.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from control.matlab import *
1212
from control.exception import slycot_check, ControlDimension
1313
from control.mateqn import care, dare
14+
from control.config import use_numpy_matrix
15+
from control.statesp import StateSpaceMatrix
1416

1517
class TestStatefbk(unittest.TestCase):
1618
"""Test state feedback functions"""
@@ -29,11 +31,15 @@ def testCtrbSISO(self):
2931
A = np.array([[1., 2.], [3., 4.]])
3032
B = np.array([[5.], [7.]])
3133
Wctrue = np.array([[5., 19.], [7., 43.]])
32-
Wc = ctrb(A, B, return_type=np.ndarray)
34+
35+
use_numpy_matrix(False)
36+
Wc = ctrb(A, B)
3337
np.testing.assert_array_almost_equal(Wc, Wctrue)
38+
self.assertTrue(isinstance(Wc, StateSpaceMatrix))
3439

35-
# Check that default type generates a warning
40+
# Check that default using np.matrix generates a warning
3641
# TODO: remove this check with matrix type is deprecated
42+
use_numpy_matrix(True)
3743
with warnings.catch_warnings(record=True) as w:
3844
Wc = ctrb(A, B)
3945
self.assertTrue(issubclass(w[-1].category, UserWarning))
@@ -53,6 +59,7 @@ def testObsvSISO(self):
5359
A = np.array([[1., 2.], [3., 4.]])
5460
C = np.array([[5., 7.]])
5561
Wotrue = np.array([[5., 7.], [26., 38.]])
62+
use_numpy_matrix(True) # set default, but override in next statement
5663
Wo = obsv(A, C, return_type=np.ndarray)
5764
np.testing.assert_array_almost_equal(Wo, Wotrue)
5865

@@ -61,6 +68,7 @@ def testObsvSISO(self):
6168

6269
# Check that default type generates a warning
6370
# TODO: remove this check with matrix type is deprecated
71+
use_numpy_matrix(True)
6472
with warnings.catch_warnings(record=True) as w:
6573
Wo = obsv(A, C)
6674
self.assertTrue(issubclass(w[-1].category, UserWarning))
@@ -95,6 +103,7 @@ def testGramWc(self):
95103

96104
# Check that default type generates a warning
97105
# TODO: remove this check with matrix type is deprecated
106+
use_numpy_matrix(True)
98107
with warnings.catch_warnings(record=True) as w:
99108
Wc = gram(sys, 'c')
100109
self.assertTrue(issubclass(w[-1].category, UserWarning))
@@ -359,7 +368,10 @@ def test_dare(self):
359368

360369

361370
def test_suite():
362-
return unittest.TestLoader().loadTestsFromTestCase(TestStatefbk)
371+
372+
status1 = unittest.TestLoader().loadTestsFromTestCase(TestStatefbk)
373+
status2 = unittest.TestLoader().loadTestsFromTestCase(TestStatefbk)
374+
return status1 and status2
363375

364376
if __name__ == '__main__':
365377
unittest.main()

0 commit comments

Comments
 (0)