Skip to content

Commit 3fb1a51

Browse files
committed
fix bug in matched transformation + address other issues in #950
1 parent 7a8581c commit 3fb1a51

4 files changed

Lines changed: 53 additions & 16 deletions

File tree

control/dtime.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@
5555
# Sample a continuous time system
5656
def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None,
5757
name=None, copy_names=True, **kwargs):
58-
"""
59-
Convert a continuous time system to discrete time by sampling.
58+
"""Convert a continuous time system to discrete time by sampling.
6059
6160
Parameters
6261
----------
@@ -67,9 +66,9 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None,
6766
method : string
6867
Method to use for conversion, e.g. 'bilinear', 'zoh' (default)
6968
alpha : float within [0, 1]
70-
The generalized bilinear transformation weighting parameter, which
71-
should only be specified with method="gbt", and is ignored
72-
otherwise. See :func:`scipy.signal.cont2discrete`.
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`.
7372
prewarp_frequency : float within [0, infinity)
7473
The frequency [rad/s] at which to match with the input continuous-
7574
time system's magnitude and phase (only valid for method='bilinear',

control/freqplot.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -894,8 +894,10 @@ def gen_zero_centered_series(val_min, val_max, period):
894894
# list of systems (e.g., "Step response for sys[1], sys[2]").
895895
#
896896

897-
# Set the initial title for the data (unique system names)
898-
sysnames = list(set([response.sysname for response in data]))
897+
# Set the initial title for the data (unique system names, preserving order)
898+
seen = set()
899+
sysnames = [response.sysname for response in data \
900+
if not (response.sysname in seen or seen.add(response.sysname))]
899901
if title is None:
900902
if data[0].title is None:
901903
title = "Bode plot for " + ", ".join(sysnames)

control/tests/discrete_test.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55

66
import numpy as np
77
import pytest
8+
import cmath
89

9-
from control import (StateSpace, TransferFunction, bode, common_timebase,
10-
feedback, forced_response, impulse_response,
11-
isctime, isdtime, rss, c2d, sample_system, step_response,
12-
timebase)
10+
import control as ct
11+
from control import StateSpace, TransferFunction, bode, common_timebase, \
12+
feedback, forced_response, impulse_response, isctime, isdtime, rss, \
13+
c2d, sample_system, step_response, timebase
1314

1415

1516
class TestDiscrete:
@@ -526,3 +527,33 @@ def test_signal_names(self, tsys):
526527
assert sysd_newnames.find_input('u') is None
527528
assert sysd_newnames.find_output('y') == 0
528529
assert sysd_newnames.find_output('x') is None
530+
531+
532+
@pytest.mark.parametrize("num, den", [
533+
([1], [1, 1]),
534+
([1, 2], [1, 3]),
535+
([1, 2], [3, 4, 5])
536+
])
537+
@pytest.mark.parametrize("dt", [True, 0.1, 2])
538+
@pytest.mark.parametrize("method", ['zoh', 'bilinear', 'matched'])
539+
def test_c2d_matched(num, den, dt, method):
540+
sys_ct = ct.tf(num, den)
541+
sys_dt = ct.sample_system(sys_ct, dt, method=method)
542+
assert sys_dt.dt == dt # make sure sampling time is OK
543+
assert cmath.isclose(sys_ct(0), sys_dt(1)) # check zero frequency gain
544+
assert cmath.isclose(
545+
sys_ct.dcgain(), sys_dt.dcgain()) # another way to check
546+
547+
if method in ['zoh', 'matched']:
548+
# Make sure that poles were properly matched
549+
zpoles = sys_dt.poles()
550+
for cpole in sys_ct.poles():
551+
zpole = zpoles[(np.abs(zpoles - cmath.exp(cpole * dt))).argmin()]
552+
assert cmath.isclose(cmath.exp(cpole * dt), zpole)
553+
554+
if method in ['matched']:
555+
# Make sure that zeros were properly matched
556+
zzeros = sys_dt.zeros()
557+
for czero in sys_ct.zeros():
558+
zzero = zzeros[(np.abs(zzeros - cmath.exp(czero * dt))).argmin()]
559+
assert cmath.isclose(cmath.exp(czero * dt), zzero)

control/xferfcn.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,9 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
11811181
if not self.issiso():
11821182
raise ControlMIMONotImplemented("Not implemented for MIMO systems")
11831183
if method == "matched":
1184-
return _c2d_matched(self, Ts)
1184+
if prewarp_frequency is not None:
1185+
warn('prewarp_frequency ignored: incompatible conversion')
1186+
return _c2d_matched(self, Ts, name=name, **kwargs)
11851187
sys = (self.num[0][0], self.den[0][0])
11861188
if prewarp_frequency is not None:
11871189
if method in ('bilinear', 'tustin') or \
@@ -1284,9 +1286,12 @@ def _isstatic(self):
12841286

12851287

12861288
# c2d function contributed by Benjamin White, Oct 2012
1287-
def _c2d_matched(sysC, Ts):
1289+
def _c2d_matched(sysC, Ts, **kwargs):
1290+
if sysC.ninputs > 1 or sysC.noutputs > 1:
1291+
raise ValueError("MIMO transfer functions not supported")
1292+
12881293
# Pole-zero match method of continuous to discrete time conversion
1289-
szeros, spoles, sgain = tf2zpk(sysC.num[0][0], sysC.den[0][0])
1294+
szeros, spoles, _ = tf2zpk(sysC.num[0][0], sysC.den[0][0])
12901295
zzeros = [0] * len(szeros)
12911296
zpoles = [0] * len(spoles)
12921297
pregainnum = [0] * len(szeros)
@@ -1302,9 +1307,9 @@ def _c2d_matched(sysC, Ts):
13021307
zpoles[idx] = z
13031308
pregainden[idx] = 1 - z
13041309
zgain = np.multiply.reduce(pregainnum) / np.multiply.reduce(pregainden)
1305-
gain = sgain / zgain
1310+
gain = sysC.dcgain() / zgain
13061311
sysDnum, sysDden = zpk2tf(zzeros, zpoles, gain)
1307-
return TransferFunction(sysDnum, sysDden, Ts)
1312+
return TransferFunction(sysDnum, sysDden, Ts, **kwargs)
13081313

13091314

13101315
# Utility function to convert a transfer function polynomial to a string

0 commit comments

Comments
 (0)