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

Control plot refactoring for consistent functionality#1034

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

Conversation

murrayrm
Copy link
Member

@murrayrmmurrayrm commentedJul 21, 2024
edited
Loading

This PR makes a (fairly large) number of changes to control plotting functions to provide consistent functionality. The majority of changes involve making functionality that was present in some plot functions but not others available consistently across all _plot() functions. Everything is backward compatible with v0.10.0.

Summary of changes:

  • Change output format for plotting commands to aControlPlot object, with lines, axes, legend, etc available. Accessing this object as a list is backward compatible with 10.0 format (with deparecation warning).
  • Make processing of theax keyword consistent across all plotting functions (usingctrlplot._process_ax_keyword).
  • Fix up thelabel keyword to operate in a consistent and more intuitive manner: labels can be specified as a single string, a simple list, or an array (for MIMO and multi-trace systems). If a single list is given for a MIMO or multi-trace system, it is reshaped as needed.
  • Changect.suptitle() tocplt.set_plot_title() (wherecplt is the returnedControlPlot obect from a plotting command), and update theplot() method to provide uniform processing of the title keyword (when present, overrides title).
  • Deprecated therelabel keyword intime_response_plot. This didn't seem to be that useful and was not implemented for other functions.
  • Updated legend processing to be consistent across all plotting functions, as described in the user documention (below). Includes unit tests + docstring updates.
  • Updated the use ofrcParams for control plotting functions: defaults are now inct.rcParams and can be reset usingct.reset_rcParams. Set up uniform processing of thercParams keyword argument for plotting functions (with unit tests).
  • Unifiedcolor and*fmt argument processing code, in addition to color management for sequential plotting (_get_color_offset,_get_color).
  • Put together a sample code skeleton for creating a control plots (at the top ofctrlplot.py.
  • Unit tests to make sure all plotting commands perform consistently, including documentation checks.

Documentation of new functionality (fromplotting.rst):

Customizing control plots

A set of common options are available to customize control plots in various ways. The following general rules apply:

  • If a plotting function is called multiple times with data that generate control plots with the same shape for the array of subplots, the new data will be overlaid with the old data, with a change in color(s) for the new data (chosen from the standard matplotlib color cycle). If not overridden, the plot title and legends will be updated to reflect all data shown on the plot.

  • If a plotting function is called and the shape for the array of subplots does not match the currently displayed plot, a new figure is created. Note that only the shape is checked, so if two different types of plotting commands that generate the same shape of subplots are called sequentially, thematplotlib.pyplot.figure command should be used to explicitly create a new figure.

  • Theax keyword argument can be used to direct the plotting function to use a specific axes or array of axes. The value of theax keyword must have the proper number of axes for the plot (so a plot generating a 2x2 array of subplots should be given a 2x2 array of axes for theax keyword).

  • Thecolor,linestyle,linewidth, and other matplotlib line property arguments can be used to override the default line properties. If these arguments are absent, the default matplotlib line properties are used and the color cycles through the default matplotlib color cycle.

    The :func:~control.bode_plot, :func:~control.time_response_plot, and selected other commands can also accept a matplotlib format string (e.g., 'r--'). The format string must appear as a positional argument right after the required data argument.

    Note that line property arguments are the same for all lines generated as part of a single plotting command call, including when multiple responses are passed as a list to the plotting command. For this reason it is often easiest to call multiple plot commands in sequence, with each command setting the line properties for that system/trace.

  • Thelabel keyword argument can be used to override the line labels that are used in generating the title and legend. If more than one line is being plotted in a given call to a plot command, thelabel argument value should be a list of labels, one for each line, in the order they will appear in the legend.

    For input/output plots (frequency and time responses), the labels that appear in the legend are of the form "<output name>, <input name>, <trace name>, <system name>". The trace name is used only for multi-trace time plots (for example, step responses for MIMO systems). Common information present in all traces is removed, so that the labels appearing in the legend represent the unique characteristics of each line.

    For non-input/output plots (e.g., Nyquist plots, pole/zero plots, root locus plots), the default labels are the system name.

    Iflabel is set toFalse, individual lines are still given labels, but no legend is generated in the plot (this can also be accomplished by settinglegend_map toFalse.

    Note: thelabel keyword argument is not implemented for describing function plots or phase plane plots, since these plots are primarily intended to be for a single system. Standardmatplotlib commands can be used to customize these plots for displaying information for multiple systems.

  • Thelegend_loc,legend_map andshow_legend keyword arguments can be used to customize the locations for legends. By default, a minimal number of legends are used such that lines can be uniquely identified and no legend is generated if there is only one line in the plot. Settingshow_legend toFalse will suppress the legend and setting it toTrue will force the legend to be displayed even if there is only a single line in each axes. In addition, if the value of thelegend_loc keyword argument is set to a string or integer, it will set the position of the legend as described in thematplotlib.legend documentation. Finally,legend_map can be set to an` array that matches the shape of the subplots, with each item being a string indicating the location of the legend for that axes (orNone for no legend).

  • ThercParams keyword argument can be used to override the default matplotlib style parameters used when creating a plot. The default parameters for all control plots are given by thect.rcParams dictionary and have the following values:

    KeyValue
    ‘axes.labelsize’‘small’
    ‘axes.titlesize’‘small’
    ‘figure.titlesize’‘medium’
    ‘legend.fontsize’‘x-small’
    ‘xtick.labelsize’‘small’
    ‘ytick.labelsize’‘small’

    Only those values that should be changed from the default need to be specified in thercParams keyword argument. To override the defaults for all control plots, update thect.rcParams dictionary entries.

    The default values for style parameters for control plots can be restored using :func:~control.reset_rcParams.

  • Thetitle keyword can be used to override the automatic creation of the plot title. The default title is a string of the form " plot for " where is a list of the sys names contained in the plot (which can be updated if the plotting is called multiple times). Usetitle=False to suppress the title completely. The title can also be updated using thecontrol.ControlPlot.set_plot_title method for the returned control plot object.

    The plot title is only generated ifax isNone.

The following code illustrates the use of some of these customization features::

P = ct.tf([0.02], [1, 0.1, 0.01])   # servomechanismC1 = ct.tf([1, 1], [1, 0])          # unstableL1 = P * C1C2 = ct.tf([1, 0.05], [1, 0])       # stableL2 = P * C2plt.rcParams.update(ct.rcParams)fig = plt.figure(figsize=[7, 4])ax_mag = fig.add_subplot(2, 2, 1)ax_phase = fig.add_subplot(2, 2, 3)ax_nyquist = fig.add_subplot(1, 2, 2)ct.bode_plot(    [L1, L2], ax=[ax_mag, ax_phase],    label=["$L_1$ (unstable)", "$L_2$ (unstable)"],    show_legend=False)ax_mag.set_title("Bode plot for $L_1$, $L_2$")ax_mag.tick_params(labelbottom=False)fig.align_labels()ct.nyquist_plot(L1, ax=ax_nyquist, label="$L_1$ (unstable)")ct.nyquist_plot(    L2, ax=ax_nyquist, label="$L_2$ (stable)",    max_curve_magnitude=22, legend_loc='upper right')ax_nyquist.set_title("Nyquist plot for $L_1$, $L_2$")fig.suptitle("Loop analysis for servomechanism control design")plt.tight_layout()

ctrlplot-servomech

@coveralls
Copy link

coveralls commentedJul 21, 2024
edited
Loading

Coverage Status

coverage: 94.694% (+0.04%) from 94.65%
when pulling1a04541 on murrayrm:ctrlplot_updates-27Jun2024
intod1cf152 on python-control:main.

@murrayrmmurrayrmforce-pushed thectrlplot_updates-27Jun2024 branch 2 times, most recently from9a407ac toa9ffba5CompareJuly 26, 2024 06:00
@murrayrmmurrayrm marked this pull request as ready for reviewJuly 26, 2024 06:02
@slivingstonslivingston self-requested a reviewJuly 26, 2024 15:22
@slivingstonslivingston self-assigned thisJul 26, 2024
@murrayrmmurrayrm mentioned this pull requestJul 27, 2024
11 tasks
@slivingston
Copy link
Member

I will review this today (Saturday, 3 Aug).

@murrayrmmurrayrmforce-pushed thectrlplot_updates-27Jun2024 branch from73c8c80 tod66f465CompareAugust 4, 2024 19:44
@slivingston
Copy link
Member

I will review this today

This is taking a bit longer; I am almost done and will submit one review (instead of many small incremental comments).

murrayrm reacted with thumbs up emoji

@murrayrmmurrayrm added this to the0.10.1 milestoneAug 8, 2024
Copy link
Member

@slivingstonslivingston left a comment

Choose a reason for hiding this comment

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

Excellent work! The consistency here is a good improvement for user/developer experience.

I made several small comments at the relevant lines and below. Ready to merge after you consider these.

If there areruff options in pyproject,ruff itself should be install-able as an optional dependency. E.g., I think it fits in thetest category (withpytest), or we can make a new category likedev in the[project.optional-dependencies] section. Here are examples in other popular projects for comparison:

Many of the first row subplots now show horizontal axis numbers, even when these are the same and aligned with the bottom row subplots. E.g.,doc/timeplot-mimo_step-default.png. I think this style is good, too, but I want to note it because there is not a commit in this PR that explicitly describes this change (so, maybe change was not intended?).

string (see :func:`~matplotlib.pyplot.legend`).
legend_loc : int or str, optional
Include a legend in the given location. Default is 'center right',
with no legend for a single response. Use False to supress legend.
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
withnolegendforasingleresponse.UseFalsetosupresslegend.
withnolegendforasingleresponse.UseFalsetosuppresslegend.

Copy link
Member

Choose a reason for hiding this comment

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

This spelling misprint occurs in several other locations of this PR, but I am only marking this one. (Easy to find withgrep supress.)

omega_num=omega_num).plot(**kwargs)
*args, omega=None, omega_limits=None, omega_num=None,
Hz=False, **kwargs):
"""Plot the response of the "Gange of 4" transfer functions for a system.
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
"""Plot the response of the "Gange of 4"transferfunctionsforasystem.
"""Plot the response of the "Gang of 4"transferfunctionsforasystem.

Comment on lines 485 to 486
labels, but no legend is generated in the plot (this can also be
accomplished by setting ``legend_map`` to ``False``.
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
labels, but no legend is generated in the plot (this can also be
accomplished by setting ``legend_map`` to ``False``.
labels, but no legend is generated in the plot. (This can also be
accomplished by setting ``legend_map`` to ``False``.)

Copy link
Member

Choose a reason for hiding this comment

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

Or delete( and make the comment a non-parenthetical sentence.

the ``legend_loc`` keyword argument is set to a string or integer, it
will set the position of the legend as described in the
:func:`matplotlib.legend` documentation. Finally, ``legend_map`` can be
set to an` array that matches the shape of the subplots, with each item
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
set to an` array that matches the shape of the subplots, with each item
set to an array that matches the shape of the subplots, with each item


The :func:`~control.bode_plot`, :func:`~control.time_response_plot`,
and selected other commands can also accept a matplotlib format
string (e.g., 'r--'). The format string must appear as a positional
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
string (e.g., 'r--'). The format string must appear as a positional
string (e.g.,``'r--'``). The format string must appear as a positional

Without the code delimiters, Sphinx renders-- as an em dash.

If present, replace automatically generated label(s) with the given
label(s). If sysdata is a list, strings should be specified for each
system.
label_freq : int, optiona
Label every nth frequency on the plot. If not specified, no labels
are generated.
legend_loc : int or str, optional
Include a legend in the given location. Default is 'center right',
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
Includealegendinthegivenlocation.Defaultis'center right',
Includealegendinthegivenlocation.Defaultis'upper right',

label(s). If data is a list, strings should be specified for each
system.
legend_loc : int or str, optional
Include a legend in the given location. Default is 'center right',
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
Includealegendinthegivenlocation.Defaultis'center right',
Includealegendinthegivenlocation.Defaultis'upper right',

use matplotlib.pyplot.gca().axis('auto') and then set the axis
limits to the desired values.

2. Pole/zero plts that use the continuous time omega-damping grid do
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
2.Pole/zeropltsthatusethecontinuoustimeomega-dampinggriddo
2.Pole/zeroplotsthatusethecontinuoustimeomega-dampinggriddo

2. Pole/zero plts that use the continuous time omega-damping grid do
not work with the ``ax`` keyword argument, due to the way that axes
grids are implemented. The ``grid`` argument must be set to
``False`` or `'empty'`` when using the ``ax`` keyword argument.
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
``False``or`'empty'`` when using the ``ax``keywordargument.
``False``or``'empty'``whenusingthe``ax``keywordargument.

Comment on lines 2171 to 2173
response : :class:`~control.FrequencyResponseData`
Frequency response with inputs 'r' and 'd' and outputs 'y', and 'u'
representing the 2x2 matrix of transfer functions in the Gang of 4.
Copy link
Member

Choose a reason for hiding this comment

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

The return type here isControlPlot.

@murrayrm
Copy link
MemberAuthor

Thanks for the careful review,@slivingston. Will address all of these in the coming day or two and try to merge this weekend (two additional, fairly minor, PRs coming after that).

@murrayrm
Copy link
MemberAuthor

Many of the first row subplots now show horizontal axis numbers, even when these are the same and aligned with the bottom row subplots. E.g.,doc/timeplot-mimo_step-default.png. I think this style is good, too, but I want to note it because there is not a commit in this PR that explicitly describes this change (so, maybe change was not intended?).

Good catch! This was not intentional and there is now an inconsistency between tickmark labels in MIMO Bode plots versus MIMO time response plots. Since this is already a very large PR, I'm going to issue a separate PI to make that more uniform (and controllable).

Remaining changes have been implemented.

@murrayrm
Copy link
MemberAuthor

Something odd going on in the unit tests:setup_examples.py is failing on "markov.py", but there is no such file in the examples subdirectory. So not sure what is going on...

@murrayrmmurrayrmforce-pushed thectrlplot_updates-27Jun2024 branch from3270392 to9f143ecCompareAugust 8, 2024 21:54
@slivingston
Copy link
Member

Something odd going on in the unit tests:setup_examples.py is failing on "markov.py", but there is no such file in the examples subdirectory. So not sure what is going on...

The file originates in#1022 so may need to rebase or merge.

@slivingston
Copy link
Member

The CI test can be fixed by adding atitle parametertoTimeResponseData instantiation in markov.py. E.g., compare withthe return value of control.impulse_response.

@slivingston
Copy link
Member

The CI test can be fixed by adding atitle parametertoTimeResponseData instantiation in markov.py. E.g., compare withthe return value of control.impulse_response.

This error is not triggered onmain branch currently because_update_suptitle() checks thattitle has typestr, whereas in this pull request, the new function_update_plot_title() checks iftitle is notFalse but not whether it isNone. However,title=None is the default in TimeResponseData.

murrayrm reacted with thumbs up emoji

@murrayrmmurrayrm merged commit373ff11 intopython-control:mainAug 9, 2024
23 checks passed
@murrayrmmurrayrm deleted the ctrlplot_updates-27Jun2024 branchAugust 9, 2024 02:07
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@slivingstonslivingstonslivingston left review comments

Assignees

@slivingstonslivingston

Labels
None yet
Projects
None yet
Milestone
0.10.1
Development

Successfully merging this pull request may close these issues.

3 participants
@murrayrm@coveralls@slivingston

[8]ページ先頭

©2009-2025 Movatter.jp