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
1616import numpy as np
1717import 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
121121vehicle = 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)
141141cruise_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
152152T = 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={}):
202202omega_range = np .linspace (0 , 700 , 701 )
203203plt .subplot (2 , 2 , 1 )
204204plt .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]' )
206206plt .ylabel ('Torque $T$ [Nm]' )
207207plt .grid (True , linestyle = 'dotted' )
208208
@@ -228,6 +228,7 @@ def motor_torque(omega, params={}):
228228plt .xlabel ('Velocity $v$ [m/s]' )
229229plt .ylabel ('Torque $T$ [Nm]' )
230230
231+ plt .tight_layout (rect = [0 , 0.03 , 1 , 0.95 ]) # Make space for suptitle
231232plt .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
273274control_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
279280cruise_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 ]
356361t , 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 ]
436441t , 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
442447t , 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 ]
464467t , 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={}):
477481plt .suptitle ('Cruise control with integrator anti-windup protection' )
478482t , 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
484489import os
0 commit comments