-
Notifications
You must be signed in to change notification settings - Fork 453
Expand file tree
/
Copy pathsteering-optimal.py
More file actions
286 lines (241 loc) · 9.73 KB
/
steering-optimal.py
File metadata and controls
286 lines (241 loc) · 9.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# steering-optimal.py - optimal control for vehicle steering
# RMM, 18 Feb 2021
#
# This file works through an optimal control example for the vehicle
# steering system. It is intended to demonstrate the functionality for
# optimal control module (control.optimal) in the python-control package.
import numpy as np
import math
import control as ct
import control.optimal as obc
import matplotlib.pyplot as plt
import logging
import time
import os
#
# Vehicle steering dynamics
#
# The vehicle dynamics are given by a simple bicycle model. We take the state
# of the system as (x, y, theta) where (x, y) is the position of the vehicle
# in the plane and theta is the angle of the vehicle with respect to
# horizontal. The vehicle input is given by (v, phi) where v is the forward
# velocity of the vehicle and phi is the angle of the steering wheel. The
# model includes saturation of the vehicle steering angle.
#
# System state: x, y, theta
# System input: v, phi
# System output: x, y
# System parameters: wheelbase, maxsteer
#
def vehicle_update(t, x, u, params):
# Get the parameters for the model
l = params.get('wheelbase', 3.) # vehicle wheelbase
phimax = params.get('maxsteer', 0.5) # max steering angle (rad)
# Saturate the steering input (use min/max instead of clip for speed)
phi = max(-phimax, min(u[1], phimax))
# Return the derivative of the state
return np.array([
math.cos(x[2]) * u[0], # xdot = cos(theta) v
math.sin(x[2]) * u[0], # ydot = sin(theta) v
(u[0] / l) * math.tan(phi) # thdot = v/l tan(phi)
])
def vehicle_output(t, x, u, params):
return x # return x, y, theta (full state)
# Define the vehicle steering dynamics as an input/output system
vehicle = ct.NonlinearIOSystem(
vehicle_update, vehicle_output, states=3, name='vehicle',
inputs=('v', 'phi'),
outputs=('x', 'y', 'theta'))
#
# Utility function to plot the results
#
def plot_lanechange(t, y, u, yf=None, figure=None):
plt.figure(figure)
# Plot the xy trajectory
plt.subplot(3, 1, 1)
plt.plot(y[0], y[1])
plt.xlabel("x [m]")
plt.ylabel("y [m]")
if yf is not None:
plt.plot(yf[0], yf[1], 'ro')
# Plot the inputs as a function of time
plt.subplot(3, 1, 2)
plt.plot(t, u[0])
plt.xlabel("t [sec]")
plt.ylabel("velocity [m/s]")
plt.subplot(3, 1, 3)
plt.plot(t, u[1])
plt.xlabel("t [sec]")
plt.ylabel("steering [rad/s]")
plt.suptitle("Lane change maneuver")
plt.tight_layout()
plt.show(block=False)
#
# Optimal control problem
#
# Perform a "lane change" maneuver over the course of 10 seconds.
#
# Initial and final conditions
x0 = np.array([0., -2., 0.]); u0 = np.array([10., 0.])
xf = np.array([100., 2., 0.]); uf = np.array([10., 0.])
Tf = 10
#
# Approach 1: standard quadratic cost
#
# We can set up the optimal control problem as trying to minimize the
# distance form the desired final point while at the same time as not
# exerting too much control effort to achieve our goal.
#
print("Approach 1: standard quadratic cost")
# Set up the cost functions
Q = np.diag([.1, 10, .1]) # keep lateral error low
R = np.diag([.1, 1]) # minimize applied inputs
quad_cost = obc.quadratic_cost(vehicle, Q, R, x0=xf, u0=uf)
# Define the time horizon (and spacing) for the optimization
timepts = np.linspace(0, Tf, 20, endpoint=True)
# Provide an initial guess
straight_line = (
np.array([x0 + (xf - x0) * time/Tf for time in timepts]).transpose(),
np.outer(u0, np.ones_like(timepts))
)
# Turn on debug level logging so that we can see what the optimizer is doing
logging.basicConfig(
level=logging.DEBUG, filename="steering-integral_cost.log",
filemode='w', force=True)
# Compute the optimal control, setting step size for gradient calculation (eps)
start_time = time.process_time()
result1 = obc.solve_ocp(
vehicle, timepts, x0, quad_cost, initial_guess=straight_line, log=True,
# minimize_method='trust-constr',
# minimize_options={'finite_diff_rel_step': 0.01},
)
print("* Total time = %5g seconds\n" % (time.process_time() - start_time))
# If we are running CI tests, make sure we succeeded
if 'PYCONTROL_TEST_EXAMPLES' in os.environ:
assert result1.success
# Plot the results from the optimization
plot_lanechange(timepts, result1.states, result1.inputs, xf, figure=1)
print("Final computed state: ", result1.states[:,-1])
# Simulate the system and see what happens
t1, u1 = result1.time, result1.inputs
t1, y1 = ct.input_output_response(vehicle, timepts, u1, x0)
plot_lanechange(t1, y1, u1, yf=xf[0:2], figure=1)
print("Final simulated state:", y1[:,-1])
#
# Approach 2: input cost, input constraints, terminal cost
#
# The previous solution integrates the position error for the entire
# horizon, and so the car changes lanes very quickly (at the cost of larger
# inputs). Instead, we can penalize the final state and impose a higher
# cost on the inputs, resuling in a more graduate lane change.
#
# We also set the solver explicitly.
#
print("\nApproach 2: input cost and constraints plus terminal cost")
# Add input constraint, input cost, terminal cost
constraints = [ obc.input_range_constraint(vehicle, [8, -0.1], [12, 0.1]) ]
traj_cost = obc.quadratic_cost(vehicle, None, np.diag([0.1, 1]), u0=uf)
term_cost = obc.quadratic_cost(vehicle, np.diag([1, 10, 10]), None, x0=xf)
# Change logging to keep less information
logging.basicConfig(
level=logging.INFO, filename="./steering-terminal_cost.log",
filemode='w', force=True)
# Use a straight line between initial and final position as initial guesss
input_guess = np.outer(u0, np.ones((1, timepts.size)))
state_guess = np.array([
x0 + (xf - x0) * time/Tf for time in timepts]).transpose()
straight_line = (state_guess, input_guess)
# Compute the optimal control
start_time = time.process_time()
result2 = obc.solve_ocp(
vehicle, timepts, x0, traj_cost, constraints, terminal_cost=term_cost,
initial_guess=straight_line, log=True,
# minimize_method='SLSQP', minimize_options={'eps': 0.01}
)
print("* Total time = %5g seconds\n" % (time.process_time() - start_time))
# If we are running CI tests, make sure we succeeded
if 'PYCONTROL_TEST_EXAMPLES' in os.environ:
assert result2.success
# Plot the results from the optimization
plot_lanechange(timepts, result2.states, result2.inputs, xf, figure=2)
print("Final computed state: ", result2.states[:,-1])
# Simulate the system and see what happens
t2, u2 = result2.time, result2.inputs
t2, y2 = ct.input_output_response(vehicle, timepts, u2, x0)
plot_lanechange(t2, y2, u2, yf=xf[0:2], figure=2)
print("Final simulated state:", y2[:,-1])
#
# Approach 3: terminal constraints
#
# We can also remove the cost function on the state and replace it
# with a terminal *constraint* on the state. If a solution is found,
# it guarantees we get to exactly the final state.
#
print("\nApproach 3: terminal constraints")
# Input cost and terminal constraints
R = np.diag([1, 1]) # minimize applied inputs
cost3 = obc.quadratic_cost(vehicle, np.zeros((3,3)), R, u0=uf)
constraints = [
obc.input_range_constraint(vehicle, [8, -0.1], [12, 0.1]) ]
terminal = [ obc.state_range_constraint(vehicle, xf, xf) ]
# Reset logging to its default values
logging.basicConfig(
level=logging.DEBUG, filename="./steering-terminal_constraint.log",
filemode='w', force=True)
# Compute the optimal control
start_time = time.process_time()
result3 = obc.solve_ocp(
vehicle, timepts, x0, cost3, constraints,
terminal_constraints=terminal, initial_guess=straight_line, log=False,
# solve_ivp_kwargs={'atol': 1e-3, 'rtol': 1e-2},
# minimize_method='trust-constr',
)
print("* Total time = %5g seconds\n" % (time.process_time() - start_time))
# If we are running CI tests, make sure we succeeded
if 'PYCONTROL_TEST_EXAMPLES' in os.environ:
assert result3.success
# Plot the results from the optimization
plot_lanechange(timepts, result3.states, result3.inputs, xf, figure=3)
print("Final computed state: ", result3.states[:,-1])
# Simulate the system and see what happens
t3, u3 = result3.time, result3.inputs
t3, y3 = ct.input_output_response(vehicle, timepts, u3, x0)
plot_lanechange(t3, y3, u3, yf=xf[0:2], figure=3)
print("Final simulated state:", y3[:,-1])
#
# Approach 4: terminal constraints w/ basis functions
#
# As a final example, we can use a basis function to reduce the size
# of the problem and get faster answers with more temporal resolution.
# Here we parameterize the input by a set of 4 Bezier curves but solve
# for a much more time resolved set of inputs.
print("\nApproach 4: Bezier basis")
import control.flatsys as flat
# Compute the optimal control
start_time = time.process_time()
result4 = obc.solve_ocp(
vehicle, timepts, x0, quad_cost,
constraints,
terminal_constraints=terminal,
initial_guess=straight_line,
basis=flat.BezierFamily(6, T=Tf),
# solve_ivp_kwargs={'method': 'RK45', 'atol': 1e-2, 'rtol': 1e-2},
# solve_ivp_kwargs={'atol': 1e-3, 'rtol': 1e-2},
# minimize_method='trust-constr', minimize_options={'disp': True},
log=False
)
print("* Total time = %5g seconds\n" % (time.process_time() - start_time))
# If we are running CI tests, make sure we succeeded
if 'PYCONTROL_TEST_EXAMPLES' in os.environ:
assert result4.success
# Plot the results from the optimization
plot_lanechange(timepts, result4.states, result4.inputs, xf, figure=4)
print("Final computed state: ", result3.states[:,-1])
# Simulate the system and see what happens
t4, u4 = result4.time, result4.inputs
t4, y4 = ct.input_output_response(vehicle, timepts, u4, x0)
plot_lanechange(t4, y4, u4, yf=xf[0:2], figure=4)
print("Final simulated state: ", y4[:,-1])
# If we are not running CI tests, display the results
if 'PYCONTROL_TEST_EXAMPLES' not in os.environ:
plt.show()