Skip to content

Sisotool and dynamic root locus zoom #209

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 25 commits into from
Apr 16, 2019

Conversation

icam0
Copy link
Contributor

@icam0 icam0 commented Jun 3, 2018

This pull request introduces two new features to the python-control package. A matlab inspired sisotool and dynamic recomputing of the root locus plot when zooming.

Sisotool
In the pictures below a comparison can be seen between the matlab sisotool, the sisotool for matlplotlib 1.x.x and matlplotlib 2.x.x respectively.

matlab
sisotool_matlab
matplotlib 1.x.x
sisotool_mpl1
matplotlib 2.x.x
sisotool_mpl2

The function is essentially a wrapper around the rootlocus, bode and step response functions. When a click is made on the rootlocus plot, the purple point(s) are moved and the other plots are updated as well. Note that a decision was made to keep most of the rootlocus plot update logic inside the rootlocus file and the logic behind updating the other 2 plots in _sisotoolupdate in sisotool.py. Also, use was made of **kwargs whenever neccessary in order to not confuse the end user with parameters in functions without documenting them. Also, this was tested on python 2.7, 3.3 and 3.6 and matplotlib 1.5.3 and 2.2.2 on mac os x. It would be greatly appreciated if more testers can try this on their machine.

**Dynamic zooming of the root locus plot **
Firstly, I want to point out an error in existing code in the following code snippet from _default_gains:

    while (indexes_too_far[0].size > 0) and (kvect.size < 5000):
        for index in indexes_too_far[0]:
            new_gains = np.linspace(kvect[index], kvect[index+1], 5)
            new_points = _RLFindRoots(num, den, new_gains[1:4])
            kvect = np.insert(kvect, index+1, new_gains[1:4])
            mymat = np.insert(mymat, index+1, new_points, axis=0)

Every iteration, 3 points are inserted at the index where the distance between 2 points is too large. What goes wrong however, is that the indexes are not updated. So when for example indexes_too_far = [0,1,2] and in the first iteration 3 points in between 0 and 1 are inserted. The next iteration the points will be inserted in between 1 and 2. However, this point has shifted its index with 3. This is fixed by the following:

    while (len(indexes_too_far) > 0) and (kvect.size < 5000):
        for counter,index in enumerate(indexes_too_far):
            index = index + counter*3
            new_gains = np.linspace(kvect[index], kvect[index + 1], 5)
            new_points = _RLFindRoots(num, den, new_gains[1:4])
            kvect = np.insert(kvect, index + 1, new_gains[1:4])
            mymat = np.insert(mymat, index + 1, new_points, axis=0)

Now a new function _indexes_filt is added which also takes into account if the user is zoomed in on a part of the plot and adds more points to indexes_too_far. If no initial points are within the zoom view, it detects at which point one of the points crosses an xlim or ylim of the zoombox and then more points are added until the zoom tolerance requirements are met.

This pull request also includes the #199 and #204 pull requests, because they are an integral part of the sisotool as well. Please note that the Travis CI build sometimes fails due to issue #194 . I was able to get the build passing (https://travis-ci.org/icam0/python-control/builds/387446241) by restarting 2 subbuilds once.

@coveralls
Copy link

Coverage Status

Coverage increased (+1.0%) to 78.881% when pulling 70ec8be on icam0:sisotool_final into 601b581 on python-control:master.

1 similar comment
@coveralls
Copy link

Coverage Status

Coverage increased (+1.0%) to 78.881% when pulling 70ec8be on icam0:sisotool_final into 601b581 on python-control:master.

@coveralls
Copy link

coveralls commented Jun 3, 2018

Coverage Status

Coverage decreased (-0.05%) to 78.222% when pulling 17c4a95 on icam0:sisotool_final into 1254ccb on python-control:master.

@coveralls
Copy link

Coverage Status

Coverage increased (+1.0%) to 78.881% when pulling 70ec8be on icam0:sisotool_final into 601b581 on python-control:master.

@repagh
Copy link
Member

repagh commented Jun 24, 2018

I tested locally, and this works fine!

@murrayrm murrayrm added this to the 0.8.1 milestone Jun 30, 2018
This was referenced Jul 14, 2018
Copy link
Member

@murrayrm murrayrm left a comment

Choose a reason for hiding this comment

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

Some small comments on docstrings.

