Skip to content

Commit df33002

Browse files
committed
add documentation for indexing by name + update docstrings
1 parent cd3d315 commit df33002

File tree

4 files changed

+148
-12
lines changed

4 files changed

+148
-12
lines changed

control/frdata.py

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ class FrequencyResponseData(LTI):
3535
3636
The FrequencyResponseData (FRD) class is used to represent systems in
3737
frequency response data form. It can be created manually using the
38-
class constructor, using the :func:~~control.frd` factory function
39-
(preferred), or via the :func:`~control.frequency_response` function.
38+
class constructor, using the :func:`~control.frd` factory function or
39+
via the :func:`~control.frequency_response` function.
4040
4141
Parameters
4242
----------
@@ -67,6 +67,28 @@ class constructor, using the :func:~~control.frd` factory function
6767
frequency point.
6868
dt : float, True, or None
6969
System timebase.
70+
squeeze : bool
71+
By default, if a system is single-input, single-output (SISO) then
72+
the outputs (and inputs) are returned as a 1D array (indexed by
73+
frequency) and if a system is multi-input or multi-output, then the
74+
outputs are returned as a 2D array (indexed by output and
75+
frequency) or a 3D array (indexed by output, trace, and frequency).
76+
If ``squeeze=True``, access to the output response will remove
77+
single-dimensional entries from the shape of the inputs and outputs
78+
even if the system is not SISO. If ``squeeze=False``, the output is
79+
returned as a 3D array (indexed by the output, input, and
80+
frequency) even if the system is SISO. The default value can be set
81+
using config.defaults['control.squeeze_frequency_response'].
82+
ninputs, noutputs, nstates : int
83+
Number of inputs, outputs, and states of the underlying system.
84+
input_labels, output_labels : array of str
85+
Names for the input and output variables.
86+
sysname : str, optional
87+
Name of the system. For data generated using
88+
:func:`~control.frequency_response`, stores the name of the system
89+
that created the data.
90+
title : str, optional
91+
Set the title to use when plotting.
7092
7193
See Also
7294
--------
@@ -259,24 +281,72 @@ def __init__(self, *args, **kwargs):
259281

260282
@property
261283
def magnitude(self):
284+
"""Magnitude of the frequency response.
285+
286+
Magnitude of the frequency response, indexed by either the output
287+
and frequency (if only a single input is given) or the output,
288+
input, and frequency (for multi-input systems). See
289+
:attr:`FrequencyResponseData.squeeze` for a description of how this
290+
can be modified using the `squeeze` keyword.
291+
292+
Input and output signal names can be used to index the data in
293+
place of integer offsets.
294+
295+
:type: 1D, 2D, or 3D array
296+
297+
"""
262298
return NamedSignal(
263299
np.abs(self.fresp), self.output_labels, self.input_labels)
264300

265301
@property
266302
def phase(self):
303+
"""Phase of the frequency response.
304+
305+
Phase of the frequency response in radians/sec, indexed by either
306+
the output and frequency (if only a single input is given) or the
307+
output, input, and frequency (for multi-input systems). See
308+
:attr:`FrequencyResponseData.squeeze` for a description of how this
309+
can be modified using the `squeeze` keyword.
310+
311+
Input and output signal names can be used to index the data in
312+
place of integer offsets.
313+
314+
:type: 1D, 2D, or 3D array
315+
316+
"""
267317
return NamedSignal(
268318
np.angle(self.fresp), self.output_labels, self.input_labels)
269319

270320
@property
271321
def frequency(self):
322+
"""Frequencies at which the response is evaluated.
323+
324+
:type: 1D array
325+
326+
"""
272327
return self.omega
273328

274329
@property
275330
def response(self):
331+
"""Complex value of the frequency response.
332+
333+
Value of the frequency response as a complex number, indexed by
334+
either the output and frequency (if only a single input is given)
335+
or the output, input, and frequency (for multi-input systems). See
336+
:attr:`FrequencyResponseData.squeeze` for a description of how this
337+
can be modified using the `squeeze` keyword.
338+
339+
Input and output signal names can be used to index the data in
340+
place of integer offsets.
341+
342+
:type: 1D, 2D, or 3D array
343+
344+
"""
276345
return NamedSignal(
277346
self.fresp, self.output_labels, self.input_labels)
278347

279348
def __str__(self):
349+
280350
"""String representation of the transfer function."""
281351

282352
mimo = self.ninputs > 1 or self.noutputs > 1

