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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy