Skip to content

Commit 821bfa9

Browse files
authored
Merge pull request #816 from murrayrm/zpk-13Dec2022
add zpk() function
2 parents b4cd96b + 7ee0c0e commit 821bfa9

File tree

6 files changed

+132
-12
lines changed

6 files changed

+132
-12
lines changed

control/matlab/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
116116
== ========================== ============================================
117117
\* :func:`tf` create transfer function (TF) models
118-
\ zpk create zero/pole/gain (ZPK) models.
118+
\* :func:`zpk` create zero/pole/gain (ZPK) models.
119119
\* :func:`ss` create state-space (SS) models
120120
\ dss create descriptor state-space models
121121
\ delayss create state-space models with delayed terms

control/statesp.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1521,7 +1521,6 @@ def output(self, t, x, u=None, params=None):
15211521

15221522

15231523
# TODO: add discrete time check
1524-
# TODO: copy signal names
15251524
def _convert_to_statespace(sys):
15261525
"""Convert a system to state space form (if needed).
15271526

control/tests/kwargs_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def test_kwarg_search(module, prefix):
9696
(control.tf, 0, 0, ([1], [1, 1]), {}),
9797
(control.tf2io, 0, 1, (), {}),
9898
(control.tf2ss, 0, 1, (), {}),
99+
(control.zpk, 0, 0, ([1], [2, 3], 4), {}),
99100
(control.InputOutputSystem, 0, 0, (),
100101
{'inputs': 1, 'outputs': 1, 'states': 1}),
101102
(control.InputOutputSystem.linearize, 1, 0, (0, 0), {}),
@@ -184,6 +185,7 @@ def test_matplotlib_kwargs(function, nsysargs, moreargs, kwargs, mplcleanup):
184185
'tf2io' : test_unrecognized_kwargs,
185186
'tf2ss' : test_unrecognized_kwargs,
186187
'sample_system' : test_unrecognized_kwargs,
188+
'zpk': test_unrecognized_kwargs,
187189
'flatsys.point_to_point':
188190
flatsys_test.TestFlatSys.test_point_to_point_errors,
189191
'flatsys.solve_flat_ocp':

control/tests/xferfcn_test.py

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import operator
99

1010
import control as ct
11-
from control import StateSpace, TransferFunction, rss, ss2tf, evalfr
11+
from control import StateSpace, TransferFunction, rss, evalfr
12+
from control import ss, ss2tf, tf, tf2ss
1213
from control import isctime, isdtime, sample_system, defaults
1314
from control.statesp import _convert_to_statespace
1415
from control.xferfcn import _convert_to_transfer_function
@@ -986,7 +987,7 @@ def test_repr(self, Hargs, ref):
986987
np.testing.assert_array_almost_equal(H.num[p][m], H2.num[p][m])
987988
np.testing.assert_array_almost_equal(H.den[p][m], H2.den[p][m])
988989
assert H.dt == H2.dt
989-
990+
990991
def test_sample_named_signals(self):
991992
sysc = ct.TransferFunction(1.1, (1, 2), inputs='u', outputs='y')
992993

@@ -1073,3 +1074,72 @@ def test_xferfcn_ndarray_precedence(op, tf, arr):
10731074
# Apply the operator to the array and transfer function
10741075
result = op(arr, tf)
10751076
assert isinstance(result, ct.TransferFunction)
1077+
1078+
1079+
@pytest.mark.parametrize(
1080+
"zeros, poles, gain, args, kwargs", [
1081+
([], [-1], 1, [], {}),
1082+
([1, 2], [-1, -2, -3], 5, [], {}),
1083+
([1, 2], [-1, -2, -3], 5, [], {'name': "sys"}),
1084+
([1, 2], [-1, -2, -3], 5, [], {'inputs': ["in"], 'outputs': ["out"]}),
1085+
([1, 2], [-1, -2, -3], 5, [0.1], {}),
1086+
(np.array([1, 2]), np.array([-1, -2, -3]), 5, [], {}),
1087+
])
1088+
def test_zpk(zeros, poles, gain, args, kwargs):
1089+
# Create the transfer function
1090+
sys = ct.zpk(zeros, poles, gain, *args, **kwargs)
1091+
1092+
# Make sure the poles and zeros match
1093+
np.testing.assert_equal(sys.zeros().sort(), zeros.sort())
1094+
np.testing.assert_equal(sys.poles().sort(), poles.sort())
1095+
1096+
# Check to make sure the gain is OK
1097+
np.testing.assert_almost_equal(
1098+
gain, sys(0) * np.prod(-sys.poles()) / np.prod(-sys.zeros()))
1099+
1100+
# Check time base
1101+
if args:
1102+
assert sys.dt == args[0]
1103+
1104+
# Check inputs, outputs, name
1105+
input_labels = kwargs.get('inputs', [])
1106+
for i, label in enumerate(input_labels):
1107+
assert sys.input_labels[i] == label
1108+
1109+
output_labels = kwargs.get('outputs', [])
1110+
for i, label in enumerate(output_labels):
1111+
assert sys.output_labels[i] == label
1112+
1113+
if kwargs.get('name'):
1114+
assert sys.name == kwargs.get('name')
1115+
1116+
@pytest.mark.parametrize("create, args, kwargs, convert", [
1117+
(StateSpace, ([-1], [1], [1], [0]), {}, ss2tf),
1118+
(StateSpace, ([-1], [1], [1], [0]), {}, ss),
1119+
(StateSpace, ([-1], [1], [1], [0]), {}, tf),
1120+
(StateSpace, ([-1], [1], [1], [0]), dict(inputs='i', outputs='o'), ss2tf),
1121+
(StateSpace, ([-1], [1], [1], [0]), dict(inputs=1, outputs=1), ss2tf),
1122+
(StateSpace, ([-1], [1], [1], [0]), dict(inputs='i', outputs='o'), ss),
1123+
(StateSpace, ([-1], [1], [1], [0]), dict(inputs='i', outputs='o'), tf),
1124+
(TransferFunction, ([1], [1, 1]), {}, tf2ss),
1125+
(TransferFunction, ([1], [1, 1]), {}, tf),
1126+
(TransferFunction, ([1], [1, 1]), {}, ss),
1127+
(TransferFunction, ([1], [1, 1]), dict(inputs='i', outputs='o'), tf2ss),
1128+
(TransferFunction, ([1], [1, 1]), dict(inputs=1, outputs=1), tf2ss),
1129+
(TransferFunction, ([1], [1, 1]), dict(inputs='i', outputs='o'), tf),
1130+
(TransferFunction, ([1], [1, 1]), dict(inputs='i', outputs='o'), ss),
1131+
])
1132+
def test_copy_names(create, args, kwargs, convert):
1133+
# Convert a system with no renaming
1134+
sys = create(*args, **kwargs)
1135+
cpy = convert(sys)
1136+
1137+
assert cpy.input_labels == sys.input_labels
1138+
assert cpy.input_labels == sys.input_labels
1139+
if cpy.nstates is not None and sys.nstates is not None:
1140+
assert cpy.state_labels == sys.state_labels
1141+
1142+
# Relabel inputs and outputs
1143+
cpy = convert(sys, inputs='myin', outputs='myout')
1144+
assert cpy.input_labels == ['myin']
1145+
assert cpy.output_labels == ['myout']

control/xferfcn.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
from .frdata import FrequencyResponseData
6666
from . import config
6767

68-
__all__ = ['TransferFunction', 'tf', 'ss2tf', 'tfdata']
68+
__all__ = ['TransferFunction', 'tf', 'zpk', 'ss2tf', 'tfdata']
6969

7070

7171
# Define module default parameter values
@@ -796,7 +796,7 @@ def zeros(self):
796796
"""Compute the zeros of a transfer function."""
797797
if self.ninputs > 1 or self.noutputs > 1:
798798
raise NotImplementedError(
799-
"TransferFunction.zero is currently only implemented "
799+
"TransferFunction.zeros is currently only implemented "
800800
"for SISO systems.")
801801
else:
802802
# for now, just give zeros of a SISO tf
@@ -1424,16 +1424,13 @@ def _convert_to_transfer_function(sys, inputs=1, outputs=1):
14241424
num = squeeze(num) # Convert to 1D array
14251425
den = squeeze(den) # Probably not needed
14261426

1427-
return TransferFunction(
1428-
num, den, sys.dt, inputs=sys.input_labels,
1429-
outputs=sys.output_labels)
1427+
return TransferFunction(num, den, sys.dt)
14301428

14311429
elif isinstance(sys, (int, float, complex, np.number)):
14321430
num = [[[sys] for j in range(inputs)] for i in range(outputs)]
14331431
den = [[[1] for j in range(inputs)] for i in range(outputs)]
14341432

1435-
return TransferFunction(
1436-
num, den, inputs=inputs, outputs=outputs)
1433+
return TransferFunction(num, den)
14371434

14381435
elif isinstance(sys, FrequencyResponseData):
14391436
raise TypeError("Can't convert given FRD to TransferFunction system.")
@@ -1577,8 +1574,54 @@ def tf(*args, **kwargs):
15771574
else:
15781575
raise ValueError("Needs 1 or 2 arguments; received %i." % len(args))
15791576

1580-
# TODO: copy signal names
1577+
1578+
def zpk(zeros, poles, gain, *args, **kwargs):
1579+
"""zpk(zeros, poles, gain[, dt])
1580+
1581+
Create a transfer function from zeros, poles, gain.
1582+
1583+
Given a list of zeros z_i, poles p_j, and gain k, return the transfer
1584+
function:
1585+
1586+
.. math::
1587+
H(s) = k \\frac{(s - z_1) (s - z_2) \\cdots (s - z_m)}
1588+
{(s - p_1) (s - p_2) \\cdots (s - p_n)}
1589+
1590+
Parameters
1591+
----------
1592+
zeros : array_like
1593+
Array containing the location of zeros.
1594+
poles : array_like
1595+
Array containing the location of zeros.
1596+
gain : float
1597+
System gain
1598+
dt : None, True or float, optional
1599+
System timebase. 0 (default) indicates continuous
1600+
time, True indicates discrete time with unspecified sampling
1601+
time, positive number is discrete time with specified
1602+
sampling time, None indicates unspecified timebase (either
1603+
continuous or discrete time).
1604+
inputs, outputs, states : str, or list of str, optional
1605+
List of strings that name the individual signals. If this parameter
1606+
is not given or given as `None`, the signal names will be of the
1607+
form `s[i]` (where `s` is one of `u`, `y`, or `x`). See
1608+
:class:`InputOutputSystem` for more information.
1609+
name : string, optional
1610+
System name (used for specifying signals). If unspecified, a generic
1611+
name <sys[id]> is generated with a unique integer id.
1612+
1613+
Returns
1614+
-------
1615+
out: :class:`TransferFunction`
1616+
Transfer function with given zeros, poles, and gain.
1617+
1618+
"""
1619+
num, den = zpk2tf(zeros, poles, gain)
1620+
return TransferFunction(num, den, *args, **kwargs)
1621+
1622+
15811623
def ss2tf(*args, **kwargs):
1624+
15821625
"""ss2tf(sys)
15831626
15841627
Transform a state space system to a transfer function.
@@ -1658,6 +1701,11 @@ def ss2tf(*args, **kwargs):
16581701
if len(args) == 1:
16591702
sys = args[0]
16601703
if isinstance(sys, StateSpace):
1704+
kwargs = kwargs.copy()
1705+
if not kwargs.get('inputs'):
1706+
kwargs['inputs'] = sys.input_labels
1707+
if not kwargs.get('outputs'):
1708+
kwargs['outputs'] = sys.output_labels
16611709
return TransferFunction(
16621710
_convert_to_transfer_function(sys), **kwargs)
16631711
else:

doc/control.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ System creation
1818
ss
1919
tf
2020
frd
21+
zpk
2122
rss
2223
drss
2324
NonlinearIOSystem

0 commit comments

Comments
 (0)