Skip to content

Commit cd3e4b6

Browse files
committed
change name to summing_junction + updated documentation and examples
1 parent 012d1d0 commit cd3e4b6

File tree

5 files changed

+154
-28
lines changed

5 files changed

+154
-28
lines changed

control/iosys.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,11 @@
33
# RMM, 28 April 2019
44
#
55
# Additional features to add
6-
# * Improve support for signal names, specially in operator overloads
7-
# - Figure out how to handle "nested" names (icsys.sys[1].x[1])
8-
# - Use this to implement signal names for operators?
96
# * Allow constant inputs for MIMO input_output_response (w/out ones)
107
# * Add support for constants/matrices as part of operators (1 + P)
118
# * Add unit tests (and example?) for time-varying systems
129
# * Allow time vector for discrete time simulations to be multiples of dt
1310
# * Check the way initial outputs for discrete time systems are handled
14-
# * Rename 'connections' as 'conlist' to match 'inplist' and 'outlist'?
15-
# * Allow signal summation in InterconnectedSystem diagrams (via new output?)
1611
#
1712

1813
"""The :mod:`~control.iosys` module contains the
@@ -44,7 +39,7 @@
4439
__all__ = ['InputOutputSystem', 'LinearIOSystem', 'NonlinearIOSystem',
4540
'InterconnectedSystem', 'LinearICSystem', 'input_output_response',
4641
'find_eqpt', 'linearize', 'ss2io', 'tf2io', 'interconnect',
47-
'summation_block']
42+
'summing_junction']
4843

4944
# Define module default parameter values
5045
_iosys_defaults = {
@@ -1982,17 +1977,26 @@ def interconnect(syslist, connections=None, inplist=[], outlist=[],
19821977
Example
19831978
-------
19841979
>>> P = control.LinearIOSystem(
1985-
>>> ct.rss(2, 2, 2, strictly_proper=True), name='P')
1980+
>>> control.rss(2, 2, 2, strictly_proper=True), name='P')
19861981
>>> C = control.LinearIOSystem(control.rss(2, 2, 2), name='C')
1987-
>>> S = control.InterconnectedSystem(
1982+
>>> T = control.interconnect(
19881983
>>> [P, C],
19891984
>>> connections = [
1990-
>>> ['P.u[0]', 'C.y[0]'], ['P.u[1]', 'C.y[0]'],
1985+
>>> ['P.u[0]', 'C.y[0]'], ['P.u[1]', 'C.y[1]'],
19911986
>>> ['C.u[0]', '-P.y[0]'], ['C.u[1]', '-P.y[1]']],
19921987
>>> inplist = ['C.u[0]', 'C.u[1]'],
19931988
>>> outlist = ['P.y[0]', 'P.y[1]'],
19941989
>>> )
19951990
1991+
For a SISO system, this example can be simplified by using the
1992+
:func:`~control.summing_block` function and the ability to automatically
1993+
interconnect signals with the same names:
1994+
1995+
>>> P = control.tf2io(control.tf(1, [1, 0]), inputs='u', outputs='y')
1996+
>>> C = control.tf2io(control.tf(10, [1, 1]), inputs='e', outputs='u')
1997+
>>> sumblk = control.summing_junction(inputs=['r', '-y'], output='e')
1998+
>>> T = control.interconnect([P, C, sumblk], inplist='r', outlist='y')
1999+
19962000
Notes
19972001
-----
19982002
If a system is duplicated in the list of systems to be connected,
@@ -2101,9 +2105,9 @@ def interconnect(syslist, connections=None, inplist=[], outlist=[],
21012105
return newsys
21022106

21032107

2104-
# Summation block
2105-
def summation_block(inputs, output='y', dimension=None, name=None, prefix='u'):
2106-
"""Create a summation block as an input/output system.
2108+
# Summing junction
2109+
def summing_junction(inputs, output='y', dimension=None, name=None, prefix='u'):
2110+
"""Create a summing junction as an input/output system.
21072111
21082112
This function creates a static input/output system that outputs the sum of
21092113
the inputs, potentially with a change in sign for each individual input.
@@ -2113,15 +2117,15 @@ def summation_block(inputs, output='y', dimension=None, name=None, prefix='u'):
21132117
Parameters
21142118
----------
21152119
inputs : int, string or list of strings
2116-
Description of the inputs to the summation block. This can be given
2120+
Description of the inputs to the summing junction. This can be given
21172121
as an integer count, a string, or a list of strings. If an integer
21182122
count is specified, the names of the input signals will be of the form
21192123
`u[i]`.
21202124
output : string, optional
21212125
Name of the system output. If not specified, the output will be 'y'.
21222126
dimension : int, optional
2123-
The dimension of the summing block. If the dimension is set to a
2124-
positive integer, a multi-input, multi-output summation block will be
2127+
The dimension of the summing junction. If the dimension is set to a
2128+
positive integer, a multi-input, multi-output summing junction will be
21252129
created. The input and output signal names will be of the form
21262130
`<signal>[i]` where `signal` is the input/output signal name specified
21272131
by the `inputs` and `output` keywords. Default value is `None`.
@@ -2137,7 +2141,14 @@ def summation_block(inputs, output='y', dimension=None, name=None, prefix='u'):
21372141
-------
21382142
sys : static LinearIOSystem
21392143
Linear input/output system object with no states and only a direct
2140-
term that implements the summation block.
2144+
term that implements the summing junction.
2145+
2146+
Example
2147+
-------
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')
2150+
>>> sumblk = control.summing_junction(inputs=['r', '-y'], output='e')
2151+
>>> T = control.interconnect((P, C, sumblk), inplist='r', outlist='y')
21412152
21422153
"""
21432154
# Utility function to parse input and output signal lists

