Add function update_path_constraints to reference path
for more robust computation of path constraintsmaster
parent
7abc906af5
commit
df6e7351c8
12
MPC.py
12
MPC.py
|
@ -105,16 +105,18 @@ class MPC:
|
||||||
# Compute equality constraint offset (B*ur)
|
# Compute equality constraint offset (B*ur)
|
||||||
uq[n * self.nx:(n+1)*self.nx] = B_lin.dot(np.array
|
uq[n * self.nx:(n+1)*self.nx] = B_lin.dot(np.array
|
||||||
([v_ref, kappa_ref])) - f
|
([v_ref, kappa_ref])) - f
|
||||||
# Compute dynamic constraints on e_y
|
|
||||||
lb, ub, cells = self.model.reference_path.update_bounds(
|
|
||||||
self.model.wp_id + n, self.model.safety_margin[1])
|
|
||||||
xmin_dyn[self.nx * n] = lb
|
|
||||||
xmax_dyn[self.nx * n] = ub
|
|
||||||
# Constrain maximum speed based on predicted car curvature
|
# Constrain maximum speed based on predicted car curvature
|
||||||
vmax_dyn = np.sqrt(self.ay_max / (np.abs(kappa_pred[n]) + 1e-12))
|
vmax_dyn = np.sqrt(self.ay_max / (np.abs(kappa_pred[n]) + 1e-12))
|
||||||
if vmax_dyn < umax_dyn[self.nu*n]:
|
if vmax_dyn < umax_dyn[self.nu*n]:
|
||||||
umax_dyn[self.nu*n] = vmax_dyn
|
umax_dyn[self.nu*n] = vmax_dyn
|
||||||
|
|
||||||
|
# Compute dynamic constraints on e_y
|
||||||
|
ub, lb, cells = self.model.reference_path.update_path_constraints(
|
||||||
|
self.model.wp_id, self.N+1, 2*self.model.safety_margin[1], 0.05)
|
||||||
|
xmin_dyn[::self.nx] = lb
|
||||||
|
xmax_dyn[::self.nx] = ub
|
||||||
|
|
||||||
# Get equality matrix
|
# Get equality matrix
|
||||||
Ax = sparse.kron(sparse.eye(self.N + 1),
|
Ax = sparse.kron(sparse.eye(self.N + 1),
|
||||||
-sparse.eye(self.nx)) + sparse.csc_matrix(A)
|
-sparse.eye(self.nx)) + sparse.csc_matrix(A)
|
||||||
|
|
1
map.py
1
map.py
|
@ -56,7 +56,6 @@ class Map:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
map = Map('map_floor2.png')
|
map = Map('map_floor2.png')
|
||||||
plt.imshow(map.data, cmap='gray')
|
plt.imshow(map.data, cmap='gray')
|
||||||
|
|
|
@ -336,6 +336,8 @@ class ReferencePath:
|
||||||
# Iterate over horizon
|
# Iterate over horizon
|
||||||
for i in range(N):
|
for i in range(N):
|
||||||
|
|
||||||
|
look_ahead = 30
|
||||||
|
|
||||||
# Get information about current waypoint
|
# Get information about current waypoint
|
||||||
current_waypoint = self.get_waypoint(i)
|
current_waypoint = self.get_waypoint(i)
|
||||||
next_waypoint = self.get_waypoint(i+1)
|
next_waypoint = self.get_waypoint(i+1)
|
||||||
|
@ -343,6 +345,9 @@ class ReferencePath:
|
||||||
li = next_waypoint - current_waypoint
|
li = next_waypoint - current_waypoint
|
||||||
# curvature of waypoint
|
# curvature of waypoint
|
||||||
ki = current_waypoint.kappa
|
ki = current_waypoint.kappa
|
||||||
|
if np.abs(ki) <= 0.1:
|
||||||
|
kis = [wp.kappa for wp in self.waypoints[i:i+look_ahead]]
|
||||||
|
ki = np.mean(kis)
|
||||||
|
|
||||||
# Fill operator matrix
|
# Fill operator matrix
|
||||||
# dynamics of acceleration
|
# dynamics of acceleration
|
||||||
|
@ -511,8 +516,8 @@ class ReferencePath:
|
||||||
plt.yticks([])
|
plt.yticks([])
|
||||||
|
|
||||||
# Plot map in gray-scale and set extent to match world coordinates
|
# Plot map in gray-scale and set extent to match world coordinates
|
||||||
canvas = np.ones(self.map.data.shape)
|
#canvas = np.ones(self.map.data.shape)
|
||||||
# canvas = np.flipud(self.map.data)
|
canvas = np.flipud(self.map.data)
|
||||||
plt.imshow(canvas, cmap='gray',
|
plt.imshow(canvas, cmap='gray',
|
||||||
extent=[self.map.origin[0], self.map.origin[0] +
|
extent=[self.map.origin[0], self.map.origin[0] +
|
||||||
self.map.width * self.map.resolution,
|
self.map.width * self.map.resolution,
|
||||||
|
@ -571,11 +576,184 @@ class ReferencePath:
|
||||||
for obstacle in self.obstacles:
|
for obstacle in self.obstacles:
|
||||||
obstacle.show()
|
obstacle.show()
|
||||||
|
|
||||||
|
def _compute_free_segments(self, wp, min_width):
|
||||||
|
"""
|
||||||
|
Compute free path segments.
|
||||||
|
:param wp: waypoint object
|
||||||
|
:param min_width: minimum width of valid segment
|
||||||
|
:return: segment candidates as list of tuples (ub_cell, lb_cell)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Candidate segments
|
||||||
|
free_segments = []
|
||||||
|
|
||||||
|
# Get waypoint's border cells in map coordinates
|
||||||
|
ub_p = self.map.w2m(wp.border_cells[0][0],
|
||||||
|
wp.border_cells[0][1])
|
||||||
|
lb_p = self.map.w2m(wp.border_cells[1][0],
|
||||||
|
wp.border_cells[1][1])
|
||||||
|
|
||||||
|
# Compute path from left border cell to right border cell
|
||||||
|
x_list, y_list = line(ub_p[0], ub_p[1], lb_p[0], lb_p[1])
|
||||||
|
|
||||||
|
# Initialize upper and lower bound of drivable area to
|
||||||
|
# upper bound of path
|
||||||
|
ub_o, lb_o = ub_p, ub_p
|
||||||
|
|
||||||
|
# Assume occupied path
|
||||||
|
free_cells = False
|
||||||
|
|
||||||
|
# Iterate over path from left border to right border
|
||||||
|
for x, y in zip(x_list[1:], y_list[1:]):
|
||||||
|
# If cell is free, update lower bound
|
||||||
|
if self.map.data[y, x] == 1:
|
||||||
|
# Free cell detected
|
||||||
|
free_cells = True
|
||||||
|
lb_o = (x, y)
|
||||||
|
# If cell is occupied or end of path, end segment. Add segment
|
||||||
|
# to list of candidates. Then, reset upper and lower bound to
|
||||||
|
# current cell.
|
||||||
|
if (self.map.data[y, x] == 0 or (x, y) == lb_p) and free_cells:
|
||||||
|
# Set lower bound to border cell of segment
|
||||||
|
lb_o = (x, y)
|
||||||
|
# Transform upper and lower bound cells to world coordinates
|
||||||
|
ub_o = self.map.m2w(ub_o[0], ub_o[1])
|
||||||
|
lb_o = self.map.m2w(lb_o[0], lb_o[1])
|
||||||
|
# If segment larger than threshold, add to candidates
|
||||||
|
if np.sqrt((ub_o[0]-lb_o[0])**2 + (ub_o[1]-lb_o[1])**2) > \
|
||||||
|
min_width:
|
||||||
|
free_segments.append((ub_o, lb_o))
|
||||||
|
# Start new segment
|
||||||
|
ub_o = (x, y)
|
||||||
|
free_cells = False
|
||||||
|
elif self.map.data[y, x] == 0 and not free_cells:
|
||||||
|
ub_o = (x, y)
|
||||||
|
lb_o = (x, y)
|
||||||
|
|
||||||
|
return free_segments
|
||||||
|
|
||||||
|
def update_path_constraints(self, wp_id, N, min_width, safety_margin):
|
||||||
|
"""
|
||||||
|
Compute upper and lower bounds of the drivable area orthogonal to
|
||||||
|
the given waypoint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# container for constraints and border cells
|
||||||
|
ub_hor = []
|
||||||
|
lb_hor = []
|
||||||
|
border_cells_hor = []
|
||||||
|
|
||||||
|
# Iterate over horizon
|
||||||
|
for n in range(N):
|
||||||
|
|
||||||
|
# get corresponding waypoint
|
||||||
|
wp = self.waypoints[wp_id+n]
|
||||||
|
|
||||||
|
# Get list of free segments
|
||||||
|
free_segments = self._compute_free_segments(wp, min_width)
|
||||||
|
|
||||||
|
# First waypoint in horizon uses largest segment
|
||||||
|
if n == 0:
|
||||||
|
segment_lengths = [np.sqrt((seg[0][0]-seg[1][0])**2 +
|
||||||
|
(seg[0][1]-seg[1][1])**2) for seg in free_segments]
|
||||||
|
ls_id = segment_lengths.index(max(segment_lengths))
|
||||||
|
ub_ls, lb_ls = free_segments[ls_id]
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
# Get border cells of selected segment at previous waypoint
|
||||||
|
ub_pw, lb_pw = border_cells_hor[n-1]
|
||||||
|
ub_pw, lb_pw = list(ub_pw), list(lb_pw)
|
||||||
|
|
||||||
|
# Project border cells onto new waypoint in path direction
|
||||||
|
wp_prev = self.waypoints[wp_id+n-1]
|
||||||
|
delta_s = wp_prev - wp
|
||||||
|
ub_pw[0] += delta_s * np.cos(wp_prev.psi)
|
||||||
|
ub_pw[1] += delta_s * np.cos(wp_prev.psi)
|
||||||
|
lb_pw[0] += delta_s * np.sin(wp_prev.psi)
|
||||||
|
lb_pw[1] += delta_s * np.sin(wp_prev.psi)
|
||||||
|
|
||||||
|
# Iterate over free segments for current waypoint
|
||||||
|
if len(free_segments) >= 2:
|
||||||
|
|
||||||
|
# container for overlap of segments with projection
|
||||||
|
segment_offsets = []
|
||||||
|
|
||||||
|
for free_segment in free_segments:
|
||||||
|
|
||||||
|
# Get border cells of segment
|
||||||
|
ub_fs, lb_fs = free_segment
|
||||||
|
|
||||||
|
# distance between upper bounds and lower bounds
|
||||||
|
d_ub = np.sqrt((ub_fs[0]-ub_pw[0])**2 + (ub_fs[1]-ub_pw[1])**2)
|
||||||
|
d_lb = np.sqrt((lb_fs[0]-lb_pw[0])**2 + (lb_fs[1]-lb_pw[1])**2)
|
||||||
|
mean_dist = (d_ub + d_lb) / 2
|
||||||
|
|
||||||
|
# Append offset to projected previous segment
|
||||||
|
segment_offsets.append(mean_dist)
|
||||||
|
|
||||||
|
# Select segment with minimum offset to projected previous
|
||||||
|
# segment
|
||||||
|
ls_id = segment_offsets.index(min(segment_offsets))
|
||||||
|
ub_ls, lb_ls = free_segments[ls_id]
|
||||||
|
|
||||||
|
# Select free segment in case of only one candidate
|
||||||
|
elif len(free_segments) == 1:
|
||||||
|
ub_ls, lb_ls = free_segments[0]
|
||||||
|
|
||||||
|
# Set waypoint coordinates as bound cells if no feasible
|
||||||
|
# segment available
|
||||||
|
else:
|
||||||
|
ub_ls, lb_ls = (wp.x, wp.y), (wp.x, wp.y)
|
||||||
|
|
||||||
|
# Check sign of upper and lower bound
|
||||||
|
angle_ub = np.mod(np.arctan2(ub_ls[1] - wp.y, ub_ls[0] - wp.x)
|
||||||
|
- wp.psi + math.pi, 2 * math.pi) - math.pi
|
||||||
|
angle_lb = np.mod(np.arctan2(lb_ls[1] - wp.y, lb_ls[0] - wp.x)
|
||||||
|
- wp.psi + math.pi, 2 * math.pi) - math.pi
|
||||||
|
sign_ub = np.sign(angle_ub)
|
||||||
|
sign_lb = np.sign(angle_lb)
|
||||||
|
|
||||||
|
# Compute upper and lower bound of largest drivable area
|
||||||
|
ub = sign_ub * np.sqrt(
|
||||||
|
(ub_ls[0] - wp.x) ** 2 + (ub_ls[1] - wp.y) ** 2)
|
||||||
|
lb = sign_lb * np.sqrt(
|
||||||
|
(lb_ls[0] - wp.x) ** 2 + (lb_ls[1] - wp.y) ** 2)
|
||||||
|
|
||||||
|
# Subtract safety margin
|
||||||
|
ub -= safety_margin
|
||||||
|
lb += safety_margin
|
||||||
|
|
||||||
|
# Check feasibility of the path
|
||||||
|
if ub < lb:
|
||||||
|
# Upper and lower bound of 0 indicate an infeasible path
|
||||||
|
# given the specified safety margin
|
||||||
|
ub, lb = 0.0, 0.0
|
||||||
|
|
||||||
|
# Compute absolute angle of bound cell
|
||||||
|
angle_ub = np.mod(math.pi / 2 + wp.psi + math.pi,
|
||||||
|
2 * math.pi) - math.pi
|
||||||
|
angle_lb = np.mod(-math.pi / 2 + wp.psi + math.pi,
|
||||||
|
2 * math.pi) - math.pi
|
||||||
|
# Compute cell on bound for computed distance ub and lb
|
||||||
|
ub_ls = wp.x + ub * np.cos(angle_ub), wp.y + ub * np.sin(
|
||||||
|
angle_ub)
|
||||||
|
lb_ls = wp.x - lb * np.cos(angle_lb), wp.y - lb * np.sin(
|
||||||
|
angle_lb)
|
||||||
|
bound_cells = (ub_ls, lb_ls)
|
||||||
|
|
||||||
|
# Append results
|
||||||
|
ub_hor.append(ub)
|
||||||
|
lb_hor.append(lb)
|
||||||
|
border_cells_hor.append(list(bound_cells))
|
||||||
|
|
||||||
|
return np.array(ub_hor), np.array(lb_hor), border_cells_hor
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
# Select Path | 'Race' or 'Q'
|
# Select Path | 'Race' or 'Q'
|
||||||
path = 'Race'
|
path = 'Q'
|
||||||
|
|
||||||
# Create Map
|
# Create Map
|
||||||
if path == 'Race':
|
if path == 'Race':
|
||||||
|
@ -615,16 +793,27 @@ if __name__ == '__main__':
|
||||||
obs2 = Obstacle(cx=-2.2, cy=-6.8, radius=0.25)
|
obs2 = Obstacle(cx=-2.2, cy=-6.8, radius=0.25)
|
||||||
obs3 = Obstacle(cx=1.7, cy=-1.0, radius=0.15)
|
obs3 = Obstacle(cx=1.7, cy=-1.0, radius=0.15)
|
||||||
obs4 = Obstacle(cx=2.0, cy=-1.2, radius=0.25)
|
obs4 = Obstacle(cx=2.0, cy=-1.2, radius=0.25)
|
||||||
reference_path.add_obstacles([obs1, obs2, obs3, obs4])
|
obs5 = Obstacle(cx=2.2, cy=-0.86, radius=0.1)
|
||||||
|
obs6 = Obstacle(cx=2.33, cy=-0.7, radius=0.1)
|
||||||
|
obs7 = Obstacle(cx=2.67, cy=-0.73, radius=0.1)
|
||||||
|
obs8 = Obstacle(cx=6.42, cy=3.97, radius=0.3)
|
||||||
|
obs9 = Obstacle(cx=7.42, cy=4.97, radius=0.3)
|
||||||
|
obs10 = Obstacle(cx=7.14, cy=5.7, radius=0.1)
|
||||||
|
reference_path.add_obstacles([obs1, obs2, obs3, obs4, obs5, obs6, obs7, obs8, obs9, obs10])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
reference_path = None
|
reference_path = None
|
||||||
print('Invalid path!')
|
print('Invalid path!')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
[reference_path.update_bounds(wp_id=wp_id, safety_margin=0.02)
|
ub, lb, border_cells = reference_path.update_path_constraints(0, reference_path.n_waypoints, 0.60, 0.1)
|
||||||
for wp_id in range(len(reference_path.waypoints))]
|
# Get x and y locations of border cells for upper and lower bound
|
||||||
|
for wp_id in range(reference_path.n_waypoints):
|
||||||
|
if ub[wp_id] > 0.0 and lb[wp_id] > 0.0:
|
||||||
|
print(wp_id)
|
||||||
|
reference_path.waypoints[wp_id].border_cells = border_cells[wp_id]
|
||||||
reference_path.show()
|
reference_path.show()
|
||||||
|
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ from scipy import sparse
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
# Select Simulation Mode | 'Race' or 'Q'
|
# Select Simulation Mode | 'Race' or 'Q'
|
||||||
sim_mode = 'Race'
|
sim_mode = 'Q'
|
||||||
|
|
||||||
# Create Map
|
# Create Map
|
||||||
if sim_mode == 'Race':
|
if sim_mode == 'Race':
|
||||||
|
@ -83,8 +83,8 @@ if __name__ == '__main__':
|
||||||
mpc = MPC(car, N, Q, R, QN, StateConstraints, InputConstraints)
|
mpc = MPC(car, N, Q, R, QN, StateConstraints, InputConstraints)
|
||||||
|
|
||||||
# Compute speed profile
|
# Compute speed profile
|
||||||
SpeedProfileConstraints = {'a_min': -2.0, 'a_max': 2.0,
|
SpeedProfileConstraints = {'a_min': -1.0, 'a_max': 1.0,
|
||||||
'v_min': 0, 'v_max': V_MAX, 'ay_max': 5.0}
|
'v_min': 0, 'v_max': V_MAX, 'ay_max': 1.0}
|
||||||
car.reference_path.compute_speed_profile(SpeedProfileConstraints)
|
car.reference_path.compute_speed_profile(SpeedProfileConstraints)
|
||||||
|
|
||||||
##############
|
##############
|
||||||
|
@ -92,7 +92,7 @@ if __name__ == '__main__':
|
||||||
##############
|
##############
|
||||||
|
|
||||||
# Sampling time
|
# Sampling time
|
||||||
Ts = 0.05
|
Ts = 0.20
|
||||||
t = 0
|
t = 0
|
||||||
car.set_sampling_time(Ts)
|
car.set_sampling_time(Ts)
|
||||||
|
|
||||||
|
@ -137,5 +137,6 @@ if __name__ == '__main__':
|
||||||
plt.title('MPC Simulation: v(t): {:.2f}, delta(t): {:.2f}, Duration: '
|
plt.title('MPC Simulation: v(t): {:.2f}, delta(t): {:.2f}, Duration: '
|
||||||
'{:.2f} s'.format(u[0], u[1], t))
|
'{:.2f} s'.format(u[0], u[1], t))
|
||||||
|
|
||||||
plt.pause(0.0001)
|
plt.pause(0.00001)
|
||||||
|
print(min(v_log))
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
Loading…
Reference in New Issue