Skip to content

Commit 5155dd5

Browse files
authored
Merge pull request #669 from sawyerbfuller/bugfixes1
fixed prewarp not working in c2d and sample_system, margin docstring improvements
2 parents ce20b24 + 4c66fb1 commit 5155dd5

File tree

5 files changed

+63
-44
lines changed

5 files changed

+63
-44
lines changed

control/dtime.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
Routines in this module:
66
77
sample_system()
8+
c2d()
89
"""
910

1011
"""Copyright (c) 2012 by California Institute of Technology
@@ -58,16 +59,19 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
5859
5960
Parameters
6061
----------
61-
sysc : LTI (StateSpace or TransferFunction)
62+
sysc : LTI (:class:`StateSpace` or :class:`TransferFunction`)
6263
Continuous time system to be converted
63-
Ts : real > 0
64+
Ts : float > 0
6465
Sampling period
6566
method : string
6667
Method to use for conversion, e.g. 'bilinear', 'zoh' (default)
67-
68-
prewarp_frequency : real within [0, infinity)
68+
alpha : float within [0, 1]
69+
The generalized bilinear transformation weighting parameter, which
70+
should only be specified with method="gbt", and is ignored
71+
otherwise. See :func:`scipy.signal.cont2discrete`.
72+
prewarp_frequency : float within [0, infinity)
6973
The frequency [rad/s] at which to match with the input continuous-
70-
time system's magnitude and phase
74+
time system's magnitude and phase (only valid for method='bilinear')
7175
7276
Returns
7377
-------
@@ -76,7 +80,7 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
7680
7781
Notes
7882
-----
79-
See :meth:`StateSpace.sample` or :meth:`TransferFunction.sample`` for
83+
See :meth:`StateSpace.sample` or :meth:`TransferFunction.sample` for
8084
further details.
8185
8286
Examples
@@ -89,7 +93,8 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
8993
if not isctime(sysc):
9094
raise ValueError("First argument must be continuous time system")
9195

92-
return sysc.sample(Ts, method, alpha, prewarp_frequency)
96+
return sysc.sample(Ts,
97+
method=method, alpha=alpha, prewarp_frequency=prewarp_frequency)
9398

9499

95100
def c2d(sysc, Ts, method='zoh', prewarp_frequency=None):
@@ -98,20 +103,19 @@ def c2d(sysc, Ts, method='zoh', prewarp_frequency=None):
98103
99104
Parameters
100105
----------
101-
sysc : LTI (StateSpace or TransferFunction)
106+
sysc : LTI (:class:`StateSpace` or :class:`TransferFunction`)
102107
Continuous time system to be converted
103-
Ts : real > 0
108+
Ts : float > 0
104109
Sampling period
105110
method : string
106111
Method to use for conversion, e.g. 'bilinear', 'zoh' (default)
107-
108112
prewarp_frequency : real within [0, infinity)
109113
The frequency [rad/s] at which to match with the input continuous-
110-
time system's magnitude and phase
114+
time system's magnitude and phase (only valid for method='bilinear')
111115
112116
Returns
113117
-------
114-
sysd : linsys
118+
sysd : LTI of the same class
115119
Discrete time system, with sampling rate Ts
116120
117121
Notes
@@ -126,6 +130,7 @@ def c2d(sysc, Ts, method='zoh', prewarp_frequency=None):
126130
"""
127131

128132
# Call the sample_system() function to do the work
129-
sysd = sample_system(sysc, Ts, method, prewarp_frequency)
133+
sysd = sample_system(sysc, Ts,
134+
method=method, prewarp_frequency=prewarp_frequency)
130135

131136
return sysd

control/margins.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -283,14 +283,16 @@ def stability_margins(sysdata, returnall=False, epsw=0.0, method='best'):
283283
-------
284284
gm : float or array_like
285285
Gain margin
286-
pm : float or array_loke
286+
pm : float or array_like
287287
Phase margin
288288
sm : float or array_like
289289
Stability margin, the minimum distance from the Nyquist plot to -1
290290
wpc : float or array_like
291-
Phase crossover frequency (where phase crosses -180 degrees)
291+
Phase crossover frequency (where phase crosses -180 degrees), which is
292+
associated with the gain margin.
292293
wgc : float or array_like
293-
Gain crossover frequency (where gain crosses 1)
294+
Gain crossover frequency (where gain crosses 1), which is associated
295+
with the phase margin.
294296
wms : float or array_like
295297
Stability margin frequency (where Nyquist plot is closest to -1)
296298
@@ -522,10 +524,12 @@ def margin(*args):
522524
Gain margin
523525
pm : float
524526
Phase margin (in degrees)
525-
wpc : float or array_like
526-
Phase crossover frequency (where phase crosses -180 degrees)
527-
wgc : float or array_like
528-
Gain crossover frequency (where gain crosses 1)
527+
wcg : float or array_like
528+
Crossover frequency associated with gain margin (phase crossover
529+
frequency), where phase crosses below -180 degrees.
530+
wcp : float or array_like
531+
Crossover frequency associated with phase margin (gain crossover
532+
frequency), where gain crosses below 1.
529533
530534
Margins are calculated for a SISO open-loop system.
531535
@@ -536,7 +540,7 @@ def margin(*args):
536540
Examples
537541
--------
538542
>>> sys = tf(1, [1, 2, 1, 0])
539-
>>> gm, pm, wg, wp = margin(sys)
543+
>>> gm, pm, wcg, wcp = margin(sys)
540544
541545
"""
542546
if len(args) == 1:

control/tests/discrete_test.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import pytest
88

99
from control import (StateSpace, TransferFunction, bode, common_timebase,
10-
evalfr, feedback, forced_response, impulse_response,
11-
isctime, isdtime, rss, sample_system, step_response,
10+
feedback, forced_response, impulse_response,
11+
isctime, isdtime, rss, c2d, sample_system, step_response,
1212
timebase)
1313

1414

@@ -382,10 +382,20 @@ def test_sample_system_prewarp(self, tsys, plantname):
382382
Ts = 0.025
383383
# test state space version
384384
plant = getattr(tsys, plantname)
385+
plant_fr = plant(wwarp * 1j)
386+
385387
plant_d_warped = plant.sample(Ts, 'bilinear', prewarp_frequency=wwarp)
386-
plant_fr = evalfr(plant, wwarp * 1j)
387388
dt = plant_d_warped.dt
388-
plant_d_fr = evalfr(plant_d_warped, np.exp(wwarp * 1.j * dt))
389+
plant_d_fr = plant_d_warped(np.exp(wwarp * 1.j * dt))
390+
np.testing.assert_array_almost_equal(plant_fr, plant_d_fr)
391+
392+
plant_d_warped = sample_system(plant, Ts, 'bilinear',
393+
prewarp_frequency=wwarp)
394+
plant_d_fr = plant_d_warped(np.exp(wwarp * 1.j * dt))
395+
np.testing.assert_array_almost_equal(plant_fr, plant_d_fr)
396+
397+
plant_d_warped = c2d(plant, Ts, 'bilinear', prewarp_frequency=wwarp)
398+
plant_d_fr = plant_d_warped(np.exp(wwarp * 1.j * dt))
389399
np.testing.assert_array_almost_equal(plant_fr, plant_d_fr)
390400

391401
def test_sample_system_errors(self, tsys):

control/tests/matlab_test.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -355,13 +355,13 @@ def testLsim_mimo(self, mimo):
355355
def testMargin(self, siso):
356356
"""Test margin()"""
357357
#! TODO: check results to make sure they are OK
358-
gm, pm, wg, wp = margin(siso.tf1)
359-
gm, pm, wg, wp = margin(siso.tf2)
360-
gm, pm, wg, wp = margin(siso.ss1)
361-
gm, pm, wg, wp = margin(siso.ss2)
362-
gm, pm, wg, wp = margin(siso.ss2 * siso.ss2 * 2)
358+
gm, pm, wcg, wcp = margin(siso.tf1)
359+
gm, pm, wcg, wcp = margin(siso.tf2)
360+
gm, pm, wcg, wcp = margin(siso.ss1)
361+
gm, pm, wcg, wcp = margin(siso.ss2)
362+
gm, pm, wcg, wcp = margin(siso.ss2 * siso.ss2 * 2)
363363
np.testing.assert_array_almost_equal(
364-
[gm, pm, wg, wp], [1.5451, 75.9933, 1.2720, 0.6559], decimal=3)
364+
[gm, pm, wcg, wcp], [1.5451, 75.9933, 1.2720, 0.6559], decimal=3)
365365

366366
def testDcgain(self, siso):
367367
"""Test dcgain() for SISO system"""
@@ -781,12 +781,12 @@ def testCombi01(self):
781781
# total open loop
782782
Hol = Hc*Hno*Hp
783783

784-
gm, pm, wg, wp = margin(Hol)
785-
# print("%f %f %f %f" % (gm, pm, wg, wp))
784+
gm, pm, wcg, wcp = margin(Hol)
785+
# print("%f %f %f %f" % (gm, pm, wcg, wcp))
786786
np.testing.assert_allclose(gm, 3.32065569155)
787787
np.testing.assert_allclose(pm, 46.9740430224)
788-
np.testing.assert_allclose(wg, 0.176469728448)
789-
np.testing.assert_allclose(wp, 0.0616288455466)
788+
np.testing.assert_allclose(wcg, 0.176469728448)
789+
np.testing.assert_allclose(wcp, 0.0616288455466)
790790

791791
def test_tf_string_args(self):
792792
"""Make sure s and z are defined properly"""

control/xferfcn.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
from itertools import chain
6565
from re import sub
6666
from .lti import LTI, common_timebase, isdtime, _process_frequency_response
67+
from .exception import ControlMIMONotImplemented
6768
from . import config
6869

6970
__all__ = ['TransferFunction', 'tf', 'ss2tf', 'tfdata']
@@ -793,9 +794,9 @@ def feedback(self, other=1, sign=-1):
793794
if (self.ninputs > 1 or self.noutputs > 1 or
794795
other.ninputs > 1 or other.noutputs > 1):
795796
# TODO: MIMO feedback
796-
raise NotImplementedError(
797-
"TransferFunction.feedback is currently only implemented "
798-
"for SISO functions.")
797+
raise ControlMIMONotImplemented(
798+
"TransferFunction.feedback is currently not implemented for "
799+
"MIMO systems.")
799800
dt = common_timebase(self.dt, other.dt)
800801

801802
num1 = self.num[0][0]
@@ -1085,12 +1086,10 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None):
10851086
* euler: Euler (or forward difference) method ("gbt" with alpha=0)
10861087
* backward_diff: Backwards difference ("gbt" with alpha=1.0)
10871088
* zoh: zero-order hold (default)
1088-
10891089
alpha : float within [0, 1]
10901090
The generalized bilinear transformation weighting parameter, which
10911091
should only be specified with method="gbt", and is ignored
1092-
otherwise.
1093-
1092+
otherwise. See :func:`scipy.signal.cont2discrete`.
10941093
prewarp_frequency : float within [0, infinity)
10951094
The frequency [rad/s] at which to match with the input continuous-
10961095
time system's magnitude and phase (the gain=1 crossover frequency,
@@ -1100,7 +1099,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None):
11001099
Returns
11011100
-------
11021101
sysd : TransferFunction system
1103-
Discrete time system, with sampling rate Ts
1102+
Discrete time system, with sample period Ts
11041103
11051104
Notes
11061105
-----
@@ -1117,7 +1116,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None):
11171116
if not self.isctime():
11181117
raise ValueError("System must be continuous time system")
11191118
if not self.issiso():
1120-
raise NotImplementedError("MIMO implementation not available")
1119+
raise ControlMIMONotImplemented("Not implemented for MIMO systems")
11211120
if method == "matched":
11221121
return _c2d_matched(self, Ts)
11231122
sys = (self.num[0][0], self.den[0][0])
@@ -1373,7 +1372,8 @@ def _convert_to_transfer_function(sys, **kw):
13731372
except ImportError:
13741373
# If slycot is not available, use signal.lti (SISO only)
13751374
if sys.ninputs != 1 or sys.noutputs != 1:
1376-
raise TypeError("No support for MIMO without slycot.")
1375+
raise ControlMIMONotImplemented("Not implemented for " +
1376+
"MIMO systems without slycot.")
13771377

13781378
# Do the conversion using sp.signal.ss2tf
13791379
# Note that this returns a 2D array for the numerator

0 commit comments

Comments
 (0)