Skip to content

Commit 29c9432

Browse files
committed
warn if prewarp-frequency is not used and tests, make c2d an identical copy to sample_system instead of a separate function
1 parent 9c26e22 commit 29c9432

File tree

5 files changed

+70
-80
lines changed

5 files changed

+70
-80
lines changed

control/dtime.py

+15-56
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,12 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None,
7272
otherwise. See :func:`scipy.signal.cont2discrete`.
7373
prewarp_frequency : float within [0, infinity)
7474
The frequency [rad/s] at which to match with the input continuous-
75-
time system's magnitude and phase (only valid for method='bilinear')
76-
name : string, optional
77-
Set the name of the sampled system. If not specified and
78-
if `copy_names` is `False`, a generic name <sys[id]> is generated
79-
with a unique integer id. If `copy_names` is `True`, the new system
80-
name is determined by adding the prefix and suffix strings in
81-
config.defaults['namedio.sampled_system_name_prefix'] and
82-
config.defaults['namedio.sampled_system_name_suffix'], with the
83-
default being to add the suffix '$sampled'.
84-
copy_names : bool, Optional
85-
If True, copy the names of the input signals, output
86-
signals, and states to the sampled system.
75+
time system's magnitude and phase (only valid for method='bilinear',
76+
'tustin', or 'gbt' with alpha=0.5)
8777
8878
Returns
8979
-------
90-
sysd : linsys
80+
sysd : LTI of the same class (:class:`StateSpace` or :class:`TransferFunction`)
9181
Discrete time system, with sampling rate Ts
9282
9383
Other Parameters
@@ -101,6 +91,17 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None,
10191
states : int, list of str, or None, optional
10292
Description of the system states. Same format as `inputs`. Only
10393
available if the system is :class:`StateSpace`.
94+
name : string, optional
95+
Set the name of the sampled system. If not specified and
96+
if `copy_names` is `False`, a generic name <sys[id]> is generated
97+
with a unique integer id. If `copy_names` is `True`, the new system
98+
name is determined by adding the prefix and suffix strings in
99+
config.defaults['namedio.sampled_system_name_prefix'] and
100+
config.defaults['namedio.sampled_system_name_suffix'], with the
101+
default being to add the suffix '$sampled'.
102+
copy_names : bool, Optional
103+
If True, copy the names of the input signals, output
104+
signals, and states to the sampled system.
104105
105106
Notes
106107
-----
@@ -126,46 +127,4 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None,
126127
method=method, alpha=alpha, prewarp_frequency=prewarp_frequency,
127128
name=name, copy_names=copy_names, **kwargs)
128129

129-
130-
def c2d(sysc, Ts, method='zoh', prewarp_frequency=None):
131-
"""
132-
Convert a continuous time system to discrete time by sampling
133-
134-
Parameters
135-
----------
136-
sysc : LTI (:class:`StateSpace` or :class:`TransferFunction`)
137-
Continuous time system to be converted
138-
Ts : float > 0
139-
Sampling period
140-
method : string
141-
Method to use for conversion, e.g. 'bilinear', 'zoh' (default)
142-
prewarp_frequency : real within [0, infinity)
143-
The frequency [rad/s] at which to match with the input continuous-
144-
time system's magnitude and phase (only valid for method='bilinear')
145-
146-
Returns
147-
-------
148-
sysd : LTI of the same class
149-
Discrete time system, with sampling rate Ts
150-
151-
Notes
152-
-----
153-
See :meth:`StateSpace.sample` or :meth:`TransferFunction.sample`` for
154-
further details.
155-
156-
Examples
157-
--------
158-
>>> Gc = ct.tf([1], [1, 2, 1])
159-
>>> Gc.isdtime()
160-
False
161-
>>> Gd = ct.sample_system(Gc, 1, method='bilinear')
162-
>>> Gd.isdtime()
163-
True
164-
165-
"""
166-
167-
# Call the sample_system() function to do the work
168-
sysd = sample_system(sysc, Ts,
169-
method=method, prewarp_frequency=prewarp_frequency)
170-
171-
return sysd
130+
c2d = sample_system

