Skip to content

Commit 6406868

Browse files
authored
Merge pull request #1019 from murrayrm/nlsys_improvements-24May2024
Small improvements to nlsys, bdalg
2 parents 7a135d1 + 8e123aa commit 6406868

File tree

8 files changed

+487
-123
lines changed

8 files changed

+487
-123
lines changed

control/bdalg.py

Lines changed: 104 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,19 @@
5454
"""
5555

5656
from functools import reduce
57-
import numpy as np
5857
from warnings import warn
59-
from . import xferfcn as tf
60-
from . import statesp as ss
58+
59+
import numpy as np
60+
6161
from . import frdata as frd
62+
from . import statesp as ss
63+
from . import xferfcn as tf
6264
from .iosys import InputOutputSystem
6365

6466
__all__ = ['series', 'parallel', 'negate', 'feedback', 'append', 'connect']
6567

6668

67-
def series(sys1, *sysn):
69+
def series(sys1, *sysn, **kwargs):
6870
r"""series(sys1, sys2, [..., sysn])
6971
7072
Return the series connection (`sysn` \* ...\ \*) `sys2` \* `sys1`.
@@ -79,6 +81,20 @@ def series(sys1, *sysn):
7981
out : scalar, array, or :class:`InputOutputSystem`
8082
Series interconnection of the systems.
8183
84+
Other Parameters
85+
----------------
86+
inputs, outputs : str, or list of str, optional
87+
List of strings that name the individual signals. If not given,
88+
signal names will be of the form `s[i]` (where `s` is one of `u`,
89+
or `y`). See :class:`InputOutputSystem` for more information.
90+
states : str, or list of str, optional
91+
List of names for system states. If not given, state names will be
92+
of of the form `x[i]` for interconnections of linear systems or
93+
'<subsys_name>.<state_name>' for interconnected nonlinear systems.
94+
name : string, optional
95+
System name (used for specifying signals). If unspecified, a generic
96+
name <sys[id]> is generated with a unique integer id.
97+
8298
Raises
8399
------
84100
ValueError
@@ -117,10 +133,12 @@ def series(sys1, *sysn):
117133
(2, 1, 5)
118134
119135
"""
120-
return reduce(lambda x, y: y * x, sysn, sys1)
136+
sys = reduce(lambda x, y: y * x, sysn, sys1)
137+
sys.update_names(**kwargs)
138+
return sys
121139

122140

123-
def parallel(sys1, *sysn):
141+
def parallel(sys1, *sysn, **kwargs):
124142
r"""parallel(sys1, sys2, [..., sysn])
125143
126144
Return the parallel connection `sys1` + `sys2` (+ ...\ + `sysn`).
@@ -135,6 +153,20 @@ def parallel(sys1, *sysn):
135153
out : scalar, array, or :class:`InputOutputSystem`
136154
Parallel interconnection of the systems.
137155
156+
Other Parameters
157+
----------------
158+
inputs, outputs : str, or list of str, optional
159+
List of strings that name the individual signals. If not given,
160+
signal names will be of the form `s[i]` (where `s` is one of `u`,
161+
or `y`). See :class:`InputOutputSystem` for more information.
162+
states : str, or list of str, optional
163+
List of names for system states. If not given, state names will be
164+
of of the form `x[i]` for interconnections of linear systems or
165+
'<subsys_name>.<state_name>' for interconnected nonlinear systems.
166+
name : string, optional
167+
System name (used for specifying signals). If unspecified, a generic
168+
name <sys[id]> is generated with a unique integer id.
169+
138170
Raises
139171
------
140172
ValueError
@@ -171,10 +203,11 @@ def parallel(sys1, *sysn):
171203
(3, 4, 7)
172204
173205
"""
174-
return reduce(lambda x, y: x + y, sysn, sys1)
175-
206+
sys = reduce(lambda x, y: x + y, sysn, sys1)
207+
sys.update_names(**kwargs)
208+
return sys
176209

177-
def negate(sys):
210+
def negate(sys, **kwargs):
178211
"""
179212
Return the negative of a system.
180213
@@ -188,15 +221,29 @@ def negate(sys):
188221
out : scalar, array, or :class:`InputOutputSystem`
189222
Negated system.
190223
191-
Notes
192-
-----
193-
This function is a wrapper for the __neg__ function in the StateSpace and
194-
TransferFunction classes. The output type is the same as the input type.
224+
Other Parameters
225+
----------------
226+
inputs, outputs : str, or list of str, optional
227+
List of strings that name the individual signals. If not given,
228+
signal names will be of the form `s[i]` (where `s` is one of `u`,
229+
or `y`). See :class:`InputOutputSystem` for more information.
230+
states : str, or list of str, optional
231+
List of names for system states. If not given, state names will be
232+
of of the form `x[i]` for interconnections of linear systems or
233+
'<subsys_name>.<state_name>' for interconnected nonlinear systems.
234+
name : string, optional
235+
System name (used for specifying signals). If unspecified, a generic
236+
name <sys[id]> is generated with a unique integer id.
195237
196238
See Also
197239
--------
198240
append, feedback, interconnect, parallel, series
199241
242+
Notes
243+
-----
244+
This function is a wrapper for the __neg__ function in the StateSpace and
245+
TransferFunction classes. The output type is the same as the input type.
246+
200247
Examples
201248
--------
202249
>>> G = ct.tf([2], [1, 1])
@@ -208,11 +255,12 @@ def negate(sys):
208255
np.float64(-2.0)
209256
210257
"""
211-
return -sys
258+
sys = -sys
259+
sys.update_names(**kwargs)
260+
return sys
212261

213262
#! TODO: expand to allow sys2 default to work in MIMO case?
214-
#! TODO: allow renaming of signals (for all bdalg operations)
215-
def feedback(sys1, sys2=1, sign=-1):
263+
def feedback(sys1, sys2=1, sign=-1, **kwargs):
216264
"""Feedback interconnection between two I/O systems.
217265
218266
Parameters
@@ -229,6 +277,20 @@ def feedback(sys1, sys2=1, sign=-1):
229277
out : scalar, array, or :class:`InputOutputSystem`
230278
Feedback interconnection of the systems.
231279
280+
Other Parameters
281+
----------------
282+
inputs, outputs : str, or list of str, optional
283+
List of strings that name the individual signals. If not given,
284+
signal names will be of the form `s[i]` (where `s` is one of `u`,
285+
or `y`). See :class:`InputOutputSystem` for more information.
286+
states : str, or list of str, optional
287+
List of names for system states. If not given, state names will be
288+
of of the form `x[i]` for interconnections of linear systems or
289+
'<subsys_name>.<state_name>' for interconnected nonlinear systems.
290+
name : string, optional
291+
System name (used for specifying signals). If unspecified, a generic
292+
name <sys[id]> is generated with a unique integer id.
293+
232294
Raises
233295
------
234296
ValueError
@@ -261,7 +323,7 @@ def feedback(sys1, sys2=1, sign=-1):
261323
# Allow anything with a feedback function to call that function
262324
# TODO: rewrite to allow __rfeedback__
263325
try:
264-
return sys1.feedback(sys2, sign)
326+
return sys1.feedback(sys2, sign, **kwargs)
265327
except (AttributeError, TypeError):
266328
pass
267329

