Skip to content

Commit dbe24ee

Browse files
committed
Fixed plot legend entries and layout
1 parent 3960446 commit dbe24ee

File tree

1 file changed

+43
-38
lines changed

1 file changed

+43
-38
lines changed

examples/cruise-control.py

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
# road. The controller compensates for these unknowns by measuring the speed
88
# of the car and adjusting the throttle appropriately.
99
#
10-
# This file explore the dynamics and control of the cruise control system,
11-
# following the material presenting in Feedback Systems by Astrom and Murray.
10+
# This file explores the dynamics and control of the cruise control system,
11+
# following the material presented in Feedback Systems by Astrom and Murray.
1212
# A full nonlinear model of the vehicle dynamics is used, with both PI and
1313
# state space control laws. Different methods of constructing control systems
14-
# are show, all using the InputOutputSystem class (and subclasses).
14+
# are shown, all using the InputOutputSystem class (and subclasses).
1515

1616
import numpy as np
1717
import matplotlib.pyplot as plt
@@ -87,7 +87,7 @@ def vehicle_update(t, x, u, params={}):
8787
# the coefficient of rolling friction and sgn(v) is the sign of v (+/- 1) or
8888
# zero if v = 0.
8989

90-
Fr = m * g * Cr * sign(v)
90+
Fr = m * g * Cr * sign(v)
9191

9292
# The aerodynamic drag is proportional to the square of the speed: Fa =
9393
# 1/\rho Cd A |v| v, where \rho is the density of air, Cd is the
@@ -120,7 +120,7 @@ def motor_torque(omega, params={}):
120120
# Define the input/output system for the vehicle
121121
vehicle = ct.NonlinearIOSystem(
122122
vehicle_update, None, name='vehicle',
123-
inputs = ('u', 'gear', 'theta'), outputs = ('v'), states=('v'))
123+
inputs=('u', 'gear', 'theta'), outputs=('v'), states=('v'))
124124

125125
# Figure 1.11: A feedback system for controlling the speed of a vehicle. In
126126
# this example, the speed of the vehicle is measured and compared to the
@@ -140,13 +140,13 @@ def motor_torque(omega, params={}):
140140
# Outputs: v (vehicle velocity)
141141
cruise_tf = ct.InterconnectedSystem(
142142
(control_tf, vehicle), name='cruise',
143-
connections = (
143+
connections=(
144144
['control.u', '-vehicle.v'],
145145
['vehicle.u', 'control.y']),
146-
inplist = ('control.u', 'vehicle.gear', 'vehicle.theta'),
147-
inputs = ('vref', 'gear', 'theta'),
148-
outlist = ('vehicle.v', 'vehicle.u'),
149-
outputs = ('v', 'u'))
146+
inplist=('control.u', 'vehicle.gear', 'vehicle.theta'),
147+
inputs=('vref', 'gear', 'theta'),
148+
outlist=('vehicle.v', 'vehicle.u'),
149+
outputs=('v', 'u'))
150150

151151
# Define the time and input vectors
152152
T = np.linspace(0, 25, 101)
@@ -168,10 +168,10 @@ def motor_torque(omega, params={}):
168168
# Compute the equilibrium state for the system
169169
X0, U0 = ct.find_eqpt(
170170
cruise_tf, [0, vref[0]], [vref[0], gear[0], theta0[0]],
171-
iu=[1, 2], y0=[vref[0], 0], iy=[0], params={'m':m})
171+
iu=[1, 2], y0=[vref[0], 0], iy=[0], params={'m': m})
172172

173173
t, y = ct.input_output_response(
174-
cruise_tf, T, [vref, gear, theta_hill], X0, params={'m':m})
174+
cruise_tf, T, [vref, gear, theta_hill], X0, params={'m': m})
175175