@@ -339,7 +339,7 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False):
def _get_ss_simo(sys, input=None, output=None):
"""Return a SISO or SIMO state-space version of sys

If input is not specified, select first input and issue warning
If input is not sfpecified, select first input and issue warning
Copy link
Member

Choose a reason for hiding this comment

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

Please remove this change, which inserts a misspelling.


def sisotool(sys, kvect = None, xlim_rlocus = None, ylim_rlocus = None, plotstr_rlocus = 'b' if int(matplotlib.__version__[0]) == 1 else 'C0',rlocus_grid = False, omega = None, dB = None, Hz = None, deg = None, omega_limits = None, omega_num = None,margins_bode = True, tvect=None):

"""Sisotool
Copy link
Member

Choose a reason for hiding this comment

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

Documentation style is a bit nonstandard. I think the Sisotool on its own line can be removed. This will look odd on readthedocs since the function name will already be listed.


"""Sisotool

Sisotool style collection of plots inspired by the matlab sisotool.
Copy link
Member

Choose a reason for hiding this comment

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

MATLAB should be in all caps.

@murrayrm
Copy link
Member

Very nice tool. I tried it out on Python 3.5 on MacOS 10.13 (High Sierra) and it worked well.

If someone can test this on Linux (and Windows?) and confirm that it works, we can incorporate into the next release.

@murrayrm
Copy link
Member

I tried out the tool and it works most of the time. But there can be problems if changing the gain if the scale of the plot is too large. For example, try the following example:

import control
sys = control.tf([10,100], [1,5,9,5])
control.sisotool(sys)

This will generate a matplotlib window and you can click on the root locus branches on the right hand side to change the gain. But if you try to click on the left branch (on the negative real axis), it doesn't get recognized.

I think the problem is that the scaling parameters that are used to determine if you are close enough to the axis don't work for the scaling on this plot.

@icam0 If there is no easy fix, we can merge this change as is and update later. I'll wait for a few days to get your input.

@icam0
Copy link
Contributor Author

icam0 commented Jan 3, 2019

I suspect the gain tolerance to be too small for some reason (lines 455 and 457 of the rlocus.py file). I will get back to you during the weekend when I take a closer look at this.

@icam0
Copy link
Contributor Author

icam0 commented Jan 13, 2019

I have looked into it, what happens is that because this plot generates a very skewed aspect ratio due to the automatic matplotlib scaling of the plot the left side of the plot 'does not seem to work'. This is due to the fact that I made a decision to vary the gain tolerance according to the zoom and xlimit and ylimit of the plot. It selects the smallest of these 2 and bases the gain tolerance on the smallest of these 2 margins. When having a skewed aspect ratio it makes it hard for the user to click because the margin becomes very small. In the original implementation, there was a 'fixed' 0.04 factor in there which meant that if you would zoom in a lot, you could click very far away from the plot resulting in floating dots far away from the plot line. Using a scaled gain factor fixed this problem.

If you were to just increase the gain tolerance the behaviour becomes more unreliable and the point sometimes snaps to the incorrect line from then a user would intend. Therefore, for now, I'd like to propose to scale axes of the root locus plot to the same length of the smallest axis. Which makes sure the initial plot is always clickable. Something like this would then be added to the end of the rlocus.py plot (an edge case for handling when the user specifies the xlim or ylim would still need to be added):

x0, x1 = ax.get_xlim()
y0, y1 = ax.get_ylim()
if abs(x0-x1) >= abs(y0-y1):
    ax.set_xlim(0. - 0.5*abs(y0-y1) ,0. +0.5*abs(y0-y1))
else:
    ax.set_ylim(0. - 0.5 * abs(x0 - x1), 0. + 0.5 * abs(x0 - x1))

However, when the user zooms in with a very skewed aspect ratio clicking the line would still be very difficult which is why I think in the future a more sophisticated algorithm for click detection would be a worthwhile enhancement. I'd like to get your thoughts on this @murrayrm

@murrayrm
Copy link
Member

murrayrm commented Apr 7, 2019

@icam0 Can you rebase this against the current master and resolve the conflicts. I think this is ready to go into release 0.8.2 as soon as that is done.

@murrayrm
Copy link
Member

Did an inline correction of the conflicts. Will merge if tests run correctly.

@murrayrm
Copy link
Member

Fixed up remaining issues. Ready to merge.

@murrayrm murrayrm merged commit 908fabe into python-control:master Apr 16, 2019
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