diff --git a/control/dtime.py b/control/dtime.py index 6197ae8af..38fcf8056 100644 --- a/control/dtime.py +++ b/control/dtime.py @@ -72,22 +72,12 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None, otherwise. See :func:`scipy.signal.cont2discrete`. prewarp_frequency : float within [0, infinity) The frequency [rad/s] at which to match with the input continuous- - time system's magnitude and phase (only valid for method='bilinear') - name : string, optional - Set the name of the sampled system. If not specified and - if `copy_names` is `False`, a generic name is generated - with a unique integer id. If `copy_names` is `True`, the new system - name is determined by adding the prefix and suffix strings in - config.defaults['namedio.sampled_system_name_prefix'] and - config.defaults['namedio.sampled_system_name_suffix'], with the - default being to add the suffix '$sampled'. - copy_names : bool, Optional - If True, copy the names of the input signals, output - signals, and states to the sampled system. + time system's magnitude and phase (only valid for method='bilinear', + 'tustin', or 'gbt' with alpha=0.5) Returns ------- - sysd : linsys + sysd : LTI of the same class (:class:`StateSpace` or :class:`TransferFunction`) Discrete time system, with sampling rate Ts Other Parameters @@ -101,6 +91,17 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None, states : int, list of str, or None, optional Description of the system states. Same format as `inputs`. Only available if the system is :class:`StateSpace`. + name : string, optional + Set the name of the sampled system. If not specified and + if `copy_names` is `False`, a generic name is generated + with a unique integer id. If `copy_names` is `True`, the new system + name is determined by adding the prefix and suffix strings in + config.defaults['namedio.sampled_system_name_prefix'] and + config.defaults['namedio.sampled_system_name_suffix'], with the + default being to add the suffix '$sampled'. + copy_names : bool, Optional + If True, copy the names of the input signals, output + signals, and states to the sampled system. Notes ----- @@ -126,46 +127,4 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None, method=method, alpha=alpha, prewarp_frequency=prewarp_frequency, name=name, copy_names=copy_names, **kwargs) - -def c2d(sysc, Ts, method='zoh', prewarp_frequency=None): - """ - Convert a continuous time system to discrete time by sampling - - Parameters - ---------- - sysc : LTI (:class:`StateSpace` or :class:`TransferFunction`) - Continuous time system to be converted - Ts : float > 0 - Sampling period - method : string - Method to use for conversion, e.g. 'bilinear', 'zoh' (default) - prewarp_frequency : real within [0, infinity) - The frequency [rad/s] at which to match with the input continuous- - time system's magnitude and phase (only valid for method='bilinear') - - Returns - ------- - sysd : LTI of the same class - Discrete time system, with sampling rate Ts - - Notes - ----- - See :meth:`StateSpace.sample` or :meth:`TransferFunction.sample`` for - further details. - - Examples - -------- - >>> Gc = ct.tf([1], [1, 2, 1]) - >>> Gc.isdtime() - False - >>> Gd = ct.sample_system(Gc, 1, method='bilinear') - >>> Gd.isdtime() - True - - """ - - # Call the sample_system() function to do the work - sysd = sample_system(sysc, Ts, - method=method, prewarp_frequency=prewarp_frequency) - - return sysd +c2d = sample_system \ No newline at end of file diff --git a/control/statesp.py b/control/statesp.py index 41f92ae21..4f80bdb2f 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -170,9 +170,9 @@ class StateSpace(LTI): The StateSpace class is used to represent state-space realizations of linear time-invariant (LTI) systems: - + .. math:: - + dx/dt &= A x + B u \\ y &= C x + D u @@ -1368,10 +1368,13 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, """ if not self.isctime(): raise ValueError("System must be continuous time system") - - if (method == 'bilinear' or (method == 'gbt' and alpha == 0.5)) and \ - prewarp_frequency is not None: - Twarp = 2 * np.tan(prewarp_frequency * Ts/2)/prewarp_frequency + if prewarp_frequency is not None: + if method in ('bilinear', 'tustin') or \ + (method == 'gbt' and alpha == 0.5): + Twarp = 2*np.tan(prewarp_frequency*Ts/2)/prewarp_frequency + else: + warn('prewarp_frequency ignored: incompatible conversion') + Twarp = Ts else: Twarp = Ts sys = (self.A, self.B, self.C, self.D) diff --git a/control/tests/discrete_test.py b/control/tests/discrete_test.py index 4cf28a21b..4415fac0c 100644 --- a/control/tests/discrete_test.py +++ b/control/tests/discrete_test.py @@ -376,28 +376,51 @@ def test_sample_system(self, tsys): @pytest.mark.parametrize("plantname", ["siso_ss1c", "siso_tf1c"]) - def test_sample_system_prewarp(self, tsys, plantname): + @pytest.mark.parametrize("wwarp", + [.1, 1, 3]) + @pytest.mark.parametrize("Ts", + [.1, 1]) + @pytest.mark.parametrize("discretization_type", + ['bilinear', 'tustin', 'gbt']) + def test_sample_system_prewarp(self, tsys, plantname, discretization_type, wwarp, Ts): """bilinear approximation with prewarping test""" - wwarp = 50 - Ts = 0.025 # test state space version plant = getattr(tsys, plantname) plant_fr = plant(wwarp * 1j) + alpha = 0.5 if discretization_type == 'gbt' else None - plant_d_warped = plant.sample(Ts, 'bilinear', prewarp_frequency=wwarp) + plant_d_warped = plant.sample(Ts, discretization_type, + prewarp_frequency=wwarp, alpha=alpha) dt = plant_d_warped.dt plant_d_fr = plant_d_warped(np.exp(wwarp * 1.j * dt)) np.testing.assert_array_almost_equal(plant_fr, plant_d_fr) - plant_d_warped = sample_system(plant, Ts, 'bilinear', - prewarp_frequency=wwarp) + plant_d_warped = sample_system(plant, Ts, discretization_type, + prewarp_frequency=wwarp, alpha=alpha) plant_d_fr = plant_d_warped(np.exp(wwarp * 1.j * dt)) np.testing.assert_array_almost_equal(plant_fr, plant_d_fr) - plant_d_warped = c2d(plant, Ts, 'bilinear', prewarp_frequency=wwarp) + plant_d_warped = c2d(plant, Ts, discretization_type, + prewarp_frequency=wwarp, alpha=alpha) plant_d_fr = plant_d_warped(np.exp(wwarp * 1.j * dt)) np.testing.assert_array_almost_equal(plant_fr, plant_d_fr) + @pytest.mark.parametrize("plantname", + ["siso_ss1c", + "siso_tf1c"]) + @pytest.mark.parametrize("discretization_type", + ['euler', 'backward_diff', 'zoh']) + def test_sample_system_prewarp_warning(self, tsys, plantname, discretization_type): + plant = getattr(tsys, plantname) + wwarp = 1 + Ts = 0.1 + with pytest.warns(UserWarning, match="prewarp_frequency ignored: incompatible conversion"): + plant_d_warped = plant.sample(Ts, discretization_type, prewarp_frequency=wwarp) + with pytest.warns(UserWarning, match="prewarp_frequency ignored: incompatible conversion"): + plant_d_warped = sample_system(plant, Ts, discretization_type, prewarp_frequency=wwarp) + with pytest.warns(UserWarning, match="prewarp_frequency ignored: incompatible conversion"): + plant_d_warped = c2d(plant, Ts, discretization_type, prewarp_frequency=wwarp) + def test_sample_system_errors(self, tsys): # Check errors with pytest.raises(ValueError): @@ -446,11 +469,11 @@ def test_discrete_bode(self, tsys): np.testing.assert_array_almost_equal(omega, omega_out) np.testing.assert_array_almost_equal(mag_out, np.absolute(H_z)) np.testing.assert_array_almost_equal(phase_out, np.angle(H_z)) - + def test_signal_names(self, tsys): "test that signal names are preserved in conversion to discrete-time" - ssc = StateSpace(tsys.siso_ss1c, - inputs='u', outputs='y', states=['a', 'b', 'c']) + ssc = StateSpace(tsys.siso_ss1c, + inputs='u', outputs='y', states=['a', 'b', 'c']) ssd = ssc.sample(0.1) tfc = TransferFunction(tsys.siso_tf1c, inputs='u', outputs='y') tfd = tfc.sample(0.1) @@ -467,7 +490,7 @@ def test_signal_names(self, tsys): assert ssd.output_labels == ['y'] assert tfd.input_labels == ['u'] assert tfd.output_labels == ['y'] - + # system names and signal name override sysc = StateSpace(1.1, 1, 1, 1, inputs='u', outputs='y', states='a') @@ -488,14 +511,14 @@ def test_signal_names(self, tsys): assert sysd_nocopy.find_state('a') is None # if signal names are provided, they should override those of sysc - sysd_newnames = sample_system(sysc, 0.1, + sysd_newnames = sample_system(sysc, 0.1, inputs='v', outputs='x', states='b') assert sysd_newnames.find_input('v') == 0 assert sysd_newnames.find_input('u') is None assert sysd_newnames.find_output('x') == 0 assert sysd_newnames.find_output('y') is None assert sysd_newnames.find_state('b') == 0 - assert sysd_newnames.find_state('a') is None + assert sysd_newnames.find_state('a') is None # test just one name sysd_newnames = sample_system(sysc, 0.1, inputs='v') assert sysd_newnames.find_input('v') == 0 diff --git a/control/tests/kwargs_test.py b/control/tests/kwargs_test.py index f94009549..83026391c 100644 --- a/control/tests/kwargs_test.py +++ b/control/tests/kwargs_test.py @@ -190,6 +190,7 @@ def test_matplotlib_kwargs(function, nsysargs, moreargs, kwargs, mplcleanup): 'tf2io' : test_unrecognized_kwargs, 'tf2ss' : test_unrecognized_kwargs, 'sample_system' : test_unrecognized_kwargs, + 'c2d' : test_unrecognized_kwargs, 'zpk': test_unrecognized_kwargs, 'flatsys.point_to_point': flatsys_test.TestFlatSys.test_point_to_point_errors, @@ -210,7 +211,7 @@ def test_matplotlib_kwargs(function, nsysargs, moreargs, kwargs, mplcleanup): 'NonlinearIOSystem.__init__': interconnect_test.test_interconnect_exceptions, 'StateSpace.__init__': test_unrecognized_kwargs, - 'StateSpace.sample': test_unrecognized_kwargs, + 'StateSpace.sample': test_unrecognized_kwargs, 'TimeResponseData.__call__': trdata_test.test_response_copy, 'TransferFunction.__init__': test_unrecognized_kwargs, 'TransferFunction.sample': test_unrecognized_kwargs, diff --git a/control/xferfcn.py b/control/xferfcn.py index a6a00c5d7..8ef7e1084 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -1134,7 +1134,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, Method to use for sampling: * gbt: generalized bilinear transformation - * bilinear: Tustin's approximation ("gbt" with alpha=0.5) + * bilinear or tustin: Tustin's approximation ("gbt" with alpha=0.5) * euler: Euler (or forward difference) method ("gbt" with alpha=0) * backward_diff: Backwards difference ("gbt" with alpha=1.0) * zoh: zero-order hold (default) @@ -1192,9 +1192,13 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, if method == "matched": return _c2d_matched(self, Ts) sys = (self.num[0][0], self.den[0][0]) - if (method == 'bilinear' or (method == 'gbt' and alpha == 0.5)) and \ - prewarp_frequency is not None: - Twarp = 2*np.tan(prewarp_frequency*Ts/2)/prewarp_frequency + if prewarp_frequency is not None: + if method in ('bilinear', 'tustin') or \ + (method == 'gbt' and alpha == 0.5): + Twarp = 2*np.tan(prewarp_frequency*Ts/2)/prewarp_frequency + else: + warn('prewarp_frequency ignored: incompatible conversion') + Twarp = Ts else: Twarp = Ts numd, dend, _ = cont2discrete(sys, Twarp, method, alpha) 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