Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Update frequency response plots to use _response/_plot pattern#924

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

Merged
murrayrm merged 17 commits intopython-control:mainfrommurrayrm:freq_plots-30Jun2023
Sep 16, 2023

Conversation

murrayrm
Copy link
Member

@murrayrmmurrayrm commentedJul 22, 2023
edited
Loading

This PR updates frequency response plots to use the _response/_plot calling pattern described in#645. For Bode, Nichols, and singular values plots, the following patterns will work:

response = frequency_response(syslist, response_options)lines = response.plot(plot_options)lines = name_plot(response, plot_options)lines = name_plot(syslist, response_options, plot_options)

There are also updates togangof4_response/plot andnyquist_response/plot.

Everything is mostly backwards compatible except that the outputs from a _plot function are now an array of Line2D objects instead of other information (mag/phase/frequency, counts, etc). You can still get the original data using the _response function and there is some legacy processing if you used theplot keyword (eg,plot=False) to try let some code work without changes.

The changes to Nyquist plots are illustrative of where code might break. Before, you could do this:

count = nyquist_plot(sys)

to get both a Nyquist plot and a count of the number of encirclements. That will no longer work, sincenyquist_plot returns an array of lines. Instead, you need to do this

response = nyquist_response(sys)count = response.countlines = response.plot()

There are also some changes when you pass a list of systems. Before, you would say

counts = nyquist_plot([sys1, sys2])

This no longer works because nyquist_response returns a list of responses (one response for each system). In the new version, you say

responses = nyquist_response([sys1, sys2])counts = [response.count for response in responses]lines = responses.plot()

Note that even thoughnyquist_response is returning a list of responses, you can still sayresponses.plot() to get the (single) Nyquist plot for the list of systems (with different systems in different colors). This (and similar functionality forfrequency_response /bode_plot) works through returning a specialNyquistResponseList object (FrequencyResponseList forfrequency_response) that extends the Pythonlist data type and adds aplot method).

Summary of changes:

  • All frequency response plots now have a_response functions and a_plot function. You can access the latter via the.plot() method on the response.
  • Frequency response plots accept either the output of the_response function or a list of systems (in which case the_response function is called internally). This allows the common pattern ofbode_plot(sys),nyquist_plot(sys) to work as expected.
  • For a frequency response, you can set the type of plot that you want using theplot_type keyword in theplot method (soct.frequency_response(sys).plot(plot_type='nichols') will work).
  • Default plot types are set up so that you get what you expect (eg,ct.singular_values_plot(sys).plot() generates a singular values plot, not a Bode plot).
  • The short versionbode,nyquist, andnichols are still there.
  • Thecontrol.matlab version ofbode returnsmag, phase, freq (compatible with MATLAB)
  • Added unit tests plus user documentation.
  • Some other small fixes, code streamlining, etc along the way.
  • Removed deprecated functionality in frequency plotting code (e.g.Plot andlabelFreq keywords).

This PR is going to break existing code. It would be great if a few people could try this out so that we can make sure we are OK with the changes here. There are still a few things I am implementing (see top offreqplot.py) so I'll leave this in draft mode for a bit, but wanted to start getting feedback on the changes, since they are pretty substantial.

Examples (from the user documentation):

Linear time invariant (LTI) systems can be analyzed in terms of their frequency response and python-control provides a variety of tools for carrying out frequency response analysis. The most basic of these is thefrequency_response function, which will compute the frequency response for one or more linear systems:

sys1 = ct.tf([1], [1, 2, 1], name='sys1')sys2 = ct.tf([1, 0.2], [1, 1, 3, 1, 1], name='sys2')response = ct.frequency_response([sys1, sys2])

A Bode plot provide a graphical view of the response an LTI system and can be generated using thebode_plot function:

ct.bode_plot(response, initial_phase=0)

freqplot-siso_bode-default

Computing the response for multiple systems at the same time yields a common frequency range that covers the features of all listed systems.

