E5FA update docs, tests for InterconnectedSystem + small refactoring of ga… · python-control/python-control@4e46c4a · GitHub
[go: up one dir, main page]

Skip to content

Commit 4e46c4a

Browse files
committed
update docs, tests for InterconnectedSystem + small refactoring of gain parsing
1 parent 50e61a0 commit 4e46c4a

File tree

4 files changed

+289
-104
lines changed

4 files changed

+289
-104
lines changed

control/iosys.py

Lines changed: 107 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ def __mul__(sys2, sys1):
214214
elif isinstance(sys1, StateSpace) and isinstance(sys2, StateSpace):
215215
# Special case: maintain linear systems structure
216216
new_ss_sys = StateSpace.__mul__(sys2, sys1)
217-
# TODO: set input and output names
218217
new_io_sys = LinearIOSystem(new_ss_sys)
219218

220219
return new_io_sys
@@ -825,60 +824,70 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
825824
826825
Parameters
827826
----------
828-
syslist : array_like of InputOutputSystems
827+
syslist : list of InputOutputSystems
829828
The list of input/output systems to be connected
830829
831-
connections : list of tuple of connection specifications, optional
832-
Description of the internal connections between the subsystems.
830+
connections : list of connections, optional
831+
Description of the internal connections between the subsystems:
833832
834833
[connection1, connection2, ...]
835834
836-
Each connection is a tuple that describes an input to one of the
837-
subsystems. The entries are of the form:
835+
Each connection is itself a list that describes an input to one of
836+
the subsystems. The entries are of the form:
838837
839-
(input-spec, output-spec1, output-spec2, ...)
838+
[input-spec, output-spec1, output-spec2, ...]
840839
841-
The input-spec should be a tuple of the form `(subsys_i, inp_j)`
840+
The input-spec can be in a number of different forms. The lowest
841+
level representation is a tuple of the form `(subsys_i, inp_j)`
842842
where `subsys_i` is the index into `syslist` and `inp_j` is the
843843
index into the input vector for the subsystem. If `subsys_i` has
844844
a single input, then the subsystem index `subsys_i` can be listed
845845
as the input-spec. If systems and signals are given names, then
846846
the form 'sys.sig' or ('sys', 'sig') are also recognized.
847847
848-
Each output-spec should be a tuple of the form `(subsys_i, out_j,
849-
gain)`. The input will be constructed by summing the listed
850-
outputs after multiplying by the gain term. If the gain term is
851-
omitted, it is assumed to be 1. If the system has a single
852-
output, then the subsystem index `subsys_i` can be listed as the
853-
input-spec. If systems and signals are given names, then the form
854-
'sys.sig', ('sys', 'sig') or ('sys', 'sig', gain) are also
855-
recognized, and the special form '-sys.sig' can be used to specify
856-
a signal with gain -1.
848+
Similarly, each output-spec should describe an output signal from
849+
one of the susystems. The lowest level representation is a tuple
850+
of the form `(subsys_i, out_j, gain)`. The input will be
851+
constructed by summing the listed outputs after multiplying by the
852+
gain term. If the gain term is omitted, it is assumed to be 1.
853+
If the system has a single output, then the subsystem index
854+
`subsys_i` can be listed as the input-spec. If systems and
855+
signals are given names, then the form 'sys.sig', ('sys', 'sig')
856+
or ('sys', 'sig', gain) are also recognized, and the special form
857+
'-sys.sig' can be used to specify a signal with gain -1.
857858
858859
If omitted, the connection map (matrix) can be specified using the
859860
:func:`~control.InterconnectedSystem.set_connect_map` method.
860861
861-
inplist : List of tuple of input specifications, optional
862-
List of specifications for how the inputs for the overall system
862+
inplist : list of input connections, optional
863+
List of connections for how the inputs for the overall system
863864
are mapped to the subsystem inputs. The input specification is
864-
similar to the form defined in the connection specification, except
865-
that connections do not specify an input-spec, since these are
866-
the system inputs. The entries are thus of the form:
865+
similar to the form defined in the connection specification,
866+
except that connections do not specify an input-spec, since these
867+
are the system inputs. The entries for a connection are thus of
868+
the form:
867869
868-
(output-spec1, output-spec2, ...)
870+
[input-spec1, input-spec2, ...]
869871
870872
Each system input is added to the input for the listed subsystem.
873+
If the system input connects to only one subsystem input, a single
874+
input specification can be given (without the inner list).
871875
872876
If omitted, the input map can be specified using the
873877
`set_input_map` method.
874878
875-
outlist : tuple of output specifications, optional
876-
List of specifications for how the outputs for the subsystems are
877-
mapped to overall system outputs. The output specification is the
878-
same as the form defined in the inplist specification
879-
(including the optional gain term). Numbered outputs must be
880-
chosen from the list of subsystem outputs, but named outputs can
881-
also be contained in the list of subsystem inputs.
879+
outlist : list of output connections, optional
880+
List of connections for how the outputs from the subsystems are
881+
mapped to overall system outputs. The output connection
882+
description is the same as the form defined in the inplist
883+
specification (including the optional gain term). Numbered
884+
outputs must be chosen from the list of subsystem outputs, but
885+
named outputs can also be contained in the list of subsystem
886+
inputs.
887+
888+
If an output connection contains more than one signal
889+
specification, then those signals are added together (multiplying
890+
by the any gain term) to form the system output.
882891
883892
If omitted, the output map can be specified using the
884893
`set_output_map` method.
@@ -896,9 +905,10 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
896905
Description of the system outputs. Same format as `inputs`.
897906
898907
states : int, list of str, or None, optional
899-
Description of the system states. Same format as `inputs`, except
900-
the state names will be of the form '<subsys_name>.<state_name>',
901-
for each subsys in syslist and each state_name of each subsys.
908+
Description of the system states. Same format as `inputs`. The
909+
default is `None`, in which case the states will be given names of
910+
the form '<subsys_name>.<state_name>', for each subsys in syslist
911+
and each state_name of each subsys.
902912
903913
params : dict, optional
904914
Parameter values for the systems. Passed to the evaluation
@@ -919,6 +929,29 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
919929
System name (used for specifying signals). If unspecified, a
920930
generic name <sys[id]> is generated with a unique integer id.
921931
932+
Example
933+
-------
934+
P = control.LinearIOSystem(
935+
ct.rss(2, 2, 2, strictly_proper=True), name='P')
936+
C = control.LinearIOSystem(control.rss(2, 2, 2), name='C')
937+
S = control.InterconnectedSystem(
938+
[P, C],
939+
connections = [
940+
['P.u[0]', 'C.y[0]'], ['P.u[1]', 'C.y[0]'],
941+
['C.u[0]', '-P.y[0]'], ['C.u[1]', '-P.y[1]']],
942+
inplist = ['C.u[0]', 'C.u[1]'],
943+
outlist = ['P.y[0]', 'P.y[1]'],
944+
)
945+
946+
Notes
947+
-----
948+
It is possible to replace lists in most of arguments with tuples
949+
instead, but strictly speaking the only use of tuples should be in the
950+
specification of an input- or output-signal via the tuple notation
951+
`(subsys_i, signal_j, gain)` (where `gain` is optional). If you get
952+
an unexpected error message about a specification being of the wrong
953+
type, check your use of tuples.
954+
922955
"""
923956
# Convert input and output names to lists if they aren't already
924957
if not isinstance(inplist, (list, tuple)):
@@ -1006,24 +1039,40 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
10061039
input_index = self._parse_input_spec(connection[0])
10071040
for output_spec in connection[1:]:
10081041
output_index, gain = self._parse_output_spec(output_spec)
1009-
self.connect_map[input_index, output_index] = gain
1042+
if self.connect_map[input_index, output_index] != 0:
1043+
warn("multiple connections given for input %d" %
1044+
input_index + ". Combining with previous entries.")
1045+
self.connect_map[input_index, output_index] += gain
10101046

10111047
# Convert the input list to a matrix: maps system to subsystems
10121048
self.input_map = np.zeros((ninputs, self.ninputs))
10131049
for index, inpspec in enumerate(inplist):
10141050
if isinstance(inpspec, (int, str, tuple)):
10151051
inpspec = [inpspec]
1052+
if not isinstance(inpspec, list):
1053+
raise ValueError("specifications in inplist must be of type "
1054+
"int, str, tuple or list.")
10161055
for spec in inpspec:
1017-
self.input_map[self._parse_input_spec(spec), index] = 1
1056+
ulist_index = self._parse_input_spec(spec)
1057+
if self.input_map[ulist_index, index] != 0:
1058+
warn("multiple connections given for input %d" %
1059+
index + ". Combining with previous entries.")
1060+
self.input_map[ulist_index, index] += 1
10181061

10191062
# Convert the output list to a matrix: maps subsystems to system
10201063
self.output_map = np.zeros((self.noutputs, noutputs + ninputs))
10211064
for index, outspec in enumerate(outlist):
10221065
if isinstance(outspec, (int, str, tuple)):
10231066
outspec = [outspec]
1067+
if not isinstance(outspec, list):
1068+
raise ValueError("specifications in outlist must be of type "
1069+
"int, str, tuple or list.")
10241070
for spec in outspec:
10251071
ylist_index, gain = self._parse_output_spec(spec)
1026-
self.output_map[index, ylist_index] = gain
1072+
if self.output_map[index, ylist_index] != 0:
1073+
warn("multiple connections given for output %d" %
1074+
index + ". Combining with previous entries.")
1075+
self.output_map[index, ylist_index] += gain
10271076

10281077
# Save the parameters for the system
10291078
self.params = params.copy()
@@ -1166,7 +1215,9 @@ def _parse_input_spec(self, spec):
11661215
11671216
"""
11681217
# Parse the signal that we received
1169-
subsys_index, input_index = self._parse_signal(spec, 'input')
1218+
subsys_index, input_index, gain = self._parse_signal(spec, 'input')
1219+
if gain != 1:
1220+
raise ValueError("gain not allowed in spec '%s'." % str(spec))
11701221

