Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions control/dtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@
# Sample a continuous time system
def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None,
name=None, copy_names=True, **kwargs):
"""
Convert a continuous time system to discrete time by sampling.
"""Convert a continuous time system to discrete time by sampling.

Parameters
----------
Expand All @@ -67,9 +66,9 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None,
method : string
Method to use for conversion, e.g. 'bilinear', 'zoh' (default)
alpha : float within [0, 1]
The generalized bilinear transformation weighting parameter, which
should only be specified with method="gbt", and is ignored
otherwise. See :func:`scipy.signal.cont2discrete`.
The generalized bilinear transformation weighting parameter, which
should only be specified with method="gbt", and is ignored
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',
Expand Down
6 changes: 4 additions & 2 deletions control/freqplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -894,8 +894,10 @@ def gen_zero_centered_series(val_min, val_max, period):
# list of systems (e.g., "Step response for sys[1], sys[2]").
#

# Set the initial title for the data (unique system names)
sysnames = list(set([response.sysname for response in data]))
# Set the initial title for the data (unique system names, preserving order)
seen = set()
sysnames = [response.sysname for response in data \
if not (response.sysname in seen or seen.add(response.sysname))]
if title is None:
if data[0].title is None:
title = "Bode plot for " + ", ".join(sysnames)
Expand Down
39 changes: 35 additions & 4 deletions control/tests/discrete_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

import numpy as np
import pytest
import cmath

from control import (StateSpace, TransferFunction, bode, common_timebase,
feedback, forced_response, impulse_response,
isctime, isdtime, rss, c2d, sample_system, step_response,
timebase)
import control as ct
from control import StateSpace, TransferFunction, bode, common_timebase, \
feedback, forced_response, impulse_response, isctime, isdtime, rss, \
c2d, sample_system, step_response, timebase


class TestDiscrete:
Expand Down Expand Up @@ -526,3 +527,33 @@ def test_signal_names(self, tsys):
assert sysd_newnames.find_input('u') is None
assert sysd_newnames.find_output('y') == 0
assert sysd_newnames.find_output('x') is None


@pytest.mark.parametrize("num, den", [
([1], [1, 1]),
([1, 2], [1, 3]),
([1, 2], [3, 4, 5])
])
@pytest.mark.parametrize("dt", [True, 0.1, 2])
@pytest.mark.parametrize("method", ['zoh', 'bilinear', 'matched'])
def test_c2d_matched(num, den, dt, method):
sys_ct = ct.tf(num, den)
sys_dt = ct.sample_system(sys_ct, dt, method=method)
assert sys_dt.dt == dt # make sure sampling time is OK
assert cmath.isclose(sys_ct(0), sys_dt(1)) # check zero frequency gain
assert cmath.isclose(
sys_ct.dcgain(), sys_dt.dcgain()) # another way to check

if method in ['zoh', 'matched']:
# Make sure that poles were properly matched
zpoles = sys_dt.poles()
for cpole in sys_ct.poles():
zpole = zpoles[(np.abs(zpoles - cmath.exp(cpole * dt))).argmin()]
assert cmath.isclose(cmath.exp(cpole * dt), zpole)

if method in ['matched']:
# Make sure that zeros were properly matched
zzeros = sys_dt.zeros()
for czero in sys_ct.zeros():
zzero = zzeros[(np.abs(zzeros - cmath.exp(czero * dt))).argmin()]
assert cmath.isclose(cmath.exp(czero * dt), zzero)
15 changes: 10 additions & 5 deletions control/xferfcn.py
Original file line number Diff line number Diff line change
Expand Up @@ -1181,7 +1181,9 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
if not self.issiso():
raise ControlMIMONotImplemented("Not implemented for MIMO systems")
if method == "matched":
return _c2d_matched(self, Ts)
if prewarp_frequency is not None:
warn('prewarp_frequency ignored: incompatible conversion')
return _c2d_matched(self, Ts, name=name, **kwargs)
sys = (self.num[0][0], self.den[0][0])
if prewarp_frequency is not None:
if method in ('bilinear', 'tustin') or \
Expand Down Expand Up @@ -1284,9 +1286,12 @@ def _isstatic(self):


# c2d function contributed by Benjamin White, Oct 2012
def _c2d_matched(sysC, Ts):
def _c2d_matched(sysC, Ts, **kwargs):
if sysC.ninputs > 1 or sysC.noutputs > 1:
Comment thread
murrayrm marked this conversation as resolved.
Resolved
raise ValueError("MIMO transfer functions not supported")

# Pole-zero match method of continuous to discrete time conversion
szeros, spoles, sgain = tf2zpk(sysC.num[0][0], sysC.den[0][0])
szeros, spoles, _ = tf2zpk(sysC.num[0][0], sysC.den[0][0])
zzeros = [0] * len(szeros)
zpoles = [0] * len(spoles)
pregainnum = [0] * len(szeros)
Expand All @@ -1302,9 +1307,9 @@ def _c2d_matched(sysC, Ts):
zpoles[idx] = z
pregainden[idx] = 1 - z
zgain = np.multiply.reduce(pregainnum) / np.multiply.reduce(pregainden)
gain = sgain / zgain
gain = sysC.dcgain() / zgain
sysDnum, sysDden = zpk2tf(zzeros, zpoles, gain)
return TransferFunction(sysDnum, sysDden, Ts)
return TransferFunction(sysDnum, sysDden, Ts, **kwargs)


# Utility function to convert a transfer function polynomial to a string
Expand Down