From 7ed68ecb347a6b94ceebf84eaefb662dbba1436a Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Thu, 24 Dec 2020 14:23:44 -0800 Subject: [PATCH 1/3] updated markov() to add tranpose keyword + default warning; tests, PEP8 --- control/modelsimp.py | 218 ++++++++++++++++---------- control/tests/modelsimp_array_test.py | 24 ++- control/tests/modelsimp_test.py | 12 +- 3 files changed, 164 insertions(+), 90 deletions(-) diff --git a/control/modelsimp.py b/control/modelsimp.py index 9fd36923e..9f21c4e25 100644 --- a/control/modelsimp.py +++ b/control/modelsimp.py @@ -45,17 +45,20 @@ # External packages and modules import numpy as np -from .exception import ControlSlycot +import warnings +from .exception import ControlSlycot, ControlMIMONotImplemented from .lti import isdtime, isctime from .statesp import StateSpace from .statefbk import gram __all__ = ['hsvd', 'balred', 'modred', 'era', 'markov', 'minreal'] + # Hankel Singular Value Decomposition -# The following returns the Hankel singular values, which are singular values -#of the matrix formed by multiplying the controllability and observability -#grammians +# +# The following returns the Hankel singular values, which are singular values +# of the matrix formed by multiplying the controllability and observability +# Gramians def hsvd(sys): """Calculate the Hankel singular values. @@ -90,8 +93,8 @@ def hsvd(sys): if (isdtime(sys, strict=True)): raise NotImplementedError("Function not implemented in discrete time") - Wc = gram(sys,'c') - Wo = gram(sys,'o') + Wc = gram(sys, 'c') + Wo = gram(sys, 'o') WoWc = np.dot(Wo, Wc) w, v = np.linalg.eig(WoWc) @@ -101,6 +104,7 @@ def hsvd(sys): # Return the Hankel singular values, high to low return hsv[::-1] + def modred(sys, ELIM, method='matchdc'): """ Model reduction of `sys` by eliminating the states in `ELIM` using a given @@ -136,21 +140,20 @@ def modred(sys, ELIM, method='matchdc'): >>> rsys = modred(sys, ELIM, method='truncate') """ - #Check for ss system object, need a utility for this? + # Check for ss system object, need a utility for this? - #TODO: Check for continous or discrete, only continuous supported right now - # if isCont(): - # dico = 'C' - # elif isDisc(): - # dico = 'D' - # else: + # TODO: Check for continous or discrete, only continuous supported for now + # if isCont(): + # dico = 'C' + # elif isDisc(): + # dico = 'D' + # else: if (isctime(sys)): dico = 'C' else: raise NotImplementedError("Function not implemented in discrete time") - - #Check system is stable + # Check system is stable if np.any(np.linalg.eigvals(sys.A).real >= 0.0): raise ValueError("Oops, the system is unstable!") @@ -160,22 +163,22 @@ def modred(sys, ELIM, method='matchdc'): # A1 is a matrix of all columns of sys.A not to eliminate A1 = sys.A[:, NELIM[0]].reshape(-1, 1) for i in NELIM[1:]: - A1 = np.hstack((A1, sys.A[:,i].reshape(-1, 1))) - A11 = A1[NELIM,:] - A21 = A1[ELIM,:] + A1 = np.hstack((A1, sys.A[:, i].reshape(-1, 1))) + A11 = A1[NELIM, :] + A21 = A1[ELIM, :] # A2 is a matrix of all columns of sys.A to eliminate A2 = sys.A[:, ELIM[0]].reshape(-1, 1) for i in ELIM[1:]: - A2 = np.hstack((A2, sys.A[:,i].reshape(-1, 1))) - A12 = A2[NELIM,:] - A22 = A2[ELIM,:] + A2 = np.hstack((A2, sys.A[:, i].reshape(-1, 1))) + A12 = A2[NELIM, :] + A22 = A2[ELIM, :] - C1 = sys.C[:,NELIM] - C2 = sys.C[:,ELIM] - B1 = sys.B[NELIM,:] - B2 = sys.B[ELIM,:] + C1 = sys.C[:, NELIM] + C2 = sys.C[:, ELIM] + B1 = sys.B[NELIM, :] + B2 = sys.B[ELIM, :] - if method=='matchdc': + if method == 'matchdc': # if matchdc, residualize # Check if the matrix A22 is invertible @@ -195,7 +198,7 @@ def modred(sys, ELIM, method='matchdc'): Br = B1 - np.dot(A12, A22I_B2) Cr = C1 - np.dot(C2, A22I_A21) Dr = sys.D - np.dot(C2, A22I_B2) - elif method=='truncate': + elif method == 'truncate': # if truncate, simply discard state x2 Ar = A11 Br = B1 @@ -204,12 +207,12 @@ def modred(sys, ELIM, method='matchdc'): else: raise ValueError("Oops, method is not supported!") - rsys = StateSpace(Ar,Br,Cr,Dr) + rsys = StateSpace(Ar, Br, Cr, Dr) return rsys + def balred(sys, orders, method='truncate', alpha=None): - """ - Balanced reduced order model of sys of a given order. + """Balanced reduced order model of sys of a given order. States are eliminated based on Hankel singular value. If sys has unstable modes, they are removed, the balanced realization is done on the stable part, then @@ -229,22 +232,23 @@ def balred(sys, orders, method='truncate', alpha=None): method: string Method of removing states, either ``'truncate'`` or ``'matchdc'``. alpha: float - Redefines the stability boundary for eigenvalues of the system matrix A. - By default for continuous-time systems, alpha <= 0 defines the stability - boundary for the real part of A's eigenvalues and for discrete-time - systems, 0 <= alpha <= 1 defines the stability boundary for the modulus - of A's eigenvalues. See SLICOT routines AB09MD and AB09ND for more - information. + Redefines the stability boundary for eigenvalues of the system + matrix A. By default for continuous-time systems, alpha <= 0 + defines the stability boundary for the real part of A's eigenvalues + and for discrete-time systems, 0 <= alpha <= 1 defines the stability + boundary for the modulus of A's eigenvalues. See SLICOT routines + AB09MD and AB09ND for more information. Returns ------- rsys: StateSpace - A reduced order model or a list of reduced order models if orders is a list + A reduced order model or a list of reduced order models if orders is + a list. Raises ------ ValueError - * if `method` is not ``'truncate'`` or ``'matchdc'`` + If `method` is not ``'truncate'`` or ``'matchdc'`` ImportError if slycot routine ab09ad, ab09md, or ab09nd is not found @@ -256,70 +260,78 @@ def balred(sys, orders, method='truncate', alpha=None): >>> rsys = balred(sys, orders, method='truncate') """ - if method!='truncate' and method!='matchdc': + if method != 'truncate' and method != 'matchdc': raise ValueError("supported methods are 'truncate' or 'matchdc'") - elif method=='truncate': + elif method == 'truncate': try: from slycot import ab09md, ab09ad except ImportError: - raise ControlSlycot("can't find slycot subroutine ab09md or ab09ad") - elif method=='matchdc': + raise ControlSlycot( + "can't find slycot subroutine ab09md or ab09ad") + elif method == 'matchdc': try: from slycot import ab09nd except ImportError: raise ControlSlycot("can't find slycot subroutine ab09nd") - #Check for ss system object, need a utility for this? + # Check for ss system object, need a utility for this? - #TODO: Check for continous or discrete, only continuous supported right now - # if isCont(): - # dico = 'C' - # elif isDisc(): - # dico = 'D' - # else: + # TODO: Check for continous or discrete, only continuous supported for now + # if isCont(): + # dico = 'C' + # elif isDisc(): + # dico = 'D' + # else: dico = 'C' - job = 'B' # balanced (B) or not (N) - equil = 'N' # scale (S) or not (N) + job = 'B' # balanced (B) or not (N) + equil = 'N' # scale (S) or not (N) if alpha is None: if dico == 'C': alpha = 0. elif dico == 'D': alpha = 1. - rsys = [] #empty list for reduced systems + rsys = [] # empty list for reduced systems - #check if orders is a list or a scalar + # check if orders is a list or a scalar try: order = iter(orders) - except TypeError: #if orders is a scalar + except TypeError: # if orders is a scalar orders = [orders] for i in orders: - n = np.size(sys.A,0) - m = np.size(sys.B,1) - p = np.size(sys.C,0) + n = np.size(sys.A, 0) + m = np.size(sys.B, 1) + p = np.size(sys.C, 0) if method == 'truncate': - #check system stability + # check system stability if np.any(np.linalg.eigvals(sys.A).real >= 0.0): - #unstable branch - 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) + # unstable branch + 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) else: - #stable branch - Nr, Ar, Br, Cr, hsv = ab09ad(dico,job,equil,n,m,p,sys.A,sys.B,sys.C,nr=i,tol=0.0) + # stable branch + Nr, Ar, Br, Cr, hsv = ab09ad( + dico, job, equil, n, m, p, sys.A, sys.B, sys.C, + nr=i, tol=0.0) rsys.append(StateSpace(Ar, Br, Cr, sys.D)) elif method == 'matchdc': - 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) + 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) rsys.append(StateSpace(Ar, Br, Cr, Dr)) - #if orders was a scalar, just return the single reduced model, not a list + # if orders was a scalar, just return the single reduced model, not a list if len(orders) == 1: return rsys[0] - #if orders was a list/vector, return a list/vector of systems + # if orders was a list/vector, return a list/vector of systems else: return rsys + def minreal(sys, tol=None, verbose=True): ''' Eliminates uncontrollable or unobservable states in state-space @@ -347,9 +359,10 @@ def minreal(sys, tol=None, verbose=True): nstates=len(sys.pole()) - len(sysr.pole()))) return sysr + def era(YY, m, n, nin, nout, r): - """ - Calculate an ERA model of order `r` based on the impulse-response data `YY`. + """Calculate an ERA model of order `r` based on the impulse-response data + `YY`. .. note:: This function is not implemented yet. @@ -376,41 +389,76 @@ def era(YY, m, n, nin, nout, r): Examples -------- >>> rsys = era(YY, m, n, nin, nout, r) + """ raise NotImplementedError('This function is not implemented yet.') -def markov(Y, U, m): - """ - Calculate the first `M` Markov parameters [D CB CAB ...] + +def markov(Y, U, m, transpose=None): + """Calculate the first `M` Markov parameters [D CB CAB ...] from input `U`, output `Y`. Parameters ---------- - Y: array_like - Output data - U: array_like - Input data - m: int - Number of Markov parameters to output + Y : array_like + Output data. If the array is 1D, the system is assumed to be single + input. If the array is 2D and transpose=False, the columns of `Y` + are taken as time points, otherwise the rows of `Y` are taken as + time points. + U : array_like + Input data, arranged in the same way as `Y`. + m : int + Number of Markov parameters to output. + transpose : bool, optional + Assume that input data is transposed relative to the standard + :ref:`time-series-convention`. The default value is true for + backward compatibility with legacy code. Returns ------- - H: ndarray + H : ndarray First m Markov parameters Notes ----- - Currently only works for SISO + 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 -------- - >>> H = markov(Y, U, m) - """ + >>> T = numpy.linspace(0, 10, 100) + >>> U = numpy.ones((1, 100)) + >>> Y = forced_response(tf([1], [1, 1]), T, U) + >>> H = markov(Y, U, m, transpose=False) - # Convert input parameters to matrices (if they aren't already) - Ymat = np.array(Y) - Umat = np.array(U) - n = np.size(U) + """ + # Check on the specified format of the input + if transpose is None: + # For backwards compatibility, assume time series in rows but warn user + warnings.warn( + "Time-series data assumed to be in rows. This will change in a " + "future release. Use `transpose=True` to preserve current " + "behavior.") + transpose = True + + # Convert input parameters to 2D arrays (if they aren't already) + Umat = np.array(U, ndmin=2) + Ymat = np.array(Y, ndmin=2) + + # Transpose the data from our normal convention to match algorithm + # TODO: rewrite the algorithm to use standard conventions + if not transpose: + Umat = np.transpose(Umat) + Ymat = np.transpose(Ymat) + + # Make sure the system is a SISO system + if Umat.shape[1] != 1 or Ymat.shape[1] != 1: + raise ControlMIMONotImplemented + n = Umat.shape[0] # Construct a matrix of control inputs to invert UU = Umat @@ -424,6 +472,6 @@ def markov(Y, U, m): UU = np.hstack((UU, Ulast)) # Invert and solve for Markov parameters - H = np.linalg.lstsq(UU, Y)[0] + H = np.linalg.lstsq(UU, Ymat)[0] return H diff --git a/control/tests/modelsimp_array_test.py b/control/tests/modelsimp_array_test.py index 4a6f591e6..b78d5e22c 100644 --- a/control/tests/modelsimp_array_test.py +++ b/control/tests/modelsimp_array_test.py @@ -9,7 +9,7 @@ import control from control.modelsimp import * from control.matlab import * -from control.exception import slycot_check +from control.exception import slycot_check, ControlMIMONotImplemented class TestModelsimp(unittest.TestCase): def setUp(self): @@ -50,13 +50,31 @@ def testHSVD(self): control.use_numpy_matrix(False) def testMarkov(self): - U = np.array([[1.], [1.], [1.], [1.], [1.]]) + U = np.array([[1., 1., 1., 1., 1.]]) Y = U M = 3 - H = markov(Y,U,M) + H = markov(Y, U, M, transpose=False) Htrue = np.array([[1.], [0.], [0.]]) np.testing.assert_array_almost_equal( H, Htrue ) + # Make sure that transposed data also works + H = markov(np.transpose(Y), np.transpose(U), M, transpose=True) + + # Test example from docstring + T = np.linspace(0, 10, 100) + U = np.ones((1, 100)) + _, Y, _ = control.forced_response(control.tf([1], [1, 1]), T, U) + H = markov(Y, U, M, transpose=False) + + # Test example from issue #395 + inp = np.array([1, 2]) + outp = np.array([2, 4]) + mrk = markov(outp, inp, 1, transpose=False) + + # Make sure MIMO generates an error + U = np.ones((2, 100)) # 2 inputs (Y unchanged, with 1 output) + np.testing.assert_raises(ControlMIMONotImplemented, markov, U, Y, M) + def testModredMatchDC(self): #balanced realization computed in matlab for the transfer function: # num = [1 11 45 32], den = [1 15 60 200 60] diff --git a/control/tests/modelsimp_test.py b/control/tests/modelsimp_test.py index 2368bd92f..50cf884bb 100644 --- a/control/tests/modelsimp_test.py +++ b/control/tests/modelsimp_test.py @@ -22,13 +22,21 @@ def testHSVD(self): np.testing.assert_array_almost_equal(hsv, hsvtrue) def testMarkov(self): - U = np.matrix("1.; 1.; 1.; 1.; 1.") + U = np.matrix("1., 1., 1., 1., 1.") Y = U M = 3 - H = markov(Y,U,M) + H = markov(Y, U, M, transpose=False) Htrue = np.matrix("1.; 0.; 0.") np.testing.assert_array_almost_equal( H, Htrue ) + # Make sure that transposed data also works + H = markov(np.transpose(Y), np.transpose(U), M, transpose=True) + np.testing.assert_array_almost_equal( H, Htrue ) + + # Default (in v0.8.4 and below) should be transpose=True + H = markov(np.transpose(Y), np.transpose(U), M) + np.testing.assert_array_almost_equal( H, Htrue ) + def testModredMatchDC(self): #balanced realization computed in matlab for the transfer function: # num = [1 11 45 32], den = [1 15 60 200 60] From bb15b8541d2ed9ab97cb33e5468ad02f9d0dc6e3 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 26 Dec 2020 08:23:36 -0800 Subject: [PATCH 2/3] updated markov calculation + new unit tests --- control/modelsimp.py | 136 +++++++++++++++++++++----- control/tests/modelsimp_array_test.py | 75 ++++++++++++-- control/tests/modelsimp_test.py | 12 +-- 3 files changed, 179 insertions(+), 44 deletions(-) diff --git a/control/modelsimp.py b/control/modelsimp.py index 9f21c4e25..16a196ef7 100644 --- a/control/modelsimp.py +++ b/control/modelsimp.py @@ -46,7 +46,8 @@ # External packages and modules import numpy as np import warnings -from .exception import ControlSlycot, ControlMIMONotImplemented +from .exception import ControlSlycot, ControlMIMONotImplemented, \ + ControlDimension from .lti import isdtime, isctime from .statesp import StateSpace from .statefbk import gram @@ -394,10 +395,22 @@ def era(YY, m, n, nin, nout, r): raise NotImplementedError('This function is not implemented yet.') -def markov(Y, U, m, transpose=None): - """Calculate the first `M` Markov parameters [D CB CAB ...] +def markov(Y, U, m=None, transpose=None): + """Calculate the first `m` Markov parameters [D CB CAB ...] from input `U`, output `Y`. + This function computes the Markov parameters for a discrete time system + + .. math:: + + x[k+1] &= A x[k] + B u[k] \\\\ + y[k] &= C x[k] + D u[k] + + given data for u and y. The algorithm assumes that that C A^k B = 0 for + k > m-2 (see [1]). Note that the problem is ill-posed if the length of + the input data is less than the desired number of Markov parameters (a + warning message is generated in this case). + Parameters ---------- Y : array_like @@ -407,8 +420,8 @@ def markov(Y, U, m, transpose=None): time points. U : array_like Input data, arranged in the same way as `Y`. - m : int - Number of Markov parameters to output. + m : int, optional + Number of Markov parameters to output. Defaults to len(U). transpose : bool, optional Assume that input data is transposed relative to the standard :ref:`time-series-convention`. The default value is true for @@ -417,7 +430,14 @@ def markov(Y, U, m, transpose=None): Returns ------- H : ndarray - First m Markov parameters + First m Markov parameters, [D CB CAB ...] + + References + ---------- + .. [1] J.-N. Juang, M. Phan, L. G. Horta, and R. W. Longman, + Identification of observer/Kalman filter Markov parameters - Theory + and experiments. Journal of Guidance Control and Dynamics, 16(2), + 320–329, 2012. http://doi.org/10.2514/3.21006 Notes ----- @@ -432,8 +452,8 @@ def markov(Y, U, m, transpose=None): -------- >>> T = numpy.linspace(0, 10, 100) >>> U = numpy.ones((1, 100)) - >>> Y = forced_response(tf([1], [1, 1]), T, U) - >>> H = markov(Y, U, m, transpose=False) + >>> T, Y, _ = forced_response(tf([1], [1, 0.5], True), T, U) + >>> H = markov(Y, U, 3, transpose=False) """ # Check on the specified format of the input @@ -449,29 +469,93 @@ def markov(Y, U, m, transpose=None): Umat = np.array(U, ndmin=2) Ymat = np.array(Y, ndmin=2) - # Transpose the data from our normal convention to match algorithm - # TODO: rewrite the algorithm to use standard conventions - if not transpose: - Umat = np.transpose(Umat) - Ymat = np.transpose(Ymat) + # 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[1] != 1 or Ymat.shape[1] != 1: + if Umat.shape[0] != 1 or Ymat.shape[0] != 1: raise ControlMIMONotImplemented - n = Umat.shape[0] - # Construct a matrix of control inputs to invert + # 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] + + # If number of desired parameters was not given, set to size of input data + if m is None: + m = Umat.shape[1] + + # Make sure there is enough data to compute parameters + if m > n: + warn.warning("Not enough data for requested number of parameters") + + # + # 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 + # + # This algorithm sets up the following problem and solves it for + # the Markov parameters + # + # [ 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 ] + # + # 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 + # for j > m-2. See equation (3) in + # + # J.-N. Juang, M. Phan, L. G. Horta, and R. W. Longman, Identification + # of observer/Kalman filter Markov parameters - Theory and + # experiments. Journal of Guidance Control and Dynamics, 16(2), + # 320–329, 2012. http://doi.org/10.2514/3.21006 + # + + # Create matrix of (shifted) inputs UU = Umat - for i in range(1, m-1): - # TODO: second index on UU doesn't seem right; could be neg or pos?? - newCol = np.vstack((0, np.reshape(UU[0:n-1, i-2], (-1, 1)))) - UU = np.hstack((UU, newCol)) - Ulast = np.vstack((0, np.reshape(UU[0:n-1, m-2], (-1, 1)))) - for i in range(n-1, 0, -1): - Ulast[i] = np.sum(Ulast[0:i-1]) - UU = np.hstack((UU, Ulast)) + 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) # Invert and solve for Markov parameters - H = np.linalg.lstsq(UU, Ymat)[0] + YY = np.transpose(Ymat) + H, _, _, _ = np.linalg.lstsq(UU, YY, rcond=None) - return H + # Return the first m Markov parameters + return H if transpose else np.transpose(H) diff --git a/control/tests/modelsimp_array_test.py b/control/tests/modelsimp_array_test.py index b78d5e22c..dbd6a5796 100644 --- a/control/tests/modelsimp_array_test.py +++ b/control/tests/modelsimp_array_test.py @@ -49,22 +49,41 @@ def testHSVD(self): # Go back to using the normal np.array representation control.use_numpy_matrix(False) - def testMarkov(self): + def testMarkovSignature(self): U = np.array([[1., 1., 1., 1., 1.]]) Y = U - M = 3 - H = markov(Y, U, M, transpose=False) - Htrue = np.array([[1.], [0.], [0.]]) + m = 3 + H = markov(Y, U, m, transpose=False) + Htrue = np.array([[1., 0., 0.]]) np.testing.assert_array_almost_equal( H, Htrue ) # Make sure that transposed data also works - H = markov(np.transpose(Y), np.transpose(U), M, transpose=True) + H = markov(np.transpose(Y), np.transpose(U), m, transpose=True) + np.testing.assert_array_almost_equal( H, np.transpose(Htrue) ) + + # Default (in v0.8.4 and below) should be transpose=True (w/ warning) + import warnings + warnings.simplefilter('always', UserWarning) # don't supress + with warnings.catch_warnings(record=True) as w: + # Set up warnings filter to only show warnings in control module + warnings.filterwarnings("ignore") + warnings.filterwarnings("always", module="control") + + # Generate Markov parameters without any arguments + H = markov(np.transpose(Y), np.transpose(U), m) + np.testing.assert_array_almost_equal( H, np.transpose(Htrue) ) + + # Make sure we got a warning + self.assertEqual(len(w), 1) + self.assertIn("assumed to be in rows", str(w[-1].message)) + self.assertIn("change in a future release", str(w[-1].message)) # Test example from docstring T = np.linspace(0, 10, 100) U = np.ones((1, 100)) - _, Y, _ = control.forced_response(control.tf([1], [1, 1]), T, U) - H = markov(Y, U, M, transpose=False) + T, Y, _ = control.forced_response( + control.tf([1], [1, 0.5], True), T, U) + H = markov(Y, U, 3, transpose=False) # Test example from issue #395 inp = np.array([1, 2]) @@ -73,7 +92,47 @@ def testMarkov(self): # Make sure MIMO generates an error U = np.ones((2, 100)) # 2 inputs (Y unchanged, with 1 output) - np.testing.assert_raises(ControlMIMONotImplemented, markov, U, Y, M) + np.testing.assert_raises(ControlMIMONotImplemented, markov, Y, U, m) + + # Make sure markov() returns the right answer + def testMarkovResults(self): + # + # Test over a range of parameters + # + # k = order of the system + # m = number of Markov parameters + # n = size of the data vector + # + # Values should match exactly for n = m, otherewise you get a + # close match but errors due to the assumption that C A^k B = + # 0 for k > m-2 (see modelsimp.py). + # + for k, m, n in \ + ((2, 2, 2), (2, 5, 5), (5, 2, 2), (5, 5, 5), (5, 10, 10)): + + # Generate stable continuous time system + Hc = control.rss(k, 1, 1) + + # Choose sampling time based on fastest time constant / 10 + w, _ = np.linalg.eig(Hc.A) + Ts = np.min(-np.real(w)) / 10. + + # Convert to a discrete time system via sampling + Hd = control.c2d(Hc, Ts, 'zoh') + + # Compute the Markov parameters from state space + Mtrue = np.hstack([Hd.D] + [np.dot( + Hd.C, np.dot(np.linalg.matrix_power(Hd.A, i), + Hd.B)) for i in range(m-1)]) + + # Generate input/output data + T = np.array(range(n)) * Ts + U = np.cos(T) + np.sin(T/np.pi) + _, Y, _ = control.forced_response(Hd, T, U, squeeze=True) + Mcomp = markov(Y, U, m, transpose=False) + + # Compare to results from markov() + np.testing.assert_array_almost_equal(Mtrue, Mcomp) def testModredMatchDC(self): #balanced realization computed in matlab for the transfer function: diff --git a/control/tests/modelsimp_test.py b/control/tests/modelsimp_test.py index 50cf884bb..c0ba72a3b 100644 --- a/control/tests/modelsimp_test.py +++ b/control/tests/modelsimp_test.py @@ -22,21 +22,13 @@ def testHSVD(self): np.testing.assert_array_almost_equal(hsv, hsvtrue) def testMarkov(self): - U = np.matrix("1., 1., 1., 1., 1.") + U = np.matrix("1.; 1.; 1.; 1.; 1.") Y = U M = 3 - H = markov(Y, U, M, transpose=False) + H = markov(Y, U, M) Htrue = np.matrix("1.; 0.; 0.") np.testing.assert_array_almost_equal( H, Htrue ) - # Make sure that transposed data also works - H = markov(np.transpose(Y), np.transpose(U), M, transpose=True) - np.testing.assert_array_almost_equal( H, Htrue ) - - # Default (in v0.8.4 and below) should be transpose=True - H = markov(np.transpose(Y), np.transpose(U), M) - np.testing.assert_array_almost_equal( H, Htrue ) - def testModredMatchDC(self): #balanced realization computed in matlab for the transfer function: # num = [1 11 45 32], den = [1 15 60 200 60] From 438c17810faf7918a4a6ca5ae51c2c36c069e460 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 26 Dec 2020 09:53:24 -0800 Subject: [PATCH 3/3] TRV: remove non-essential non-ASCII character for Python 2.7 compatibility --- control/modelsimp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control/modelsimp.py b/control/modelsimp.py index 16a196ef7..4cfcf4048 100644 --- a/control/modelsimp.py +++ b/control/modelsimp.py @@ -437,7 +437,7 @@ def markov(Y, U, m=None, transpose=None): .. [1] J.-N. Juang, M. Phan, L. G. Horta, and R. W. Longman, Identification of observer/Kalman filter Markov parameters - Theory and experiments. Journal of Guidance Control and Dynamics, 16(2), - 320–329, 2012. http://doi.org/10.2514/3.21006 + 320-329, 2012. http://doi.org/10.2514/3.21006 Notes ----- @@ -542,7 +542,7 @@ def markov(Y, U, m=None, transpose=None): # J.-N. Juang, M. Phan, L. G. Horta, and R. W. Longman, Identification # of observer/Kalman filter Markov parameters - Theory and # experiments. Journal of Guidance Control and Dynamics, 16(2), - # 320–329, 2012. http://doi.org/10.2514/3.21006 + # 320-329, 2012. http://doi.org/10.2514/3.21006 # # Create matrix of (shifted) inputs pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy