- Notifications
You must be signed in to change notification settings - Fork441
Fixed InterconnectedSystems name bugs.#400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
672f229
15a442a
77f9441
d45a6c0
b688729
c8fba97
bd38dea
35676f2
90705d4
69cc8c5
e76a4f8
abb1f19
f955b3a
3ec2f02
8c957b2
b2c8d44
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -76,7 +76,8 @@ class for a set of subclasses that are used to implement specific | ||
Parameter values for the systems. Passed to the evaluation functions | ||
for the system as default values, overriding internal defaults. | ||
name : string, optional | ||
System name (used for specifying signals). If unspecified, a generic | ||
name <sys[id]> is generated with a unique integer id. | ||
Attributes | ||
---------- | ||
@@ -108,6 +109,14 @@ class for a set of subclasses that are used to implement specific | ||
The default is to return the entire system state. | ||
""" | ||
idCounter = 0 | ||
samlaf marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
def name_or_default(self, name=None): | ||
if name is None: | ||
name = "sys[{}]".format(InputOutputSystem.idCounter) | ||
InputOutputSystem.idCounter += 1 | ||
bnavigator marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
return name | ||
def __init__(self, inputs=None, outputs=None, states=None, params={}, | ||
dt=None, name=None): | ||
"""Create an input/output system. | ||
@@ -143,7 +152,8 @@ def __init__(self, inputs=None, outputs=None, states=None, params={}, | ||
functions for the system as default values, overriding internal | ||
defaults. | ||
name : string, optional | ||
System name (used for specifying signals). If unspecified, a generic | ||
name <sys[id]> is generated with a unique integer id. | ||
Returns | ||
------- | ||
@@ -152,9 +162,9 @@ def __init__(self, inputs=None, outputs=None, states=None, params={}, | ||
""" | ||
# Store the input arguments | ||
self.params = params.copy()# default parameters | ||
self.dt = dt# timebase | ||
self.name =self.name_or_default(name) # system name | ||
# Parse and store the number of inputs, outputs, and states | ||
self.set_inputs(inputs) | ||
@@ -204,29 +214,19 @@ def __mul__(sys2, sys1): | ||
if dt is False: | ||
raise ValueError("System timebases are not compabile") | ||
inplist = [(0,i) for i in range(sys1.ninputs)] | ||
outlist = [(1,i) for i in range(sys2.noutputs)] | ||
# Return the series interconnection between the systems | ||
newsys = InterconnectedSystem((sys1, sys2), inplist=inplist, outlist=outlist) | ||
# Set up theconnection map manually | ||
newsys.set_connect_map(np.block( | ||
[[np.zeros((sys1.ninputs, sys1.noutputs)), | ||
np.zeros((sys1.ninputs, sys2.noutputs))], | ||
[np.eye(sys2.ninputs, sys1.noutputs), | ||
np.zeros((sys2.ninputs, sys2.noutputs))]] | ||
)) | ||
# Return the newly created system | ||
return newsys | ||
@@ -271,18 +271,10 @@ def __add__(sys1, sys2): | ||
ninputs = sys1.ninputs | ||
noutputs = sys1.noutputs | ||
inplist = [[(0,i),(1,i)] for i in range(ninputs)] | ||
outlist = [[(0,i),(1,i)] for i in range(noutputs)] | ||
# Create a new system to handle the composition | ||
newsys = InterconnectedSystem((sys1, sys2), inplist=inplist, outlist=outlist) | ||
# Return the newly created system | ||
return newsys | ||
@@ -301,16 +293,10 @@ def __neg__(sys): | ||
if sys.ninputs is None or sys.noutputs is None: | ||
raise ValueError("Can't determine number of inputs or outputs") | ||
inplist = [(0,i) for i in range(sys.ninputs)] | ||
outlist = [(0,i,-1) for i in range(sys.noutputs)] | ||
# Create a new system to hold the negation | ||
newsys = InterconnectedSystem((sys,), dt=sys.dt, inplist=inplist, outlist=outlist) | ||
# Return the newly created system | ||
return newsys | ||
@@ -482,29 +468,20 @@ def feedback(self, other=1, sign=-1, params={}): | ||
if dt is False: | ||
raise ValueError("System timebases are not compabile") | ||
inplist = [(0,i) for i in range(self.ninputs)] | ||
outlist = [(0,i) for i in range(self.noutputs)] | ||
# Return the series interconnection between the systems | ||
newsys = InterconnectedSystem((self, other), inplist=inplist, outlist=outlist, | ||
params=params, dt=dt) | ||
# Set up the connecton map manually | ||
newsys.set_connect_map(np.block( | ||
[[np.zeros((self.ninputs, self.noutputs)), | ||
sign * np.eye(self.ninputs, other.noutputs)], | ||
[np.eye(other.ninputs, self.noutputs), | ||
np.zeros((other.ninputs, other.noutputs))]] | ||
)) | ||
# Return the newly created system | ||
return newsys | ||
@@ -564,9 +541,11 @@ def linearize(self, x0, u0, t=0, params={}, eps=1e-6): | ||
linsys = StateSpace(A, B, C, D, self.dt, remove_useless=False) | ||
return LinearIOSystem(linsys) | ||
def copy(self, newname=None): | ||
"""Make a copy of an input/output system.""" | ||
newsys = copy.copy(self) | ||
newsys.name = self.name_or_default("copy of " + self.name if not newname else newname) | ||
return newsys | ||
class LinearIOSystem(InputOutputSystem, StateSpace): | ||
@@ -610,7 +589,8 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None, | ||
functions for the system as default values, overriding internal | ||
defaults. | ||
name : string, optional | ||
System name (used for specifying signals). If unspecified, a generic | ||
name <sys[id]> is generated with a unique integer id. | ||
Returns | ||
------- | ||
@@ -728,7 +708,8 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None, | ||
* dt = True Discrete time with unspecified sampling time | ||
name : string, optional | ||
System name (used for specifying signals). If unspecified, a generic | ||
name <sys[id]> is generated with a unique integer id. | ||
Returns | ||
------- | ||
@@ -808,10 +789,13 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[], | ||
syslist : array_like of InputOutputSystems | ||
The list of input/output systems to be connected | ||
connections :list oftuple of connection specifications, optional | ||
Description of the internal connections between the subsystems. | ||
[connection1, connection2, ...] | ||
Each connection is a tuple that describes an input to one of the | ||
subsystems. The entries are of the form: | ||
(input-spec, output-spec1, output-spec2, ...) | ||
@@ -835,10 +819,15 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[], | ||
If omitted, the connection map (matrix) can be specified using the | ||
:func:`~control.InterconnectedSystem.set_connect_map` method. | ||
inplist :List oftuple of input specifications, optional | ||
List of specifications for how the inputs for the overall system | ||
are mapped to the subsystem inputs. The input specification is | ||
similar to the form defined in the connection specification, except | ||
that connections do not specify an input-spec, since these are | ||
the system inputs. The entries are thus of the form: | ||
(output-spec1, output-spec2, ...) | ||
Each system input is added to the input for the listed subsystem. | ||
If omitted, the input map can be specified using the | ||
@@ -847,14 +836,31 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[], | ||
outlist : tuple of output specifications, optional | ||
List of specifications for how the outputs for the subsystems are | ||
mapped to overall system outputs. The output specification is the | ||
same as the form defined in theinplist specification | ||
(including the optional gain term). Numbered outputs must be | ||
chosen from the list of subsystem outputs, but named outputs can | ||
also be contained in the list of subsystem inputs. | ||
If omitted, the output map can be specified using the | ||
`set_output_map` method. | ||
inputs : int, list of str or None, optional | ||
Description of the system inputs. This can be given as an integer | ||
count or as a list of strings that name the individual signals. | ||
If an integer count is specified, the names of the signal will be | ||
of the form `s[i]` (where `s` is one of `u`, `y`, or `x`). If | ||
this parameter is not given or given as `None`, the relevant | ||
quantity will be determined when possible based on other | ||
information provided to functions using the system. | ||
outputs : int, list of str or None, optional | ||
Description of the system outputs. Same format as `inputs`. | ||
states : int, list of str, or None, optional | ||
Description of the system states. Same format as `inputs`, except | ||
the state names will be of the form '<subsys_name>.<state_name>', | ||
for each subsys in syslist and each state_name of each subsys. | ||
params : dict, optional | ||
Parameter values for the systems. Passed to the evaluation | ||
functions for the system as default values, overriding internal | ||
@@ -871,7 +877,8 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[], | ||
* dt = True Discrete time with unspecified sampling time | ||
name : string, optional | ||
System name (used for specifying signals). If unspecified, a generic | ||
name <sys[id]> is generated with a unique integer id. | ||
""" | ||
# Convert input and output names to lists if they aren't already | ||
@@ -885,8 +892,9 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[], | ||
nstates = 0; self.state_offset = [] | ||
ninputs = 0; self.input_offset = [] | ||
noutputs = 0; self.output_offset = [] | ||
sysobj_name_dct = {} | ||
sysname_count_dct = {} | ||
for sysidx, sys in enumerate(syslist): | ||
# Make sure time bases are consistent | ||
# TODO: Use lti._find_timebase() instead? | ||
if dt is None and sys.dt is not None: | ||
@@ -912,36 +920,44 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[], | ||
ninputs += sys.ninputs | ||
noutputs += sys.noutputs | ||
# Check for duplicate systems or duplicate names | ||
# Duplicates are renamed sysname_1, sysname_2, etc. | ||
if sys in sysobj_name_dct: | ||
sys = sys.copy() | ||
warn("Duplicate object found in system list: %s. Making a copy" % str(sys)) | ||
if sys.name is not None and sys.name in sysname_count_dct: | ||
count = sysname_count_dct[sys.name] | ||
sysname_count_dct[sys.name] += 1 | ||
sysname = sys.name + "_" + str(count) | ||
sysobj_name_dct[sys] = sysname | ||
self.syslist_index[sysname] = sysidx | ||
warn("Duplicate name found in system list. Renamed to {}".format(sysname)) | ||
else: | ||
sysname_count_dct[sys.name] = 1 | ||
sysobj_name_dct[sys] = sys.name | ||
self.syslist_index[sys.name] = sysidx | ||
if states is None: | ||
states = [] | ||
for sys, sysname in sysobj_name_dct.items(): | ||
states += [sysname + '.' + statename for statename in sys.state_index.keys()] | ||
ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. @murrayrm This is the part that would break if you add a named signal with a generic 'sys[i]' that will cause a conflict (it's no different from having two systems with the same name). The only "bad" thing that happens is that the system will be missing the state names of the systems with conflicting names (only the first 'sys[i].states' will be present). Edit: see the comment above this snippet of code... I'm new to this and not sure how to include it in the comment. | ||
# Create the I/O system | ||
super(InterconnectedSystem, self).__init__( | ||
inputs=len(inplist), outputs=len(outlist), | ||
states=states, params=params, dt=dt, name=name) | ||
# If input or output list was specified, update it | ||
if inputs is not None: | ||
nsignals, self.input_index = \ | ||
self._process_signal_list(inputs, prefix='u') | ||
if nsignals is not None and len(inplist) != nsignals: | ||
raise ValueError("Wrong number/type of inputs given.") | ||
if outputs is not None: | ||
nsignals, self.output_index = \ | ||
self._process_signal_list(outputs, prefix='y') | ||
if nsignals is not None and len(outlist) != nsignals: | ||
raise ValueError("Wrong number/type of outputs given.") | ||
# Convert the list of interconnections to a connection map (matrix) | ||
self.connect_map = np.zeros((ninputs, noutputs)) | ||
@@ -960,9 +976,11 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[], | ||
# Convert the output list to a matrix: maps subsystems to system | ||
self.output_map = np.zeros((self.noutputs, noutputs + ninputs)) | ||
for index, outspec in enumerate(outlist): | ||
if isinstance(outspec, (int, str, tuple)): outspec = [outspec] | ||
for spec in outspec: | ||
ylist_index, gain = self._parse_output_spec(spec) | ||
self.output_map[index, ylist_index] = gain | ||
# Save the parameters for the system | ||
self.params = params.copy() | ||
Uh oh!
There was an error while loading.Please reload this page.