176176
# Plot the velocity
177177
plt.sca(vel_axes)
@@ -202,7 +202,7 @@ def motor_torque(omega, params={}):
202202
omega_range = np.linspace(0, 700, 701)
203203
plt.subplot(2, 2, 1)
204204
plt.plot(omega_range, [motor_torque(w) for w in omega_range])
205-
plt.xlabel('Angular velocity $\omega$ [rad/s]')
205+
plt.xlabel(r'Angular velocity $\omega$ [rad/s]')
206206
plt.ylabel('Torque $T$ [Nm]')
207207
plt.grid(True, linestyle='dotted')
208208

@@ -228,6 +228,7 @@ def motor_torque(omega, params={}):
228228
plt.xlabel('Velocity $v$ [m/s]')
229229
plt.ylabel('Torque $T$ [Nm]')
230230

231+
plt.tight_layout(rect=[0, 0.03, 1, 0.95]) # Make space for suptitle
231232
plt.show(block=False)
232233

233234
# Figure 4.3: Car with cruise control encountering a sloping road
@@ -272,8 +273,8 @@ def pi_output(t, x, u, params={}):
272273

273274
control_pi = ct.NonlinearIOSystem(
274275
pi_update, pi_output, name='control',
275-
inputs = ['v', 'vref'], outputs = ['u'], states = ['z'],
276-
params = {'kp':0.5, 'ki':0.1})
276+
inputs=['v', 'vref'], outputs=['u'], states=['z'],
277+
params={'kp': 0.5, 'ki': 0.1})
277278

278279
# Create the closed loop system
279280
cruise_pi = ct.InterconnectedSystem(
@@ -290,8 +291,10 @@ def pi_output(t, x, u, params={}):
290291
# desired velocity is recovered after 20 s.
291292

292293
# Define a function for creating a "standard" cruise control plot
293-
def cruise_plot(sys, t, y, t_hill=5, vref=20, antiwindup=False,
294-
linetype='b-', subplots=[None, None]):
294+
def cruise_plot(sys, t, y, label=None, t_hill=None, vref=20, antiwindup=False,
295+
linetype='b-', subplots=None, legend=None):
296+
if subplots is None:
297+
subplots = [None, None]
295298
# Figure out the plot bounds and indices
296299
v_min = vref-1.2; v_max = vref+0.5; v_ind = sys.find_output('v')
297300
u_min = 0; u_max = 2 if antiwindup else 1; u_ind = sys.find_output('u')
@@ -310,7 +313,8 @@ def cruise_plot(sys, t, y, t_hill=5, vref=20, antiwindup=False,
310313
plt.sca(subplots[0])
311314
plt.plot(t, y[v_ind], linetype)
312315
plt.plot(t, vref*np.ones(t.shape), 'k-')
313-
plt.plot([t_hill, t_hill], [v_min, v_max], 'k--')
316+
if t_hill:
317+
plt.axvline(t_hill, color='k', linestyle='--', label='t hill')
314318
plt.axis([0, t[-1], v_min, v_max])
315319
plt.xlabel('Time $t$ [s]')
316320
plt.ylabel('Velocity $v$ [m/s]')
@@ -320,17 +324,18 @@ def cruise_plot(sys, t, y, t_hill=5, vref=20, antiwindup=False,
320324
subplot_axes[1] = plt.subplot(2, 1, 2)
321325
else:
322326
plt.sca(subplots[1])
323-
plt.plot(t, y[u_ind], 'r--' if antiwindup else linetype)
324-
plt.plot([t_hill, t_hill], [u_min, u_max], 'k--')
325-
plt.axis([0, t[-1], u_min, u_max])
326-
plt.xlabel('Time $t$ [s]')
327-
plt.ylabel('Throttle $u$')
328-
327+
plt.plot(t, y[u_ind], 'r--' if antiwindup else linetype, label=label)
329328
# Applied input profile
330329
if antiwindup:
331330
# TODO: plot the actual signal from the process?
332-
plt.plot(t, np.clip(y[u_ind], 0, 1), linetype)
333-
plt.legend(['Commanded', 'Applied'], frameon=False)
331+
plt.plot(t, np.clip(y[u_ind], 0, 1), linetype, label='Applied')
332+
if t_hill:
333+
plt.axvline(t_hill, color='k', linestyle='--')
334+
if legend:
335+
plt.legend(frameon=False)
336+
plt.axis([0, t[-1], u_min, u_max])
337+
plt.xlabel('Time $t$ [s]')
338+
plt.ylabel('Throttle $u$')
334339

335340
return subplot_axes
336341

@@ -354,7 +359,7 @@ def cruise_plot(sys, t, y, t_hill=5, vref=20, antiwindup=False,
354359
4./180. * pi * (t-5) if t <= 6 else
355360
4./180. * pi for t in T]
356361
t, y = ct.input_output_response(cruise_pi, T, [vref, gear, theta_hill], X0)
357-
cruise_plot(cruise_pi, t, y)
362+
cruise_plot(cruise_pi, t, y, t_hill=5)
358363

359364
#
360365
# Example 7.8: State space feedback with integral action
@@ -435,17 +440,15 @@ def sf_output(t, z, u, params={}):
435440
4./180. * pi for t in T]
436441
t, y = ct.input_output_response(
437442
cruise_sf, T, [vref, gear, theta_hill], [X0[0], 0],
438-
params={'K':K, 'kf':kf, 'ki':0.0, 'kf':kf, 'xd':xd, 'ud':ud, 'yd':yd})
439-
subplots = cruise_plot(cruise_sf, t, y, t_hill=8, linetype='b--')
443+
params={'K': K, 'kf': kf, 'ki': 0.0, 'kf': kf, 'xd': xd, 'ud': ud, 'yd': yd})
444+
subplots = cruise_plot(cruise_sf, t, y, label='Proportional', linetype='b--')
440445

441446
# Response of the system with state feedback + integral action
442447
t, y = ct.input_output_response(
443448
cruise_sf, T, [vref, gear, theta_hill], [X0[0], 0],
444-
params={'K':K, 'kf':kf, 'ki':0.1, 'kf':kf, 'xd':xd, 'ud':ud, 'yd':yd})
445-
cruise_plot(cruise_sf, t, y, t_hill=8, linetype='b-', subplots=subplots)
446-
447-
# Add a legend
448-
plt.legend(['Proportional', 'PI control'], frameon=False)
449+
params={'K': K, 'kf': kf, 'ki': 0.1, 'kf': kf, 'xd': xd, 'ud': ud, 'yd': yd})
450+
cruise_plot(cruise_sf, t, y, label='PI control', t_hill=8, linetype='b-',
451+
subplots=subplots, legend=True)
449452

