10BC0 introduce new keyword for sisotool: initial_gain, which denotes gain … · python-control/python-control@4107950 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4107950

Browse files
committed
introduce new keyword for sisotool: initial_gain, which denotes gain that the plots start with. Deprecate kvect kwarg with warning. rlocus now doesn't recomupte kvect if it is passed
1 parent e808adb commit 4107950

File tree

4 files changed

+63
-48
lines changed

4 files changed

+63
-48
lines changed

control/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ def reset_defaults():
9797
from .rlocus import _rlocus_defaults
9898
defaults.update(_rlocus_defaults)
9999

100+
from .sisotool import _sisotool_defaults
101+
defaults.update(_sisotool_defaults)
102+
100103
from .namedio import _namedio_defaults
101104
defaults.update(_namedio_defaults)
102105

control/rlocus.py

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from .sisotool import _SisotoolUpdate
6262
from .grid import sgrid, zgrid
6363
from . import config
64+
import warnings
6465

6566
__all__ = ['root_locus', 'rlocus']
6667

@@ -76,7 +77,7 @@
7677
# Main function: compute a root locus diagram
7778
def root_locus(sys, kvect=None, xlim=None, ylim=None,
7879
plotstr=None, plot=True, print_gain=None, grid=None, ax=None,
79-
**kwargs):
80+
initial_gain=None, **kwargs):
8081

8182
"""Root locus plot
8283
@@ -88,8 +89,8 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
8889
----------
8990
sys : LTI object
9091
Linear input/output systems (SISO only, for now).
91-
kvect : float or array_like, optional
92-
List of gains to use in computing diagram.
92+
kvect : array_like, optional
93+
Gains to use in computing plot of closed-loop poles.
9394
xlim : tuple or list, optional
9495
Set limits of x axis, normally with tuple
9596
(see :doc:`matplotlib:api/axes_api`).
@@ -107,6 +108,8 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
107108
If True plot omega-damping grid. Default is False.
108109
ax : :class:`matplotlib.axes.Axes`
109110
Axes on which to create root locus plot
111+
initial_gain : float, optional
112+
Used by :func:`sisotool` to indicate initial gain.
110113
111114
Returns
112115
-------
@@ -126,15 +129,13 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
126129
"""
127130
# Check to see if legacy 'Plot' keyword was used
128131
if 'Plot' in kwargs:
129-
import warnings
130132
warnings.warn("'Plot' keyword is deprecated in root_locus; "
131133
"use 'plot'", FutureWarning)
132134
# Map 'Plot' keyword to 'plot' keyword
133135
plot = kwargs.pop('Plot')
134136

135137
# Check to see if legacy 'PrintGain' keyword was used
136138
if 'PrintGain' in kwargs:
137-
import warnings
138139
warnings.warn("'PrintGain' keyword is deprecated in root_locus; "
139140
"use 'print_gain'", FutureWarning)
140141
# Map 'PrintGain' keyword to 'print_gain' keyword
@@ -146,12 +147,17 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
146147
print_gain = config._get_param(
147148
'rlocus', 'print_gain', print_gain, _rlocus_defaults)
148149

149-
if not sys.issiso():
150+
# Check for sisotool mode
151+
sisotool = kwargs.get('sisotool', False)
152+
153+
# make sure siso. sisotool has different requirements
154+
if not sys.issiso() and not sisotool:
150155
raise ControlMIMONotImplemented(
151156
'sys must be single-input single-output (SISO)')
152157

158+
sys_loop = sys[0,0]
153159
# Convert numerator and denominator to polynomials if they aren't
154-
(nump, denp) = _systopoly1d(sys)
160+
(nump, denp) = _systopoly1d(sys_loop)
155161

156162
# if discrete-time system and if xlim and ylim are not given,
157163
# that we a view of the unit circle
@@ -161,16 +167,16 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
161167
xlim = (-1.3, 1.3)
162168

163169
if kvect is None:
164-
start_roots = _RLFindRoots(nump, denp, 1)
165170
kvect, root_array, xlim, ylim = _default_gains(nump, denp, xlim, ylim)
171+
recompute_on_zoom = True
166172
else:
167173
kvect = np.atleast_1d(kvect)
168-
start_roots = _RLFindRoots(nump, denp, kvect[0])
169174
root_array = _RLFindRoots(nump, denp, kvect)
170175
root_array = _RLSortRoots(root_array)
176+
recompute_on_zoom = False
171177

