Skip to content

in nyquist plots, draw contour at specified magnitude if threshold is exceeded #671

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

Closed
wants to merge 6 commits into from

Conversation

sawyerbfuller
Copy link
Contributor

This pr was motivated by a feedback system in which the default indent radius resulted in a very small, confusing contour. In this PR, the maximum radius is specified explicitly. This also helps visualize -1 point because the nyquist plot has a standard size relative to that point. To do so, the default indent radius is reduced and an extra contour is drawn when the regular contour leaves the plot window.

Before:
image
image
image
image (dt system)
After:
image
image
image
image(dt system)

@coveralls
Copy link

coveralls commented Nov 21, 2021

Coverage Status

Coverage increased (+0.07%) to 90.091% when pulling 5e9b179 on sawyerbfuller:indent2 into 5155dd5 on python-control:master.

@murrayrm
Copy link
Member

Nice! Will try to look through the code in the coming days and leave any comments.

Copy link
Contributor

@bnavigator bnavigator left a comment

Choose a reason for hiding this comment

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

Nice idea!

Comment on lines 864 to 865
ax.set_xlim(-maximum_magnitude*1.1, maximum_magnitude*1.1)
ax.set_ylim(-maximum_magnitude*1.1, maximum_magnitude*1.1)
Copy link
Contributor

Choose a reason for hiding this comment

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

The plot limits are really tied to the maximum_magnitude now. Even for systems like this:

from control import TransferFunction, nyquist_plot
G = TransferFunction([1],[500, 500, 0])
nyquist_plot(G)
Before After
Figure_1_before Figure_1

Is that desired?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think (though I am not positive) that @bnavigator's system might be a pathological case because you are mostly concerned with systems cascaded with their controllers, in which case you won't have a very low loop gain as this system does.

That said, I rewrote the indentation code to add a consistent number of points at every indent, and further reduced the indent radius, which make it so everything but the most extremely low-gain systems have large contours that go outside of the plot window. This also helps with other pathological cases.

also:

  • removed an indent test that confirms that no indentation happens when first omega is too large
  • made the red lines have the same line style as their associated contour.
    Updated after plots:

image

image

And @bnavigator's example:

image

System with poles not at origin:
Before:
image

After:
image

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, bad example.

Let's consider a system without undamped resonance:

In [1]: from control import TransferFunction, nyquist_plot
   ...: G = TransferFunction([0.5],[1, 1, 1])
   ...: nyquist_plot(G)
before after
nyquist_before nyquist_after

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you think this would be resolved by adding a test for whether there was any maximum_magintude contour drawn, and if there wasn't, then to let it zoom the plot by the default amount?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds reasonable.

… setting it to zero; docstring improvement; old indentation method still available if omega is explicitly specified
@sawyerbfuller
Copy link
Contributor Author

Further updates and now ready to merge of ok'd:

  • rename maximum_magnitude to plot_maximum_magitude. Option to turn off this plot by setting its value to 0.
  • docstring description of behavior
  • if and only if omega is specified, the old approach to indentation is used, otherwise indentation points are added.

Comment on lines -539 to +541
small indentation. The portion of the Nyquist contour at infinity is not
small indentation. If portions of the Nyquist plot The portion of the Nyquist contour at infinity is not
Copy link
Contributor

Choose a reason for hiding this comment

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

Some unintended text insertion here, I guess.

@@ -550,7 +552,9 @@ def nyquist_plot(syslist, omega=None, plot=True, omega_limits=None,
If True, plot magnitude

omega : array_like
Set of frequencies to be evaluated, in rad/sec.
Set of frequencies to be evaluated, in rad/sec. Remark: if omega is
specified, you may need to specify specify a larger `indent_radius`
Copy link
Contributor

Choose a reason for hiding this comment

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

double specify

@sawyerbfuller sawyerbfuller marked this pull request as draft November 24, 2021 00:26
@murrayrm
Copy link
Member

murrayrm commented Nov 25, 2021

I've been playing around with an example that did not work quite right in the original code because of the way that the points were generated along the frequency axis (basically points weren't dense enough, so you could miss encirclements around unstable poles at non-zero frequencies). It looks like the new version of the code has some similar problems as well as introducing some new issues that I am not sure I understand yet.