11711222
# Return the index into the input vector list (ylist)
11721223
return self.input_offset[subsys_index] + input_index
@@ -1195,27 +1246,18 @@ def _parse_output_spec(self, spec):
11951246
the gain to use for that output.
11961247
11971248
"""
1198-
gain = 1 # Default gain
1199-
1200-
# Check for special forms of the input
1201-
if isinstance(spec, tuple) and len(spec) == 3:
1202-
gain = spec[2]
1203-
spec = spec[:2]
1204-
elif isinstance(spec, str) and spec[0] == '-':
1205-
gain = -1
1206-
spec = spec[1:]
1207-
12081249
# Parse the rest of the spec with standard signal parsing routine
12091250
try:
12101251
# Start by looking in the set of subsystem outputs
1211-
subsys_index, output_index = self._parse_signal(spec, 'output')
1252+
subsys_index, output_index, gain = \
1253+
self._parse_signal(spec, 'output')
12121254

12131255
# Return the index into the input vector list (ylist)
12141256
return self.output_offset[subsys_index] + output_index, gain
12151257

12161258
except ValueError:
12171259
# Try looking in the set of subsystem *inputs*
1218-
subsys_index, input_index = self._parse_signal(
1260+
subsys_index, input_index, gain = self._parse_signal(
12191261
spec, 'input or output', dictname='input_index')
12201262

12211263
# Return the index into the input vector list (ylist)
@@ -1240,17 +1282,27 @@ def _parse_signal(self, spec, signame='input', dictname=None):
12401282
"""
12411283
import re
12421284

