Repository navigation
Improve markov function, add mimo support, change api to TimeResponseData #1022
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
f2f0e3e
6c95fbc
14ce769
efa3f39
a597bbc
6023480
92307f0
a646100
42962f2
a3276b7
7ac6ee0
8d62775
e61ac53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,11 +43,11 @@ | |
| # External packages and modules | ||
| import numpy as np | ||
| import warnings | ||
| from .exception import ControlSlycot, ControlMIMONotImplemented, \ | ||
| ControlDimension | ||
| from .exception import ControlSlycot, ControlArgument, ControlDimension | ||
| from .iosys import isdtime, isctime | ||
| from .statesp import StateSpace | ||
| from .statefbk import gram | ||
| from .timeresp import TimeResponseData | ||
|
|
||
| __all__ = ['hsvd', 'balred', 'modred', 'era', 'markov', 'minreal'] | ||
|
|
||
|
|
@@ -402,9 +402,11 @@ def era(YY, m, n, nin, nout, r): | |
| raise NotImplementedError('This function is not implemented yet.') | ||
|
|
||
|
|
||
| def markov(Y, U, m=None, transpose=False): | ||
| """Calculate the first `m` Markov parameters [D CB CAB ...] | ||
| from input `U`, output `Y`. | ||
| def markov(*args, m=None, transpose=False, dt=True, truncate=False): | ||
| """markov(Y, U, [, m]) | ||
|
|
||
| Calculate the first `m` Markov parameters [D CB CAB ...] | ||
| from data | ||
|
|
||
| This function computes the Markov parameters for a discrete time system | ||
|
|
||
|
|
@@ -418,6 +420,16 @@ def markov(Y, U, m=None, transpose=False): | |
| the input data is less than the desired number of Markov parameters (a | ||
| warning message is generated in this case). | ||
|
|
||
| The function can be called with either 1, 2 or 3 arguments: | ||
|
|
||
| * ``H = markov(response)`` | ||
| * ``H = markov(respnose, m)`` | ||
| * ``H = markov(Y, U)`` | ||
| * ``H = markov(Y, U, m)`` | ||
|
|
||
| where `response` is an `TimeResponseData` object, and `Y`, `U`, are 1D or 2D | ||
| array and m is an integer. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| Y : array_like | ||
|
|
@@ -427,11 +439,22 @@ def markov(Y, U, m=None, transpose=False): | |
| time points. | ||
| U : array_like | ||
| Input data, arranged in the same way as `Y`. | ||
| data : TimeResponseData | ||
| Response data from which the Markov parameters where estimated. | ||
| Input and output data must be 1D or 2D array. | ||
| m : int, optional | ||
| Number of Markov parameters to output. Defaults to len(U). | ||
| dt : True of float, optional | ||
| True indicates discrete time with unspecified sampling time, | ||
| positive number is discrete time with specified sampling time.It | ||
| can be used to scale the markov parameters in order to match the | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Markov should be capitalized.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the case that a TimeResponseData object is given, should
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| impulse response of this library. Default is True. | ||
| truncate : bool, optional | ||
| Do not use first m equation for least least squares. Default is False. | ||
| transpose : bool, optional | ||
| Assume that input data is transposed relative to the standard | ||
| :ref:`time-series-convention`. Default value is False. | ||
| :ref:`time-series-convention`. For TimeResponseData this parameter | ||
| is ignored. Default is False. | ||
|
|
||
| Returns | ||
| ------- | ||
|
|
@@ -445,15 +468,6 @@ def markov(Y, U, m=None, transpose=False): | |
| and experiments. Journal of Guidance Control and Dynamics, 16(2), | ||
| 320-329, 2012. http://doi.org/10.2514/3.21006 | ||
|
|
||
| Notes | ||
| ----- | ||
| Currently only works for SISO systems. | ||
|
|
||
| This function does not currently comply with the Python Control Library | ||
| :ref:`time-series-convention` for representation of time series data. | ||
| Use `transpose=False` to make use of the standard convention (this | ||
| will be updated in a future release). | ||
|
|
||
| Examples | ||
| -------- | ||
| >>> T = np.linspace(0, 10, 100) | ||
|
|
@@ -463,77 +477,80 @@ def markov(Y, U, m=None, transpose=False): | |
|
|
||
| """ | ||
| # Convert input parameters to 2D arrays (if they aren't already) | ||
| Umat = np.array(U, ndmin=2) | ||
| Ymat = np.array(Y, ndmin=2) | ||
|
|
||
| # If data is in transposed format, switch it around | ||
| if transpose: | ||
| Umat, Ymat = np.transpose(Umat), np.transpose(Ymat) | ||
|
|
||
| # Make sure the system is a SISO system | ||
| if Umat.shape[0] != 1 or Ymat.shape[0] != 1: | ||
| raise ControlMIMONotImplemented | ||
| # Get the system description | ||
| if (len(args) < 1): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parenthesis are not needed here. The more standard style convention would be
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| raise ControlArgument("not enough input arguments") | ||
|
|
||
| if isinstance(args[0], TimeResponseData): | ||
| Umat = np.array(args[0].inputs, ndmin=2) | ||
| Ymat = np.array(args[0].outputs, ndmin=2) | ||
| transpose = args[0].transpose | ||
| if args[0].transpose and not args[0].issiso: | ||
| Umat, Ymat = np.transpose(Umat), np.transpose(Ymat) | ||
| if (len(args) == 2): | ||
| m = args[1] | ||
| elif (len(args) > 2): | ||
| raise ControlArgument("too many positional arguments") | ||
| else: | ||
| if (len(args) < 2): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK to remove parens (here and below).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| raise ControlArgument("not enough input arguments") | ||
| Umat = np.array(args[1], ndmin=2) | ||
| Ymat = np.array(args[0], ndmin=2) | ||
| if transpose: | ||
| Umat, Ymat = np.transpose(Umat), np.transpose(Ymat) | ||
| if (len(args) == 3): | ||
| m = args[2] | ||
| elif (len(args) > 3): | ||
| raise ControlArgument("too many positional arguments") | ||
|
|
||
| # Make sure the number of time points match | ||
| if Umat.shape[1] != Ymat.shape[1]: | ||
| raise ControlDimension( | ||
| "Input and output data are of differnent lengths") | ||
| n = Umat.shape[1] | ||
| l = Umat.shape[1] | ||
|
|
||
| # If number of desired parameters was not given, set to size of input data | ||
| if m is None: | ||
| m = Umat.shape[1] | ||
| m = l | ||
|
|
||
| t = 0 | ||
| if truncate: | ||
| t = m | ||
|
|
||
| q = Ymat.shape[0] # number of outputs | ||
| p = Umat.shape[0] # number of inputs | ||
|
|
||
| # Make sure there is enough data to compute parameters | ||
| if m > n: | ||
| if m*p > (l-t): | ||
| warnings.warn("Not enough data for requested number of parameters") | ||
|
|
||
| # the algorithm - Construct a matrix of control inputs to invert | ||
| # | ||
| # Original algorithm (with mapping to standard order) | ||
| # | ||
| # RMM note, 24 Dec 2020: This algorithm sets the problem up correctly | ||
| # until the final column of the UU matrix is created, at which point it | ||
| # makes some modifications that I don't understand. This version of the | ||
| # algorithm does not seem to return the actual Markov parameters for a | ||
| # system. | ||
| # | ||
| # # Create the matrix of (shifted) inputs | ||
| # UU = np.transpose(Umat) | ||
| # for i in range(1, m-1): | ||
| # # Shift previous column down and add a zero at the top | ||
| # newCol = np.vstack((0, np.reshape(UU[0:n-1, i-1], (-1, 1)))) | ||
| # UU = np.hstack((UU, newCol)) | ||
| # | ||
| # # Shift previous column down and add a zero at the top | ||
| # Ulast = np.vstack((0, np.reshape(UU[0:n-1, m-2], (-1, 1)))) | ||
| # | ||
| # # Replace the elements of the last column new values (?) | ||
| # # Each row gets the sum of the rows above it (?) | ||
| # for i in range(n-1, 0, -1): | ||
| # Ulast[i] = np.sum(Ulast[0:i-1]) | ||
| # UU = np.hstack((UU, Ulast)) | ||
| # | ||
| # # Solve for the Markov parameters from Y = H @ UU | ||
| # # H = [[D], [CB], [CAB], ..., [C A^{m-3} B], [???]] | ||
| # H = np.linalg.lstsq(UU, np.transpose(Ymat))[0] | ||
| # | ||
| # # Markov parameters are in rows => transpose if needed | ||
| # return H if transpose else np.transpose(H) | ||
|
|
||
| # | ||
| # New algorithm - Construct a matrix of control inputs to invert | ||
| # (q,l) = (q,p*m) @ (p*m,l) | ||
| # YY.T = H @ UU.T | ||
| # | ||
| # This algorithm sets up the following problem and solves it for | ||
| # the Markov parameters | ||
| # | ||
| # (l,q) = (l,p*m) @ (p*m,q) | ||
| # YY = UU @ H.T | ||
| # | ||
| # [ y(0) ] [ u(0) 0 0 ] [ D ] | ||
| # [ y(1) ] [ u(1) u(0) 0 ] [ C B ] | ||
| # [ y(2) ] = [ u(2) u(1) u(0) ] [ C A B ] | ||
| # [ : ] [ : : : : ] [ : ] | ||
| # [ y(n-1) ] [ u(n-1) u(n-2) u(n-3) ... u(n-m) ] [ C A^{m-2} B ] | ||
| # [ y(l-1) ] [ u(l-1) u(l-2) u(l-3) ... u(l-m) ] [ C A^{m-2} B ] | ||
| # | ||
| # truncated version t=m, do not use first m equation | ||
| # | ||
| # [ y(t) ] [ u(t) u(t-1) u(t-2) u(t-m) ] [ D ] | ||
| # [ y(t+1) ] [ u(t+1) u(t) u(t-1) u(t-m+1)] [ C B ] | ||
| # [ y(t+2) ] = [ u(t+2) u(t+1) u(t) u(t-m+2)] [ C B ] | ||
| # [ : ] [ : : : : ] [ : ] | ||
| # [ y(l-1) ] [ u(l-1) u(l-2) u(l-3) ... u(l-m) ] [ C A^{m-2} B ] | ||
| # | ||
| # Note: if the number of Markov parameters (m) is less than the size of | ||
| # the input/output data (n), then this algorithm assumes C A^{j} B = 0 | ||
| # Note: This algorithm assumes C A^{j} B = 0 | ||
| # for j > m-2. See equation (3) in | ||
| # | ||
| # J.-N. Juang, M. Phan, L. G. Horta, and R. W. Longman, Identification | ||
|
|
@@ -542,17 +559,26 @@ def markov(Y, U, m=None, transpose=False): | |
| # 320-329, 2012. http://doi.org/10.2514/3.21006 | ||
| # | ||
|
|
||
| # Set up the full problem | ||
| # Create matrix of (shifted) inputs | ||
| UU = Umat | ||
| for i in range(1, m): | ||
| # Shift previous column down and add a zero at the top | ||
| new_row = np.hstack((0, UU[i-1, 0:-1])) | ||
| UU = np.vstack((UU, new_row)) | ||
| UU = np.transpose(UU) | ||
| UUT = np.zeros((p*m,(l))) | ||
| for i in range(m): | ||
| # Shift previous column down and keep zeros at the top | ||
| UUT[i*p:(i+1)*p,i:] = Umat[:,:l-i] | ||
|
|
||
| # Truncate first t=0 or t=m time steps, transpose the problem for lsq | ||
| YY = Ymat[:,t:].T | ||
| UU = UUT[:,t:].T | ||
|
|
||
| # Solve for the Markov parameters from YY = UU @ H.T | ||
| HT, _, _, _ = np.linalg.lstsq(UU, YY, rcond=None) | ||
| H = HT.T/dt # scaling | ||
|
|
||
| H = H.reshape(q,m,p) # output, time*input -> output, time, input | ||
| H = H.transpose(0,2,1) # output, input, time | ||
|
|
||
| # Invert and solve for Markov parameters | ||
| YY = np.transpose(Ymat) | ||
| H, _, _, _ = np.linalg.lstsq(UU, YY, rcond=None) | ||
| if q == 1 and p == 1: | ||
| H = np.squeeze(H) | ||
|
|
||
| # Return the first m Markov parameters | ||
| return H if transpose else np.transpose(H) | ||
| return H if not transpose else np.transpose(H) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../examples/markov.py |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should end in a period.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.