control/statesp.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,9 @@ class StateSpace(LTI):
170170
171171
The StateSpace class is used to represent state-space realizations of
172172
linear time-invariant (LTI) systems:
173-
173+
174174
.. math::
175-
175+
176176
dx/dt &= A x + B u \\
177177
y &= C x + D u
178178
@@ -1368,10 +1368,13 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
13681368
"""
13691369
if not self.isctime():
13701370
raise ValueError("System must be continuous time system")
1371-
1372-
if (method == 'bilinear' or (method == 'gbt' and alpha == 0.5)) and \
1373-
prewarp_frequency is not None:
1374-
Twarp = 2 * np.tan(prewarp_frequency * Ts/2)/prewarp_frequency
1371+
if prewarp_frequency is not None:
1372+
if method in ('bilinear', 'tustin') or \
1373+
(method == 'gbt' and alpha == 0.5):
1374+
Twarp = 2*np.tan(prewarp_frequency*Ts/2)/prewarp_frequency
1375+
else:
1376+
warn('prewarp_frequency ignored: incompatible conversion')
1377+
Twarp = Ts
13751378
else:
13761379
Twarp = Ts
13771380
sys = (self.A, self.B, self.C, self.D)

control/tests/discrete_test.py

+36-13
Original file line numberDiff line numberDiff line change
@@ -376,28 +376,51 @@ def test_sample_system(self, tsys):
376376
@pytest.mark.parametrize("plantname",
377377
["siso_ss1c",
378378
"siso_tf1c"])
379-
def test_sample_system_prewarp(self, tsys, plantname):
379+
@pytest.mark.parametrize("wwarp",
380+
[.1, 1, 3])
381+
@pytest.mark.parametrize("Ts",
382+
[.1, 1])
383+
@pytest.mark.parametrize("discretization_type",
384+
['bilinear', 'tustin', 'gbt'])
385+
def test_sample_system_prewarp(self, tsys, plantname, discretization_type, wwarp, Ts):
380386
"""bilinear approximation with prewarping test"""
381-
wwarp = 50
382-
Ts = 0.025
383387
# test state space version
384388
plant = getattr(tsys, plantname)
385389
plant_fr = plant(wwarp * 1j)
390+
alpha = 0.5 if discretization_type == 'gbt' else None
386391

387-
plant_d_warped = plant.sample(Ts, 'bilinear', prewarp_frequency=wwarp)
392+
plant_d_warped = plant.sample(Ts, discretization_type,
393+
prewarp_frequency=wwarp, alpha=alpha)
388394
dt = plant_d_warped.dt
389395
plant_d_fr = plant_d_warped(np.exp(wwarp * 1.j * dt))
390396
np.testing.assert_array_almost_equal(plant_fr, plant_d_fr)
391397

392-
plant_d_warped = sample_system(plant, Ts, 'bilinear',
393-
prewarp_frequency=wwarp)
398+
plant_d_warped = sample_system(plant, Ts, discretization_type,
399+
prewarp_frequency=wwarp, alpha=alpha)
394400
plant_d_fr = plant_d_warped(np.exp(wwarp * 1.j * dt))
395401
np.testing.assert_array_almost_equal(plant_fr, plant_d_fr)
396402

397-
plant_d_warped = c2d(plant, Ts, 'bilinear', prewarp_frequency=wwarp)
403+
plant_d_warped = c2d(plant, Ts, discretization_type,
404+
prewarp_frequency=wwarp, alpha=alpha)
398405
plant_d_fr = plant_d_warped(np.exp(wwarp * 1.j * dt))
399406
np.testing.assert_array_almost_equal(plant_fr, plant_d_fr)
400407

408+
@pytest.mark.parametrize("plantname",
409+
["siso_ss1c",
410+
"siso_tf1c"])
411+
@pytest.mark.parametrize("discretization_type",
412+
['euler', 'backward_diff', 'zoh'])
413+
def test_sample_system_prewarp_warning(self, tsys, plantname, discretization_type):
414+
plant = getattr(tsys, plantname)
415+
wwarp = 1
416+
Ts = 0.1
417+
with pytest.warns(UserWarning, match="prewarp_frequency ignored: incompatible conversion"):
418+
plant_d_warped = plant.sample(Ts, discretization_type, prewarp_frequency=wwarp)
419+
with pytest.warns(UserWarning, match="prewarp_frequency ignored: incompatible conversion"):
420+
plant_d_warped = sample_system(plant, Ts, discretization_type, prewarp_frequency=wwarp)
421+
with pytest.warns(UserWarning, match="prewarp_frequency ignored: incompatible conversion"):
422+
plant_d_warped = c2d(plant, Ts, discretization_type, prewarp_frequency=wwarp)
423+
401424
def test_sample_system_errors(self, tsys):
402425
# Check errors
403426
with pytest.raises(ValueError):
@@ -446,11 +469,11 @@ def test_discrete_bode(self, tsys):
446469
np.testing.assert_array_almost_equal(omega, omega_out)
447470
np.testing.assert_array_almost_equal(mag_out, np.absolute(H_z))
448471
np.testing.assert_array_almost_equal(phase_out, np.angle(H_z))
449-
472+
450473
def test_signal_names(self, tsys):
451474
"test that signal names are preserved in conversion to discrete-time"
452-
ssc = StateSpace(tsys.siso_ss1c,
453-
inputs='u', outputs='y', states=['a', 'b', 'c'])
475+
ssc = StateSpace(tsys.siso_ss1c,
476+
inputs='u', outputs='y', states=['a', 'b', 'c'])
454477
ssd = ssc.sample(0.1)
455478
tfc = TransferFunction(tsys.siso_tf1c, inputs='u', outputs='y')
456479
tfd = tfc.sample(0.1)
@@ -467,7 +490,7 @@ def test_signal_names(self, tsys):
467490
assert ssd.output_labels == ['y']
468491
assert tfd.input_labels == ['u']
469492
assert tfd.output_labels == ['y']
470-
493+
471494
# system names and signal name override
472495
sysc = StateSpace(1.1, 1, 1, 1, inputs='u', outputs='y', states='a')
473496

@@ -488,14 +511,14 @@ def test_signal_names(self, tsys):
488511
assert sysd_nocopy.find_state('a') is None
489512

490513
# if signal names are provided, they should override those of sysc
491-
sysd_newnames = sample_system(sysc, 0.1,
514+
sysd_newnames = sample_system(sysc, 0.1,
492515
inputs='v', outputs='x', states='b')
493516
assert sysd_newnames.find_input('v') == 0
494517
assert sysd_newnames.find_input('u') is None
495518
assert sysd_newnames.find_output('x') == 0
496519
assert sysd_newnames.find_output('y') is None
497520
assert sysd_newnames.find_state('b') == 0
498-
assert sysd_newnames.find_state('a') is None
521+
assert sysd_newnames.find_state('a') is None
499522
# test just one name
500523
sysd_newnames = sample_system(sysc, 0.1, inputs='v')
501524
assert sysd_newnames.find_input('v') == 0

control/tests/kwargs_test.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ def test_matplotlib_kwargs(function, nsysargs, moreargs, kwargs, mplcleanup):
190190
'tf2io' : test_unrecognized_kwargs,
191191
'tf2ss' : test_unrecognized_kwargs,
192192
'sample_system' : test_unrecognized_kwargs,
193+
'c2d' : test_unrecognized_kwargs,
193194
'zpk': test_unrecognized_kwargs,
194195
'flatsys.point_to_point':
195196
flatsys_test.TestFlatSys.test_point_to_point_errors,
@@ -210,7 +211,7 @@ def test_matplotlib_kwargs(function, nsysargs, moreargs, kwargs, mplcleanup):
210211
'NonlinearIOSystem.__init__':
211212
interconnect_test.test_interconnect_exceptions,
212213
'StateSpace.__init__': test_unrecognized_kwargs,
213-
'StateSpace.sample': test_unrecognized_kwargs,
214+
'StateSpace.sample': test_unrecognized_kwargs,
214215
'TimeResponseData.__call__': trdata_test.test_response_copy,
215216
'TransferFunction.__init__': test_unrecognized_kwargs,
216217
'TransferFunction.sample': test_unrecognized_kwargs,

control/xferfcn.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1134,7 +1134,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
11341134
Method to use for sampling:
11351135
11361136
* gbt: generalized bilinear transformation
1137-
* bilinear: Tustin's approximation ("gbt" with alpha=0.5)
1137+
* bilinear or tustin: Tustin's approximation ("gbt" with alpha=0.5)
11381138
* euler: Euler (or forward difference) method ("gbt" with alpha=0)
11391139
* backward_diff: Backwards difference ("gbt" with alpha=1.0)
11401140
* zoh: zero-order hold (default)
@@ -1192,9 +1192,13 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
11921192
if method == "matched":
11931193
return _c2d_matched(self, Ts)
11941194
sys = (self.num[0][0], self.den[0][0])
1195-
if (method == 'bilinear' or (method == 'gbt' and alpha == 0.5)) and \
1196-
prewarp_frequency is not None:
1197-
Twarp = 2*np.tan(prewarp_frequency*Ts/2)/prewarp_frequency
1195+
if prewarp_frequency is not None:
1196+
if method in ('bilinear', 'tustin') or \
1197+
(method == 'gbt' and alpha == 0.5):
1198+
Twarp = 2*np.tan(prewarp_frequency*Ts/2)/prewarp_frequency
1199+
else:
1200+
warn('prewarp_frequency ignored: incompatible conversion')
1201+
Twarp = Ts
11981202
else:
11991203
Twarp = Ts
12001204
numd, dend, _ = cont2discrete(sys, Twarp, method, alpha)

0 commit comments

Comments
 (0)