@@ -284,9 +346,11 @@ def feedback(sys1, sys2=1, sign=-1):
284346
else:
285347
sys1 = ss._convert_to_statespace(sys1)
286348

287-
return sys1.feedback(sys2, sign)
349+
sys = sys1.feedback(sys2, sign)
350+
sys.update_names(**kwargs)
351+
return sys
288352

289-
def append(*sys):
353+
def append(*sys, **kwargs):
290354
"""append(sys1, sys2, [..., sysn])
291355
292356
Group LTI state space models by appending their inputs and outputs.
@@ -299,6 +363,20 @@ def append(*sys):
299363
sys1, sys2, ..., sysn: scalar, array, or :class:`StateSpace`
300364
I/O systems to combine.
301365
366+
Other Parameters
367+
----------------
368+
inputs, outputs : str, or list of str, optional
369+
List of strings that name the individual signals. If not given,
370+
signal names will be of the form `s[i]` (where `s` is one of `u`,
371+
or `y`). See :class:`InputOutputSystem` for more information.
372+
states : str, or list of str, optional
373+
List of names for system states. If not given, state names will be
374+
of of the form `x[i]` for interconnections of linear systems or
375+
'<subsys_name>.<state_name>' for interconnected nonlinear systems.
376+
name : string, optional
377+
System name (used for specifying signals). If unspecified, a generic
378+
name <sys[id]> is generated with a unique integer id.
379+
302380
Returns
303381
-------
304382
out: :class:`StateSpace`
@@ -327,6 +405,7 @@ def append(*sys):
327405
s1 = ss._convert_to_statespace(sys[0])
328406
for s in sys[1:]:
329407
s1 = s1.append(s)
408+
s1.update_names(**kwargs)
330409
return s1
331410

332411
def connect(sys, Q, inputv, outputv):
@@ -370,6 +449,12 @@ def connect(sys, Q, inputv, outputv):
370449
--------
371450
append, feedback, interconnect, negate, parallel, series
372451
452+
Notes
453+
-----
454+
The :func:`~control.interconnect` function in the :ref:`input/output
455+
systems <iosys-module>` module allows the use of named signals and
456+
provides an alternative method for interconnecting multiple systems.
457+
373458
Examples
374459
--------
375460
>>> G = ct.rss(7, inputs=2, outputs=2)
@@ -378,12 +463,6 @@ def connect(sys, Q, inputv, outputv):
378463
>>> T.ninputs, T.noutputs, T.nstates
379464
(1, 2, 7)
380465
381-
Notes
382-
-----
383-
The :func:`~control.interconnect` function in the :ref:`input/output
384-
systems <iosys-module>` module allows the use of named signals and
385-
provides an alternative method for interconnecting multiple systems.
386-
387466
"""
388467
# TODO: maintain `connect` for use in MATLAB submodule (?)
389468
warn("`connect` is deprecated; use `interconnect`", DeprecationWarning)