1285+
gain = 1 # Default gain
1286+
1287+
# Check for special forms of the input
1288+
if isinstance(spec, tuple) and len(spec) == 3:
1289+
gain = spec[2]
1290+
spec = spec[:2]
1291+
elif isinstance(spec, str) and spec[0] == '-':
1292+
gain = -1
1293+
spec = spec[1:]
1294+
12431295
# Process cases where we are given indices as integers
12441296
if isinstance(spec, int):
1245-
return spec, 0
1297+
return spec, 0, gain
12461298

12471299
elif isinstance(spec, tuple) and len(spec) == 1 \
12481300
and isinstance(spec[0], int):
1249-
return spec[0], 0
1301+
return spec[0], 0, gain
12501302

12511303
elif isinstance(spec, tuple) and len(spec) == 2 \
12521304
and all([isinstance(index, int) for index in spec]):
1253-
return spec
1305+
return spec + (gain,)
12541306

12551307
# Figure out the name of the dictionary to use
12561308
if dictname is None:
@@ -1276,7 +1328,7 @@ def _parse_signal(self, spec, signame='input', dictname=None):
12761328
raise ValueError("Couldn't find %s signal '%s.%s'." %
12771329
(signame, namelist[0], namelist[1]))
12781330

1279-
return system_index, signal_index
1331+
return system_index, signal_index, gain
12801332

12811333
# Handle the ('sys', 'sig'), (i, j), and mixed cases
12821334
elif isinstance(spec, tuple) and len(spec) == 2 and \
@@ -1289,7 +1341,7 @@ def _parse_signal(self, spec, signame='input', dictname=None):
12891341
else:
12901342
system_index = self._find_system(spec[0])
12911343
if system_index is None:
1292-
raise ValueError("Couldn't find system %s." % spec[0])
1344+
raise ValueError("Couldn't find system '%s'." % spec[0])
12931345

12941346
if isinstance(spec[1], int):
12951347
signal_index = spec[1]
@@ -1302,7 +1354,7 @@ def _parse_signal(self, spec, signame='input', dictname=None):
13021354
if signal_index is None:
13031355
raise ValueError("Couldn't find signal %s.%s." % tuple(spec))
13041356

1305-
return system_index, signal_index
1357+
return system_index, signal_index, gain
13061358

13071359
else:
13081360
raise ValueError("Couldn't parse signal reference %s." % str(spec))

0 commit comments

Comments
 (0)
0