Skip to content

Commit 2538a06

Browse files
committed
allow parameter variants and inplist/outlist defaults
1 parent ada3eb2 commit 2538a06

File tree

2 files changed

+104
-13
lines changed

2 files changed

+104
-13
lines changed

control/iosys.py

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ class LinearIOSystem(InputOutputSystem, StateSpace):
612612
613613
"""
614614
def __init__(self, linsys, inputs=None, outputs=None, states=None,
615-
name=None):
615+
name=None, **kwargs):
616616
"""Create an I/O system from a state space linear system.
617617
618618
Converts a :class:`~control.StateSpace` system into an
@@ -658,6 +658,10 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None,
658658
if not isinstance(linsys, StateSpace):
659659
raise TypeError("Linear I/O system must be a state space object")
660660

661+
# Look for 'input' and 'output' parameter name variants
662+
inputs = _parse_signal_parameter(inputs, 'input', kwargs)
663+
outputs = _parse_signal_parameter(outputs, 'output', kwargs, end=True)
664+
661665
# Create the I/O system object
662666
super(LinearIOSystem, self).__init__(
663667
inputs=linsys.ninputs, outputs=linsys.noutputs,
@@ -707,8 +711,7 @@ class NonlinearIOSystem(InputOutputSystem):
707711
708712
"""
709713
def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
710-
states=None, params={},
711-
name=None, **kwargs):
714+
states=None, params={}, name=None, **kwargs):
712715
"""Create a nonlinear I/O system given update and output functions.
713716
714717
Creates an :class:`~control.InputOutputSystem` for a nonlinear system
@@ -775,17 +778,25 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
775778
Nonlinear system represented as an input/output system.
776779
777780
"""
781+
# Look for 'input' and 'output' parameter name variants
782+
inputs = _parse_signal_parameter(inputs, 'input', kwargs)
783+
outputs = _parse_signal_parameter(outputs, 'output', kwargs)
784+
778785
# Store the update and output functions
779786
self.updfcn = updfcn
780787
self.outfcn = outfcn
781788

782789
# Initialize the rest of the structure
783-
dt = kwargs.get('dt', config.defaults['control.default_dt'])
790+
dt = kwargs.pop('dt', config.defaults['control.default_dt'])
784791
super(NonlinearIOSystem, self).__init__(
785792
inputs=inputs, outputs=outputs, states=states,
786793
params=params, dt=dt, name=name
787794
)
788795

796+
# Make sure all input arguments got parsed
797+
if kwargs:
798+
raise TypeError("unknown parameters %s" % kwargs)
799+
789800
# Check to make sure arguments are consistent
790801
if updfcn is None:
791802
if self.nstates is None:
@@ -834,7 +845,7 @@ class InterconnectedSystem(InputOutputSystem):
834845
"""
835846
def __init__(self, syslist, connections=[], inplist=[], outlist=[],
836847
inputs=None, outputs=None, states=None,
837-
params={}, dt=None, name=None):
848+
params={}, dt=None, name=None, **kwargs):
838849
"""Create an I/O system from a list of systems + connection info.
839850
840851
The InterconnectedSystem class is used to represent an input/output
@@ -846,6 +857,10 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
846857
See :func:`~control.interconnect` for a list of parameters.
847858
848859
"""
860+
# Look for 'input' and 'output' parameter name variants
861+
inputs = _parse_signal_parameter(inputs, 'input', kwargs)
862+
outputs = _parse_signal_parameter(outputs, 'output', kwargs, end=True)
863+
849864
# Convert input and output names to lists if they aren't already
850865
if not isinstance(inplist, (list, tuple)):
851866
inplist = [inplist]
@@ -1810,6 +1825,15 @@ def linearize(sys, xeq, ueq=[], t=0, params={}, **kw):
18101825
return sys.linearize(xeq, ueq, t=t, params=params, **kw)
18111826

18121827