Bode plots can also be created directly using theFrequencyResponseData.plot method:

sys_mimo = ct.tf(    [[[1], [0.1]], [[0.2], [1]]],    [[[1, 0.6, 1], [1, 1, 1]], [[1, 0.4, 1], [1, 2, 1]]], name="sys_mimo")ct.frequency_response(sys_mimo).plot()

freqplot-mimo_bode-default

A variety of options are available for customizing Bode plots, for example allowing the display of the phase to be turned off or overlaying the inputs or outputs:

ct.frequency_response(sys_mimo).plot(    plot_phase=False, overlay_inputs=True, overlay_outputs=True)

freqplot-mimo_bode-magonly

Thesingular_values_response function can be used to generate Bode plots that show the singular values of a transfer function:

ct.singular_values_response(sys_mimo).plot()

freqplot-mimo_svplot-default

Different types of plots can also be specified for a given frequency response. For example, to plot the frequency response using a a Nichols plot, useplot_type='nichols':

response.plot(plot_type='nichols')

freqplot-siso_nichols-default

Another response function that can be used to generate Bode plots is the :func:gangof4 function, which computes the four primary sensitivity functions for a feedback control system in standard form:

  proc = ct.tf([1], [1, 1, 1], name="process")  ctrl = ct.tf([100], [1, 5], name="control")  response = rect.gangof4_response(proc, ctrl)  ct.bode_plot(response)# or response.plot()

freqplot-gangof4

@murrayrmmurrayrm marked this pull request as draftJuly 22, 2023 16:56
@coveralls
Copy link

coveralls commentedJul 22, 2023
edited
Loading

Coverage Status

coverage: 95.01% (+0.04%) from 94.969% when pulling8e84d00 on murrayrm:freq_plots-30Jun2023 into0a6146b on python-control:main.

@sawyerbfuller
Copy link
Contributor

Just a few quick thoughts/comments, I may have more late if/when I have a chance to test it.

Overall this seems to be very much in the spirit of the original discussion and will fix some oddities as well as making mimo plots easier to work with.

I think breaking code that doescounts = ct.nyquist_plot(sys) seems reasonable because we probably shouldn't mix plotting and computation functionality; a plot function should return references to plotting objects (as it will with this PR).

Clever idea for how to handle lists of responses. Is this considered a legit pythonic way to do that?

