Skip to content

Commit 8ad1e74

Browse files
committed
add/move interconnect unit tests(); clean up list/tuple; small fixes
1 parent 40e6021 commit 8ad1e74

File tree

4 files changed

+157
-65
lines changed

4 files changed

+157
-65
lines changed

control/iosys.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -889,9 +889,9 @@ def __init__(self, syslist, connections=None, inplist=None, outlist=None,
889889
params=None, warn_duplicate=None, **kwargs):
890890
"""Create an I/O system from a list of systems + connection info."""
891891
# Convert input and output names to lists if they aren't already
892-
if inplist is not None and not isinstance(inplist, (list, tuple)):
892+
if inplist is not None and not isinstance(inplist, list):
893893
inplist = [inplist]
894-
if outlist is not None and not isinstance(outlist, (list, tuple)):
894+
if outlist is not None and not isinstance(outlist, list):
895895
outlist = [outlist]
896896

897897
# Check if dt argument was given; if not, pull from systems
@@ -2552,7 +2552,7 @@ def interconnect(
25522552
subsystem are used. If systems and signals are given names, then
25532553
the form 'sys.sig', ('sys', 'sig') or ('sys', 'sig', gain) are also
25542554
recognized, and the special form '-sys.sig' can be used to specify
2555-
a signal with gain -1. Lists, slices, and base names can also be
2555+
a signal with gain -1. Lists, slices, and base namess can also be
25562556
used, as long as the number of elements for each output spec
25572557
mataches the input spec.
25582558
@@ -2797,7 +2797,7 @@ def interconnect(
27972797
#
27982798
new_connections = []
27992799
for connection in connections:
2800-
if not isinstance(connection, (list, tuple)):
2800+
if not isinstance(connection, list):
28012801
raise ValueError(
28022802
f"invalid connection {connection}: should be a list")
28032803
# Parse and expand the input specification
@@ -2825,7 +2825,7 @@ def interconnect(
28252825
# number of elements in `inplist` will match the number of inputs for
28262826
# the interconnected system.
28272827
#
2828-
if not isinstance(inplist, (list, tuple)):
2828+
if not isinstance(inplist, list):
28292829
inplist = [inplist]
28302830
new_inplist = []
28312831
for connection in inplist:
@@ -2877,7 +2877,7 @@ def interconnect(
28772877
# additionally take into account the fact that you can list subsystem
28782878
# inputs as system outputs.
28792879
#
2880-
if not isinstance(outlist, (list, tuple)):
2880+
if not isinstance(outlist, list):
28812881
outlist = [outlist]
28822882
new_outlist = []
28832883
for connection in outlist:
@@ -3136,7 +3136,7 @@ def _parse_spec(syslist, spec, signame, dictname=None):
31363136
if len(namelist) > 2:
31373137
# TODO: expand to allow nested signal names
31383138
raise ValueError(f"couldn't parse signal reference '{spec}'")
3139-
elif isinstance(spec, (tuple, list)) and len(spec) <= 3:
3139+
elif isinstance(spec, tuple) and len(spec) <= 3:
31403140
system_spec = spec[0]
31413141
signal_spec = None if len(spec) < 2 else spec[1]
31423142
gain = None if len(spec) < 3 else spec[2]
@@ -3180,7 +3180,6 @@ def _parse_spec(syslist, spec, signame, dictname=None):
31803180
nsignals = len(signal_dict)
31813181

31823182
# Figure out the signal indices
3183-
# TODO: move this logic to _find_signals()
31843183
if signal_spec is None:
31853184
# No indices given => use the entire range of signals
31863185
signal_indices = list(range(nsignals))

control/namedio.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,12 @@ def _find_signals(self, name_list, sigdict):
132132
for var in sigdict:
133133
msig = re.match(name + r'\[([\d]+)\]$', var)
134134
if msig:
135-
index_list.append(int(msig.group(1)))
135+
index_list.append(sigdict.get(var))
136136
else:
137137
index_list.append(sigdict.get(name, None))
138138

139-
return None if any([idx is None for idx in index_list]) else index_list
139+
return None if len(index_list) == 0 or \
140+
any([idx is None for idx in index_list]) else index_list
140141

141142
def _copy_names(self, sys, prefix="", suffix="", prefix_suffix_name=None):
142143
"""copy the signal and system name of sys. Name is given as a keyword

control/tests/interconnect_test.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,150 @@ def test_linear_interconnect():
297297
inplist=['sum.r'], inputs='r',
298298
outlist=['plant.y'], outputs='y')
299299
assert clsys.syslist[0].name == 'ctrl'
300+
301+
@pytest.mark.parametrize(
302+
"connections, inplist, outlist, inputs, outputs", [
303+
pytest.param(
304+
[['sys2', 'sys1']], 'sys1', 'sys2', None, None,
305+
id="sysname only, no i/o args"),
306+
pytest.param(
307+
[['sys2', 'sys1']], 'sys1', 'sys2', 3, 3,
308+
id="i/o signal counts"),
309+
pytest.param(
310+
[[('sys2', [0, 1, 2]), ('sys1', [0, 1, 2])]],
311+
[('sys1', [0, 1, 2])], [('sys2', [0, 1, 2])],
312+
3, 3,
313+
id="signal lists, i/o counts"),
314+
pytest.param(
315+
[['sys2.u[0:3]', 'sys1.y[:]']],
316+
'sys1.u[:]', ['sys2.y[0:3]'], None, None,
317+
id="signal slices"),
318+
pytest.param(
319+
['sys2.u', 'sys1.y'], 'sys1.u', 'sys2.y', None, None,
320+
id="signal basenames"),
321+
pytest.param(
322+
[[('sys2', [0, 1, 2]), ('sys1', [0, 1, 2])]],
323+
[('sys1', [0, 1, 2])], [('sys2', [0, 1, 2])],
324+
None, None,
325+
id="signal lists, no i/o counts"),
326+
pytest.param(
327+
[[(1, ['u[0]', 'u[1]', 'u[2]']), (0, ['y[0]', 'y[1]', 'y[2]'])]],
328+
[('sys1', [0, 1, 2])], [('sys2', [0, 1, 2])],
329+
3, ['y1', 'y2', 'y3'],
330+
id="mixed specs"),
331+
pytest.param(
332+
[[f'sys2.u[{i}]', f'sys1.y[{i}]'] for i in range(3)],
333+
[f'sys1.u[{i}]' for i in range(3)],
334+
[f'sys2.y[{i}]' for i in range(3)],
335+
[f'u[{i}]' for i in range(3)], [f'y[{i}]' for i in range(3)],
336+
id="full enumeration"),
337+
])
338+
def test_interconnect_series(connections, inplist, outlist, inputs, outputs):
339+
# Create an interconnected system for testing
340+
sys1 = ct.rss(4, 3, 3, name='sys1')
341+
sys2 = ct.rss(4, 3, 3, name='sys2')
342+
series = sys2 * sys1
343+
344+
# Simple series interconnection
345+
icsys = ct.interconnect(
346+
[sys1, sys2], connections=connections,
347+
inplist=inplist, outlist=outlist, inputs=inputs, outputs=outputs
348+
)
349+
np.testing.assert_allclose(icsys.A, series.A)
350+
np.testing.assert_allclose(icsys.B, series.B)
351+
np.testing.assert_allclose(icsys.C, series.C)
352+
np.testing.assert_allclose(icsys.D, series.D)
353+
354+
355+
@pytest.mark.parametrize(
356+
"connections, inplist, outlist", [
357+
pytest.param(
358+
[['P', 'C'], ['C', '-P']], 'C', 'P',
359+
id="sysname only, no i/o args"),
360+
pytest.param(
361+
[['P.u', 'C.y'], ['C.u', '-P.y']], 'C.u', 'P.y',
362+
id="sysname only, no i/o args"),
363+
pytest.param(
364+
[['P.u[:]', 'C.y[0:2]'],
365+
[('C', 'u'), ('P', ['y[0]', 'y[1]'], -1)]],
366+
['C.u[0]', 'C.u[1]'], ('P', [0, 1]),
367+
id="mixed cases"),
368+
])
369+
def test_interconnect_feedback(connections, inplist, outlist):
370+
# Create an interconnected system for testing
371+
P = ct.rss(4, 2, 2, name='P', strictly_proper=True)
372+
C = ct.rss(4, 2, 2, name='C')
373+
feedback = ct.feedback(P * C, np.eye(2))
374+
375+
# Simple feedback interconnection
376+
icsys = ct.interconnect(
377+
[C, P], connections=connections,
378+
inplist=inplist, outlist=outlist
379+
)
380+
np.testing.assert_allclose(icsys.A, feedback.A)
381+
np.testing.assert_allclose(icsys.B, feedback.B)
382+
np.testing.assert_allclose(icsys.C, feedback.C)
383+
np.testing.assert_allclose(icsys.D, feedback.D)
384+
385+
386+
@pytest.mark.parametrize(
387+
"pinputs, poutputs, connections, inplist, outlist", [
388+
pytest.param(
389+
['w[0]', 'w[1]', 'u[0]', 'u[1]'], # pinputs
390+
['z[0]', 'z[1]', 'y[0]', 'y[1]'], # poutputs
391+
[[('P', [2, 3]), ('C', [0, 1])], [('C', [0, 1]), ('P', [2, 3], -1)]],
392+
[('C', [0, 1]), ('P', [0, 1])], # inplist
393+
[('P', [0, 1, 2, 3]), ('C', [0, 1])], # outlist
394+
id="signal indices"),
395+
pytest.param(
396+
['w[0]', 'w[1]', 'u[0]', 'u[1]'], # pinputs
397+
['z[0]', 'z[1]', 'y[0]', 'y[1]'], # poutputs
398+
[[('P', [2, 3]), ('C', [0, 1])], [('C', [0, 1]), ('P', [2, 3], -1)]],
399+
['C', ('P', [0, 1])], ['P', 'C'], # inplist, outlist
400+
id="signal indices, when needed"),
401+
pytest.param(
402+
4, 4, # default I/O names
403+
[['P.u[2:4]', 'C.y[:]'], ['C.u', '-P.y[2:]']],
404+
['C', 'P.u[:2]'], ['P.y[:]', 'P.u[2:]'], # inplist, outlist
405+
id="signal slices"),
406+
pytest.param(
407+
['w[0]', 'w[1]', 'u[0]', 'u[1]'], # pinputs
408+
['z[0]', 'z[1]', 'y[0]', 'y[1]'], # poutputs
409+
[['P.u', 'C.y'], ['C.u', '-P.y']], # connections
410+
['C.u', 'P.w'], ['P.z', 'P.y', 'C.y'], # inplist, outlist
411+
id="basename, control output"),
412+
pytest.param(
413+
['w[0]', 'w[1]', 'u[0]', 'u[1]'], # pinputs
414+
['z[0]', 'z[1]', 'y[0]', 'y[1]'], # poutputs
415+
[['P.u', 'C.y'], ['C.u', '-P.y']], # connections
416+
['C.u', 'P.w'], ['P.z', 'P.y', 'P.u'], # inplist, outlist
417+
id="basename, process input"),
418+
])
419+
def test_interconnect_partial_feedback(
420+
pinputs, poutputs, connections, inplist, outlist):
421+
P = ct.rss(
422+
states=6, name='P', strictly_proper=True,
423+
inputs=pinputs, outputs=poutputs)
424+
C = ct.rss(4, 2, 2, name='C')
425+
426+
# Low level feedback connection (feedback around "lower" process I/O)
427+
partial = ct.interconnect(
428+
[C, P],
429+
connections=[
430+
[(1, 2), (0, 0)], [(1, 3), (0, 1)],
431+
[(0, 0), (1, 2, -1)], [(0, 1), (1, 3, -1)]],
432+
inplist=[(0, 0), (0, 1), (1, 0), (1, 1)], # C.u, P.w
433+
outlist=[(1, 0), (1, 1), (1, 2), (1, 3),
434+
(0, 0), (0, 1)], # P.z, P.y, C.y
435+
)
436+
437+
# High level feedback conections
438+
icsys = ct.interconnect(
439+
[C, P], connections=connections,
440+
inplist=inplist, outlist=outlist
441+
)
442+
np.testing.assert_allclose(icsys.A, partial.A)
443+
np.testing.assert_allclose(icsys.B, partial.B)
444+
np.testing.assert_allclose(icsys.C, partial.C)
445+
np.testing.assert_allclose(icsys.D, partial.D)
446+
>>>>>>> 7c86239 (add/move interconnect unit tests(); clean up list/tuple; small fixes)

control/tests/iosys_test.py

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2047,58 +2047,3 @@ def test_iosys_sample():
20472047
dsys = ct.sample_system(csys, 0.1)
20482048
assert isinstance(dsys, ct.LinearIOSystem)
20492049
assert dsys.dt == 0.1
2050-
2051-
# TODO: convert to multiple marks, to go through all possibilities
2052-
@pytest.mark.parametrize(
2053-
"connections, inplist, outlist, inputs, outputs", [
2054-
pytest.param(
2055-
[['sys2', 'sys1']], 'sys1', 'sys2', None, None,
2056-
id="sysname only, no i/o args"),
2057-
pytest.param(
2058-
[['sys2', 'sys1']], 'sys1', 'sys2', 3, 3,
2059-
id="i/o signal counts"),
2060-
pytest.param(
2061-
[[('sys2', [0, 1, 2]), ('sys1', [0, 1, 2])]],
2062-
[('sys1', [0, 1, 2])], [('sys2', [0, 1, 2])],
2063-
3, 3,
2064-
id="signal lists, i/o counts"),
2065-
pytest.param(
2066-
[['sys2.u[0:3]', 'sys1.y[:]']],
2067-
'sys1.u[:]', ['sys2.y[0:3]'], None, None,
2068-
id="signal slices"),
2069-
pytest.param(
2070-
['sys2.u', 'sys1.y'], 'sys1.u', 'sys2.y', None, None,
2071-
id="signal basenames"),
2072-
pytest.param(
2073-
[[('sys2', [0, 1, 2]), ('sys1', [0, 1, 2])]],
2074-
[('sys1', [0, 1, 2])], [('sys2', [0, 1, 2])],
2075-
None, None,
2076-
id="signal lists, no i/o counts"),
2077-
pytest.param(
2078-
[[(1, ['u[0]', 'u[1]', 'u[2]']), (0, ['y[0]', 'y[1]', 'y[2]'])]],
2079-
[('sys1', [0, 1, 2])], [('sys2', [0, 1, 2])],
2080-
3, ['y1', 'y2', 'y3'],
2081-
id="mixed specs"),
2082-
pytest.param(
2083-
[[f'sys2.u[{i}]', f'sys1.y[{i}]'] for i in range(3)],
2084-
[f'sys1.u[{i}]' for i in range(3)],
2085-
[f'sys2.y[{i}]' for i in range(3)],
2086-
[f'u[{i}]' for i in range(3)], [f'y[{i}]' for i in range(3)],
2087-
id="full enumeration"),
2088-
])
2089-
2090-
def test_iosys_signal_spec(connections, inplist, outlist, inputs, outputs):
2091-
# Create an interconnected system for testing
2092-
sys1 = ct.rss(4, 3, 3, name='sys1')
2093-
sys2 = ct.rss(4, 3, 3, name='sys2')
2094-
series = sys2 * sys1
2095-
2096-
# Simple series interconnection
2097-
icsys = ct.interconnect(
2098-
[sys1, sys2], connections=connections,
2099-
inplist=inplist, outlist=outlist, inputs=inputs, outputs=outputs
2100-
)
2101-
np.testing.assert_allclose(icsys.A, series.A)
2102-
np.testing.assert_allclose(icsys.B, series.B)
2103-
np.testing.assert_allclose(icsys.C, series.C)
2104-
np.testing.assert_allclose(icsys.D, series.D)

0 commit comments

Comments
 (0)