450453
# Example 11.5: simulate the effect of a (steeper) hill at t = 5 seconds
451454
#
@@ -463,8 +466,9 @@ def sf_output(t, z, u, params={}):
463466
6./180. * pi for t in T]
464467
t, y = ct.input_output_response(
465468
cruise_pi, T, [vref, gear, theta_hill], X0,
466-
params={'kaw':0})
467-
cruise_plot(cruise_pi, t, y, antiwindup=True)
469+
params={'kaw': 0})
470+
cruise_plot(cruise_pi, t, y, label='Commanded', t_hill=5, antiwindup=True,
471+
legend=True)
468472

469473
# Example 11.6: add anti-windup compensation
470474
#
@@ -477,8 +481,9 @@ def sf_output(t, z, u, params={}):
477481
plt.suptitle('Cruise control with integrator anti-windup protection')
478482
t, y = ct.input_output_response(
479483
cruise_pi, T, [vref, gear, theta_hill], X0,
480-
params={'kaw':2.})
481-
cruise_plot(cruise_pi, t, y, antiwindup=True)
484+
params={'kaw': 2.})
485+
cruise_plot(cruise_pi, t, y, label='Commanded', t_hill=5, antiwindup=True,
486+
legend=True)
482487

483488
# If running as a standalone program, show plots and wait before closing
484489
import os

0 commit comments

Comments
 (0)