172-
# Check for sisotool mode
173-
sisotool = False if 'sisotool' not in kwargs else True
178+
if sisotool:
179+
start_roots = _RLFindRoots(nump, denp, initial_gain)
174180

175181
# Make sure there were no extraneous keywords
176182
if not sisotool and kwargs:
@@ -204,7 +210,7 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
204210
zeta = -1 * s.real / abs(s)
205211
fig.suptitle(
206212
"Clicked at: %10.4g%+10.4gj gain: %10.4g damp: %10.4g" %
207-
(s.real, s.imag, kvect[0], zeta),
213+
(s.real, s.imag, initial_gain, zeta),
208214
fontsize=12 if int(mpl.__version__[0]) == 1 else 10)
209215
fig.canvas.mpl_connect(
210216
'button_release_event',
@@ -214,14 +220,16 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
214220
bode_plot_params=kwargs['bode_plot_params'],
215221
tvect=kwargs['tvect']))
216222

217-
# zoom update on xlim/ylim changed, only then data on new limits
218-
# is available, i.e., cannot combine with _RLClickDispatcher
219-
dpfun = partial(
220-
_RLZoomDispatcher, sys=sys, ax_rlocus=ax, plotstr=plotstr)
221-
# TODO: the next too lines seem to take a long time to execute
222-
# TODO: is there a way to speed them up? (RMM, 6 Jun 2019)
223-
ax.callbacks.connect('xlim_changed', dpfun)
224-
ax.callbacks.connect('ylim_changed', dpfun)
223+
224+
if recompute_on_zoom:
225+
# update gains and roots when xlim/ylim change. Only then are
226+
# data on available. I.e., cannot combine with _RLClickDispatcher
227+
dpfun = partial(
228+
_RLZoomDispatcher, sys=sys, ax_rlocus=ax, plotstr=plotstr)
229+
# TODO: the next too lines seem to take a long time to execute
230+
# TODO: is there a way to speed them up? (RMM, 6 Jun 2019)
231+
ax.callbacks.connect('xlim_changed', dpfun)
232+
ax.callbacks.connect('ylim_changed', dpfun)
225233

226234
# plot open loop poles
227235
poles = array(denp.r)
@@ -552,7 +560,8 @@ def _RLSortRoots(roots):
552560

553561
def _RLZoomDispatcher(event, sys, ax_rlocus, plotstr):
554562
"""Rootlocus plot zoom dispatcher"""
555-
nump, denp = _systopoly1d(sys)
563+
sys_loop = sys[0,0]
564+
nump, denp = _systopoly1d(sys_loop)
556565
xlim, ylim = ax_rlocus.get_xlim(), ax_rlocus.get_ylim()
557566