control/statesp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,15 +223,15 @@ def __init__(self, *args, **kwargs):
223223
224224
The default constructor is StateSpace(A, B, C, D), where A, B, C, D
225225
are matrices or equivalent objects. To create a discrete time system,
226-
use StateSpace(A, B, C, D, dt) where 'dt' is the sampling time (or
226+
use StateSpace(A, B, C, D, dt) where `dt` is the sampling time (or
227227
True for unspecified sampling time). To call the copy constructor,
228228
call StateSpace(sys), where sys is a StateSpace object.
229229
230230
The `remove_useless_states` keyword can be used to scan the A, B, and
231231
C matrices for rows or columns of zeros. If the zeros are such that a
232232
particular state has no effect on the input-output dynamics, then that
233233
state is removed from the A, B, and C matrices. If not specified, the
234-
value is read from `config.defaults['statesp.remove_useless_states']
234+
value is read from `config.defaults['statesp.remove_useless_states']`
235235
(default = False).
236236
237237
"""

control/tests/interconnect_test.py

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
['u', 'y', 2, [[1, 0], [0, 1]] ],
3131
[['r', '-y'], ['e'], 2, [[1, 0, -1, 0], [0, 1, 0, -1]] ],
3232
])
33-
def test_summation_block(inputs, output, dimension, D):
33+
def test_summing_junction(inputs, output, dimension, D):
3434
ninputs = 1 if isinstance(inputs, str) else \
3535
inputs if isinstance(inputs, int) else len(inputs)
36-
sum = ct.summation_block(
36+
sum = ct.summing_junction(
3737
inputs=inputs, output=output, dimension=dimension)
3838
dim = 1 if dimension is None else dimension
3939
np.testing.assert_array_equal(sum.A, np.ndarray((0, 0)))
@@ -45,15 +45,15 @@ def test_summation_block(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.summation_block(None, 'y')
48+
sumblk = ct.summing_junction(None, 'y')
4949

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

5454
# Bad input dimension
5555
with pytest.raises(ValueError, match="unrecognized dimension"):
56-
sumblk = ct.summation_block('u', 'y', dimension=False)
56+
sumblk = ct.summing_junction('u', 'y', dimension=False)
5757

5858

5959
def test_interconnect_implicit():
@@ -83,8 +83,8 @@ def test_interconnect_implicit():
8383
np.testing.assert_almost_equal(Tio_exp.C, Tss.C)
8484
np.testing.assert_almost_equal(Tio_exp.D, Tss.D)
8585

86-
# Construct the interconnection via a summation block
87-
sumblk = ct.summation_block(inputs=['r', '-y'], output='e', name="sum")
86+
# Construct the interconnection via a summing junction
87+
sumblk = ct.summing_junction(inputs=['r', '-y'], output='e', name="sum")
8888
Tio_sum = ct.interconnect(
8989
(C, P, sumblk), inplist=['r'], outlist=['y'])
9090

@@ -108,6 +108,17 @@ def test_interconnect_implicit():
108108
np.testing.assert_almost_equal(Tio_sum.C, Tss.C)
109109
np.testing.assert_almost_equal(Tio_sum.D, Tss.D)
110110

111+
# TODO: interconnect a MIMO system using implicit connections
112+
# P = control.ss2io(
113+
# control.rss(2, 2, 2, strictly_proper=True),
114+
# input_prefix='u', output_prefix='y', name='P')
115+
# C = control.ss2io(
116+
# control.rss(2, 2, 2),
117+
# input_prefix='e', output_prefix='u', name='C')
118+
# sumblk = control.summing_junction(
119+
# inputs=['r', '-y'], output='e', dimension=2)
120+
# S = control.interconnect([P, C, sumblk], inplist='r', outlist='y')
121+
111122
# Make sure that repeated inplist/outlist names generate an error
112123
# Input not unique
113124
Cbad = ct.tf2io(ct.tf(10, [1, 1]), inputs='r', outputs='x', name='C')
@@ -129,3 +140,35 @@ def test_interconnect_implicit():
129140
with pytest.raises(ValueError, match="could not find"):
130141
Tio_sum = ct.interconnect(
131142
(C, P, sumblk), inplist=['r'], outlist=['x'])
143+
144+
def test_interconnect_docstring():
145+
"""Test the examples from the interconnect() docstring"""
146+
147+
# MIMO interconnection (note: use [C, P] instead of [P, C] for state order)
148+
P = ct.LinearIOSystem(
149+
ct.rss(2, 2, 2, strictly_proper=True), name='P')
150+
C = ct.LinearIOSystem(ct.rss(2, 2, 2), name='C')
151+
T = ct.interconnect(
152+
[C, P],
153+
connections = [
154+
['P.u[0]', 'C.y[0]'], ['P.u[1]', 'C.y[1]'],
155+
['C.u[0]', '-P.y[0]'], ['C.u[1]', '-P.y[1]']],
156+
inplist = ['C.u[0]', 'C.u[1]'],
157+
outlist = ['P.y[0]', 'P.y[1]'],
158+
)
159+
T_ss = ct.feedback(P * C, ct.ss([], [], [], np.eye(2)))
160+
np.testing.assert_almost_equal(T.A, T_ss.A)
161+
np.testing.assert_almost_equal(T.B, T_ss.B)
162+
np.testing.assert_almost_equal(T.C, T_ss.C)
163+
np.testing.assert_almost_equal(T.D, T_ss.D)
164+
165+
# Implicit interconnection (note: use [C, P, sumblk] for proper state order)
166+
P = ct.tf2io(ct.tf(1, [1, 0]), inputs='u', outputs='y')
167+
C = ct.tf2io(ct.tf(10, [1, 1]), inputs='e', outputs='u')
168+
sumblk = ct.summing_junction(inputs=['r', '-y'], output='e')
169+
T = ct.interconnect([C, P, sumblk], inplist='r', outlist='y')
170+
T_ss = ct.feedback(P * C, 1)
171+
np.testing.assert_almost_equal(T.A, T_ss.A)
172+
np.testing.assert_almost_equal(T.B, T_ss.B)
173+
np.testing.assert_almost_equal(T.C, T_ss.C)
174+
np.testing.assert_almost_equal(T.D, T_ss.D)

doc/control.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ Nonlinear system support
144144
linearize
145145
input_output_response
146146
ss2io
147+
summing_junction
147148
tf2io
148149
flatsys.point_to_point
149150

doc/iosys.rst

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ The input to the controller is `u`, consisting of the vector of hare and lynx
156156
populations followed by the desired lynx population.
157157

158158
To connect the controller to the predatory-prey model, we create an
159-
:class:`~control.InterconnectedSystem`:
159+
:class:`~control.InterconnectedSystem` using the :func:`~control.interconnect`
160+
function:
160161

161162
.. code-block:: python
162163
@@ -189,13 +190,83 @@ Finally, we simulate the closed loop system:
189190
plt.legend(['input'])
190191
plt.show(block=False)
191192
193+
Additional features
194+
===================
195+
196+
The I/O systems module has a number of other features that can be used to
197+
simplify the creation of interconnected input/output systems.
198+
199+
Summing junction
200+
----------------
201+
202+
The :func:`~control.summing_junction` function can be used to create an
203+
input/output system that takes the sum of an arbitrary number of inputs. For
204+
ezample, to create an input/output system that takes the sum of three inputs,
205+
use the command
206+
207+
.. code-block:: python
208+
209+
sumblk = ct.summing_junction(3)
210+
211+
By default, the name of the inputs will be of the form ``u[i]`` and the output
212+
will be ``y``. This can be changed by giving an explicit list of names::
213+
214+
sumblk = ct.summing_junction(inputs=['a', 'b', 'c'], output='d')
215+
216+
A more typical usage would be to define an input/output system that compares a
217+
reference signal to the output of the process and computes the error::
218+
219+
sumblk = ct.summing_junction(inputs=['r', '-y'], output='e')
220+
221+
Note the use of the minus sign as a means of setting the sign of the input 'y'
222+
to be negative instead of positive.
223+
224+
It is also possible to define "vector" summing blocks that take
225+
multi-dimensional inputs and produce a multi-dimensional output. For example,
226+
the command
227+
228+
.. code-block:: python
229+
230+
sumblk = ct.summing_junction(inputs=['r', '-y'], output='e', dimension=2)
231+
232+
will produce an input/output block that implements ``e[0] = r[0] - y[0]`` and
233+
``e[1] = r[1] - y[1]``.
234+
235+
Automatic connections using signal names
236+
----------------------------------------
237+
238+
The :func:`~control.interconnect` function allows the interconnection of
239+
multiple systems by using signal names of the form ``sys.signal``. In many
240+
situations, it can be cumbersome to explicitly connect all of the appropriate
241+
inputs and outputs. As an alternative, if the ``connections`` keyword is
242+
omitted, the :func:`~control.interconnect` function will connect all signals
243+
of the same name to each other. This can allow for simplified methods of
244+
interconnecting systems, especially when combined with the
245+
:func:`~control.summing_junction` function. For example, the following code
246+
will create a unity gain, negative feedback system::
247+
248+
P = control.tf2io(control.tf(1, [1, 0]), inputs='u', outputs='y')
249+
C = control.tf2io(control.tf(10, [1, 1]), inputs='e', outputs='u')
250+
sumblk = control.summing_junction(inputs=['r', '-y'], output='e')
251+
T = control.interconnect([P, C, sumblk], inplist='r', outlist='y')
252+
253+
If a signal name appears in multiple outputs then that signal will be summed
254+
when it is interconnected. Similarly, if a signal name appears in multiple
255+
inputs then all systems using that signal name will receive the same input.
256+
The :func:`~control.interconnect` function will generate an error if an signal
257+
listed in ``inplist`` or ``outlist`` (corresponding to the inputs and outputs
258+
of the interconnected system) is not found, but inputs and outputs of
259+
individual systems that are not connected to other systems are left
260+
unconnected (so be careful!).
261+
262+
192263
Module classes and functions
193264
============================
194265

195266
Input/output system classes
196267
---------------------------
197268
.. autosummary::
198-
269+
199270
~control.InputOutputSystem
200271
~control.InterconnectedSystem
201272
~control.LinearICSystem
@@ -211,5 +282,5 @@ Input/output system functions
211282
~control.input_output_response
212283
~control.interconnect
213284
~control.ss2io
285+
~control.summing_junction
214286
~control.tf2io
215-

0 commit comments

Comments
 (0)