Figure 6.14: Linear versus nonlinear response for a vehicle with PI cruise control
Jump to navigation
Jump to search
Chapter | Linear Systems |
---|---|
Figure number | 6.14 |
Figure title | Linear versus nonlinear response for a vehicle with PI cruise control |
GitHub URL | https://github.com/murrayrm/fbs2e-python/blob/main/figure-6.14-cruise linearized.py |
Requires | python-control |
Figure 6.14: Simulated response of a vehicle with PI cruise control as it climbs a hill with a slope of (smaller velocity deviation/throttle) and a slope of ◦ (larger velocity deviation/throttle). The solid line is the simulation based on a nonlinear model, and the dashed line shows the corresponding simulation using a linear model. The controller gains are and and include anti-windup compensation (described in more detail in Example 11.6).
# cruise_linearized.py - linear versus nonlinear response, cruise w/ PI # RMM, 20 Apr 2024 import matplotlib.pyplot as plt import numpy as np import control as ct import fbs # FBS plotting customizations # System definition from cruise import vehicle_dynamics as vehicle # Figure out the equilibrium point for the system at 20 m/s xe, ue = ct.find_eqpt(vehicle, 20, u0=[0, 4, 0], iu=[1, 2], y0=20, iy=[0]) # Linearized dynamics vehicle_lin = vehicle.linearize(xe, ue) # Controller: PI + antiwindup ctrl_params = {'kp': 0.5, 'ki': 0.1, 'kaw': 2} def ctrl_update(t, x, u, params): e = u[1] - u[0] # v - vref v_nom = -params['kp'] * e + x[0] # nominal control input (PI) v_sat = np.clip(v_nom, 0, 1) # clipped control input return -params['ki'] * e + params['kaw'] * (v_sat - v_nom) def ctrl_output(t, x, u, params): e = u[1] - u[0] # v - vref v_nom = -params['kp'] * e + x[0] # nominal control input (PI) v_sat = np.clip(v_nom, 0, 1) # clipped control input return v_sat ctrl = ct.nlsys( ctrl_update, ctrl_output, states=1, name='ctrl', inputs=['vref', 'v'], outputs='u', params=ctrl_params) # Figure out the equilibrium point for the system at 20 m/s xe, ue = ct.find_eqpt(vehicle, 20, u0=[0, 4, 0], iu=[1, 2], y0=20, iy=[0]) # Linearized dynamics vehicle_lin = vehicle.linearize( xe, ue, inputs=vehicle.input_labels, outputs=vehicle.output_labels) # Controller: PI + antiwindup ctrl_params = {'kp': 0.5, 'ki': 0.1, 'kaw': 2} def ctrl_update(t, x, u, params): e = u[1] - u[0] # v - vref v_nom = -params['kp'] * e + x[0] # nominal control input (PI) v_sat = np.clip(v_nom, 0, 1) # clipped control input return -params['ki'] * e + params['kaw'] * (v_sat - v_nom) def ctrl_output(t, x, u, params): e = u[1] - u[0] # v - vref v_nom = -params['kp'] * e + x[0] # nominal control input (PI) v_sat = np.clip(v_nom, 0, 1) # clipped control input return v_sat ctrl = ct.nlsys( ctrl_update, ctrl_output, states=1, name='ctrl', inputs=['vref', 'v'], outputs='u', params=ctrl_params) # Full system (linear and nonlinear) nlsys = ct.interconnect( [vehicle, ctrl], inputs=['vref', 'gear', 'theta'], outputs=['v', 'u']) lnsys = ct.interconnect( [vehicle_lin, ctrl], inputs=['vref', 'gear', 'theta'], outputs=['v', 'u']) # Compute system response: flat then a hill T1 = np.linspace(0, 5, 40) # Flat section T2 = np.linspace(5, 30) # Hill section # Nonlinear response nl_resp1 = ct.input_output_response(nlsys, T1, [20, 4, 0], X0=[20, ue[0]]) nl_resp2a = ct.input_output_response( nlsys, T2, [20, 4, 0.07], X0=nl_resp1.states[:, -1]) nl_resp2b = ct.input_output_response( nlsys, T2, [20, 4, 0.105], X0=nl_resp1.states[:, -1]) # Linear response ln_resp1 = ct.input_output_response(lnsys, T1, [20, 4, 0], X0=[20, ue[0]]) ln_resp2a = ct.input_output_response( lnsys, T2, [20, 4, 0.07], X0=ln_resp1.states[:, -1]) ln_resp2b = ct.input_output_response( lnsys, T2, [20, 4, 0.105], X0=ln_resp1.states[:, -1]) # Plot the velocity response fig, axs = plt.subplots(2, 1, figsize=[3.4, 3.4], sharex=True) axs[0].plot(nl_resp1.time, nl_resp1.outputs[0], 'b') axs[0].plot(nl_resp2a.time, nl_resp2a.outputs[0], 'b') axs[0].plot(nl_resp2b.time, nl_resp2b.outputs[0], 'b') axs[0].plot(ln_resp2a.time, ln_resp2a.outputs[0], 'r--') axs[0].plot(nl_resp2b.time, ln_resp2b.outputs[0], '--') axs[0].axis([0, 30, 18.5, 20.5]) axs[0].set_yticks([19, 20]) axs[0].set_ylabel("Velocity $v$ [m/s]") # Plot the throttle command axs[1].plot(nl_resp1.time, nl_resp1.outputs[1], 'b') axs[1].plot(nl_resp2a.time, nl_resp2a.outputs[1], 'b') axs[1].plot(nl_resp2b.time, nl_resp2b.outputs[1], 'b') axs[1].plot(ln_resp2a.time, ln_resp2a.outputs[1], 'r--') axs[1].plot(nl_resp2b.time, ln_resp2b.outputs[1], '--') axs[1].set_title(" ") # Hack to adjust spacing between plots axs[1].set_ylim([0, 1.25]) axs[1].set_yticks([0, 0.5, 1]) axs[1].set_yticklabels(["0", "0.5", "1"]) axs[1].set_ylabel("Throttle $u$") axs[1].set_xlabel("Time $t$ [s]") fbs.savefig('figure-6.14-cruise_linearized.png')