Repository navigation
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
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
54583d5
first sisotool chechpoint
icam0 2e1bf7d
second sisotool checkpoint
icam0 f4545dd
changed matplotlib event handler
icam0 e3c1f31
improved sisotool readability
icam0 72ffb63
Merge branch 'bode_margins_plot' into sisotool_merge
icam0 bde60f5
Added display of GM and PM in bode plot. Fixed Hz bug support. Fixed …
icam0 bc74a0a
Added support for both matplotlib 1.x.x. and 2.x.x and fixed redrawin…
icam0 1e3a925
Added docstring and unittest
icam0 94a0ea1
removed bug where a pole or zero would be deleted if only 1 of them w…
icam0 c0e533e
root locus smooth with zoom bug
icam0 fe13ae1
Fixed zooming on plot. Now displays smoother root locus plot.
icam0 2ba8277
Fixed paning on plot and clicking on the root locus plot while zoomed
icam0 c26187f
Added unit test for rootlocus zoom functionality
icam0 9962cea
Fixed sisotool unittest
icam0 938ae88
Fixed frequency response unittest
icam0 9f13a77
small change to sisotool test
icam0 6e06a9c
small change to unittests
icam0 cdb9f42
fixed python 2 integer division error in false gain of rootlocus
icam0 90959bd
changed root locus test to a numerically more significant one.
icam0 70ec8be
changed root locus test indices.
icam0 9f50e13
Merge branch 'master' into sisotool_final
repagh 3e7cad6
Merge branch 'master' into sisotool_final
murrayrm 43d8b95
Merge branch 'master' into sisotool_final
murrayrm 03f87a3
Fix small typo from manual merge
murrayrm 17c4a95
fixed issues identified in review
murrayrm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Added docstring and unittest
- Loading branch information
commit 1e3a925ccc804348c52a1f50fda460df30d2240d
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,9 +5,56 @@ | |
| from .lti import issiso | ||
| import matplotlib | ||
| import matplotlib.pyplot as plt | ||
|
|
||
| def sisotool(sys, kvect = None, xlim = None, ylim = 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 = True, tvect=None): | ||
|
|
||
| import warnings | ||
|
|
||
| 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 | ||
|
|
||
| Sisotool style collection of plots inspired by the matlab sisotool. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MATLAB should be in all caps. |
||
| The left two plots contain the bode magnitude and phase diagrams. | ||
| The top right plot is a clickable root locus plot, clicking on the | ||
| root locus will change the gain of the system. The bottom left plot | ||
| shows a closed loop time response. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| sys : LTI object | ||
| Linear input/output systems (SISO only) | ||
| kvect : list or ndarray, optional | ||
| List of gains to use for plotting root locus | ||
| xlim_rlocus : tuple or list, optional | ||
| control of x-axis range, normally with tuple (see matplotlib.axes) | ||
| ylim_rlocus : tuple or list, optional | ||
| control of y-axis range | ||
| plotstr_rlocus : Additional options to matplotlib | ||
| plotting style for the root locus plot(color, linestyle, etc) | ||
| rlocus_grid: boolean (default = False) | ||
| If True plot s-plane grid. | ||
| omega : freq_range | ||
| Range of frequencies in rad/sec for the bode plot | ||
| dB : boolean | ||
| If True, plot result in dB for the bode plot | ||
| Hz : boolean | ||
| If True, plot frequency in Hz for the bode plot (omega must be provided in rad/sec) | ||
| deg : boolean | ||
| If True, plot phase in degrees for the bode plot (else radians) | ||
| omega_limits: tuple, list, ... of two values | ||
| Limits of the to generate frequency vector. | ||
| If Hz=True the limits are in Hz otherwise in rad/s. | ||
| omega_num: int | ||
| number of samples | ||
| margins_bode : boolean | ||
| If True, plot gain and phase margin in the bode plot | ||
| tvect : list or ndarray, optional | ||
| List of timesteps to use for closed loop step response | ||
|
|
||
| Examples | ||
| -------- | ||
| >>> sys = tf([1000], [1,25,100,0]) | ||
| >>> sisotool(sys) | ||
|
|
||
| """ | ||
| from .rlocus import root_locus | ||
|
|
||
| # Check if it is a single SISO system | ||
|
|
@@ -30,14 +77,14 @@ def sisotool(sys, kvect = None, xlim = None, ylim = None, plotstr_rlocus = 'b' i | |
| 'omega_num' : omega_num, | ||
| 'sisotool': True, | ||
| 'fig': fig, | ||
| 'margins': margins | ||
| 'margins': margins_bode | ||
| } | ||
|
|
||
| # First time call to setup the bode and step response plots | ||
| _SisotoolUpdate(sys, fig,1 if kvect is None else kvect[0],bode_plot_params) | ||
|
|
||
| # Setup the root-locus plot window | ||
| root_locus(sys,kvect=kvect,xlim=xlim,ylim = ylim,plotstr=plotstr_rlocus,grid = rlocus_grid,fig=fig,bode_plot_params=bode_plot_params,tvect=tvect,sisotool=True) | ||
| root_locus(sys,kvect=kvect,xlim=xlim_rlocus,ylim = ylim_rlocus,plotstr=plotstr_rlocus,grid = rlocus_grid,fig=fig,bode_plot_params=bode_plot_params,tvect=tvect,sisotool=True) | ||
|
|
||
| def _SisotoolUpdate(sys,fig,K,bode_plot_params,tvect=None): | ||
|
|
||
|
|
@@ -50,7 +97,11 @@ def _SisotoolUpdate(sys,fig,K,bode_plot_params,tvect=None): | |
|
|
||
| # Get the subaxes and clear them | ||
| ax_mag,ax_rlocus,ax_phase,ax_step = fig.axes[0],fig.axes[1],fig.axes[2],fig.axes[3] | ||
| ax_mag.cla(),ax_phase.cla(),ax_step.cla() | ||
|
|
||
| # Catch matplotlib 2.1.x and higher userwarnings when clearing a log axis | ||
| with warnings.catch_warnings(): | ||
| warnings.simplefilter("ignore") | ||
| ax_step.clear(), ax_mag.clear(), ax_phase.clear() | ||
|
|
||
| # Update the bodeplot | ||
| bode_plot_params['syslist'] = sys*K.real | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import unittest | ||
| import numpy as np | ||
| from control.sisotool import sisotool | ||
| from control.tests.margin_test import assert_array_almost_equal | ||
| from control.rlocus import _RLFeedbackClicksSisotool | ||
| from control.xferfcn import TransferFunction | ||
| import matplotlib.pyplot as plt | ||
|
|
||
| class TestSisotool(unittest.TestCase): | ||
| """These are tests for the sisotool in sisotool.py.""" | ||
|
|
||
| def setUp(self): | ||
| """This contains some random LTI systems and scalars for testing.""" | ||
|
|
||
| # Two random SISO systems. | ||
| sys1 = TransferFunction([1000],[1,25,100,0]) | ||
| sys2 = TransferFunction([1,1,1],[1,1]) | ||
| self.systems = (sys1, sys2) | ||
|
|
||
| def test_sisotool(self): | ||
| sisotool(self.systems[0]) | ||
| fig = plt.gcf() | ||
| ax_mag,ax_rlocus,ax_phase,ax_step = fig.axes[0],fig.axes[1],fig.axes[2],fig.axes[3] | ||
|
|
||
| # Check the initial root locus plot points | ||
| initial_point_0 = (np.array([-22.53155977]),np.array([0.])) | ||
| initial_point_1 = (np.array([-1.23422011]), np.array([-6.54667031])) | ||
| initial_point_2 = (np.array([-1.23422011]), np.array([06.54667031])) | ||
| assert_array_almost_equal(ax_rlocus.lines[0].get_data(),initial_point_0) | ||
| assert_array_almost_equal(ax_rlocus.lines[1].get_data(),initial_point_1) | ||
| assert_array_almost_equal(ax_rlocus.lines[2].get_data(),initial_point_2) | ||
|
|
||
| # Check the bode plot magnitude line | ||
| bode_mag_original = np.array([ 15.12670407, 12.48358602, 10.28367521, 8.44961576, 6.91740918, 5.63436301, 4.55750665, 3.65237575, 2.89200358, 2.25587634]) | ||
| assert_array_almost_equal(ax_mag.lines[0].get_data()[1][10:20],bode_mag_original) | ||
|
|
||
| # Check the step response before moving the point | ||
| print(ax_step.lines[0].get_data()[1][:10]) | ||
| step_response_original = np.array([ 0., 0.02233651, 0.13118374, 0.33078542, 0.5907113, 0.87041549, 1.13038536, 1.33851053, 1.47374666, 1.52757114]) | ||
| assert_array_almost_equal(ax_step.lines[0].get_data()[1][:10],step_response_original) | ||
|
|
||
| bode_plot_params = { | ||
| 'omega': None, | ||
| 'dB': False, | ||
| 'Hz': False, | ||
| 'deg': True, | ||
| 'omega_limits': None, | ||
| 'omega_num': None, | ||
| 'sisotool': True, | ||
| 'fig': fig, | ||
| 'margins': True | ||
| } | ||
|
|
||
| # Move the rootlocus to another point | ||
| event = type('test', (object,), {'xdata': 2.31206868287,'ydata':15.5983051046, 'inaxes':ax_rlocus.axes})() | ||
| _RLFeedbackClicksSisotool(event=event, sys=self.systems[0], fig=fig, bode_plot_params=bode_plot_params, tvect=None) | ||
|
|
||
| # Check the moved root locus plot points | ||
| moved_point_0 = (np.array([-29.91742755]), np.array([0.])) | ||
| moved_point_1 = (np.array([2.45871378]), np.array([-15.52647768])) | ||
| moved_point_2 = (np.array([2.45871378]), np.array([15.52647768])) | ||
| assert_array_almost_equal(ax_rlocus.lines[-3].get_data(),moved_point_0) | ||
| assert_array_almost_equal(ax_rlocus.lines[-2].get_data(),moved_point_1) | ||
| assert_array_almost_equal(ax_rlocus.lines[-1].get_data(),moved_point_2) | ||
|
|
||
| # Check if the bode_mag line has moved | ||
| bode_mag_moved = np.array([ 111.83321224, 92.29238035, 76.02822315, 62.46884113, 51.14108703, 41.6554004, 33.69409534, 27.00237344, 21.38086717, 16.67791585]) | ||
| assert_array_almost_equal(ax_mag.lines[0].get_data()[1][10:20],bode_mag_moved) | ||
|
|
||
| # Check if the step response has changed | ||
| step_response_moved = np.array([[ 0., 0.02458187, 0.16529784 , 0.46602716 , 0.91012035 , 1.43364313, 1.93996334 , 2.3190105 , 2.47041552 , 2.32724853] ]) | ||
| assert_array_almost_equal(ax_step.lines[0].get_data()[1][:10],step_response_moved) | ||
|
|
||
| def test_suite(): | ||
| return unittest.TestLoader().loadTestsFromTestCase(TestSisotool) | ||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
readthedocssince the function name will already be listed.