control/timeresp.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -228,16 +228,12 @@ class TimeResponseData:
228228
t, y = step_response(sys)
229229
t, y, x = step_response(sys, return_x=True)
230230
231-
When using this (legacy) interface, the state vector is not affected
232-
by the `squeeze` parameter.
231+
Similarly, the class has ``__getitem__`` and ``__len__`` methods that
232+
allow the return value to be indexed:
233233
234-
For backward compatibility with earlier version of python-control, this
235-
class has ``__getitem__`` and ``__len__`` methods that allow the return
236-
value to be indexed:
237-
238-
response[0]: returns the time vector
239-
response[1]: returns the output vector
240-
response[2]: returns the state vector
234+
* response[0]: returns the time vector
235+
* response[1]: returns the output vector
236+
* response[2]: returns the state vector
241237
242238
When using this (legacy) interface, the state vector is not affected
243239
by the `squeeze` parameter.
@@ -580,6 +576,10 @@ def outputs(self):
580576
(for multiple traces). See :attr:`TimeResponseData.squeeze` for a
581577
description of how this can be modified using the `squeeze` keyword.
582578
579+
Input and output signal names can be used to index the data in
580+
place of integer offsets, with the input signal names being used to
581+
access multi-input data.
582+
583583
:type: 1D, 2D, or 3D array
584584
585585
"""
@@ -600,6 +600,10 @@ def states(self):
600600
for a description of how this can be modified using the `squeeze`
601601
keyword.
602602
603+
Input and output signal names can be used to index the data in
604+
place of integer offsets, with the input signal names being used to
605+
access multi-input data.
606+
603607
:type: 2D or 3D array
604608
605609
"""
@@ -639,6 +643,10 @@ def inputs(self):
639643
the two. If a 3D vector is passed, then it represents a multi-trace,
640644
multi-input signal, indexed by input, trace, and time.
641645
646+
Input and output signal names can be used to index the data in
647+
place of integer offsets, with the input signal names being used to
648+
access multi-input data.
649+
642650
See :attr:`TimeResponseData.squeeze` for a description of how the
643651
dimensions of the input vector can be modified using the `squeeze`
644652
keyword.

doc/conventions.rst

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,20 @@ argument::
9898

9999
mag, phase, omega = response(squeeze=False)
100100

101+
Frequency response objects are also available as named properties of the
102+
`response` object: `response.magnitude`, `response.phase`, and
103+
`response.response` (for the complex response). For MIMO systems, these
104+
elements of the frequency response can be accessed using the names of the
105+
inputs and outputs::
106+
107+
response.magnitude['y[0]', 'u[1]']
108+
109+
where the signal names are based on the system that generated the frequency
110+
response.
111+
112+
Note: The `fresp` data member is stored as a NumPy array and cannot be
113+
accessed with signal names. Use `response.response` to access the complex
114+
frequency response using signal names.
101115

102116
Discrete time systems
103117
---------------------
@@ -132,6 +146,21 @@ constructor for the desired data type using the original system as the sole
132146
argument or using the explicit conversion functions :func:`ss2tf` and
133147
:func:`tf2ss`.
134148

149+
Subsystems
150+
----------
151+
Subsets of input/output pairs for LTI systems can be obtained by indexing
152+
the system using either numerical indices (including slices) or signal
153+
names::
154+
155+
subsys = sys[[0, 2], 0:2]
156+
subsys = sys[['y[0]', 'y[2]'], ['u[0]', 'u[1]']]
157+
158+
Signal names for an indexed subsystem are preserved from the original
159+
system and the subsystem name is set according to the values of
160+
`control.config.defaults['iosys.indexed_system_name_prefix'] and
161+
`control.config.defaults['iosys.indexed_system_name_suffix']. The default
162+
subsystem name is the original system name with '$indexed' appended.
163+
135164
Simulating LTI systems
136165
======================
137166

@@ -233,7 +262,7 @@ properties::
233262

234263
sys = ct.rss(4, 1, 1)
235264
response = ct.step_response(sys)
236-
plot(response.time, response.outputs)
265+
plt.plot(response.time, response.outputs)
237266

238267
The dimensions of the response properties depend on the function being
239268
called and whether the system is SISO or MIMO. In addition, some time
@@ -242,6 +271,17 @@ such as the :func:`step_response` function applied to a MIMO system,
242271
which will compute the step response for each input/output pair. See
243272
:class:`TimeResponseData` for more details.
244273

274+
The input, output, and state elements of the response can be access using
275+
signal names in place of integer offsets::
276+
277+
plt.plot(response. time, response.states['x[1]']
278+
279+
For multi-trace systems generated by :func:`step_response` and
280+
:func:`impulse_response`, the input name used to generate the trace can be
281+
used to access the appropriate input output pair::
282+
283+
plt.plot(response.time, response.outputs['y[0]', 'u[1]'])
284+
245285
The time response functions can also be assigned to a tuple, which extracts
246286
the time and output (and optionally the state, if the `return_x` keyword is
247287
used). This allows simple commands for plotting::

doc/plotting.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ the data from the simulation::
7979
for j in range(2):
8080
axs[i, j].plot(time, outputs[i, j])
8181

82+
In addition to accessing time response data via integer indices, signal
83+
names can allow be used::
84+
85+
plt.plot(response.time, response.outputs['y[0]', 'u[1]'])
86+
8287
A number of options are available in the `plot` method to customize
8388
the appearance of input output data. For data produced by the
8489
:func:`~control.impulse_response` and :func:`~control.step_response`
@@ -278,6 +283,19 @@ maximum frequencies in the (log-spaced) frequency range::
278283
The number of (log-spaced) points in the frequency can be specified using
279284
the ``omega_num`` keyword parameter.
280285

286+
Frequency response data can also be accessed directly and plotted manually::
287+
288+
sys = ct.rss(4, 2, 2, strictly_proper=True) # 2x2 MIMO system
289+
fresp = ct.frequency_response(sys)
290+
plt.loglog(fresp.omega, fresp.magnitude['y[1]', 'u[0]'])
291+
292+
Access to frequency response data is available via the attributes
293+
``omega``, ``magnitude``,` `phase``, and ``response``, where ``response``
294+
represents the complex value of the frequency response at each frequency.
295+
The ``magnitude``,` `phase``, and ``response`` arrays can be indexed using
296+
either input/output indices or signal names, with the first index
297+
corresponding to the output signal and the second input corresponding to
298+
the input signal.
281299

282300
Pole/zero data
283301
==============

0 commit comments

Comments
 (0)