48
48
from .iosys import isdtime , isctime
49
49
from .statesp import StateSpace
50
50
from .st
10000
atefbk import gram
51
+ from .timeresp import TimeResponseData
51
52
52
53
__all__ = ['hsvd' , 'balred' , 'modred' , 'era' , 'markov' , 'minreal' ]
53
54
@@ -402,9 +403,9 @@ def era(YY, m, n, nin, nout, r):
402
403
raise NotImplementedError ('This function is not implemented yet.' )
403
404
404
405
405
- def markov (Y , U , m = None , transpose = False ):
406
+ def markov (data , m = None , dt = True , truncate = False ):
406
407
"""Calculate the first `m` Markov parameters [D CB CAB ...]
407
- from input `U`, output `Y`.
408
+ from data
408
409
409
410
This function computes the Markov parameters for a discrete time system
410
411
@@ -420,23 +421,45 @@ def markov(Y, U, m=None, transpose=False):
420
421
421
422
Parameters
422
423
----------
423
- Y : array_like
424
- Output data. If the array is 1D, the system is assumed to be single
425
- input. If the array is 2D and transpose=False, the columns of `Y`
426
- are taken as time points, otherwise the rows of `Y` are taken as
427
- time points.
428
- U : array_like
429
- Input data, arranged in the same way as `Y`.
424
+ data : TimeResponseData
425
+ Response data from which the Markov parameters where estimated.
426
+ Input and output data must be 1D or 2D array.
430
427
m : int, optional
431
428
Number of Markov parameters to output. Defaults to len(U).
432
- transpose : bool, optional
433
- Assume that input data is transposed relative to the standard
434
- :ref:`time-series-convention`. Default value is False.
429
+ dt : (True of float, optional)
430
+ True indicates discrete time with unspecified sampling time,
431
+ positive number is discrete time with specified sampling time.
432
+ It can be used to scale the markov parameters in order to match
433
+ the impulse response of this library.
434
+ Default values is True.
435
+ truncate : bool, optional
436
+ Do not use first m equation for least least squares.
437
+ Default value is False.
435
438
<
10000
span class=pl-s>
436
439
Returns
437
440
-------
438
- H : ndarray
439
- First m Markov parameters, [D CB CAB ...]
441
+ H : TimeResponseData
442
+ Markov parameters / impulse response [D CB CAB ...] represented as
443
+ a :class:`TimeResponseData` object containing the following properties:
444
+
445
+ * time (array): Time values of the output.
446
+
447
+ * outputs (array): Response of the system. If the system is SISO,
448
+ the array is 1D (indexed by time). If the
449
+ system is not SISO, the array is 3D (indexed
450
+ by the output, trace, and time).
451
+
452
+ * inputs (array): Inputs of the system. If the system is SISO,
453
+ the array is 1D (indexed by time). If the
454
+ system is not SISO, the array is 3D (indexed
455
+ by the output, trace, and time).
456
+
457
+ Notes
458
+ -----
459
+ It works for SISO and MIMO systems.
460
+
461
+ This function does comply with the Python Control Library
462
+ :ref:`time-series-convention` for representation of time series data.
440
463
441
464
References
442
465
----------
@@ -445,95 +468,69 @@ def markov(Y, U, m=None, transpose=False):
445
468
and experiments. Journal of Guidance Control and Dynamics, 16(2),
446
469
320-329, 2012. http://doi.org/10.2514/3.21006
447
470
448
- Notes
449
- -----
450
- Currently only works for SISO systems.
451
-
452
- This function does not currently comply with the Python Control Library
453
- :ref:`time-series-convention` for representation of time series data.
454
- Use `transpose=False` to make use of the standard convention (this
455
- will be updated in a future release).
456
-
457
471
Examples
458
472
--------
459
473
>>> T = np.linspace(0, 10, 100)
460
474
>>> U = np.ones((1, 100))
461
- >>> T, Y = ct.forced_response(ct.tf([1], [1, 0.5], True), T, U)
462
- >>> H = ct.markov(Y, U, 3, transpose=False )
475
+ >>> response = ct.forced_response(ct.tf([1], [1, 0.5], True), T, U)
476
+ >>> H = ct.markov(response, 3 )
463
477
464
478
"""
465
479
# Convert input parameters to 2D arrays (if they aren't already)
466
- Umat = np .array (U , ndmin = 2 )
467
- Ymat = np .array (Y , ndmin = 2 )
480
+ Umat = np .array (data . inputs , ndmin = 2 )
481
+ Ymat = np .array (data . outputs , ndmin = 2 )
468
482
469
483
# If data is in transposed format, switch it around
470
- if transpose :
484
+ if data . transpose and not data . issiso :
471
485
Umat , Ymat = np .transpose (Umat ), np .transpose (Ymat )
472
486
473
- # Make sure the system is a SISO system
474
- if Umat .shape [0 ] != 1 or Ymat .shape [0 ] != 1 :
475
- raise ControlMIMONotImplemented
476
-
477
487
# Make sure the number of time points match
478
488
if Umat .shape [1 ] != Ymat .shape [1 ]:
479
489
raise ControlDimension (
480
490
"Input and output data are of differnent lengths" )
481
- n = Umat .shape [1 ]
491
+ l = Umat .shape [1 ]
482
492
483
493
# If number of desired parameters was not given, set to size of input data
484
494
if m is None :
485
- m = Umat .shape [1 ]
495
+ m = l
496
+
497
+ t = 0
498
+ if truncate :
499
+ t = m
500
+
501
+ q = Ymat .shape [0 ] # number of outputs
502
+ p = Umat .shape [0 ] # number of inputs
486
503
487
504
# Make sure there is enough data to compute parameters
488
- if m > n :
505
+ if m * p > ( l - t ) :
489
506
warnings .warn ("Not enough data for requested number of parameters" )
490
507
508
+ # the algorithm - Construct a matrix of control inputs to invert
491
509
#
492
- # Original algorithm (with mapping to standard order)
493
- #
494
- # RMM note, 24 Dec 2020: This algorithm sets the problem up correctly
495
- # until the final column of the UU matrix is created, at which point it
496
- # makes some modifications that I don't understand. This version of the
497
- # algorithm does not seem to return the actual Markov parameters for a
498
- # system.
499
- #
500
- # # Create the matrix of (shifted) inputs
501
- # UU = np.transpose(Umat)
502
- # for i in range(1, m-1):
503
- # # Shift previous column down and add a zero at the top
504
- # newCol = np.vstack((0, np.reshape(UU[0:n-1, i-1], (-1, 1))))
505
- # UU = np.hstack((UU, newCol))
506
- #
507
- # # Shift previous column down and add a zero at the top
508
- # Ulast = np.vstack((0, np.reshape(UU[0:n-1, m-2], (-1, 1))))
509
- #
510
- # # Replace the elements of the last column new values (?)
511
- # # Each row gets the sum of the rows above it (?)
512
- # for i in range(n-1, 0, -1):
513
- # Ulast[i] = np.sum(Ulast[0:i-1])
514
- # UU = np.hstack((UU, Ulast))
515
- #
516
- # # Solve for the Markov parameters from Y = H @ UU
517
- # # H = [[D], [CB], [CAB], ..., [C A^{m-3} B], [???]]
518
- # H = np.linalg.lstsq(UU, np.transpose(Ymat))[0]
519
- #
520
- # # Markov parameters are in rows => transpose if needed
521
- # return H if transpose else np.transpose(H)
522
-
523
- #
524
- # New algorithm - Construct a matrix of control inputs to invert
510
+ # (q,l) = (q,p*m) @ (p*m,l)
511
+ # YY.T = H @ UU.T
525
512
#
526
513
# This algorithm sets up the following problem and solves it for
527
514
# the Markov parameters
528
515
#
516
+ # (l,q) = (l,p*m) @ (p*m,q)
517
+ # YY = UU @ H.T
518
+ #
529
519
# [ y(0) ] [ u(0) 0 0 ] [ D ]
530
520
# [ y(1) ] [ u(1) u(0) 0 ] [ C B ]
531
521
# [ y(2) ] = [ u(2) u(1) u(0) ] [ C A B ]
532
522
# [ : ] [ : : : : ] [ : ]
533
- # [ y(n -1) ] [ u(n -1) u(n -2) u(n -3) ... u(n -m) ] [ C A^{m-2} B ]
523
+ # [ y(l -1) ] [ u(l -1) u(l -2) u(l -3) ... u(l -m) ] [ C A^{m-2} B ]
534
524
#
535
- # Note: if the number of Markov parameters (m) is less than the size of
536
- # the input/output data (n), then this algorithm assumes C A^{j} B = 0
525
+ # truncated version t=m, do not use first m equation
526
+ #
527
+ # [ y(t) ] [ u(t) u(t-1) u(t-2) u(t-m) ] [ D ]
528
+ # [ y(t+1) ] [ u(t+1) u(t) u(t-1) u(t-m+1)] [ C B ]
529
+ # [ y(t+2) ] = [ u(t+2) u(t+1) u(t) u(t-m+2)] [ C B ]
530
+ # [ : ] [ : : : : ] [ : ]
531
+ # [ y(l-1) ] [ u(l-1) u(l-2) u(l-3) ... u(l-m) ] [ C A^{m-2} B ]
532
+ #
533
+ # Note: This algorithm assumes C A^{j} B = 0
537
534
# for j > m-2. See equation (3) in
538
535
#
539
536
# J.-N. Juang, M. Phan, L. G. Horta, and R. W. Longman, Identification
@@ -542,17 +539,40 @@ def markov(Y, U, m=None, transpose=False):
542
539
# 320-329, 2012. http://doi.org/10.2514/3.21006
543
540
#
544
541
542
+ # Set up the full problem
545
543
# Create matrix of (shifted) inputs
546
- UU = Umat
547
- for i in range (1 , m ):
548
- # Shift previous column down and add a zero at the top
549
- new_row = np .hstack ((0 , UU [i - 1 , 0 :- 1 ]))
550
- UU = np .vstack ((UU , new_row ))
551
- UU = np .transpose (UU )
552
-
553
- # Invert and solve for Markov parameters
554
- YY = np .transpose (Ymat )
555
- H , _ , _ , _ = np .linalg .lstsq (UU , YY , rcond = None )
556
-
544
+ UUT = np .zeros ((p * m ,(l )))
545
+ for i in range (m ):
546
+ # Shift previous column down and keep zeros at the top
547
+ UUT [i * p :(i + 1 )* p ,i :] = Umat [:,:l - i ]
548
+
549
+ # Truncate first t=0 or t=m time steps, transpose the problem for lsq
550
+ YY = Ymat [:,t :].T
551
+ UU = UUT [:,t :].T
552
+
553
+ # Solve for the Markov parameters from YY = UU @ H.T
554
+ HT , _ , _ , _ = np .linalg .lstsq (UU , YY , rcond = None )
555
+ H = HT .T / dt # scaling
556
+
557
+ H = H .reshape (q ,m ,p ) # output, time*input -> output, time, input
558
+ H = H .transpose (0 ,2 ,1 ) # output, input, time
559
+
560
+ # Create unit area impulse inputs
561
+ inputs = np .zeros ((q ,p ,m ))
562
+ trace_labels , trace_types = [], []
563
+ for i in range (p ):
564
+ inputs [i ,i ,0 ] = 1 / dt # unit area impulse
565
+ trace_labels .append (f"From { data .input_labels [i ]} " )
566
+ trace_types .append ('impulse' )
567
+
568
+ # Markov parameters as TimeResponseData with unit area impulse inputs
557
569
# Return the first m Markov parameters
558
- return H if transpose else np .transpose (H )
570
+ return TimeResponseData (time = data .time [:m ],
571
+ outputs = H ,
572
+ output_labels = data .output_labels ,
573
+ inputs = inputs ,
574
+ input_labels = data .input_labels ,
575
+ trace_labels = trace_labels ,
576
+ trace_types = trace_types ,
577
+ transpose = data .transpose ,
578
+ issiso = data .issiso )
0 commit comments