Skip to content

Commit 45f3555

Browse files
committed
renamed function to highlight that it is based on root locus, set initial gain to 1, new noplot argument for faster testing
1 parent bf4b479 commit 45f3555

File tree

3 files changed

+45
-31
lines changed

3 files changed

+45
-31
lines changed

control/rlocus.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
188188
zeta = -1 * s.real / abs(s)
189189
fig.suptitle(
190190
"Clicked at: %10.4g%+10.4gj gain: %10.4g damp: %10.4g" %
191-
(s.real, s.imag, 1, zeta),
191+
(s.real, s.imag, kvect[0], zeta),
192192
fontsize=12 if int(mpl.__version__[0]) == 1 else 10)
193193
fig.canvas.mpl_connect(
194194
'button_release_event',

control/sisotool.py

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__all__ = ['sisotool', 'pid_designer']
1+
__all__ = ['sisotool', 'rootlocus_pid_designer']
22

33
from control.exception import ControlMIMONotImplemented
44
from .freqplot import bode_plot
@@ -180,19 +180,21 @@ def _SisotoolUpdate(sys, fig, K, bode_plot_params, tvect=None):
180180
fig.subplots_adjust(top=0.9,wspace = 0.3,hspace=0.35)
181181
fig.canvas.draw()
182182

183-
# contributed by Sawyer Fuller, minster@uw.edu 2021.11.02
184-
def pid_designer(plant, gain='P', sign=+1, input_signal='r',
183+
# contributed by Sawyer Fuller, minster@uw.edu 2021.11.02, based on
184+
# an implementation in Matlab by Martin Berg.
185+
def rootlocus_pid_designer(plant, gain='P', sign=+1, input_signal='r',
185186
Kp0=0, Ki0=0, Kd0=0, tau=0.01,
186-
C_ff=0, derivative_in_feedback_path=False):
187-
"""Manual PID controller design using sisotool
187+
C_ff=0, derivative_in_feedback_path=False,
188+
noplot=False):
189+
"""Manual PID controller design based on root locus using Sisotool
188190
189191
Uses `Sisotool` to investigate the effect of adding or subtracting an
190192
amount `deltaK` to the proportional, integral, or derivative (PID) gains of
191193
a controller. One of the PID gains, `Kp`, `Ki`, or `Kd`, respectively, can
192194
be modified at a time. `Sisotool` plots the step response, frequency
193195
response, and root locus.
194196
195-
When first run, `deltaK` is set to 1; click on a branch of the root locus
197+
When first run, `deltaK` is set to 0; click on a branch of the root locus
196198
plot to try a different value. Each click updates plots and prints
197199
the corresponding `deltaK`. To tune all three PID gains, repeatedly call
198200
`pid_designer`, and select a different `gain` each time (`'P'`, `'I'`,
@@ -240,13 +242,13 @@ def pid_designer(plant, gain='P', sign=+1, input_signal='r',
240242
plant : :class:`LTI` (:class:`TransferFunction` or :class:`StateSpace` system)
241243
The dynamical system to be controlled
242244
gain : string (optional)
243-
Which gain to vary by deltaK. Must be one of 'P', 'I', or 'D'
245+
Which gain to vary by `deltaK`. Must be one of `'P'`, `'I'`, or `'D'`
244246
(proportional, integral, or derative)
245247
sign : int (optional)
246248
The sign of deltaK gain perturbation
247249
input : string (optional)
248-
The input used for the step response; must be 'r' (reference) or
249-
'd' (disturbance) (see figure above)
250+
The input used for the step response; must be `'r'` (reference) or
251+
`'d'` (disturbance) (see figure above)
250252
Kp0, Ki0, Kd0 : float (optional)
251253
Initial values for proportional, integral, and derivative gains,
252254
respectively
@@ -257,16 +259,24 @@ def pid_designer(plant, gain='P', sign=+1, input_signal='r',
257259
C_ff : float or :class:`LTI` system (optional)
258260
Feedforward controller. If :class:`LTI`, must have timebase that is
259261
compatible with plant.
262+
derivative_in_feedback_path : bool (optional)
263+
Whether to place the derivative term in feedback transfer function
264+
`C_b` instead of the forward transfer function `C_f`.
265+
noplot : bool (optional)
266+
267+
Returns
268+
----------
269+
closedloop : class:`StateSpace` system
270+
The closed-loop system using initial gains.
260271
"""
261272

262273
plant = _convert_to_statespace(plant)
263274
if plant.ninputs == 1:
264275
plant = ss2io(plant, inputs='u', outputs='y')
265276
elif plant.ninputs == 2:
266-
plant = ss2io(plant, inputs=('u', 'd'), outputs='y')
277+
plant = ss2io(plant, inputs=['u', 'd'], outputs='y')
267278
else:
268279
raise ValueError("plant must have one or two inputs")
269-
#plant = ss2io(plant, inputs='u', outputs='y')
270280
C_ff = ss2io(_convert_to_statespace(C_ff), inputs='r', outputs='uff')
271281
dt = common_timebase(plant, C_ff)
272282

@@ -277,29 +287,30 @@ def pid_designer(plant, gain='P', sign=+1, input_signal='r',
277287
else:
278288
u_summer = summing_junction(['ufb', 'uff', 'd'], 'u')
279289

280-
prop = tf(1,1)
281290
if isctime(plant):
282-
integ = tf(1,[1, 0])
291+
prop = tf(1, 1)
292+
integ = tf(1, [1, 0])
283293
deriv = tf([1, 0], [tau, 1])
284-
else:
285-
integ = tf([dt/2, dt/2],[1, -1], dt)
286-
deriv = tf([1, -1],[dt, 0], dt)
294+
else: # discrete-time
295+
prop = tf(1, 1, dt)
296+
integ = tf([dt/2, dt/2], [1, -1], dt)
297+
deriv = tf([1, -1], [dt, 0], dt)
287298

288-
# add signal names
299+
# add signal names by turning into iosystems
289300
prop = tf2io(prop, inputs='e', outputs='prop_e')
290301
integ = tf2io(integ, inputs='e', outputs='int_e')
291302
if derivative_in_feedback_path:
292-
deriv = tf2io(-deriv, inputs='y', outputs='deriv_')
303+
deriv = tf2io(-deriv, inputs='y', outputs='deriv')
293304
else:
294-
deriv = tf2io(deriv, inputs='e', outputs='deriv_')
305+
deriv = tf2io(deriv, inputs='e', outputs='deriv')
295306

296307
# create gain blocks
297308
Kpgain = tf2io(tf(Kp0, 1), inputs='prop_e', outputs='ufb')
298309
Kigain = tf2io(tf(Ki0, 1), inputs='int_e', outputs='ufb')
299-
Kdgain = tf2io(tf(Kd0, 1), inputs='deriv_', outputs='ufb')
310+
Kdgain = tf2io(tf(Kd0, 1), inputs='deriv', outputs='ufb')
300311

301-
# for the gain that is varied, create a special gain block with an
302-
# 'input' and an 'output' signal to create the loop transfer function
312+
# for the gain that is varied, replace gain block with a special block
313+
# that has an 'input' and an 'output' that creates loop transfer function
303314
if gain in ('P', 'p'):
304315
Kpgain = ss2io(ss([],[],[],[[0, 1], [-sign, Kp0]]),
305316
inputs=['input', 'prop_e'], outputs=['output', 'ufb'])
@@ -308,13 +319,15 @@ def pid_designer(plant, gain='P', sign=+1, input_signal='r',
308319
inputs=['input', 'int_e'], outputs=['output', 'ufb'])
309320
elif gain in ('D', 'd'):
310321
Kdgain = ss2io(ss([],[],[],[[0, 1], [-sign, Kd0]]),
311-
inputs=['input', 'deriv_'], outputs=['output', 'ufb'])
322+
inputs=['input', 'deriv'], outputs=['output', 'ufb'])
312323
else:
313324
raise ValueError(gain + ' gain not recognized.')
314325

315326
# the second input and output are used by sisotool to plot step response
316327
loop = interconnect((plant, Kpgain, Kigain, Kdgain, prop, integ, deriv,
317328
C_ff, e_summer, u_summer),
318-
inplist=['input', input_signal], outlist=['output', 'y'])
319-
sisotool(loop)
320-
return loop[1, 1]
329+
inplist=['input', input_signal],
330+
outlist=['output', 'y'])
331+
if ~noplot:
332+
sisotool(loop, kvect=(0.,))
333+
return _convert_to_statespace(loop[1, 1])

control/tests/sisotool_test.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from numpy.testing import assert_array_almost_equal
77
import pytest
88

9-
from control.sisotool import sisotool, pid_designer
9+
from control.sisotool import sisotool, rootlocus_pid_designer
1010
from control.rlocus import _RLClickDispatcher
1111
from control.xferfcn import TransferFunction
1212
from control.statesp import StateSpace
@@ -151,13 +151,14 @@ def plant(self, request):
151151
'syscont221':StateSpace([[-.3, 0],[1,0]],[[-1,],[.1,]], [0, -.3], 0)}
152152
return plants[request.param]
153153

154+
# check
154155
# cont or discrete, vary P I or D
155156
# @pytest.mark.parametrize('plant', (syscont, sysdisc1))
156157
@pytest.mark.parametrize('plant', ('syscont', 'sysdisc1'), indirect=True)
157158
@pytest.mark.parametrize('gain', ('P', 'I', 'D'))
158-
@pytest.mark.parametrize("kwargs", [{'Kp0':0.01},])
159+
@pytest.mark.parametrize("kwargs", [{'Kp0':0.1, 'noplot':True},])
159160
def test_pid_designer_1(self, plant, gain, kwargs):
160-
pid_designer(plant, gain, **kwargs)
161+
rootlocus_pid_designer(plant, gain, **kwargs)
161162

162163
# input from reference or disturbance
163164
@pytest.mark.parametrize('plant', ('syscont', 'syscont221'), indirect=True)
@@ -166,5 +167,5 @@ def test_pid_designer_1(self, plant, gain, kwargs):
166167
{'input_signal':'r', 'Kp0':0.01, 'derivative_in_feedback_path':True},
167168
{'input_signal':'d', 'Kp0':0.01, 'derivative_in_feedback_path':True},])
168169
def test_pid_designer_2(self, plant, kwargs):
169-
pid_designer(plant, **kwargs)
170+
rootlocus_pid_designer(plant, **kwargs)
170171

0 commit comments

Comments
 (0)