control/iosys.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
# FrequencyResponseData, InterconnectedSystem and other similar classes
77
# that allow naming of signals.
88

9-
import numpy as np
9+
import re
1010
from copy import deepcopy
1111
from warnings import warn
12-
import re
12+
13+
import numpy as np
14+
1315
from . import config
1416

1517
__all__ = ['InputOutputSystem', 'issiso', 'timebase', 'common_timebase',
@@ -366,6 +368,51 @@ def find_states(self, name_list):
366368
lambda self: list(self.state_index.keys()), # getter
367369
set_states) # setter
368370

371+
# TODO: add dict as a means to selective change names? [GH #1019]
372+
def update_names(self, **kwargs):
373+
"""update_names([name, inputs, outputs, states])
374+
375+
Update signal and system names for an I/O system.
376+
377+
Parameters
378+
----------
379+
name : str, optional
380+
New system name.
381+
inputs : list of str, int, or None, optional
382+
List of strings that name the individual input signals. If
383+
given as an integer or None, signal names default to the form
384+
`u[i]`. See :class:`InputOutputSystem` for more information.
385+
outputs : list of str, int, or None, optional
386+
Description of output signals; defaults to `y[i]`.
387+
states : int, list of str, int, or None, optional
388+
Description of system states; defaults to `x[i]`.
389+
390+
"""
391+
self.name = kwargs.pop('name', self.name)
392+
if 'inputs' in kwargs:
393+
ninputs, input_index = _process_signal_list(
394+
kwargs.pop('inputs'), prefix=kwargs.pop('input_prefix', 'u'))
395+
if self.ninputs and self.ninputs != ninputs:
396+
raise ValueError("number of inputs does not match system size")
397+
self.input_index = input_index
398+
if 'outputs' in kwargs:
399+
noutputs, output_index = _process_signal_list(
400+
kwargs.pop('outputs'), prefix=kwargs.pop('output_prefix', 'y'))
401+
if self.noutputs and self.noutputs != noutputs:
402+
raise ValueError("number of outputs does not match system size")
403+
self.output_index = output_index
404+
if 'states' in kwargs:
405+
nstates, state_index = _process_signal_list(
406+
kwargs.pop('states'), prefix=kwargs.pop('state_prefix', 'x'))
407+
if self.nstates != nstates:
408+
raise ValueError("number of states does not match system size")
409+
self.state_index = state_index
410+
411+
# Make sure we processed all of the arguments
412+
if kwargs:
413+
raise TypeError("unrecognized keywords: ", str(kwargs))
414+
415+
369416
def isctime(self, strict=False):
370417
"""
371418
Check to see if a system is a continuous-time system.
@@ -823,7 +870,6 @@ def _process_labels(labels, name, default):
823870
# This function returns the subsystem index, a list of indices for the
824871
# system signals, and the gain to use for that set of signals.
825872
#
826-
import re
827873

828874
def _parse_spec(syslist, spec, signame, dictname=None):
829875
"""Parse a signal specification, returning system and signal index."""

0 commit comments

Comments
 (0)