Here is an example (derived from something more complicated) that works with the previous version but does not work with this version:

import numpy as np
import matplotlib.pyplot as plt
import control as ct
import control.matlab as matlab

sys = ct.ss(ct.tf(*matlab.zpk2tf(
    [-3e-5 + 3j, -3e-5 - 3j], 
    [0, 1e-4 + 2j, 1e-4 - 2j, -1e-2], 
    1)))
K, S, E = ct.lqr(sys, np.eye(sys.nstates), 1)
L, P, E = ct.lqe(sys.A, sys.B, sys.C, 1, 1)
ctrl = ct.ss(sys.A - L @ sys.C - sys.B @ K, L, K, 0)
ct.nyquist_plot(sys * ctrl)

This code creates a process sys that has a pole at the origin and two unstable poles at 1e-4 +/- 2j and then creates a stabilizing controller using LQR with an estimator. Since the original system is unstable but the closed loop is stable (easy to check), the loop transfer function should have two CCW encirclements of the -1 point, so nyquist_plot() should return -2 (clockwise encirclements).

Running the original code yields the following plot and a return value of -2:
image

Running the new code yields the following plot and a return value of 0 (which is not correct):
image

Based on work I did on my original example, I suspect that the problem is that we are not handling addition of points around the poles at 1e-4 +/- 2j correctly. In particular, note that these not that close to the imaginary axis (so np.isclose will not pick them up), but you need to make sure to have a reasonably dense set of sample points near that frequency or you will missing the "inner" loops in the first figure.

In addition to the wrong contour and the wrong count of encirclements, there is something going on with the arrows that needs to be fixed, I think.

I have a more complicated example where you have to be very careful with the indent_radius in order to get the right answer. I want to test that example as well, but we should probably find fix the errors above first. (We should also add some unit tests that capture the incorrect encirclement count bug.)

@sawyerbfuller
Copy link
Contributor Author

@murrayrm what you suggest sounds like a reasonable explanation to me.

So this is a second, unrelated reason why it might be nice to have automatic frequency vector generation code that adds more points near poles (the other being that then the shape of the bode plot can be better visualized).

I have no idea what is going on with those arrows but it may be related to having discontinuous points

@murrayrm
Copy link
Member

A few additional comments after playing around with this a bit:

  • Is there a way to have multiple overlapping extra contours offset a bit so that you can see that there are multiple encirclements? The "system with poles not at origin" example above is one case showing why this could be useful (in the original diagram you can see two distinct curves, but in the modified version these curves overlap in a way that is non-obvious).

  • Rather than setting plot_maximum_magnitude to zero to turn off the functionality, perhaps set it to None?

  • I feel like the default value for plot_maximum_magnitude should be None so that you get a "regular" Nyquist plot by default, since otherwise people might be confused by this non-standard representation.

Also, I've put some preliminary code that does a better job at adding points near poles here. I'm thinking of pulling that code into the _determine_omega_vector function in freqplot.py, in which case it would also provide more resolution in Bode plots.

@sawyerbfuller
Copy link
Contributor Author

@murrayrm re mulitple contours: makes sense, good idea.

re turning off plot_maximum_magintude plot: I tried using None first but got stuck because I thinkconfig._get_param interprets a None keyword as meaning that the default should be used.

re default value for plot_maximum_magnitude being off: I agree it may be non-standard, but isn't the previous way also? The difference is the new way allows you to set a reasonable radius to show in response to indentations, rather than have it depend on indent size and the magnitude of the system at that indent.

One alternative to consider: maybe we could stick with the old way, but size each indent according to the size of the residue (or maybe there is some better formula), so that encirclements are always roughly the same size. You would probably need to further adjust the indent for each pole to avoid overlap with other indentations.

This is a tricky interface issue.

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.

4 participants