8000 Merge pull request #883 from sawyerbfuller/rlpd_blank · python-control/python-control@c06a205 · GitHub
[go: up one dir, main page]

Skip to content

Commit c06a205

Browse files
authored
Merge pull request #883 from sawyerbfuller/rlpd_blank
fix blank bode plot in rootlocus_pid_designer
2 parents 1e1c8eb + fabcb37 commit c06a205

File tree

2 files changed

+88
-67
lines changed

2 files changed

+88
-67
lines changed

control/sisotool.py

Lines changed: 83 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
from control.exception import ControlMIMONotImplemented
44
from .freqplot import bode_plot
55
from .timeresp import step_response
6-
from .namedio import issiso, common_timebase, isctime, isdtime
6+
from .namedio import common_timebase, isctime, isdtime
77
from .xferfcn import tf
88
from .iosys import ss
99
from .bdalg import append, connect
10-
from .iosys import tf2io, ss2io, summing_junction, interconnect
11-
from control.statesp import _convert_to_statespace, StateSpace
10+
from .iosys import ss, tf2io, summing_junction, interconnect
11+
from control.statesp import _convert_to_statespace
1212
from . import config
1313
import numpy as np
1414
import matplotlib.pyplot as plt
@@ -22,8 +22,8 @@ def sisotool(sys, initial_gain=None, xlim_rlocus=None, ylim_rlocus=None,
2222
plotstr_rlocus='C0', rlocus_grid=False, omega=None, dB=None,
2323
Hz=None, deg=None, omega_limits=None, omega_num=None,
2424
margins_bode=True, tvect=None, kvect=None):
25-
"""
26-
Sisotool style collection of plots inspired by MATLAB's sisotool.
25+
"""Sisotool style collection of plots inspired by MATLAB's sisotool.
26+
2727
The left two plots contain the bode magnitude and phase diagrams.
2828
The top right plot is a clickable root locus plot, clicking on the
2929
root locus will change the gain of the system. The bottom left plot
@@ -32,52 +32,52 @@ def sisotool(sys, initial_gain=None, xlim_rlocus=None, ylim_rlocus=None,
3232
Parameters
3333
----------
3434
sys : LTI object
35-
Linear input/output systems. If sys is SISO, use the same
36-
system for the root locus and step response. If it is desired to
37-
see a different step response than feedback(K*sys,1), such as a
38-
disturbance response, sys can be provided as a two-input, two-output
39-
system (e.g. by using :func:`bdgalg.connect' or
40-
:func:`iosys.interconnect`). For two-input, two-output
41-
system, sisotool inserts the negative of the selected gain K between
42-
the first output and first input and uses the second input and output
43-
for computing the step response. To see the disturbance response,
44-
configure your plant to have as its second input the disturbance input.
45-
To view the step response with a feedforward controller, give your
46-
plant two identical inputs, and sum your feedback controller and your
47-
feedforward controller and multiply them into your plant's second
48-
input. It is also possible to accomodate a system with a gain in the
49-
feedback.
35+
Linear input/output systems. If sys is SISO, use the same system for
36+
the root locus and step response. If it is desired to see a different
37+
step response than feedback(K*sys,1), such as a disturbance response,
38+
sys can be provided as a two-input, two-output system (e.g. by using
39+
:func:`bdgalg.connect' or :func:`iosys.interconnect`). For two-input,
40+
two-output system, sisotool inserts the negative of the selected gain
41+
K between the first output and first input and uses the second input
42+
and output for computing the step response. To see the disturbance
43+
response, configure your plant to have as its second input the
44+
disturbance input. To view the step response with a feedforward
45+
controller, give your plant two identical inputs, and sum your
46+
feedback controller and your feedforward controller and multiply them
47+
into your plant's second input. It is also possible to accomodate a
48+
system with a gain in the feedback.
5049
initial_gain : float, optional
5150
Initial gain to use for plotting root locus. Defaults to 1
5251
(config.defaults['sisotool.initial_gain']).
5352
xlim_rlocus : tuple or list, optional
54-
control of x-axis range, normally with tuple
53+
Control of x-axis range, normally with tuple
5554
(see :doc:`matplotlib:api/axes_api`).
5655
ylim_rlocus : tuple or list, optional
5756
control of y-axis range
5857
plotstr_rlocus : :func:`matplotlib.pyplot.plot` format string, optional
59-
plotting style for the root locus plot(color, linestyle, etc)
58+
Plotting style for the root locus plot(color, linestyle, etc).
6059
rlocus_grid : boolean (default = False)
6160
If True plot s- or z-plane grid.
6261
omega : array_like
63-
List of frequencies in rad/sec to be used for bode plot
62+
List of frequencies in rad/sec to be used for bode plot.
6463
dB : boolean
65-
If True, plot result in dB for the bode plot
64+
If True, plot result in dB for the bode plot.
6665
Hz : boolean
67-
If True, plot frequency in Hz for the bode plot (omega must be provided in rad/sec)
66+
If True, plot frequency in Hz for the bode plot (omega must be
67+
provided in rad/sec).
6868
deg : boolean
69-
If True, plot phase in degrees for the bode plot (else radians)
69+
If True, plot phase in degrees for the bode plot (else radians).
7070
omega_limits : array_like of two values
71-
Limits of the to generate frequency vector.
72-
If Hz=True the limits are in Hz otherwise in rad/s. Ignored if omega
73-
is provided, and auto-generated if omitted.
71+
Limits of the to generate frequency vector. If Hz=True the limits
72+
are in Hz otherwise in rad/s. Ignored if omega is provided, and
73+
auto-generated if omitted.
7474
omega_num : int
7575
Number of samples to plot. Defaults to
7676
config.defaults['freqplot.number_of_samples'].
7777
margins_bode : boolean
78-
If True, plot gain and phase margin in the bode plot
78+
If True, plot gain and phase margin in the bode plot.
7979
tvect : list or ndarray, optional
80-
List of timesteps to use for closed loop step response
80+
List of timesteps to use for closed loop step response.
8181
8282
Examples
8383
--------
@@ -202,28 +202,47 @@ def _SisotoolUpdate(sys, fig, K, bode_plot_params, tvect=None):
202202
# contributed by Sawyer Fuller, minster@uw.edu 2021.11.02, based on
203203
# an implementation in Matlab by Martin Berg.
204204
def rootlocus_pid_designer(plant, gain='P', sign=+1, input_signal='r',
205-
Kp0=0, Ki0=0, Kd0=0, tau=0.01,
205+
Kp0=0, Ki0=0, Kd0=0, deltaK=0.001, tau=0.01,
206206
C_ff=0, derivative_in_feedback_path=False,
207207
plot=True):
208208
"""Manual PID controller design based on root locus using Sisotool
209209
210-
Uses `Sisotool` to investigate the effect of adding or subtracting an
210+
Uses `sisotool` to investigate the effect of adding or subtracting an
211211
amount `deltaK` to the proportional, integral, or derivative (PID) gains of
212212
a controller. One of the PID gains, `Kp`, `Ki`, or `Kd`, respectively, can
213213
be modified at a time. `Sisotool` plots the step response, frequency
214-
response, and root locus.
215-
216-
When first run, `deltaK` is set to 0; click on a branch of the root locus
217-
plot to try a different value. Each click updates plots and prints
218-
the corresponding `deltaK`. To tune all three PID gains, repeatedly call
219-
`rootlocus_pid_designer`, and select a different `gain` each time (`'P'`,
220-
`'I'`, or `'D'`). Make sure to add the resulting `deltaK` to your chosen
221-
initial gain on the next iteration.
214+
response, and root locus of the closed-loop system controlling the
215+
dynamical system specified by `plant`. Can be used with either non-
216+
interactive plots (e.g. in a Jupyter Notebook), or interactive plots.
217+
218+
To use non-interactively, choose starting-point PID gains `Kp0`, `Ki0`,
219+
and `Kd0` (you might want to start with all zeros to begin with), select
220+
which gain you would like to vary (e.g. gain=`'P'`, `'I'`, or `'D'`), and
221+
choose a value of `deltaK` (default 0.001) to specify by how much you
222+
would like to change that gain. Repeatedly run `rootlocus_pid_designer`
223+
with different values of `deltaK` until you are satisfied with the
224+
performance for that gain. Then, to tune a different gain, e.g. `'I'`,
225+
make sure to add your chosen `deltaK` to the previous gain you you were
226+
tuning.
222227
223228
Example: to examine the effect of varying `Kp` starting from an intial
224-
value of 10, use the arguments `gain='P', Kp0=10`. Suppose a `deltaK`
225-
value of 5 gives satisfactory performance. Then on the next iteration,
226-
to tune the derivative gain, use the arguments `gain='D', Kp0=15`.
229+
value of 10, use the arguments `gain='P', Kp0=10` and try varying values
230+
of `deltaK`. Suppose a `deltaK` of 5 gives satisfactory performance. Then,
231+
to tune the derivative gain, add your selected `deltaK` to `Kp0` in the
232+
next call using the arguments `gain='D', Kp0=15`, to see how adding
233+
different values of `deltaK` to your derivative gain affects performance.
234+
235+
To use with interactive plots, you will need to enable interactive mode
236+
if you are in a Jupyter Notebook, e.g. using `%matplotlib`. See
237+
`Interactive Plots <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.ion.html>`_
238+
for more information. Click on a branch of the root locus plot to try
239+
different values of `deltaK`. Each click updates plots and prints the
240+
corresponding `deltaK`. It may be helpful to zoom in using the magnifying
241+
glass on the plot to get more locations to click. Just make sure to
242+
deactivate magnification mode when you are done by clicking the magnifying
243+
glass. Otherwise you will not be able to be able to choose a gain on the
244+
root locus plot. When you are done, `%matplotlib inline` returns to inline,
245+
non-interactive ploting.
227246
228247
By default, all three PID terms are in the forward path C_f in the diagram
229248
shown below, that is,
@@ -253,26 +272,23 @@ def rootlocus_pid_designer(plant, gain='P', sign=+1, input_signal='r',
253272
If `plant` is a 2-input system, the disturbance `d` is fed directly into
254273
its second input rather than being added to `u`.
255274
256-
Remark: It may be helpful to zoom in using the magnifying glass on the
257-
plot. Just ake sure to deactivate magnification mode when you are done by
258-
clicking the magnifying glass. Otherwise you will not be able to be able
259-
to choose a gain on the root locus plot.
260-
261275
Parameters
262276
----------
263277
plant : :class:`LTI` (:class:`TransferFunction` or :class:`StateSpace` system)
264-
The dynamical system to be controlled
278+
The dynamical system to be controlled.
265279
gain : string (optional)
266280
Which gain to vary by `deltaK`. Must be one of `'P'`, `'I'`, or `'D'`
267-
(proportional, integral, or derative)
281+
(proportional, integral, or derative).
268282
sign : int (optional)
269-
The sign of deltaK gain perturbation
283+
The sign of deltaK gain perturbation.
270284
input : string (optional)
271285
The input used for the step response; must be `'r'` (reference) or
272-
`'d'` (disturbance) (see figure above)
286+
`'d'` (disturbance) (see figure above).
273287
Kp0, Ki0, Kd0 : float (optional)
274288
Initial values for proportional, integral, and derivative gains,
275-
respectively
289+
respectively.
290+
deltaK : float (optional)
291+
Perturbation value for gain specified by the `gain` keywoard.
276292
tau : float (optional)
277293
The time constant associated with the pole in the continuous-time
278294
derivative term. This is required to make the derivative transfer
@@ -291,16 +307,20 @@ def rootlocus_pid_designer(plant, gain='P', sign=+1, input_signal='r',
291307
closedloop : class:`StateSpace` system
292308
The closed-loop system using initial gains.
293309
310+
Notes
311+
-----
312+
When running using iPython or Jupyter, use `%matplotlib` to configure
313+
the session for interactive support.
314+
294315
"""
295316

296-
plant = _convert_to_statespace(plant)
297317
if plant.ninputs == 1:
298-
plant = ss2io(plant, inputs='u', outputs='y')
318+
plant = ss(plant, inputs='u', outputs='y')
299319
elif plant.ninputs == 2:
300-
plant = ss2io(plant, inputs=['u', 'd'], outputs='y')
320+
plant = ss(plant, inputs=['u', 'd'], outputs='y')
301321
else:
302322
raise ValueError("plant must have one or two inputs")
303-
C_ff = ss2io(_convert_to_statespace(C_ff), inputs='r', outputs='uff')
323+
C_ff = ss(_convert_to_statespace(C_ff), inputs='r', outputs='uff')
304324
dt = common_timebase(plant, C_ff)
305325

306326
# create systems used for interconnections
@@ -335,13 +355,13 @@ def rootlocus_pid_designer(plant, gain='P', sign=+1, input_signal='r',
335355
# for the gain that is varied, replace gain block with a special block
336356
# that has an 'input' and an 'output' that creates loop transfer function
337357
if gain in ('P', 'p'):
338-
Kpgain = ss2io(ss([],[],[],[[0, 1], [-sign, Kp0]]),
358+
Kpgain = ss([],[],[],[[0, 1], [-sign, Kp0]],
339359
inputs=['input', 'prop_e'], outputs=['output', 'ufb'])
340360
elif gain in ('I', 'i'):
341-
Kigain = ss2io(ss([],[],[],[[0, 1], [-sign, Ki0]]),
361+
Kigain = ss([],[],[],[[0, 1], [-sign, Ki0]],
342362
inputs=['input', 'int_e'], outputs=['output', 'ufb'])
343363
elif gain in ('D', 'd'):
344-
Kdgain = ss2io(ss([],[],[],[[0, 1], [-sign, Kd0]]),
364+
Kdgain = ss([],[],[],[[0, 1], [-sign, Kd0]],
345365
inputs=['input', 'deriv'], outputs=['output', 'ufb'])
346366
else:
347367
raise ValueError(gain + ' gain not recognized.')
@@ -352,6 +372,6 @@ def rootlocus_pid_designer(plant, gain='P', sign=+1, input_signal='r',
352372
inplist=['input', input_signal],
353373
outlist=['output', 'y'], check_unused=False)
354374
if plot:
355-
sisotool(loop, kvect=(0.,))
375+
sisotool(loop, initial_gain=deltaK)
356376
cl = loop[1, 1] # closed loop transfer function with initial gains
357-
return StateSpace(cl.A, cl.B, cl.C, cl.D, cl.dt)
377+
return ss(cl.A, cl.B, cl.C, cl.D, cl.dt)

control/tests/sisotool_test.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,22 +182,23 @@ def plant(self, request):
182182
@pytest.mark.parametrize('Kp0', (0,))
183183
@pytest.mark.parametrize('Ki0', (1.,))
184184
@pytest.mark.parametrize('Kd0', (0.1,))
185+
@pytest.mark.parametrize('deltaK', (1.,))
185186
@pytest.mark.parametrize('tau', (0.01,))
186187
@pytest.mark.parametrize('C_ff', (0, 1,))
187188
@pytest.mark.parametrize('derivative_in_feedback_path', (True, False,))
188189
@pytest.mark.parametrize("kwargs", [{'plot':False},])
189-
def test_pid_designer_1(self, plant, gain, sign, input_signal, Kp0, Ki0, Kd0, tau, C_ff,
190+
def test_pid_designer_1(self, plant, gain, sign, input_signal, Kp0, Ki0, Kd0, deltaK, tau, C_ff,
190191
derivative_in_feedback_path, kwargs):
191-
rootlocus_pid_designer(plant, gain, sign, input_signal, Kp0, Ki0, Kd0, tau, C_ff,
192+
rootlocus_pid_designer(plant, gain, sign, input_signal, Kp0, Ki0, Kd0, deltaK, tau, C_ff,
192193
derivative_in_feedback_path, **kwargs)
193194

194195
# test creation of sisotool plot
195196
# input from reference or disturbance
196-
@pytest.mark.skip("Bode plot is incorrect; generates spurious warnings")
197197
@pytest.mark.parametrize('plant', ('syscont', 'syscont221'), indirect=True)
198198
@pytest.mark.parametrize("kwargs", [
199199
{'input_signal':'r', 'Kp0':0.01, 'derivative_in_feedback_path':True},
200-
{'input_signal':'d', 'Kp0':0.01, 'derivative_in_feedback_path':True},])
200+
{'input_signal':'d', 'Kp0':0.01, 'derivative_in_feedback_path':True},
201+
{'input_signal':'r', 'Kd0':0.01, 'derivative_in_feedback_path':True}])
201202
def test_pid_designer_2(self, plant, kwargs):
202203
rootlocus_pid_designer(plant, **kwargs)
203204

0 commit comments

Comments
 (0)
0