Skip to content

Allow signal names to be used for time/freq responses and subsystem indexing #1069

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Dec 4, 2024

Conversation

murrayrm
Copy link
Member

@murrayrm murrayrm commented Dec 1, 2024

This PR adds the ability to access response signals and subsystem inputs and outputs using signal names. As described in the documentation:

Time signal indexing:

The input, output, and state elements of the response can be access using signal names in place of integer offsets::

plt.plot(response. time, response.states['x[1]']

For multi-trace systems generated by :func:step_response and :func:impulse_response, the input name used to generate the trace can be used to access the appropriate input output pair::

plt.plot(response.time, response.outputs['y[0]', 'u[1]'])

Frequency response indexing:

Frequency response objects are also available as named properties of the response object: response.magnitude, response.phase, and response.response (for the complex response). For MIMO systems, these elements of the frequency response can be accessed using the names of the inputs and outputs::

response.magnitude['y[0]', 'u[1]']

where the signal names are based on the system that generated the frequency response.

Subsystem indexing:

Subsets of input/output pairs for LTI systems can be obtained by indexing the system using either numerical indices (including slices) or signal names::

subsys = sys[[0, 2], 0:2]
subsys = sys[['y[0]', 'y[2]'], ['u[0]', 'u[1]']]

Signal names for an indexed subsystem are preserved from the original system and the subsystem name is set according to the values of control.config.defaults['iosys.indexed_system_name_prefix'] and control.config.defaults['iosys.indexed_system_name_suffix']`. The default subsystem name is the original system name with '$indexed' appended.

Summary of changes:

  • Signals are now returned as a NamedSignal object, which is a subclass of np.ndarray that overrides the __getitem__ method to allow processing of signal names via the _parse_key method.
  • All signal/subsystem indexing now allows integers, strings (new), lists of integers or strings (new), or slices.
  • Refactored code for processing signal names to make everything consistent across time and frequency domain signals as well as state and input/output vectors. See iosys.NamedSignal._parse_key and changes in frdata.py and timeresp.py.
  • Refactored code for processing subsystem indices and made index processing consistent for StateSpace, TransferFunction, and FrequencyResponseData systems. See iosys._process_subsys_index and changes in frdata.py, statesp.py, and xferfcn.py.
  • Unit tests, docstrings, and user documentation updated to cover new functiionality.

@coveralls
Copy link

coveralls commented Dec 1, 2024

Coverage Status

coverage: 94.721% (+0.02%) from 94.697%
when pulling 7ef09a0 on murrayrm:named_signals-29Nov2024
into 57b5307 on python-control:main.

@slivingston slivingston self-requested a review December 1, 2024 22:26
Copy link
Member

@slivingston slivingston left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost done with review; it will be done tomorrow. For now, I am sending minor style comments.

control/iosys.py Outdated

def __array_finalize__(self, obj):
# See https://numpy.org/doc/stable/user/basics.subclassing.html
if obj is None: return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if obj is None: return
if obj is None:
return

return should go on the next line to make it more obvious what is in the if-block. Also, this is consistent with PEP 8:

Compound statements (multiple statements on the same line) are generally discouraged

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example in the NumPy docs has obj is None: return (on 1 line), but my style comment remains. I do not know why the example there has this 1-line style, but I suspect it is to keep examples short, which would also justify there being only 1 blank line after import in those examples.

control/iosys.py Outdated
idx = idx[0]

# Convert int to slice so that numpy doesn't drop dimension
if isinstance(idx, int): idx = slice(idx, idx+1, 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if isinstance(idx, int): idx = slice(idx, idx+1, 1)
if isinstance(idx, int):
idx = slice(idx, idx+1, 1)

Similar to my other comment, statements should be on separate lines for clarity of style (as per PEP 8).

control/iosys.py Outdated
#
def _process_subsys_index(idx, sys_labels, slice_to_list=False):
if not isinstance(idx, (slice, list, int)):
raise TypeError(f"system indices must be integers, slices, or lists")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This string does not need the f prefix, but OK to keep it if you anticipate expressions being added to it later.

sys = ct.rss(4, 3, 3)
subsys = sys[key]

# Construct the system to be test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Construct the system to be test
# Construct the system to be tested


response(tranpose=True).input

See :meth:`TimeResponseData.__call__` for more information.
See :meth:`TimeResponseData.__call__` for more information.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
See :meth:`TimeResponseData.__call__` for more information.
See :meth:`TimeResponseData.__call__` for more information.

@@ -242,6 +271,17 @@ such as the :func:`step_response` function applied to a MIMO system,
which will compute the step response for each input/output pair. See
:class:`TimeResponseData` for more details.

The input, output, and state elements of the response can be access using
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The input, output, and state elements of the response can be access using
The input, output, and state elements of the response can be accessed using

doc/plotting.rst Outdated
Access to frequency response data is available via the attributes
``omega``, ``magnitude``,` `phase``, and ``response``, where ``response``
represents the complex value of the frequency response at each frequency.
The ``magnitude``,` `phase``, and ``response`` arrays can be indexed using
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The ``magnitude``,` `phase``, and ``response`` arrays can be indexed using
The ``magnitude``, ``phase``, and ``response`` arrays can be indexed using

Comment on lines 160 to 161
`control.config.defaults['iosys.indexed_system_name_prefix']` and
`control.config.defaults['iosys.indexed_system_name_suffix']`. The default
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`control.config.defaults['iosys.indexed_system_name_prefix']` and
`control.config.defaults['iosys.indexed_system_name_suffix']`. The default
``control.config.defaults['iosys.indexed_system_name_prefix']`` and
``control.config.defaults['iosys.indexed_system_name_suffix']``. The default

rst files built with Sphinx require double backtick (``) to render as in-line code.

Comment on lines 112 to 113
Note: The `fresp` data member is stored as a NumPy array and cannot be
accessed with signal names. Use `response.response` to access the complex
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Note: The `fresp` data member is stored as a NumPy array and cannot be
accessed with signal names. Use `response.response` to access the complex
Note: The ``fresp`` data member is stored as a NumPy array and cannot be
accessed with signal names. Use ``response.response`` to access the complex

Comment on lines 102 to 103
`response` object: `response.magnitude`, `response.phase`, and
`response.response` (for the complex response). For MIMO systems, these
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`response` object: `response.magnitude`, `response.phase`, and
`response.response` (for the complex response). For MIMO systems, these
``response`` object: ``response.magnitude``, ``response.phase``, and
``response.response`` (for the complex response). For MIMO systems, these

@murrayrm murrayrm merged commit 12dda4e into python-control:main Dec 4, 2024
23 checks passed
@murrayrm murrayrm added this to the 0.10.2 milestone Feb 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants