45
45
46
46
# External packages and modules
47
47
import numpy as np
48
- from .exception import ControlSlycot
48
+ import warnings
49
+ from .exception import ControlSlycot , ControlMIMONotImplemented
49
50
from .lti import isdtime , isctime
50
51
from .statesp import StateSpace
51
52
from .statefbk import gram
52
53
53
54
__all__ = ['hsvd' , 'balred' , 'modred' , 'era' , 'markov' , 'minreal' ]
54
55
56
+
55
57
# Hankel Singular Value Decomposition
56
- # The following returns the Hankel singular values, which are singular values
57
- #of the matrix formed by multiplying the controllability and observability
58
- #grammians
58
+ #
59
+ # The following returns the Hankel singular values, which are singular values
60
+ # of the matrix formed by multiplying the controllability and observability
61
+ # Gramians
59
62
def hsvd (sys ):
60
63
"""Calculate the Hankel singular values.
61
64
@@ -90,8 +93,8 @@ def hsvd(sys):
90
93
if (isdtime (sys , strict = True )):
91
94
raise NotImplementedError ("Function not implemented in discrete time" )
92
95
93
- Wc = gram (sys ,'c' )
94
- Wo = gram (sys ,'o' )
96
+ Wc = gram (sys , 'c' )
97
+ Wo = gram (sys , 'o' )
95
98
WoWc = np .dot (Wo , Wc )
96
99
w , v = np .linalg .eig (WoWc )
97
100
@@ -101,6 +104,7 @@ def hsvd(sys):
101
104
# Return the Hankel singular values, high to low
102
105
return hsv [::- 1 ]
103
106
107
+
104
108
def modred (sys , ELIM , method = 'matchdc' ):
105
109
"""
106
110
Model reduction of `sys` by eliminating the states in `ELIM` using a given
@@ -136,21 +140,20 @@ def modred(sys, ELIM, method='matchdc'):
136
140
>>> rsys = modred(sys, ELIM, method='truncate')
137
141
"""
138
142
139
- #Check for ss system object, need a utility for this?
143
+ # Check for ss system object, need a utility for this?
140
144
141
- #TODO: Check for continous or discrete, only continuous supported right now
142
- # if isCont():
143
- # dico = 'C'
144
- # elif isDisc():
145
- # dico = 'D'
146
- # else:
145
+ # TODO: Check for continous or discrete, only continuous supported for now
146
+ # if isCont():
147
+ # dico = 'C'
148
+ # elif isDisc():
149
+ # dico = 'D'
150
+ # else:
147
151
if (isctime (sys )):
148
152
dico = 'C'
149
153
else :
150
154
raise NotImplementedError ("Function not implemented in discrete time" )
151
155
152
-
153
- #Check system is stable
156
+ # Check system is stable
154
157
if np .any (np .linalg .eigvals (sys .A ).real >= 0.0 ):
155
158
raise ValueError ("Oops, the system is unstable!" )
156
159
@@ -160,22 +163,22 @@ def modred(sys, ELIM, method='matchdc'):
160
163
# A1 is a matrix of all columns of sys.A not to eliminate
161
164
A1 = sys .A [:, NELIM [0 ]].reshape (- 1 , 1 )
162
165
for i in NELIM [1 :]:
163
- A1 = np .hstack ((A1 , sys .A [:,i ].reshape (- 1 , 1 )))
164
- A11 = A1 [NELIM ,:]
165
- A21 = A1 [ELIM ,:]
166
+ A1 = np .hstack ((A1 , sys .A [:, i ].reshape (- 1 , 1 )))
167
+ A11 = A1 [NELIM , :]
168
+ A21 = A1 [ELIM , :]
166
169
# A2 is a matrix of all columns of sys.A to eliminate
167
170
A2 = sys .A [:, ELIM [0 ]].reshape (- 1 , 1 )
168
171
for i in ELIM [1 :]:
169
- A2 = np .hstack ((A2 , sys .A [:,i ].reshape (- 1 , 1 )))
170
- A12 = A2 [NELIM ,:]
171
- A22 = A2 [ELIM ,:]
172
+ A2 = np .hstack ((A2 , sys .A [:, i ].reshape (- 1 , 1 )))
173
+ A12 = A2 [NELIM , :]
174
+ A22 = A2 [ELIM , :]
172
175
173
- C1 = sys .C [:,NELIM ]
174
- C2 = sys .C [:,ELIM ]
175
- B1 = sys .B [NELIM ,:]
176
- B2 = sys .B [ELIM ,:]
176
+ C1 = sys .C [:, NELIM ]
177
+ C2 = sys .C [:, ELIM ]
178
+ B1 = sys .B [NELIM , :]
179
+ B2 = sys .B [ELIM , :]
177
180
178
- if method == 'matchdc' :
181
+ if method == 'matchdc' :
179
182
# if matchdc, residualize
180
183
181
184
# Check if the matrix A22 is invertible
@@ -195,7 +198,7 @@ def modred(sys, ELIM, method='matchdc'):
195
198
Br = B1 - np .dot (A12 , A22I_B2 )
196
199
Cr = C1 - np .dot (C2 , A22I_A21 )
197
200
Dr = sys .D - np .dot (C2 , A22I_B2 )
198
- elif method == 'truncate' :
201
+ elif method == 'truncate' :
199
202
# if truncate, simply discard state x2
200
203
Ar = A11
201
204
Br = B1
@@ -204,12 +207,12 @@ def modred(sys, ELIM, method='matchdc'):
204
207
else :
205
208
raise ValueError ("Oops, method is not supported!" )
206
209
207
- rsys = StateSpace (Ar ,Br ,Cr ,Dr )
210
+ rsys = StateSpace (Ar , Br , Cr , Dr )
208
211
return rsys
209
212
213
+
210
214
def balred (sys , orders , method = 'truncate' , alpha = None ):
211
- """
212
- Balanced reduced order model of sys of a given order.
215
+ """Balanced reduced order model of sys of a given order.
213
216
States are eliminated based on Hankel singular value.
214
217
If sys has unstable modes, they are removed, the
215
218
balanced realization is done on the stable part, then
@@ -229,22 +232,23 @@ def balred(sys, orders, method='truncate', alpha=None):
229
232
method: string
230
233
Method of removing states, either ``'truncate'`` or ``'matchdc'``.
231
234
alpha: float
232
- Redefines the stability boundary for eigenvalues of the system matrix A.
233
- By default for continuous-time systems, alpha <= 0 defines the stability
234
- boundary for the real part of A's eigenvalues and for discrete-time
235
- systems, 0 <= alpha <= 1 defines the stability boundary for the modulus
236
- of A's eigenvalues. See SLICOT routines AB09MD and AB09ND for more
237
- information.
235
+ Redefines the stability boundary for eigenvalues of the system
236
+ matrix A. By default for continuous-time systems, alpha <= 0
237
+ defines the stability boundary for the real part of A's eigenvalues
238
+ and for discrete-time systems, 0 <= alpha <= 1 defines the stability
239
+ boundary for the modulus of A's eigenvalues. See SLICOT routines
240
+ AB09MD and AB09ND for more information.
238
241
239
242
Returns
240
243
-------
241
244
rsys: StateSpace
242
- A reduced order model or a list of reduced order models if orders is a list
245
+ A reduced order model or a list of reduced order models if orders is
246
+ a list.
243
247
244
248
Raises
245
249
------
246
250
ValueError
247
- * if `method` is not ``'truncate'`` or ``'matchdc'``
251
+ If `method` is not ``'truncate'`` or ``'matchdc'``
248
252
ImportError
249
253
if slycot routine ab09ad, ab09md, or ab09nd is not found
250
254
@@ -256,70 +260,78 @@ def balred(sys, orders, method='truncate', alpha=None):
256
260
>>> rsys = balred(sys, orders, method='truncate')
257
261
258
262
"""
259
- if method != 'truncate' and method != 'matchdc' :
263
+ if method != 'truncate' and method != 'matchdc' :
260
264
raise ValueError ("supported methods are 'truncate' or 'matchdc'" )
261
- elif method == 'truncate' :
265
+ elif method == 'truncate' :
262
266
try :
263
267
from slycot import ab09md , ab09ad
264
268
except ImportError :
265
- raise ControlSlycot ("can't find slycot subroutine ab09md or ab09ad" )
266
- elif method == 'matchdc' :
269
+ raise ControlSlycot (
270
+ "can't find slycot subroutine ab09md or ab09ad" )
271
+ elif method == 'matchdc' :
267
272
try :
268
273
from slycot import ab09nd
269
274
except ImportError :
270
275
raise ControlSlycot ("can't find slycot subroutine ab09nd" )
271
276
272
- #Check for ss system object, need a utility for this?
277
+ # Check for ss system object, need a utility for this?
273
278
274
- #TODO: Check for continous or discrete, only continuous supported right now
275
- # if isCont():
276
- # dico = 'C'
277
- # elif isDisc():
278
- # dico = 'D'
279
- # else:
279
+ # TODO: Check for continous or discrete, only continuous supported for now
280
+ # if isCont():
281
+ # dico = 'C'
282
+ # elif isDisc():
283
+ # dico = 'D'
284
+ # else:
280
285
dico = 'C'
281
286
282
- job = 'B' # balanced (B) or not (N)
283
- equil = 'N' # scale (S) or not (N)
287
+ job = 'B' # balanced (B) or not (N)
288
+ equil = 'N' # scale (S) or not (N)
284
289
if alpha is None :
285
290
if dico == 'C' :
286
291
alpha = 0.
287
292
elif dico == 'D' :
288
293
alpha = 1.
289
294
290
- rsys = [] # empty list for reduced systems
295
+ rsys = [] # empty list for reduced systems
291
296
292
- #check if orders is a list or a scalar
297
+ # check if orders is a list or a scalar
293
298
try :
294
299
order = iter (orders )
295
- except TypeError : # if orders is a scalar
300
+ except TypeError : # if orders is a scalar
296
301
orders = [orders ]
297
302
298
303
for i in orders :
299
- n = np .size (sys .A ,0 )
300
- m = np .size (sys .B ,1 )
301
- p = np .size (sys .C ,0 )
304
+ n = np .size (sys .A , 0 )
305
+ m = np .size (sys .B , 1 )
306
+ p = np .size (sys .C , 0 )
302
307
if method == 'truncate' :
303
- #check system stability
308
+ # check system stability
304
309
if np .any (np .linalg .eigvals (sys .A ).real >= 0.0 ):
305
- #unstable branch
306
- Nr , Ar , Br , Cr , Ns , hsv = ab09md (dico ,job ,equil ,n ,m ,p ,sys .A ,sys .B ,sys .C ,alpha = alpha ,nr = i ,tol = 0.0 )
310
+ # unstable branch
311
+ Nr , Ar , Br , Cr , Ns , hsv = ab09md (
312
+ dico , job , equil , n , m , p , sys .A , sys .B , sys .C ,
313
+ alpha = alpha , nr = i , tol = 0.0 )
307
314
else :
308
- #stable branch
309
- Nr , Ar , Br , Cr , hsv = ab09ad (dico ,job ,equil ,n ,m ,p ,sys .A ,sys .B ,sys .C ,nr = i ,tol = 0.0 )
315
+ # stable branch
316
+ Nr , Ar , Br , Cr , hsv = ab09ad (
317
+ dico , job , equil , n , m , p , sys .A , sys .B , sys .C ,
318
+ nr = i , tol = 0.0 )
310
319
rsys .append (StateSpace (Ar , Br , Cr , sys .D ))
311
320
312
321
elif method == 'matchdc' :
313
- Nr , Ar , Br , Cr , Dr , Ns , hsv = ab09nd (dico ,job ,equil ,n ,m ,p ,sys .A ,sys .B ,sys .C ,sys .D ,alpha = alpha ,nr = i ,tol1 = 0.0 ,tol2 = 0.0 )
322
+ Nr , Ar , Br , Cr , Dr , Ns , hsv = ab09nd (
323
+ dico , job , equil , n , m , p , sys .A , sys .B , sys .C , sys .D ,
324
+ alpha = alpha , nr = i , tol1 = 0.0 , tol2 = 0.0 )
314
325
rsys .append (StateSpace (Ar , Br , Cr , Dr ))
315
326
316
- #if orders was a scalar, just return the single reduced model, not a list
327
+ # if orders was a scalar, just return the single reduced model, not a list
317
328
if len (orders ) == 1 :
318
329
return rsys [0 ]
319
- #if orders was a list/vector, return a list/vector of systems
330
+ # if orders was a list/vector, return a list/vector of systems
320
331
else :
321
332
return rsys
322
333
334
+
323
335
def minreal (sys , tol = None , verbose = True ):
324
336
'''
325
337
Eliminates uncontrollable or unobservable states in state-space
@@ -347,9 +359,10 @@ def minreal(sys, tol=None, verbose=True):
347
359
nstates = len (sys .pole ()) - len (sysr .pole ())))
348
360
return sysr
349
361
362
+
350
363
def era (YY , m , n , nin , nout , r ):
351
- """
352
- Calculate an ERA model of order `r` based on the impulse-response data `YY`.
364
+ """Calculate an ERA model of order `r` based on the impulse-response data
365
+ `YY`.
353
366
354
367
.. note:: This function is not implemented yet.
355
368
@@ -376,41 +389,76 @@ def era(YY, m, n, nin, nout, r):
376
389
Examples
377
390
--------
378
391
>>> rsys = era(YY, m, n, nin, nout, r)
392
+
379
393
"""
380
394
raise NotImplementedError ('This function is not implemented yet.' )
381
395
382
- def markov ( Y , U , m ):
383
- """
384
- Calculate the first `M` Markov parameters [D CB CAB ...]
396
+
397
+ def markov ( Y , U , m , transpose = None ):
398
+ """ Calculate the first `M` Markov parameters [D CB CAB ...]
385
399
from input `U`, output `Y`.
386
400
387
401
Parameters
388
402
----------
389
- Y: array_like
390
- Output data
391
- U: array_like
392
- Input data
393
- m: int
394
- Number of Markov parameters to output
403
+ Y : array_like
404
+ Output data. If the array is 1D, the system is assumed to be single
405
+ input. If the array is 2D and transpose=False, the columns of `Y`
406
+ are taken as time points, otherwise the rows of `Y` are taken as
407
+ time points.
408
+ U : array_like
409
+ Input data, arranged in the same way as `Y`.
410
+ m : int
411
+ Number of Markov parameters to output.
412
+ transpose : bool, optional
413
+ Assume that input data is transposed relative to the standard
414
+ :ref:`time-series-convention`. The default value is true for
415
+ backward compatibility with legacy code.
395
416
396
417
Returns
397
418
-------
398
- H: ndarray
419
+ H : ndarray
399
420
First m Markov parameters
400
421
401
422
Notes
402
423
-----
403
- Currently only works for SISO
424
+ Currently only works for SISO systems.
425
+
426
+ This function does not currently comply with the Python Control Library
427
+ :ref:`time-series-convention` for representation of time series data.
428
+ Use `transpose=False` to make use of the standard convention (this
429
+ will be updated in a future release).
404
430
405
431
Examples
406
432
--------
407
- >>> H = markov(Y, U, m)
408
- """
433
+ >>> T = numpy.linspace(0, 10, 100)
434
+ >>> U = numpy.ones((1, 100))
435
+ >>> Y = forced_response(tf([1], [1, 1]), T, U)
436
+ >>> H = markov(Y, U, m, transpose=False)
409
437
410
- # Convert input parameters to matrices (if they aren't already)
411
- Ymat = np .array (Y )
412
- Umat = np .array (U )
413
- n = np .size (U )
438
+ """
439
+ # Check on the specified format of the input
440
+ if transpose is None :
441
+ # For backwards compatibility, assume time series in rows but warn user
442
+ warnings .warn (
443
+ "Time-series data assumed to be in rows. This will change in a "
444
+ "future release. Use `transpose=True` to preserve current "
445
+ "behavior." )
446
+ transpose = True
447
+
448
+ # Convert input parameters to 2D arrays (if they aren't already)
449
+ Umat = np .array (U , ndmin = 2 )
450
+ Ymat = np .array (Y , ndmin = 2 )
451
+
452
+ # Transpose the data from our normal convention to match algorithm
453
+ # TODO: rewrite the algorithm to use standard conventions
454
+ if not transpose :
455
+ Umat = np .transpose (Umat )
456
+ Ymat = np .transpose (Ymat )
457
+
458
+ # Make sure the system is a SISO system
459
+ if Umat .shape [1 ] != 1 or Ymat .shape [1 ] != 1 :
460
+ raise ControlMIMONotImplemented
461
+ n = Umat .shape [0 ]
414
462
415
463
# Construct a matrix of control inputs to invert
416
464
UU = Umat
@@ -424,6 +472,6 @@ def markov(Y, U, m):
424
472
UU = np .hstack ((UU , Ulast ))
425
473
426
474
# Invert and solve for Markov parameters
427
- H = np .linalg .lstsq (UU , Y )[0 ]
475
+ H = np .linalg .lstsq (UU , Ymat )[0 ]
428
476
429
477
return H
0 commit comments