@murrayrmmurrayrm marked this pull request as ready for reviewAugust 3, 2023 05:42
plot=True, omega_limits=None, omega_num=None,
margins=None, method='best', *args, **kwargs):
def bode_plot(
data, omega=None, *fmt, ax=None, omega_limits=None, omega_num=None,
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to be the time to deprecateomega,omega_limits, andomega_num in favor of the more generalfrequency,frequency_limits, andfrequency_num given that omega suggests rad/s even though these are specified in either rad/s or hz depending on the defaults.

Copy link
Contributor

Choose a reason for hiding this comment

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

Edit: looking elsewhere, sticking withomega may make more sense for nyquist and nichols. not sure about bode yet. Would be nice to be able to specify ranges in Hz for the bode plot though - was this a feature before?

@sawyerbfuller
Copy link
Contributor

This is looking good and fixes some long-standing bugs/cobwebs. A few comments/questions, some of which I might be able to answer by deeply looking into the code, but I a quick reply giving the high level thinking might be very informative:

  • does theresponse know what kind of plot it is intended for? is it different for a nyquist plot than for a bode plot? in other words,response = nyquist_response(sys); response.plot() will create a nyquist plot andresponse=frequency_response(sys); response.plot() will create a bode plot? are the frequency points and ranges actually the same?
  • and you can override the default type using theplot_type argument?
  • seems like the_plot functions should be the ones that are strongy emphasized in the docs becase of the aforementioned subtleties
  • relatedly, in the discussion ofin nyquist plots, draw contour at specified magnitude if threshold is exceeded #671, you mentioned inthis post that you had created a branch that included enhancements to computing points near poles. Not sure if that ever made it in, but I am including it here for reference.

@murrayrm
Copy link
MemberAuthor

Clever idea for how to handle lists of responses. Is this considered a legit pythonic way to do that?

I think so. It is allows to uselist as the base class of a new object (since Python 2.2) and presumably the reason is to add functionality to the list class, which is what was done here. Others may know more.

This seems to be the time to deprecateomega,omega_limits, andomega_num in favor of the more generalfrequency,frequency_limits, andfrequency_num given that omega suggests rad/s even though these are specified in either rad/s or hz depending on the defaults.

Edit: looking elsewhere, sticking withomega may make more sense for nyquist and nichols. not sure about bode yet. Would be nice to be able to specify ranges in Hz for the bode plot though - was this a feature before?

This is worth thinking about. I find theomega_limits andomega_num to be a bit odd and it would be nice to have a consistent way of dealing with this. This is not directly related to the new plotting paradigm, so perhaps for a separate PR.

  • does theresponse know what kind of plot it is intended for? is it different for a nyquist plot than for a bode plot? in other words,response = nyquist_response(sys); response.plot() will create a nyquist plot andresponse=frequency_response(sys); response.plot() will create a bode plot? are the frequency points and ranges actually the same?

Yes. All of the plots keep track of the type of command that generated them and then call the appropriateplot function. This also happens in the time response plots, where the default output for a step response is different than a forced response (the former shows output only, by default, the latter shows outputs and inputs).

Regarding the frequency points/ranges: these are different for Bode and Nyquist. A Bode plot uses the frequency response, which evaluates an LTI system on the positive imaginary axis. A Nyquist plot evaluates the response along the Nyquist "D" contour. So these are not exactly interchangeable (more below).

and you can override the default type using theplot_type argument?

Yes, but not for Bode and Nyquist, since those are actually different responses. Here's a summary of various things that work:

  • Thesingular_value_response function generates a frequency response and, by default, plots the singular values (magnitude only, all outputs on a single graph). You can change that usingplot_type='bode', which will then show that response as standard Bode plot. You can do the same in the reverse direction: if you callplot_type='svplot' on a frequency response, it will attempt to generate a singular value plot. (Usually that is the wrong thing to do since singular values are real and so a warning is issued if the imaginary part of a frequency response is nonzero for a singular value plot.)
  • The types of plots supported byFrequencyResponseData.plot() using theplot_type keyword are 'bode', 'svplot', and 'nichols' (for a Nichols chart).
  • There is noplot_type='nyquist' functionality because these are different responses.
  • In principle, we could set things up to generate a Nyquist plot from a frequency response object by passing the response to thenyquist_response (ornyquist_plot) function. However, this could generate odd responses since we don't know how to complete the Nyquist curve around poles, at the origin (which is a real value), or at infinity (if the transfer function is not strictly proper).

seems like the_plot functions should be the ones that are strongy emphasized in the docs becase of the aforementioned subtleties

Agree.

relatedly, in the discussion ofin nyquist plots, draw contour at specified magnitude if threshold is exceeded #671, you mentioned inthis post that you had created a branch that included enhancements to computing points near poles. Not sure if that ever made it in, but I am including it here for reference.

Those changes are all in the main branch now (via#534).

I'll leave this open for a bit longer in case others have comments, but looks to me like this is OK to merge, with a later PR for thinking through improvingomega_{limits,num}.

@murrayrmmurrayrm merged commita11d3be intopython-control:mainSep 16, 2023
@murrayrmmurrayrm deleted the freq_plots-30Jun2023 branchSeptember 16, 2023 17:30
@murrayrmmurrayrm added this to the0.10.0 milestoneMar 31, 2024
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@sawyerbfullersawyerbfullersawyerbfuller left review comments

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
0.10.0
Development

Successfully merging this pull request may close these issues.

3 participants
@murrayrm@coveralls@sawyerbfuller

[8]ページ先頭

©2009-2025 Movatter.jp