def test_MatrixToStateSpace(self): """_convertToStateSpace(matrix) gives ss([],[],[],D)""" D = np.matrix([[1,2,3],[4,5,6]]) g = _convertToStateSpace(D) def empty(shape): m = np.matrix([]) m.shape = shape return m np.testing.assert_array_equal(empty((0,0)), g.A) np.testing.assert_array_equal(empty((0,D.shape[1])), g.B) np.testing.assert_array_equal(empty((D.shape[0],0)), g.C) np.testing.assert_array_equal(D,g.D)
def ssdata(sys): ''' Return state space data objects for a system Parameters ---------- sys: Lti (StateSpace, or TransferFunction) LTI system whose data will be returned Returns ------- (A, B, C, D): list of matrices State space data for the system ''' ss = _convertToStateSpace(sys) return (ss.A, ss.B, ss.C, ss.D)
def testMIMO(self): """Test conversion of a single input, two-output state-space system against the same TF""" s = TransferFunction([1, 0], [1]) b0 = 0.2 b1 = 0.1 b2 = 0.5 a0 = 2.3 a1 = 6.3 a2 = 3.6 a3 = 1.0 h = (b0 + b1 * s + b2 * s ** 2) / (a0 + a1 * s + a2 * s ** 2 + a3 * s ** 3) H = TransferFunction([[h.num[0][0]], [(h * s).num[0][0]]], [[h.den[0][0]], [h.den[0][0]]]) sys = _convertToStateSpace(H) H2 = _convertToTransferFunction(sys) np.testing.assert_array_almost_equal(H.num[0][0], H2.num[0][0]) np.testing.assert_array_almost_equal(H.den[0][0], H2.den[0][0]) np.testing.assert_array_almost_equal(H.num[1][0], H2.num[1][0]) np.testing.assert_array_almost_equal(H.den[1][0], H2.den[1][0])
def testAppendTF(self): """Test appending a state-space system with a tf""" A1 = [[-2, 0.5, 0], [0.5, -0.3, 0], [0, 0, -0.1]] B1 = [[0.3, -1.3], [0.1, 0.], [1.0, 0.0]] C1 = [[0., 0.1, 0.0], [-0.3, -0.2, 0.0]] D1 = [[0., -0.8], [-0.3, 0.]] s = TransferFunction([1, 0], [1]) h = 1/(s+1)/(s+2) sys1 = StateSpace(A1, B1, C1, D1) sys2 = _convertToStateSpace(h) sys3c = sys1.append(sys2) np.testing.assert_array_almost_equal(sys1.A, sys3c.A[:3,:3]) np.testing.assert_array_almost_equal(sys1.B, sys3c.B[:3,:2]) np.testing.assert_array_almost_equal(sys1.C, sys3c.C[:2,:3]) np.testing.assert_array_almost_equal(sys1.D, sys3c.D[:2,:2]) np.testing.assert_array_almost_equal(sys2.A, sys3c.A[3:,3:]) np.testing.assert_array_almost_equal(sys2.B, sys3c.B[3:,2:]) np.testing.assert_array_almost_equal(sys2.C, sys3c.C[2:,3:]) np.testing.assert_array_almost_equal(sys2.D, sys3c.D[2:,2:]) np.testing.assert_array_almost_equal(sys3c.A[:3,3:], np.zeros( (3, 2)) ) np.testing.assert_array_almost_equal(sys3c.A[3:,:3], np.zeros( (2, 3)) )
def tf2ss(*args): """ Transform a transfer function to a state space system. The function accepts either 1 or 2 parameters: ``tf2ss(sys)`` Convert a linear system into transfer function form. Always creates a new system, even if sys is already a TransferFunction object. ``tf2ss(num, den)`` Create a transfer function system from its numerator and denominator polynomial coefficients. For details see: :func:`tf` Parameters ---------- sys: Lti (StateSpace or TransferFunction) A linear system num: array_like, or list of list of array_like Polynomial coefficients of the numerator den: array_like, or list of list of array_like Polynomial coefficients of the denominator Returns ------- out: StateSpace New linear system in state space form Raises ------ ValueError if `num` and `den` have invalid or unequal dimensions, or if an invalid number of arguments is passed in TypeError if `num` or `den` are of incorrect type, or if sys is not a TransferFunction object See Also -------- ss tf ss2tf Examples -------- >>> num = [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]]] >>> den = [[[9., 8., 7.], [6., 5., 4.]], [[3., 2., 1.], [-1., -2., -3.]]] >>> sys1 = tf2ss(num, den) >>> sys_tf = tf(num, den) >>> sys2 = tf2ss(sys_tf) """ if len(args) == 2 or len(args) == 3: # Assume we were given the num, den return _convertToStateSpace(TransferFunction(*args)) elif len(args) == 1: sys = args[0] if not isinstance(sys, TransferFunction): raise TypeError("tf2ss(sys): sys must be a TransferFunction \ object.") return _convertToStateSpace(sys) else: raise ValueError("Needs 1 or 2 arguments; received %i." % len(args))
def feedback(sys1, sys2=1, sign=-1): """ Feedback interconnection between two I/O systems. Parameters ---------- sys1: scalar, StateSpace, or TransferFunction The primary plant. sys2: scalar, StateSpace, or TransferFunction The feedback plant (often a feedback controller). sign: scalar The sign of feedback. `sign` = -1 indicates negative feedback, and `sign` = 1 indicates positive feedback. `sign` is an optional argument; it assumes a value of -1 if not specified. Returns ------- out: StateSpace or TransferFunction Raises ------ ValueError if `sys1` does not have as many inputs as `sys2` has outputs, or if `sys2` does not have as many inputs as `sys1` has outputs NotImplementedError if an attempt is made to perform a feedback on a MIMO TransferFunction object See Also -------- series parallel Notes ----- This function is a wrapper for the feedback function in the StateSpace and TransferFunction classes. It calls TransferFunction.feedback if `sys1` is a TransferFunction object, and StateSpace.feedback if `sys1` is a StateSpace object. If `sys1` is a scalar, then it is converted to `sys2`'s type, and the corresponding feedback function is used. If `sys1` and `sys2` are both scalars, then TransferFunction.feedback is used. """ # Check for correct input types. if not isinstance(sys1, (int, float, complex, tf.TransferFunction, ss.StateSpace)): raise TypeError("sys1 must be a TransferFunction or StateSpace object, \ or a scalar.") if not isinstance(sys2, (int, float, complex, tf.TransferFunction, ss.StateSpace)): raise TypeError("sys2 must be a TransferFunction or StateSpace object, \ or a scalar.") # If sys1 is a scalar, convert it to the appropriate LTI type so that we can # its feedback member function. if isinstance(sys1, (int, float, complex)): if isinstance(sys2, tf.TransferFunction): sys1 = tf._convertToTransferFunction(sys1) elif isinstance(sys2, ss.StateSpace): sys1 = ss._convertToStateSpace(sys1) else: # sys2 is a scalar. sys1 = tf._convertToTransferFunction(sys1) sys2 = tf._convertToTransferFunction(sys2) return sys1.feedback(sys2, sign)
def impulse_response(sys, T=None, X0=0., input=0, output=0, transpose=False, **keywords): #pylint: disable=W0622 """Impulse response of a linear system If the system has multiple inputs or outputs (MIMO), one input and one output have to be selected for the simulation. The parameters `input` and `output` do this. All other inputs are set to 0, all other outputs are ignored. For information on the **shape** of parameters `T`, `X0` and return values `T`, `yout` see: :ref:`time-series-convention` Parameters ---------- sys: StateSpace, TransferFunction LTI system to simulate T: array-like object, optional Time vector (argument is autocomputed if not given) X0: array-like object or number, optional Initial condition (default = 0) Numbers are converted to constant arrays with the correct shape. input: int Index of the input that will be used in this simulation. output: int Index of the output that will be used in this simulation. transpose: bool If True, transpose all input and output arrays (for backward compatibility with MATLAB and scipy.signal.lsim) **keywords: Additional keyword arguments control the solution algorithm for the differential equations. These arguments are passed on to the function :func:`lsim`, which in turn passes them on to :func:`scipy.integrate.odeint`. See the documentation for :func:`scipy.integrate.odeint` for information about these arguments. Returns ------- T: array Time values of the output yout: array Response of the system See Also -------- ForcedReponse, initial_response, step_response Examples -------- >>> T, yout = impulse_response(sys, T, X0) """ sys = _convertToStateSpace(sys) sys = _mimo2siso(sys, input, output, warn_conversion=True) # System has direct feedthrough, can't simulate impulse response numerically if np.any(sys.D != 0) and isctime(sys): warnings.warn('System has direct feedthrough: ``D != 0``. The infinite ' 'impulse at ``t=0`` does not appear in the output. \n' 'Results may be meaningless!') # create X0 if not given, test if X0 has correct shape. # Must be done here because it is used for computations here. n_states = sys.A.shape[0] X0 = _check_convert_array(X0, [(n_states,), (n_states,1)], 'Parameter ``X0``: \n', squeeze=True) # Compute new X0 that contains the impulse # We can't put the impulse into U because there is no numerical # representation for it (infinitesimally short, infinitely high). # See also: http://www.mathworks.com/support/tech-notes/1900/1901.html B = np.asarray(sys.B).squeeze() new_X0 = B + X0 # Compute T and U, no checks necessary, they will be checked in lsim if T is None: T = _default_response_times(sys.A, 100) U = np.zeros_like(T) T, yout, _xout = forced_response(sys, T, U, new_X0, \ transpose=transpose, **keywords) return T, yout
def initial_response(sys, T=None, X0=0., input=0, output=0, transpose=False, **keywords): #pylint: disable=W0622 """Initial condition response of a linear system If the system has multiple inputs or outputs (MIMO), one input and one output have to be selected for the simulation. The parameters `input` and `output` do this. All other inputs are set to 0, all other outputs are ignored. For information on the **shape** of parameters `T`, `X0` and return values `T`, `yout` see: :ref:`time-series-convention` Parameters ---------- sys: StateSpace, or TransferFunction LTI system to simulate T: array-like object, optional Time vector (argument is autocomputed if not given) X0: array-like object or number, optional Initial condition (default = 0) Numbers are converted to constant arrays with the correct shape. input: int Index of the input that will be used in this simulation. output: int Index of the output that will be used in this simulation. transpose: bool If True, transpose all input and output arrays (for backward compatibility with MATLAB and scipy.signal.lsim) **keywords: Additional keyword arguments control the solution algorithm for the differential equations. These arguments are passed on to the function :func:`lsim`, which in turn passes them on to :func:`scipy.integrate.odeint`. See the documentation for :func:`scipy.integrate.odeint` for information about these arguments. Returns ------- T: array Time values of the output yout: array Response of the system See Also -------- forced_response, impulse_response, step_response Examples -------- >>> T, yout = initial_response(sys, T, X0) """ sys = _convertToStateSpace(sys) sys = _mimo2siso(sys, input, output, warn_conversion=True) # Create time and input vectors; checking is done in forced_response(...) # The initial vector X0 is created in forced_response(...) if necessary if T is None: T = _default_response_times(sys.A, 100) U = np.zeros_like(T) T, yout, _xout = forced_response(sys, T, U, X0, transpose=transpose, **keywords) return T, yout
def forced_response(sys, T=None, U=0., X0=0., transpose=False, **keywords): """Simulate the output of a linear system. As a convenience for parameters `U`, `X0`: Numbers (scalars) are converted to constant arrays with the correct shape. The correct shape is inferred from arguments `sys` and `T`. For information on the **shape** of parameters `U`, `T`, `X0` and return values `T`, `yout`, `xout` see: :ref:`time-series-convention` Parameters ---------- sys: Lti (StateSpace, or TransferFunction) LTI system to simulate T: array-like Time steps at which the input is defined, numbers must be (strictly monotonic) increasing. U: array-like or number, optional Input array giving input at each time `T` (default = 0). If `U` is ``None`` or ``0``, a special algorithm is used. This special algorithm is faster than the general algorithm, which is used otherwise. X0: array-like or number, optional Initial condition (default = 0). transpose: bool If True, transpose all input and output arrays (for backward compatibility with MATLAB and scipy.signal.lsim) **keywords: Additional keyword arguments control the solution algorithm for the differential equations. These arguments are passed on to the function :func:`scipy.integrate.odeint`. See the documentation for :func:`scipy.integrate.odeint` for information about these arguments. Returns ------- T: array Time values of the output. yout: array Response of the system. xout: array Time evolution of the state vector. See Also -------- step_response, initial_response, impulse_response Examples -------- >>> T, yout, xout = forced_response(sys, T, u, X0) """ if not isinstance(sys, Lti): raise TypeError('Parameter ``sys``: must be a ``Lti`` object. ' '(For example ``StateSpace`` or ``TransferFunction``)') sys = _convertToStateSpace(sys) A, B, C, D = np.asarray(sys.A), np.asarray(sys.B), np.asarray(sys.C), \ np.asarray(sys.D) # d_type = A.dtype n_states = A.shape[0] n_inputs = B.shape[1] # Set and/or check time vector in discrete time case if isdtime(sys, strict=True): if T == None: if U == None: raise ValueError('Parameters ``T`` and ``U`` can\'t both be' 'zero for discrete-time simulation') # Set T to integers with same length as U T = range(len(U)) else: # Make sure the input vector and time vector have same length # TODO: allow interpolation of the input vector if len(U) != len(T): ValueError('Pamameter ``T`` must have same length as' 'input vector ``U``') # Test if T has shape (n,) or (1, n); # T must be array-like and values must be increasing. # The length of T determines the length of the input vector. if T is None: raise ValueError('Parameter ``T``: must be array-like, and contain ' '(strictly monotonic) increasing numbers.') T = _check_convert_array(T, [('any',), (1,'any')], 'Parameter ``T``: ', squeeze=True, transpose = transpose) if not all(T[1:] - T[:-1] > 0): raise ValueError('Parameter ``T``: time values must be ' '(strictly monotonic) increasing numbers.') n_steps = len(T) # number of simulation steps #create X0 if not given, test if X0 has correct shape X0 = _check_convert_array(X0, [(n_states,), (n_states,1)], 'Parameter ``X0``: ', squeeze=True) # Separate out the discrete and continuous time cases if isctime(sys): # Solve the differential equation, copied from scipy.signal.ltisys. dot, squeeze, = np.dot, np.squeeze #Faster and shorter code # Faster algorithm if U is zero if U is None or (isinstance(U, (int, float)) and U == 0): # Function that computes the time derivative of the linear system def f_dot(x, _t): return dot(A,x) xout = sp.integrate.odeint(f_dot, X0, T, **keywords) yout = dot(C, xout.T) # General algorithm that interpolates U in between output points else: # Test if U has correct shape and type legal_shapes = [(n_steps,), (1,n_steps)] if n_inputs == 1 else \ [(n_inputs, n_steps)] U = _check_convert_array(U, legal_shapes, 'Parameter ``U``: ', squeeze=False, transpose=transpose) # convert 1D array to D2 array with only one row if len(U.shape) == 1: U = U.reshape(1,-1) #pylint: disable=E1103 # Create a callable that uses linear interpolation to # calculate the input at any time. compute_u = \ sp.interpolate.interp1d(T, U, kind='linear', copy=False, axis=-1, bounds_error=False, fill_value=0) # Function that computes the time derivative of the linear system def f_dot(x, t): return dot(A,x) + squeeze(dot(B,compute_u([t]))) xout = sp.integrate.odeint(f_dot, X0, T, **keywords) yout = dot(C, xout.T) + dot(D, U) yout = squeeze(yout) xout = xout.T else: # Discrete time simulation using signal processing toolbox dsys = (A, B, C, D, sys.dt) tout, yout, xout = sp.signal.dlsim(dsys, U, T, X0) # See if we need to transpose the data back into MATLAB form if (transpose): T = np.transpose(T) yout = np.transpose(yout) xout = np.transpose(xout) return T, yout, xout
def test_zero_empty(self): """Test to make sure zero() works with no zeros in system.""" sys = _convertToStateSpace(TransferFunction([1], [1, 2, 1])) np.testing.assert_array_equal(sys.zero(), np.array([]))
import numpy as np from control.matlab import tf from matplotlib import pyplot as plot from numpy.linalg import lstsq from control.statesp import _convertToStateSpace # Exact Process Model K = 10 numer = [1] denom = [5,1] sys1 = tf(numer,denom) #denominator = tf([[5,1+K]]) #Transformation to State-Space sys = _convertToStateSpace(sys1) A, B, C, D = np.asarray(sys.A), np.asarray(sys.B), np.asarray(sys.C), \ np.asarray(sys.D) Nstates = A.shape[0] z = np.zeros((Nstates, 1)) t_start = 0 t_end = 30 dt = 0.05 tspan = np.arange(t_start, t_end, dt) npoints = len(tspan) #List for storages yplot = np.zeros(npoints) ym_plot = np.zeros(npoints)
def forced_response(sys, T=None, U=0., X0=0., transpose=False, **keywords): """Simulate the output of a linear system. As a convenience for parameters `U`, `X0`: Numbers (scalars) are converted to constant arrays with the correct shape. The correct shape is inferred from arguments `sys` and `T`. For information on the **shape** of parameters `U`, `T`, `X0` and return values `T`, `yout`, `xout` see: :ref:`time-series-convention` Parameters ---------- sys: Lti (StateSpace, or TransferFunction) LTI system to simulate T: array-like Time steps at which the input is defined, numbers must be (strictly monotonic) increasing. U: array-like or number, optional Input array giving input at each time `T` (default = 0). If `U` is ``None`` or ``0``, a special algorithm is used. This special algorithm is faster than the general algorithm, which is used otherwise. X0: array-like or number, optional Initial condition (default = 0). transpose: bool If True, transpose all input and output arrays (for backward compatibility with MATLAB and scipy.signal.lsim) **keywords: Additional keyword arguments control the solution algorithm for the differential equations. These arguments are passed on to the function :func:`scipy.integrate.odeint`. See the documentation for :func:`scipy.integrate.odeint` for information about these arguments. Returns ------- T: array Time values of the output. yout: array Response of the system. xout: array Time evolution of the state vector. See Also -------- step_response, initial_response, impulse_response Examples -------- >>> T, yout, xout = forced_response(sys, T, u, X0) """ if not isinstance(sys, Lti): raise TypeError('Parameter ``sys``: must be a ``Lti`` object. ' '(For example ``StateSpace`` or ``TransferFunction``)') sys = _convertToStateSpace(sys) A, B, C, D = np.asarray(sys.A), np.asarray(sys.B), np.asarray(sys.C), \ np.asarray(sys.D) # d_type = A.dtype n_states = A.shape[0] n_inputs = B.shape[1] # Set and/or check time vector in discrete time case if isdtime(sys, strict=True): if T == None: if U == None: raise ValueError('Parameters ``T`` and ``U`` can\'t both be' 'zero for discrete-time simulation') # Set T to integers with same length as U T = range(len(U)) else: # Make sure the input vector and time vector have same length # TODO: allow interpolation of the input vector if len(U) != len(T): ValueError('Pamameter ``T`` must have same length as' 'input vector ``U``') # Test if T has shape (n,) or (1, n); # T must be array-like and values must be increasing. # The length of T determines the length of the input vector. if T is None: raise ValueError('Parameter ``T``: must be array-like, and contain ' '(strictly monotonic) increasing numbers.') T = _check_convert_array(T, [('any', ), (1, 'any')], 'Parameter ``T``: ', squeeze=True, transpose=transpose) if not all(T[1:] - T[:-1] > 0): raise ValueError('Parameter ``T``: time values must be ' '(strictly monotonic) increasing numbers.') n_steps = len(T) # number of simulation steps #create X0 if not given, test if X0 has correct shape X0 = _check_convert_array(X0, [(n_states, ), (n_states, 1)], 'Parameter ``X0``: ', squeeze=True) # Separate out the discrete and continuous time cases if isctime(sys): # Solve the differential equation, copied from scipy.signal.ltisys. dot, squeeze, = np.dot, np.squeeze #Faster and shorter code # Faster algorithm if U is zero if U is None or (isinstance(U, (int, float)) and U == 0): # Function that computes the time derivative of the linear system def f_dot(x, _t): return dot(A, x) xout = sp.integrate.odeint(f_dot, X0, T, **keywords) yout = dot(C, xout.T) # General algorithm that interpolates U in between output points else: # Test if U has correct shape and type legal_shapes = [(n_steps,), (1,n_steps)] if n_inputs == 1 else \ [(n_inputs, n_steps)] U = _check_convert_array(U, legal_shapes, 'Parameter ``U``: ', squeeze=False, transpose=transpose) # convert 1D array to D2 array with only one row if len(U.shape) == 1: U = U.reshape(1, -1) #pylint: disable=E1103 # Create a callable that uses linear interpolation to # calculate the input at any time. compute_u = \ sp.interpolate.interp1d(T, U, kind='linear', copy=False, axis=-1, bounds_error=False, fill_value=0) # Function that computes the time derivative of the linear system def f_dot(x, t): return dot(A, x) + squeeze(dot(B, compute_u([t]))) xout = sp.integrate.odeint(f_dot, X0, T, **keywords) yout = dot(C, xout.T) + dot(D, U) yout = squeeze(yout) xout = xout.T else: # Discrete time simulation using signal processing toolbox dsys = (A, B, C, D, sys.dt) tout, yout, xout = sp.signal.dlsim(dsys, U, T, X0) # See if we need to transpose the data back into MATLAB form if (transpose): T = np.transpose(T) yout = np.transpose(yout) xout = np.transpose(xout) return T, yout, xout