1828+
# Utility function to parse a signal parameter
1829+
def _parse_signal_parameter(value, name, kwargs, end=False):
1830+
if value is None and name in kwargs:
1831+
value = list(kwargs.pop(name))
1832+
if end and kwargs:
1833+
raise TypeError("unknown parameters %s" % kwargs)
1834+
return value
1835+
1836+
18131837
def _find_size(sysval, vecval):
18141838
"""Utility function to find the size of a system parameter
18151839
@@ -1849,7 +1873,7 @@ def tf2io(*args, **kwargs):
18491873
# Function to create an interconnected system
18501874
def interconnect(syslist, connections=None, inplist=[], outlist=[],
18511875
inputs=None, outputs=None, states=None,
1852-
params={}, dt=None, name=None):
1876+
params={}, dt=None, name=None, **kwargs):
18531877
"""Interconnect a set of input/output systems.
18541878
18551879
This function creates a new system that is an interconnection of a set of
@@ -1995,7 +2019,7 @@ def interconnect(syslist, connections=None, inplist=[], outlist=[],
19952019
>>> P = control.tf2io(control.tf(1, [1, 0]), inputs='u', outputs='y')
19962020
>>> C = control.tf2io(control.tf(10, [1, 1]), inputs='e', outputs='u')
19972021
>>> sumblk = control.summing_junction(inputs=['r', '-y'], output='e')
1998-
>>> T = control.interconnect([P, C, sumblk], inplist='r', outlist='y')
2022+
>>> T = control.interconnect([P, C, sumblk], input='r', output='y')
19992023
20002024
Notes
20012025
-----
@@ -2020,7 +2044,14 @@ def interconnect(syslist, connections=None, inplist=[], outlist=[],
20202044
treated as both a :class:`~control.StateSpace` system as well as an
20212045
:class:`~control.InputOutputSystem`.
20222046
2047+
The `input` and `output` keywords can be used instead of `inputs` and
2048+
`outputs`, for more natural naming of SISO systems.
2049+
20232050
"""
2051+
# Look for 'input' and 'output' parameter name variants
2052+
inputs = _parse_signal_parameter(inputs, 'input', kwargs)
2053+
outputs = _parse_signal_parameter(outputs, 'output', kwargs, end=True)
2054+
20242055
# If connections was not specified, set up default connection list
20252056
if connections is None:
20262057
# For each system input, look for outputs with the same name
@@ -2037,6 +2068,12 @@ def interconnect(syslist, connections=None, inplist=[], outlist=[],
20372068
# Use an empty connections list
20382069
connections = []
20392070

2071+
# If inplist/outlist is not present, try using inputs/outputs instead
2072+
if not inplist and inputs is not None:
2073+
inplist = list(inputs)
2074+
if not outlist and outputs is not None:
2075+
outlist = list(outputs)
2076+
20402077
# Process input list
20412078
if not isinstance(inplist, (list, tuple)):
20422079
inplist = [inplist]
@@ -2106,7 +2143,9 @@ def interconnect(syslist, connections=None, inplist=[], outlist=[],
21062143

21072144

21082145
# Summing junction
2109-
def summing_junction(inputs, output='y', dimension=None, name=None, prefix='u'):
2146+
def summing_junction(
2147+
inputs=None, output=None, dimension=None, name=None,
2148+
prefix='u', **kwargs):
21102149
"""Create a summing junction as an input/output system.
21112150
21122151
This function creates a static input/output system that outputs the sum of
@@ -2145,10 +2184,10 @@ def summing_junction(inputs, output='y', dimension=None, name=None, prefix='u'):
21452184
21462185
Example
21472186
-------
2148-
>>> P = control.tf2io(ct.tf(1, [1, 0]), inputs='u', outputs='y')
2149-
>>> C = control.tf2io(ct.tf(10, [1, 1]), inputs='e', outputs='u')
2187+
>>> P = control.tf2io(ct.tf(1, [1, 0]), input='u', output='y')
2188+
>>> C = control.tf2io(ct.tf(10, [1, 1]), input='e', output='u')
21502189
>>> sumblk = control.summing_junction(inputs=['r', '-y'], output='e')
2151-
>>> T = control.interconnect((P, C, sumblk), inplist='r', outlist='y')
2190+
>>> T = control.interconnect((P, C, sumblk), input='r', output='y')
21522191
21532192
"""
21542193
# Utility function to parse input and output signal lists
@@ -2181,6 +2220,16 @@ def _parse_list(signals, signame='input', prefix='u'):
21812220
# Return the parsed list
21822221
return nsignals, names, gains
21832222

2223+
# Look for 'input' and 'output' parameter name variants
2224+
inputs = _parse_signal_parameter(inputs, 'input', kwargs)
2225+
output = _parse_signal_parameter(output, 'outputs', kwargs, end=True)
2226+
2227+
# Default values for inputs and output
2228+
if inputs is None:
2229+
raise TypeError("input specification is required")
2230+
if output is None:
2231+
output = 'y'
2232+
21842233
# Read the input list
21852234
ninputs, input_names, input_gains = _parse_list(
21862235
inputs, signame="input", prefix=prefix)

control/tests/interconnect_test.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ def test_summing_junction(inputs, output, dimension, D):
4545
def test_summation_exceptions():
4646
# Bad input description
4747
with pytest.raises(ValueError, match="could not parse input"):
48-
sumblk = ct.summing_junction(None, 'y')
48+
sumblk = ct.summing_junction(np.pi, 'y')
4949

5050
# Bad output description
5151
with pytest.raises(ValueError, match="could not parse output"):
52-
sumblk = ct.summing_junction('u', None)
52+
sumblk = ct.summing_junction('u', np.pi)
5353

5454
# Bad input dimension
5555
with pytest.raises(ValueError, match="unrecognized dimension"):
@@ -128,6 +128,14 @@ def test_interconnect_implicit():
128128
np.testing.assert_almost_equal(pi_io.C, pi_ss.C)
129129
np.testing.assert_almost_equal(pi_io.D, pi_ss.D)
130130

131+
# Default input and output lists, along with singular versions
132+
Tio_sum = ct.interconnect(
133+
(kp_io, ki_io, P, sumblk), input='r', output='y')
134+
np.testing.assert_almost_equal(Tio_sum.A, Tss.A)
135+
np.testing.assert_almost_equal(Tio_sum.B, Tss.B)
136+
np.testing.assert_almost_equal(Tio_sum.C, Tss.C)
137+
np.testing.assert_almost_equal(Tio_sum.D, Tss.D)
138+
131139
# Signal not found
132140
with pytest.raises(ValueError, match="could not find"):
133141
Tio_sum = ct.interconnect(
@@ -168,3 +176,37 @@ def test_interconnect_docstring():
168176
np.testing.assert_almost_equal(T.B, T_ss.B)
169177
np.testing.assert_almost_equal(T.C, T_ss.C)
170178
np.testing.assert_almost_equal(T.D, T_ss.D)
179+
180+
181+
def test_interconnect_exceptions():
182+
# First make sure the docstring example works
183+
P = ct.tf2io(ct.tf(1, [1, 0]), input='u', output='y')
184+
C = ct.tf2io(ct.tf(10, [1, 1]), input='e', output='u')
185+
sumblk = ct.summing_junction(inputs=['r', '-y'], output='e')
186+
T = ct.interconnect((P, C, sumblk), input='r', output='y')
187+
assert (T.ninputs, T.noutputs, T.nstates) == (1, 1, 2)
188+
189+
# Unrecognized arguments
190+
# LinearIOSystem
191+
with pytest.raises(TypeError, match="unknown parameter"):
192+
P = ct.LinearIOSystem(ct.rss(2, 1, 1), output_name='y')
193+
194+
# Interconnect
195+
with pytest.raises(TypeError, match="unknown parameter"):
196+
T = ct.interconnect((P, C, sumblk), input_name='r', output='y')
197+
198+
# Interconnected system
199+
with pytest.raises(TypeError, match="unknown parameter"):
200+
T = ct.InterconnectedSystem((P, C, sumblk), input_name='r', output='y')
201+
202+
# NonlinearIOSytem
203+
with pytest.raises(TypeError, match="unknown parameter"):
204+
nlios = ct.NonlinearIOSystem(
205+
None, lambda t, x, u, params: u*u, input_count=1, output_count=1)
206+
207+
# Summing junction
208+
with pytest.raises(TypeError, match="input specification is required"):
209+
sumblk = ct.summing_junction()
210+
211+
with pytest.raises(TypeError, match="unknown parameter"):
212+
sumblk = ct.summing_junction(input_count=2, output_count=2)

0 commit comments

Comments
 (0)