558567
kvect, mymat, xlim, ylim = _default_gains(
@@ -584,7 +593,8 @@ def _RLClickDispatcher(event, sys, fig, ax_rlocus, plotstr, sisotool=False,
584593

585594
def _RLFeedbackClicksPoint(event, sys, fig, ax_rlocus, sisotool=False):
586595
"""Display root-locus gain feedback point for clicks on root-locus plot"""
587-
(nump, denp) = _systopoly1d(sys)
596+
sys_loop = sys[0,0]
597+
(nump, denp) = _systopoly1d(sys_loop)
588598

589599
xlim = ax_rlocus.get_xlim()
590600
ylim = ax_rlocus.get_ylim()
@@ -595,10 +605,10 @@ def _RLFeedbackClicksPoint(event, sys, fig, ax_rlocus, sisotool=False):
595605
# Catch type error when event click is in the figure but not in an axis
596606
try:
597607
s = complex(event.xdata, event.ydata)
598-
K = -1. / sys(s)
599-
K_xlim = -1. / sys(
608+
K = -1. / sys_loop(s)
609+
K_xlim = -1. / sys_loop(
600610
complex(event.xdata + 0.05 * abs(xlim[1] - xlim[0]), event.ydata))
601-
K_ylim = -1. / sys(
611+
K_ylim = -1. / sys_loop(
602612
complex(event.xdata, event.ydata + 0.05 * abs(ylim[1] - ylim[0])))
603613

604614
except TypeError:

control/sisotool.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@
99
from .bdalg import append, connect
1010
from .iosys import tf2io, ss2io, summing_junction, interconnect
1111
from control.statesp import _convert_to_statespace, StateSpace
12+
from . import config
1213
import numpy as np
1314
import matplotlib.pyplot as plt
1415
import warnings
1516

16-
def sisotool(sys, kvect=None, xlim_rlocus=None, ylim_rlocus=None,
17+
_sisotool_defaults = {
18+
'sisotool.initial_gain': 1
19+
}
20+
< 1004E /td>21+
def sisotool(sys, initial_gain=None, xlim_rlocus=None, ylim_rlocus=None,
1722
plotstr_rlocus='C0', rlocus_grid=False, omega=None, dB=None,
1823
Hz=None, deg=None, omega_limits=None, omega_num=None,
19-
margins_bode=True, tvect=None):
24+
margins_bode=True, tvect=None, kvect=None):
2025
"""
2126
Sisotool style collection of plots inspired by MATLAB's sisotool.
2227
The left two plots contain the bode magnitude and phase diagrams.
@@ -42,13 +47,9 @@ def sisotool(sys, kvect=None, xlim_rlocus=None, ylim_rlocus=None,
4247
feedforward controller and multiply them into your plant's second
4348
input. It is also possible to accomodate a system with a gain in the
4449
feedback.
45-
kvect : float or array_like, optional
46-
List of gains to use for plotting root locus. If only one value is
47-
provided, the set of gains in the root locus plot is calculated
48-
automatically, and kvect is interpreted as if it was the value of
49-
the gain associated with the first mouse click on the root locus
50-
plot. This is useful if it is not possible to use interactive
51-
plotting.
50+
initial_gain : float, optional
51+
Initial gain to use for plotting root locus. Defaults to 1
52+
(config.defaults['sisotool.initial_gain']).
5253
xlim_rlocus : tuple or list, optional
5354
control of x-axis range, normally with tuple
5455
(see :doc:`matplotlib:api/axes_api`).
@@ -112,15 +113,19 @@ def sisotool(sys, kvect=None, xlim_rlocus=None, ylim_rlocus=None,
112113
'margins': margins_bode
113114
}
114115

115-
# make sure kvect is an array
116-
if kvect is not None and ~hasattr(kvect, '__len__'):
117-
kvect = np.atleast_1d(kvect)
116+
# Check to see if legacy 'PrintGain' keyword was used
117+
if kvect is not None:
118+
warnings.warn("'kvect' keyword is deprecated in sisotool; "
119+
"use 'initial_gain' instead", FutureWarning)
120+
initial_gain = np.atleast1d(kvect)[0]
121+
initial_gain = config._get_param('sisotool', 'initial_gain',
122+
initial_gain, _sisotool_defaults)
123+
118124
# First time call to setup the bode and step response plots
119-
_SisotoolUpdate(sys, fig,
120-
1 if kvect is None else kvect[0], bode_plot_params)
125+
_SisotoolUpdate(sys, fig, initial_gain, bode_plot_params)
121126

122127
# Setup the root-locus plot window
123-
root_locus(sys[0,0], kvect=kvect, xlim=xlim_rlocus,
128+
root_locus(sys, initial_gain=initial_gain, xlim=xlim_rlocus,
124129
ylim=ylim_rlocus, plotstr=plotstr_rlocus, grid=rlocus_grid,
125130
fig=fig, bode_plot_params=bode_plot_params, tvect=tvect, sisotool=True)
126131

control/tests/sisotool_test.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,11 @@ def test_sisotool_tvect(self, tsys):
138138

139139
@pytest.mark.skipif(plt.get_current_fig_manager().toolbar is None,
140140
reason="Requires the zoom toolbar")
141-
def test_sisotool_kvect(self, tsys):
142-
# test supply kvect
143-
kvect = np.linspace(0, 1, 10)
144-
# check if it can receive an array
145-
sisotool(tsys, kvect=kvect)
146-
# check if it can receive a singleton
147-
sisotool(tsys, kvect=1)
148-
141+
def test_sisotool_initial_gain(self, tsys):
142+
sisotool(tsys, initial_gain=1.2)
143+
# kvect keyword should give deprecation warning
144+
with pytest.warns(FutureWarning):
145+
sisotool(tsys, kvect=1.2)
149146

150147
def test_sisotool_mimo(self, sys222, sys221):
151148
# a 2x2 should not raise an error:

0 commit comments

Comments
 (0)
0