def testConvert(self): """Test state space to transfer function conversion.""" verbose = self.debug from control.statesp import _mimo2siso #print __doc__ # Machine precision for floats. eps = np.finfo(float).eps for states in range(1, self.maxStates): for inputs in range(1, self.maxIO): for outputs in range(1, self.maxIO): # start with a random SS system and transform to TF then # back to SS, check that the matrices are the same. ssOriginal = matlab.rss(states, outputs, inputs) if (verbose): self.printSys(ssOriginal, 1) # Make sure the system is not degenerate Cmat = control.ctrb(ssOriginal.A, ssOriginal.B) if (np.linalg.matrix_rank(Cmat) != states): if (verbose): print(" skipping (not reachable)") continue Omat = control.obsv(ssOriginal.A, ssOriginal.C) if (np.linalg.matrix_rank(Omat) != states): if (verbose): print(" skipping (not observable)") continue tfOriginal = matlab.tf(ssOriginal) if (verbose): self.printSys(tfOriginal, 2) ssTransformed = matlab.ss(tfOriginal) if (verbose): self.printSys(ssTransformed, 3) tfTransformed = matlab.tf(ssTransformed) if (verbose): self.printSys(tfTransformed, 4) # Check to see if the state space systems have same dim if (ssOriginal.states != ssTransformed.states): print("WARNING: state space dimension mismatch: " + \ "%d versus %d" % \ (ssOriginal.states, ssTransformed.states)) # Now make sure the frequency responses match # Since bode() only handles SISO, go through each I/O pair # For phase, take sine and cosine to avoid +/- 360 offset for inputNum in range(inputs): for outputNum in range(outputs): if (verbose): print("Checking input %d, output %d" \ % (inputNum, outputNum)) ssorig_mag, ssorig_phase, ssorig_omega = \ control.bode(_mimo2siso(ssOriginal, \ inputNum, outputNum), \ deg=False, Plot=False) ssorig_real = ssorig_mag * np.cos(ssorig_phase) ssorig_imag = ssorig_mag * np.sin(ssorig_phase) # # Make sure TF has same frequency response # num = tfOriginal.num[outputNum][inputNum] den = tfOriginal.den[outputNum][inputNum] tforig = control.tf(num, den) tforig_mag, tforig_phase, tforig_omega = \ control.bode(tforig, ssorig_omega, \ deg=False, Plot=False) tforig_real = tforig_mag * np.cos(tforig_phase) tforig_imag = tforig_mag * np.sin(tforig_phase) np.testing.assert_array_almost_equal( \ ssorig_real, tforig_real) np.testing.assert_array_almost_equal( \ ssorig_imag, tforig_imag) # # Make sure xform'd SS has same frequency response # ssxfrm_mag, ssxfrm_phase, ssxfrm_omega = \ control.bode(_mimo2siso(ssTransformed, \ inputNum, outputNum), \ ssorig_omega, \ deg=False, Plot=False) ssxfrm_real = ssxfrm_mag * np.cos(ssxfrm_phase) ssxfrm_imag = ssxfrm_mag * np.sin(ssxfrm_phase) np.testing.assert_array_almost_equal( \ ssorig_real, ssxfrm_real) np.testing.assert_array_almost_equal( \ ssorig_imag, ssxfrm_imag) # # Make sure xform'd TF has same frequency response # num = tfTransformed.num[outputNum][inputNum] den = tfTransformed.den[outputNum][inputNum] tfxfrm = control.tf(num, den) tfxfrm_mag, tfxfrm_phase, tfxfrm_omega = \ control.bode(tfxfrm, ssorig_omega, \ deg=False, Plot=False) tfxfrm_real = tfxfrm_mag * np.cos(tfxfrm_phase) tfxfrm_imag = tfxfrm_mag * np.sin(tfxfrm_phase) np.testing.assert_array_almost_equal( \ ssorig_real, tfxfrm_real) np.testing.assert_array_almost_equal( \ ssorig_imag, tfxfrm_imag)
def test_convert_MIMO_to_SISO(self): '''Convert mimo to siso systems''' #Test with our usual systems -------------------------------------------- #SISO PT2 system As, Bs, Cs, Ds = self.make_SISO_mats() sys_siso = ss(As, Bs, Cs, Ds) #MIMO system that contains two independent copies of the SISO system above Am, Bm, Cm, Dm = self.make_MIMO_mats() sys_mimo = ss(Am, Bm, Cm, Dm) # t, y = step(sys_siso) # plot(t, y, label='sys_siso d=0') sys_siso_00 = _mimo2siso(sys_mimo, input=0, output=0, warn_conversion=False) sys_siso_11 = _mimo2siso(sys_mimo, input=1, output=1, warn_conversion=False) #print("sys_siso_00 ---------------------------------------------") #print(sys_siso_00) #print("sys_siso_11 ---------------------------------------------") #print(sys_siso_11) #gain of converted system and equivalent SISO system must be the same self.assert_systems_behave_equal(sys_siso, sys_siso_00) self.assert_systems_behave_equal(sys_siso, sys_siso_11) #Test with additional systems -------------------------------------------- #They have crossed inputs and direct feedthrough #SISO system As = matrix([[-81.82, -45.45], [10., -1.]]) Bs = matrix([[9.09], [0.]]) Cs = matrix([[0, 0.159]]) Ds = matrix([[0.02]]) sys_siso = ss(As, Bs, Cs, Ds) # t, y = step(sys_siso) # plot(t, y, label='sys_siso d=0.02') # legend(loc='best') #MIMO system #The upper left sub-system uses : input 0, output 1 #The lower right sub-system uses: input 1, output 0 Am = array([[-81.82, -45.45, 0, 0], [10, -1, 0, 0], [0, 0, -81.82, -45.45], [ 0, 0, 10, -1, ]]) Bm = array([[9.09, 0], [0, 0], [0, 9.09], [0, 0]]) Cm = array([[0, 0, 0, 0.159], [0, 0.159, 0, 0]]) Dm = matrix([[0, 0.02], [0.02, 0]]) sys_mimo = ss(Am, Bm, Cm, Dm) sys_siso_01 = _mimo2siso(sys_mimo, input=0, output=1, warn_conversion=False) sys_siso_10 = _mimo2siso(sys_mimo, input=1, output=0, warn_conversion=False) # print("sys_siso_01 ---------------------------------------------") # print(sys_siso_01) # print("sys_siso_10 ---------------------------------------------") # print(sys_siso_10) #gain of converted system and equivalent SISO system must be the same self.assert_systems_behave_equal(sys_siso, sys_siso_01) self.assert_systems_behave_equal(sys_siso, sys_siso_10)
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 testConvert(self): """Test state space to transfer function conversion.""" verbose = self.debug # print __doc__ # Machine precision for floats. # eps = np.finfo(float).eps for states in range(1, self.maxStates): for inputs in range(1, self.maxIO): for outputs in range(1, self.maxIO): # start with a random SS system and transform to TF then # back to SS, check that the matrices are the same. ssOriginal = matlab.rss(states, outputs, inputs) if (verbose): self.printSys(ssOriginal, 1) # Make sure the system is not degenerate Cmat = ctrb(ssOriginal.A, ssOriginal.B) if (np.linalg.matrix_rank(Cmat) != states): if (verbose): print(" skipping (not reachable)") continue Omat = obsv(ssOriginal.A, ssOriginal.C) if (np.linalg.matrix_rank(Omat) != states): if (verbose): print(" skipping (not observable)") continue tfOriginal = matlab.tf(ssOriginal) if (verbose): self.printSys(tfOriginal, 2) ssTransformed = matlab.ss(tfOriginal) if (verbose): self.printSys(ssTransformed, 3) tfTransformed = matlab.tf(ssTransformed) if (verbose): self.printSys(tfTransformed, 4) # Check to see if the state space systems have same dim if (ssOriginal.states != ssTransformed.states): print("WARNING: state space dimension mismatch: " + \ "%d versus %d" % \ (ssOriginal.states, ssTransformed.states)) # Now make sure the frequency responses match # Since bode() only handles SISO, go through each I/O pair # For phase, take sine and cosine to avoid +/- 360 offset for inputNum in range(inputs): for outputNum in range(outputs): if (verbose): print("Checking input %d, output %d" \ % (inputNum, outputNum)) ssorig_mag, ssorig_phase, ssorig_omega = \ bode(_mimo2siso(ssOriginal, \ inputNum, outputNum), \ deg=False, plot=False) ssorig_real = ssorig_mag * np.cos(ssorig_phase) ssorig_imag = ssorig_mag * np.sin(ssorig_phase) # # Make sure TF has same frequency response # num = tfOriginal.num[outputNum][inputNum] den = tfOriginal.den[outputNum][inputNum] tforig = tf(num, den) tforig_mag, tforig_phase, tforig_omega = \ bode(tforig, ssorig_omega, \ deg=False, plot=False) tforig_real = tforig_mag * np.cos(tforig_phase) tforig_imag = tforig_mag * np.sin(tforig_phase) np.testing.assert_array_almost_equal( \ ssorig_real, tforig_real) np.testing.assert_array_almost_equal( \ ssorig_imag, tforig_imag) # # Make sure xform'd SS has same frequency response # ssxfrm_mag, ssxfrm_phase, ssxfrm_omega = \ bode(_mimo2siso(ssTransformed, \ inputNum, outputNum), \ ssorig_omega, \ deg=False, plot=False) ssxfrm_real = ssxfrm_mag * np.cos(ssxfrm_phase) ssxfrm_imag = ssxfrm_mag * np.sin(ssxfrm_phase) np.testing.assert_array_almost_equal( \ ssorig_real, ssxfrm_real) np.testing.assert_array_almost_equal( \ ssorig_imag, ssxfrm_imag) # # Make sure xform'd TF has same frequency response # num = tfTransformed.num[outputNum][inputNum] den = tfTransformed.den[outputNum][inputNum] tfxfrm = tf(num, den) tfxfrm_mag, tfxfrm_phase, tfxfrm_omega = \ bode(tfxfrm, ssorig_omega, \ deg=False, plot=False) tfxfrm_real = tfxfrm_mag * np.cos(tfxfrm_phase) tfxfrm_imag = tfxfrm_mag * np.sin(tfxfrm_phase) np.testing.assert_array_almost_equal( \ ssorig_real, tfxfrm_real) np.testing.assert_array_almost_equal( \ ssorig_imag, tfxfrm_imag)
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 test_convert_MIMO_to_SISO(self): '''Convert mimo to siso systems''' #Test with our usual systems -------------------------------------------- #SISO PT2 system As, Bs, Cs, Ds = self.make_SISO_mats() sys_siso = ss(As, Bs, Cs, Ds) #MIMO system that contains two independent copies of the SISO system above Am, Bm, Cm, Dm = self.make_MIMO_mats() sys_mimo = ss(Am, Bm, Cm, Dm) # t, y = step(sys_siso) # plot(t, y, label='sys_siso d=0') sys_siso_00 = _mimo2siso(sys_mimo, input=0, output=0, warn_conversion=False) sys_siso_11 = _mimo2siso(sys_mimo, input=1, output=1, warn_conversion=False) #print("sys_siso_00 ---------------------------------------------") #print(sys_siso_00) #print("sys_siso_11 ---------------------------------------------") #print(sys_siso_11) #gain of converted system and equivalent SISO system must be the same self.assert_systems_behave_equal(sys_siso, sys_siso_00) self.assert_systems_behave_equal(sys_siso, sys_siso_11) #Test with additional systems -------------------------------------------- #They have crossed inputs and direct feedthrough #SISO system As = matrix([[-81.82, -45.45], [ 10., -1. ]]) Bs = matrix([[9.09], [0. ]]) Cs = matrix([[0, 0.159]]) Ds = matrix([[0.02]]) sys_siso = ss(As, Bs, Cs, Ds) # t, y = step(sys_siso) # plot(t, y, label='sys_siso d=0.02') # legend(loc='best') #MIMO system #The upper left sub-system uses : input 0, output 1 #The lower right sub-system uses: input 1, output 0 Am = array([[-81.82, -45.45, 0, 0 ], [ 10, -1, 0, 0 ], [ 0, 0, -81.82, -45.45], [ 0, 0, 10, -1, ]]) Bm = array([[9.09, 0 ], [0 , 0 ], [0 , 9.09], [0 , 0 ]]) Cm = array([[0, 0, 0, 0.159], [0, 0.159, 0, 0 ]]) Dm = matrix([[0, 0.02], [0.02, 0 ]]) sys_mimo = ss(Am, Bm, Cm, Dm) sys_siso_01 = _mimo2siso(sys_mimo, input=0, output=1, warn_conversion=False) sys_siso_10 = _mimo2siso(sys_mimo, input=1, output=0, warn_conversion=False) print("sys_siso_01 ---------------------------------------------") print(sys_siso_01) print("sys_siso_10 ---------------------------------------------") print(sys_siso_10) #gain of converted system and equivalent SISO system must be the same self.assert_systems_behave_equal(sys_siso, sys_siso_01) self.assert_systems_behave_equal(sys_siso, sys_siso_10)
def testConvert(self, fixedseed, states, inputs, outputs): """Test state space to transfer function conversion. start with a random SS system and transform to TF then back to SS, check that the matrices are the same. """ ssOriginal = rss(states, outputs, inputs) if verbose: self.printSys(ssOriginal, 1) # Make sure the system is not degenerate Cmat = ctrb(ssOriginal.A, ssOriginal.B) if (np.linalg.matrix_rank(Cmat) != states): pytest.skip("not reachable") Omat = obsv(ssOriginal.A, ssOriginal.C) if (np.linalg.matrix_rank(Omat) != states): pytest.skip("not observable") tfOriginal = tf(ssOriginal) if (verbose): self.printSys(tfOriginal, 2) ssTransformed = ss(tfOriginal) if (verbose): self.printSys(ssTransformed, 3) tfTransformed = tf(ssTransformed) if (verbose): self.printSys(tfTransformed, 4) # Check to see if the state space systems have same dim if (ssOriginal.nstates != ssTransformed.nstates) and verbose: print("WARNING: state space dimension mismatch: %d versus %d" % (ssOriginal.nstates, ssTransformed.nstates)) # Now make sure the frequency responses match # Since bode() only handles SISO, go through each I/O pair # For phase, take sine and cosine to avoid +/- 360 offset for inputNum in range(inputs): for outputNum in range(outputs): if (verbose): print("Checking input %d, output %d" % (inputNum, outputNum)) ssorig_mag, ssorig_phase, ssorig_omega = \ bode(_mimo2siso(ssOriginal, inputNum, outputNum), deg=False, plot=False) ssorig_real = ssorig_mag * np.cos(ssorig_phase) ssorig_imag = ssorig_mag * np.sin(ssorig_phase) # # Make sure TF has same frequency response # num = tfOriginal.num[outputNum][inputNum] den = tfOriginal.den[outputNum][inputNum] tforig = tf(num, den) tforig_mag, tforig_phase, tforig_omega = \ bode(tforig, ssorig_omega, deg=False, plot=False) tforig_real = tforig_mag * np.cos(tforig_phase) tforig_imag = tforig_mag * np.sin(tforig_phase) np.testing.assert_array_almost_equal( ssorig_real, tforig_real) np.testing.assert_array_almost_equal( ssorig_imag, tforig_imag) # # Make sure xform'd SS has same frequency response # ssxfrm_mag, ssxfrm_phase, ssxfrm_omega = \ bode(_mimo2siso(ssTransformed, inputNum, outputNum), ssorig_omega, deg=False, plot=False) ssxfrm_real = ssxfrm_mag * np.cos(ssxfrm_phase) ssxfrm_imag = ssxfrm_mag * np.sin(ssxfrm_phase) np.testing.assert_array_almost_equal( ssorig_real, ssxfrm_real, decimal=5) np.testing.assert_array_almost_equal( ssorig_imag, ssxfrm_imag, decimal=5) # Make sure xform'd TF has same frequency response # num = tfTransformed.num[outputNum][inputNum] den = tfTransformed.den[outputNum][inputNum] tfxfrm = tf(num, den) tfxfrm_mag, tfxfrm_phase, tfxfrm_omega = \ bode(tfxfrm, ssorig_omega, deg=False, plot=False) tfxfrm_real = tfxfrm_mag * np.cos(tfxfrm_phase) tfxfrm_imag = tfxfrm_mag * np.sin(tfxfrm_phase) np.testing.assert_array_almost_equal( ssorig_real, tfxfrm_real, decimal=5) np.testing.assert_array_almost_equal( ssorig_imag, tfxfrm_imag, decimal=5)
def impulse_response(sys, T=None, X0=0., input=0, output=None, 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. Set to None to not trim outputs 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) if output == None: sys = _mimo2simo(sys, input, warn_conversion=True) else: 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=None, 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. Set to None to not trim outputs 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) if output == None: sys = _mimo2simo(sys, input, warn_conversion=False) else: sys = _mimo2siso(sys, input, output, warn_conversion=False) # 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