diff --git a/control/config.py b/control/config.py index ef3184a5e..a713e33d2 100644 --- a/control/config.py +++ b/control/config.py @@ -17,7 +17,6 @@ _control_defaults = { 'control.default_dt': 0, 'control.squeeze_frequency_response': None, - 'control.squeeze_time_response': True, 'control.squeeze_time_response': None, 'forced_response.return_x': False, } diff --git a/control/iosys.py b/control/iosys.py index d16cbce93..66ca20ebb 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -1428,8 +1428,9 @@ def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45', for i in range(len(T)): u = U[i] if len(U.shape) == 1 else U[:, i] y[:, i] = sys._out(T[i], [], u) - return _process_time_response(sys, T, y, [], transpose=transpose, - return_x=return_x, squeeze=squeeze) + return _process_time_response( + sys, T, y, np.array((0, 0, np.asarray(T).size)), + transpose=transpose, return_x=return_x, squeeze=squeeze) # create X0 if not given, test if X0 has correct shape X0 = _check_convert_array(X0, [(nstates,), (nstates, 1)], diff --git a/control/tests/matlab2_test.py b/control/tests/matlab2_test.py index 5db4156df..633ceef6f 100644 --- a/control/tests/matlab2_test.py +++ b/control/tests/matlab2_test.py @@ -121,25 +121,25 @@ def test_step(self, SISO_mats, MIMO_mats, mplcleanup): #print("gain:", dcgain(sys)) subplot2grid(plot_shape, (0, 0)) - t, y = step(sys) + y, t = step(sys) plot(t, y) subplot2grid(plot_shape, (0, 1)) T = linspace(0, 2, 100) X0 = array([1, 1]) - t, y = step(sys, T, X0) + y, t = step(sys, T, X0) plot(t, y) # Test output of state vector - t, y, x = step(sys, return_x=True) + y, t, x = step(sys, return_x=True) #Test MIMO system A, B, C, D = MIMO_mats sys = ss(A, B, C, D) subplot2grid(plot_shape, (0, 2)) - t, y = step(sys) - plot(t, y) + y, t = step(sys) + plot(t, y[:, 0, 0]) def test_impulse(self, SISO_mats, mplcleanup): A, B, C, D = SISO_mats @@ -168,8 +168,8 @@ def test_impulse_mimo(self, MIMO_mats, mplcleanup): #Test MIMO system A, B, C, D = MIMO_mats sys = ss(A, B, C, D) - t, y = impulse(sys) - plot(t, y, label='MIMO System') + y, t = impulse(sys) + plot(t, y[:, :, 0], label='MIMO System') legend(loc='best') #show() diff --git a/control/tests/timeresp_test.py b/control/tests/timeresp_test.py index e5c7471b6..4bf52b498 100644 --- a/control/tests/timeresp_test.py +++ b/control/tests/timeresp_test.py @@ -347,7 +347,7 @@ def test_impulse_response_mimo(self, mimo_ss2): yref_notrim = np.zeros((2, len(t))) yref_notrim[:1, :] = yref _t, yy = impulse_response(sys, T=t, input=0) - np.testing.assert_array_almost_equal(yy, yref_notrim, decimal=4) + np.testing.assert_array_almost_equal(yy[:,0,:], yref_notrim, decimal=4) @pytest.mark.skipif(StrictVersion(sp.__version__) < "1.3", reason="requires SciPy 1.3 or greater") @@ -639,9 +639,10 @@ def test_time_vector(self, tsystem, fun, squeeze, matarrayout): if hasattr(tsystem, 't'): # tout should always match t, which has shape (n, ) np.testing.assert_allclose(tout, tsystem.t) - if squeeze is False or sys.outputs > 1: + + if squeeze is False or not sys.issiso(): assert yout.shape[0] == sys.outputs - assert yout.shape[1] == tout.shape[0] + assert yout.shape[-1] == tout.shape[0] else: assert yout.shape == tout.shape @@ -725,21 +726,22 @@ def test_time_series_data_convention_2D(self, siso_ss1): @pytest.mark.usefixtures("editsdefaults") @pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.ss2io]) - @pytest.mark.parametrize("nstate, nout, ninp, squeeze, shape", [ - [1, 1, 1, None, (8,)], - [2, 1, 1, True, (8,)], - [3, 1, 1, False, (1, 8)], - [3, 2, 1, None, (2, 8)], - [4, 2, 1, True, (2, 8)], - [5, 2, 1, False, (2, 8)], - [3, 1, 2, None, (1, 8)], - [4, 1, 2, True, (8,)], - [5, 1, 2, False, (1, 8)], - [4, 2, 2, None, (2, 8)], - [5, 2, 2, True, (2, 8)], - [6, 2, 2, False, (2, 8)], + @pytest.mark.parametrize("nstate, nout, ninp, squeeze, shape1, shape2", [ + # state out in squeeze in/out out-only + [1, 1, 1, None, (8,), (8,)], + [2, 1, 1, True, (8,), (8,)], + [3, 1, 1, False, (1, 1, 8), (1, 8)], + [3, 2, 1, None, (2, 1, 8), (2, 8)], + [4, 2, 1, True, (2, 8), (2, 8)], + [5, 2, 1, False, (2, 1, 8), (2, 8)], + [3, 1, 2, None, (1, 2, 8), (1, 8)], + [4, 1, 2, True, (2, 8), (8,)], + [5, 1, 2, False, (1, 2, 8), (1, 8)], + [4, 2, 2, None, (2, 2, 8), (2, 8)], + [5, 2, 2, True, (2, 2, 8), (2, 8)], + [6, 2, 2, False, (2, 2, 8), (2, 8)], ]) - def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape): + def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape1, shape2): # Figure out if we have SciPy 1+ scipy0 = StrictVersion(sp.__version__) < '1.0' @@ -750,27 +752,56 @@ def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape): else: sys = fcn(ct.rss(nstate, nout, ninp, strictly_proper=True)) - # Keep track of expect users warnings - warntype = UserWarning if sys.inputs > 1 else None - # Generate the time and input vectors tvec = np.linspace(0, 1, 8) uvec = np.dot( np.ones((sys.inputs, 1)), np.reshape(np.sin(tvec), (1, 8))) + # # Pass squeeze argument and make sure the shape is correct - with pytest.warns(warntype, match="Converting MIMO system"): - _, yvec = ct.impulse_response(sys, tvec, squeeze=squeeze) - assert yvec.shape == shape + # + # For responses that are indexed by the input, check against shape1 + # For responses that have no/fixed input, check against shape2 + # - _, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze) - assert yvec.shape == shape + # Impulse response + if isinstance(sys, StateSpace): + # Check the states as well + _, yvec, xvec = ct.impulse_response( + sys, tvec, squeeze=squeeze, return_x=True) + if sys.issiso(): + assert xvec.shape == (sys.states, 8) + else: + assert xvec.shape == (sys.states, sys.inputs, 8) + else: + _, yvec = ct.impulse_response(sys, tvec, squeeze=squeeze) + assert yvec.shape == shape1 - with pytest.warns(warntype, match="Converting MIMO system"): + # Step response + if isinstance(sys, StateSpace): + # Check the states as well + _, yvec, xvec = ct.step_response( + sys, tvec, squeeze=squeeze, return_x=True) + if sys.issiso(): + assert xvec.shape == (sys.states, 8) + else: + assert xvec.shape == (sys.states, sys.inputs, 8) + else: _, yvec = ct.step_response(sys, tvec, squeeze=squeeze) - assert yvec.shape == shape + assert yvec.shape == shape1 + + # Initial response (only indexed by output) + if isinstance(sys, StateSpace): + # Check the states as well + _, yvec, xvec = ct.initial_response( + sys, tvec, 1, squeeze=squeeze, return_x=True) + assert xvec.shape == (sys.states, 8) + else: + _, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze) + assert yvec.shape == shape2 + # Forced response (only indexed by output) if isinstance(sys, StateSpace): # Check the states as well _, yvec, xvec = ct.forced_response( @@ -779,39 +810,39 @@ def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape): else: # Just check the input/output response _, yvec = ct.forced_response(sys, tvec, uvec, 0, squeeze=squeeze) - assert yvec.shape == shape + assert yvec.shape == shape2 # Test cases where we choose a subset of inputs and outputs _, yvec = ct.step_response( sys, tvec, input=ninp-1, output=nout-1, squeeze=squeeze) - # Possible code if we implemenet a squeeze='siso' option if squeeze is False: # Shape should be unsqueezed - assert yvec.shape == (1, 8) + assert yvec.shape == (1, 1, 8) else: # Shape should be squeezed assert yvec.shape == (8, ) - # For InputOutputSystems, also test input_output_response + # For InputOutputSystems, also test input/output response if isinstance(sys, ct.InputOutputSystem) and not scipy0: _, yvec = ct.input_output_response(sys, tvec, uvec, squeeze=squeeze) - assert yvec.shape == shape + assert yvec.shape == shape2 # # Changing config.default to False should return 3D frequency response # ct.config.set_defaults('control', squeeze_time_response=False) - with pytest.warns(warntype, match="Converting MIMO system"): - _, yvec = ct.impulse_response(sys, tvec) - assert yvec.shape == (sys.outputs, 8) + _, yvec = ct.impulse_response(sys, tvec) + if squeeze is not True or sys.inputs > 1 or sys.outputs > 1: + assert yvec.shape == (sys.outputs, sys.inputs, 8) - _, yvec = ct.initial_response(sys, tvec, 1) - assert yvec.shape == (sys.outputs, 8) + _, yvec = ct.step_response(sys, tvec) + if squeeze is not True or sys.inputs > 1 or sys.outputs > 1: + assert yvec.shape == (sys.outputs, sys.inputs, 8) - with pytest.warns(warntype, match="Converting MIMO system"): - _, yvec = ct.step_response(sys, tvec) - assert yvec.shape == (sys.outputs, 8) + _, yvec = ct.initial_response(sys, tvec, 1) + if squeeze is not True or sys.outputs > 1: + assert yvec.shape == (sys.outputs, 8) if isinstance(sys, ct.StateSpace): _, yvec, xvec = ct.forced_response( @@ -819,12 +850,14 @@ def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape): assert xvec.shape == (sys.states, 8) else: _, yvec = ct.forced_response(sys, tvec, uvec, 0) - assert yvec.shape == (sys.outputs, 8) + if squeeze is not True or sys.outputs > 1: + assert yvec.shape == (sys.outputs, 8) # For InputOutputSystems, also test input_output_response if isinstance(sys, ct.InputOutputSystem) and not scipy0: _, yvec = ct.input_output_response(sys, tvec, uvec) - assert yvec.shape == (sys.noutputs, 8) + if squeeze is not True or sys.outputs > 1: + assert yvec.shape == (sys.outputs, 8) @pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.ss2io]) def test_squeeze_exception(self, fcn): @@ -861,3 +894,37 @@ def test_squeeze_0_8_4(self, nstate, nout, ninp, squeeze, shape): _, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze) assert yvec.shape == shape + + @pytest.mark.parametrize( + "nstate, nout, ninp, squeeze, ysh_in, ysh_no, xsh_in", [ + [4, 1, 1, None, (8,), (8,), (8, 4)], + [4, 1, 1, True, (8,), (8,), (8, 4)], + [4, 1, 1, False, (8, 1, 1), (8, 1), (8, 4)], + [4, 2, 1, None, (8, 2, 1), (8, 2), (8, 4, 1)], + [4, 2, 1, True, (8, 2), (8, 2), (8, 4, 1)], + [4, 2, 1, False, (8, 2, 1), (8, 2), (8, 4, 1)], + [4, 1, 2, None, (8, 1, 2), (8, 1), (8, 4, 2)], + [4, 1, 2, True, (8, 2), (8,), (8, 4, 2)], + [4, 1, 2, False, (8, 1, 2), (8, 1), (8, 4, 2)], + [4, 2, 2, None, (8, 2, 2), (8, 2), (8, 4, 2)], + [4, 2, 2, True, (8, 2, 2), (8, 2), (8, 4, 2)], + [4, 2, 2, False, (8, 2, 2), (8, 2), (8, 4, 2)], + ]) + def test_response_transpose( + self, nstate, nout, ninp, squeeze, ysh_in, ysh_no, xsh_in): + sys = ct.rss(nstate, nout, ninp) + T = np.linspace(0, 1, 8) + + # Step response - input indexed + t, y, x = ct.step_response( + sys, T, transpose=True, return_x=True, squeeze=squeeze) + assert t.shape == (T.size, ) + assert y.shape == ysh_in + assert x.shape == xsh_in + + # Initial response - no input indexing + t, y, x = ct.initial_response( + sys, T, 1, transpose=True, return_x=True, squeeze=squeeze) + assert t.shape == (T.size, ) + assert y.shape == ysh_no + assert x.shape == (T.size, sys.states) diff --git a/control/timeresp.py b/control/timeresp.py index b8dc6631a..bfa2ec149 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -91,8 +91,7 @@ # Helper function for checking array-like parameters def _check_convert_array(in_obj, legal_shapes, err_msg_start, squeeze=False, transpose=False): - """ - Helper function for checking array_like parameters. + """Helper function for checking array_like parameters. * Check type and shape of ``in_obj``. * Convert ``in_obj`` to an array if necessary. @@ -128,8 +127,8 @@ def _check_convert_array(in_obj, legal_shapes, err_msg_start, squeeze=False, For example: ``array([[1,2,3]])`` is converted to ``array([1, 2, 3])`` - transpose : bool - If True, assume that input arrays are transposed for the standard + transpose : bool, optional + If True, assume that 2D input arrays are transposed from the standard format. Used to convert MATLAB-style inputs to our format. Returns @@ -137,6 +136,7 @@ def _check_convert_array(in_obj, legal_shapes, err_msg_start, squeeze=False, out_array : array The checked and converted contents of ``in_obj``. + """ # convert nearly everything to an array. out_array = np.asarray(in_obj) @@ -226,9 +226,10 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, X0 : array_like or float, optional Initial condition (default = 0). - transpose : bool, optional (default=False) + transpose : bool, optional If True, transpose all input and output arrays (for backward - compatibility with MATLAB and :func:`scipy.signal.lsim`) + compatibility with MATLAB and :func:`scipy.signal.lsim`). Default + value is False. interpolate : bool, optional (default=False) If True and system is a discrete time system, the input will @@ -456,36 +457,122 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, # Process time responses in a uniform way -def _process_time_response(sys, tout, yout, xout, transpose=None, - return_x=False, squeeze=None): - # If squeeze was not specified, figure out the default +def _process_time_response( + sys, tout, yout, xout, transpose=None, return_x=False, + squeeze=None, input=None, output=None): + """Process time response signals. + + This function processes the outputs of the time response functions and + processes the transpose and squeeze keywords. + + Parameters + ---------- + T : 1D array + Time values of the output + + yout : ndarray + Response of the system. This can either be a 1D array indexed by time + (for SISO systems), a 2D array indexed by output and time (for MIMO + systems with no input indexing, such as initial_response or forced + response) or a 3D array indexed by output, input, and time. + + xout : array, optional + Individual response of each x variable (if return_x is True). For a + SISO system (or if a single input is specified), This should be a 2D + array indexed by the state index and time (for single input systems) + or a 3D array indexed by state, input, and time. + + transpose : bool, optional + If True, transpose all input and output arrays (for backward + compatibility with MATLAB and :func:`scipy.signal.lsim`). Default + value is False. + + return_x : bool, optional + If True, return the state vector (default = False). + + squeeze : bool, optional + By default, if a system is single-input, single-output (SISO) then the + output response is returned as a 1D array (indexed by time). If + squeeze=True, remove single-dimensional entries from the shape of the + output even if the system is not SISO. If squeeze=False, keep the + output as a 3D array (indexed by the output, input, and time) even if + the system is SISO. The default value can be set using + config.defaults['control.squeeze_time_response']. + + input : int, optional + If present, the response represents only the listed input. + + output : int, optional + If present, the response represents only the listed output. + + Returns + ------- + T : 1D array + Time values of the output + + yout : ndarray + Response of the system. If the system is SISO and squeeze is not + True, the array is 1D (indexed by time). If the system is not SISO or + squeeze is False, the array is either 2D (indexed by output and time) + or 3D (indexed by input, output, and time). + + xout : array, optional + Individual response of each x variable (if return_x is True). For a + SISO system (or if a single input is specified), xout is a 2D array + indexed by the state index and time. For a non-SISO system, xout is a + 3D array indexed by the state, the input, and time. The shape of xout + is not affected by the ``squeeze`` keyword. + """ + # If squeeze was not specified, figure out the default (might remain None) if squeeze is None: squeeze = config.defaults['control.squeeze_time_response'] - # Figure out whether and now to squeeze output data + # Determine if the system is SISO + issiso = sys.issiso() or (input is not None and output is not None) + + # Figure out whether and how to squeeze output data if squeeze is True: # squeeze all dimensions yout = np.squeeze(yout) elif squeeze is False: # squeeze no dimensions pass elif squeeze is None: # squeeze signals if SISO - yout = yout[0] if sys.issiso() else yout + if issiso: + if len(yout.shape) == 3: + yout = yout[0][0] # remove input and output + else: + yout = yout[0] # remove input else: raise ValueError("unknown squeeze value") + # Figure out whether and how to squeeze the state data + if issiso and len(xout.shape) > 2: + xout = xout[:, 0, :] # remove input + # See if we need to transpose the data back into MATLAB form if transpose: + # Transpose time vector in case we are using np.matrix tout = np.transpose(tout) - yout = np.transpose(yout) - xout = np.transpose(xout) + + # For signals, put the last index (time) into the first slot + yout = np.transpose(yout, np.roll(range(yout.ndim), 1)) + xout = np.transpose(xout, np.roll(range(xout.ndim), 1)) # Return time, output, and (optionally) state return (tout, yout, xout) if return_x else (tout, yout) def _get_ss_simo(sys, input=None, output=None, squeeze=None): - """Return a SISO or SIMO state-space version of sys + """Return a SISO or SIMO state-space version of sys. + + This function converts the given system to a state space system in + preparation for simulation and sets the system matrixes to match the + desired input and output. + + If input is not specified, select first input and issue warning (legacy + behavior that should eventually not be used). + + If the output is not specified, report on all outputs. - If input is not specified, select first input and issue warning """ # If squeeze was not specified, figure out the default if squeeze is None: @@ -514,12 +601,13 @@ def _get_ss_simo(sys, input=None, output=None, squeeze=None): def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None, transpose=False, return_x=False, squeeze=None): # pylint: disable=W0622 - """Step response of a linear system + """Compute the step response for a linear system. - If the system has multiple inputs or outputs (MIMO), one input has - to be selected for the simulation. Optionally, one output may be - selected. The parameters `input` and `output` do this. All other - inputs are set to 0, all other outputs are ignored. + If the system has multiple inputs and/or multiple outputs, the step + response is computed for each input/output pair, with all other inputs set + to zero. Optionally, a single input and/or single output can be selected, + in which case all other inputs are set to 0 and all other outputs are + ignored. For information on the **shape** of parameters `T`, `X0` and return values `T`, `yout`, see :ref:`time-series-convention`. @@ -545,11 +633,12 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None, arrays with the correct shape. input : int, optional - Index of the input that will be used in this simulation. Default = 0. + Only compute the step response for the listed input. If not + specified, the step responses for each independent input are computed. output : int, optional - Index of the output that will be used in this simulation. Set to None - to not trim outputs + Only report the step response for the listed output. If not + specified, all outputs are reported. T_num : int, optional Number of time steps to use in simulation if T is not provided as an @@ -557,7 +646,8 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None, transpose : bool, optional If True, transpose all input and output arrays (for backward - compatibility with MATLAB and :func:`scipy.signal.lsim`) + compatibility with MATLAB and :func:`scipy.signal.lsim`). Default + value is False. return_x : bool, optional If True, return the state vector (default = False). @@ -567,23 +657,27 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None, output response is returned as a 1D array (indexed by time). If squeeze=True, remove single-dimensional entries from the shape of the output even if the system is not SISO. If squeeze=False, keep the - output as a 2D array (indexed by the output number and time) even if + output as a 3D array (indexed by the output, input, and time) even if the system is SISO. The default value can be set using config.defaults['control.squeeze_time_response']. Returns ------- - T : array + T : 1D array Time values of the output - yout : array + yout : ndarray Response of the system. If the system is SISO and squeeze is not True, the array is 1D (indexed by time). If the system is not SISO or - squeeze is False, the array is 2D (indexed by the output number and + squeeze is False, the array is 3D (indexed by the input, output, and time). xout : array, optional - Individual response of each x variable (if return_x is True). + Individual response of each x variable (if return_x is True). For a + SISO system (or if a single input is specified), xout is a 2D array + indexed by the state index and time. For a non-SISO system, xout is a + 3D array indexed by the state, the input, and time. The shape of xout + is not affected by the ``squeeze`` keyword. See Also -------- @@ -599,13 +693,46 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None, >>> T, yout = step_response(sys, T, X0) """ - squeeze, sys = _get_ss_simo(sys, input, output, squeeze=squeeze) + # Create the time and input vectors if T is None or np.asarray(T).size == 1: T = _default_time_vector(sys, N=T_num, tfinal=T, is_step=True) U = np.ones_like(T) - return forced_response(sys, T, U, X0, transpose=transpose, - return_x=return_x, squeeze=squeeze) + # If we are passed a transfer function and X0 is non-zero, warn the user + if isinstance(sys, TransferFunction) and np.any(X0 != 0): + warnings.warn( + "Non-zero initial condition given for transfer function system. " + "Internal conversation to state space used; may not be consistent " + "with given X0.") + + # Convert to state space so that we can simulate + sys = _convert_to_statespace(sys) + + # Set up arrays to handle the output + ninputs = sys.inputs if input is None else 1 + noutputs = sys.outputs if output is None else 1 + yout = np.empty((noutputs, ninputs, np.asarray(T).size)) + xout = np.empty((sys.states, ninputs, np.asarray(T).size)) + + # Simulate the response for each input + for i in range(sys.inputs): + # If input keyword was specified, only simulate for that input + if isinstance(input, int) and i != input: + continue + + # Create a set of single inputs system for simulation + squeeze, simo = _get_ss_simo(sys, i, output, squeeze=squeeze) + + out = forced_response(simo, T, U, X0, transpose=False, + return_x=return_x, squeeze=True) + inpidx = i if input is None else 0 + yout[:, inpidx, :] = out[1] + if return_x: + xout[:, i, :] = out[2] + + return _process_time_response( + sys, out[0], yout, xout, transpose=transpose, return_x=return_x, + squeeze=squeeze, input=input, output=output) def step_info(sys, T=None, T_num=None, SettlingTimeThreshold=0.02, @@ -788,12 +915,13 @@ def initial_response(sys, T=None, X0=0., input=0, output=None, T_num=None, def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None, transpose=False, return_x=False, squeeze=None): # pylint: disable=W0622 - """Impulse response of a linear system + """Compute the impulse response for a linear system. - If the system has multiple inputs or outputs (MIMO), one input has - to be selected for the simulation. Optionally, one output may be - selected. The parameters `input` and `output` do this. All other - inputs are set to 0, all other outputs are ignored. + If the system has multiple inputs and/or multiple outputs, the impulse + response is computed for each input/output pair, with all other inputs set + to zero. Optionally, a single input and/or single output can be selected, + in which case all other inputs are set to 0 and all other outputs are + ignored. For information on the **shape** of parameters `T`, `X0` and return values `T`, `yout`, see :ref:`time-series-convention`. @@ -813,19 +941,22 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None, Numbers are converted to constant arrays with the correct shape. input : int, optional - Index of the input that will be used in this simulation. Default = 0. + Only compute the impulse response for the listed input. If not + specified, the impulse responses for each independent input are + computed. output : int, optional - Index of the output that will be used in this simulation. Set to None - to not trim outputs + Only report the step response for the listed output. If not + specified, all outputs are reported. T_num : int, optional Number of time steps to use in simulation if T is not provided as an array (autocomputed if not given); ignored if sys is discrete-time. - transpose : bool + transpose : bool, optional If True, transpose all input and output arrays (for backward - compatibility with MATLAB and :func:`scipy.signal.lsim`) + compatibility with MATLAB and :func:`scipy.signal.lsim`). Default + value is False. return_x : bool, optional If True, return the state vector (default = False). @@ -851,7 +982,11 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None, time). xout : array, optional - Individual response of each x variable (if return_x is True). + Individual response of each x variable (if return_x is True). For a + SISO system (or if a single input is specified), xout is a 2D array + indexed by the state index and time. For a non-SISO system, xout is a + 3D array indexed by the state, the input, and time. The shape of xout + is not affected by the ``squeeze`` keyword. See Also -------- @@ -868,10 +1003,10 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None, >>> T, yout = impulse_response(sys, T, X0) """ - squeeze, sys = _get_ss_simo(sys, input, output, squeeze=squeeze) + # Convert to state space so that we can simulate + sys = _convert_to_statespace(sys) - # if system has direct feedthrough, can't simulate impulse response - # numerically + # Check to make sure there is not a direct term 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 " @@ -889,19 +1024,48 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None, T = _default_time_vector(sys, N=T_num, tfinal=T, is_step=False) U = np.zeros_like(T) - # 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 - if isctime(sys): - B = np.asarray(sys.B).squeeze() - new_X0 = B + X0 - else: - new_X0 = X0 - U[0] = 1./sys.dt # unit area impulse - - return forced_response(sys, T, U, new_X0, transpose=transpose, - return_x=return_x, squeeze=squeeze) + # Set up arrays to handle the output + ninputs = sys.inputs if input is None else 1 + noutputs = sys.outputs if output is None else 1 + yout = np.empty((noutputs, ninputs, np.asarray(T).size)) + xout = np.empty((sys.states, ninputs, np.asarray(T).size)) + + # Simulate the response for each input + for i in range(sys.inputs): + # If input keyword was specified, only handle that case + if isinstance(input, int) and i != input: + continue + + # Get the system we need to simulate + squeeze, simo = _get_ss_simo(sys, i, output, squeeze=squeeze) + + # + # 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 + # + if isctime(simo): + B = np.asarray(simo.B).squeeze() + new_X0 = B + X0 + else: + new_X0 = X0 + U[0] = 1./simo.dt # unit area impulse + + # Simulate the impulse response fo this input + out = forced_response(simo, T, U, new_X0, transpose=False, + return_x=return_x, squeeze=squeeze) + + # Store the output (and states) + inpidx = i if input is None else 0 + yout[:, inpidx, :] = out[1] + if return_x: + xout[:, i, :] = out[2] + + return _process_time_response( + sys, out[0], yout, xout, transpose=transpose, return_x=return_x, + squeeze=squeeze, input=input, output=output) # utility function to find time period and time increment using pole locations 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