Robot Motion:
Generate smooth paths
The formula you need to use in the exercise is as follows. Note that it is a little different from the ones in the video.
yi <- yi + alpha *(xi - yi) + beta *(yi + 1 + yi - 1 - 2 * yi)
There is an error in the solution presented in this video. newpath[i][j] is updated two times in lines 37 and 38. This is incorrect: instead, newpath[i][j] should only be updated one time. An example fix would be to replace lines 37-39 with the single update:
For more information, please read the forum post:http://forums.udacity.com/questions/1024800/unit-5-5-solution-bug#cs373
# -----------
# User Instructions
#
# Define a function smooth that takes a path as its input
# (with optional parameters for weight_data, weight_smooth,
# and tolerance) and returns a smooth path. The first and
# last points should remain unchanged.
#
# Smoothing should be implemented by iteratively updating
# each entry in newpath until some desired level of accuracy
# is reached. The update should be done according to the
# gradient descent equations given in the instructor's note
# below (the equations given in the video are not quite
# correct).
# -----------
from copy import deepcopy
# thank you to EnTerr for posting this on our discussion forum
def printpaths(path,newpath):
for old,new in zip(path,newpath):
print '['+ ', '.join('%.3f'%x for x in old) + \
'] -> ['+ ', '.join('%.3f'%x for x in new) +']'
# Don't modify path inside your function.
path = [[0, 0],
[0, 1],
[0, 2],
[1, 2],
[2, 2],
[3, 2],
[4, 2],
[4, 3],
[4, 4]]
def smooth(path, weight_data = 0.5, weight_smooth = 0.1, tolerance = 0.000001):
# Make a deep copy of path into newpath
newpath = deepcopy(path)
#######################
### ENTER CODE HERE ###
point_num = len(path)
point_dim = 2
if point_num > 2:
max_change = tolerance+1
while max_change > tolerance:
change = []
for i in range(1,point_num-1):
for d in range(point_dim):
change.append(weight_data*(path[i][d] - newpath[i][d])\
+ weight_smooth*(newpath[i-1][d] + newpath[i+1][d] - 2*newpath[i][d]))
newpath[i][d] += change[-1]
max_change = max(change)
#######################
return newpath # Leave this line for the grader!
printpaths(path,smooth(path))
results:
[0.000, 0.000] -> [0.000, 0.000]
[0.000, 1.000] -> [0.021, 0.979]
[0.000, 2.000] -> [0.149, 1.851]
[1.000, 2.000] -> [1.021, 1.979]
[2.000, 2.000] -> [2.000, 2.000]
[3.000, 2.000] -> [2.979, 2.021]
[4.000, 2.000] -> [3.851, 2.149]
[4.000, 3.000] -> [3.979, 3.021]
[4.000, 4.000] -> [4.000, 4.000]
NOTE:
- 没法import math,所以没法用math.inf,所以这里用change这个list的空间复杂度比较高。
- 还有更高效的方法应该是用numpy整体操作,而不是用for循环
- 对于weight的选取,应该有稳定性准则。这里weight太大相当于有限差分里的时间步长太大,相对path的点的数量(i.e. 空间步长)。或者可能需要用二阶的optimize、或者adam这类一阶的优化会更稳定。
PID control
Implement P controller:
# -----------
# User Instructions
#
# Implement a P controller by running 100 iterations
# of robot motion. The desired trajectory for the
# robot is the x-axis. The steering angle should be set
# by the parameter tau so that:
#
# steering = -tau * crosstrack_error
#
# You'll only need to modify the `run` function at the bottom.
# ------------
import random
import numpy as np
import matplotlib.pyplot as plt
# ------------------------------------------------
#
# this is the Robot class
#
class Robot(object):
def __init__(self, length=20.0):
"""
Creates robot and initializes location/orientation to 0, 0, 0.
"""
self.x = 0.0
self.y = 0.0
self.orientation = 0.0
self.length = length
self.steering_noise = 0.0
self.distance_noise = 0.0
self.steering_drift = 0.0
def set(self, x, y, orientation):
"""
Sets a robot coordinate.
"""
self.x = x
self.y = y
self.orientation = orientation % (2.0 * np.pi)
def set_noise(self, steering_noise, distance_noise):
"""
Sets the noise parameters.
"""
# makes it possible to change the noise parameters
# this is often useful in particle filters
self.steering_noise = steering_noise
self.distance_noise = distance_noise
def set_steering_drift(self, drift):
"""
Sets the systematical steering drift parameter
"""
self.steering_drift = drift
def move(self, steering, distance, tolerance=0.001, max_steering_angle=np.pi / 4.0):
"""
steering = front wheel steering angle, limited by max_steering_angle
distance = total distance driven, most be non-negative
"""
if steering > max_steering_angle:
steering = max_steering_angle
if steering < -max_steering_angle:
steering = -max_steering_angle
if distance < 0.0:
distance = 0.0
# apply noise
steering2 = random.gauss(steering, self.steering_noise)
distance2 = random.gauss(distance, self.distance_noise)
# apply steering drift
steering2 += self.steering_drift
# Execute motion
turn = np.tan(steering2) * distance2 / self.length
if abs(turn) < tolerance:
# approximate by straight line motion
self.x += distance2 * np.cos(self.orientation)
self.y += distance2 * np.sin(self.orientation)
self.orientation = (self.orientation + turn) % (2.0 * np.pi)
else:
# approximate bicycle model for motion
radius = distance2 / turn
cx = self.x - (np.sin(self.orientation) * radius)
cy = self.y + (np.cos(self.orientation) * radius)
self.orientation = (self.orientation + turn) % (2.0 * np.pi)
self.x = cx + (np.sin(self.orientation) * radius)
self.y = cy - (np.cos(self.orientation) * radius)
def __repr__(self):
return '[x=%.5f y=%.5f orient=%.5f]' % (self.x, self.y, self.orientation)
############## ADD / MODIFY CODE BELOW ####################
# ------------------------------------------------------------------------
#
# run - does a single control run
robot = Robot()
robot.set(0.0, 1.0, 0.0)
def run(robot, tau, n=100, speed=1.0):
x_trajectory = []
y_trajectory = []
# TODO: your code here
for i in range(n):
steer = -tau*robot.y
robot.move(steer,speed)
x_trajectory.append(robot.x)
y_trajectory.append(robot.y)
return x_trajectory, y_trajectory
x_trajectory, y_trajectory = run(robot, 0.1)
n = len(x_trajectory)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 8))
ax1.plot(x_trajectory, y_trajectory, 'g', label='P controller')
# previous P controller
def run_p(robot, tau, n=100, speed=1.0):
x_trajectory = []
y_trajectory = []
for i in range(n):
cte = robot.y
steer = -tau * cte
robot.move(steer, speed)
x_trajectory.append(robot.x)
y_trajectory.append(robot.y)
return x_trajectory, y_trajectory
robot = Robot()
robot.set(0, 1, 0)
def run(robot, tau_p, tau_d, n=100, speed=1.0):
x_trajectory = []
y_trajectory = []
# TODO: your code here
last_error = robot.y
for i in range(n):
steering = - tau_p*robot.y - tau_d*(robot.y-last_error)
last_error = robot.y
robot.move(steering,speed)
x_trajectory.append(robot.x)
y_trajectory.append(robot.y)
return x_trajectory, y_trajectory
x_trajectory, y_trajectory = run(robot, 0.2, 3.0)
n = len(x_trajectory)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 8))
ax1.plot(x_trajectory, y_trajectory, 'g', label='PD controller')
ax1.plot(x_trajectory, np.zeros(n), 'r', label='reference')
robot = Robot()
robot.set(0.0, 1.0, 0.0)
robot.steering_drift = 10.0
def run(robot, tau, n=100, speed=1.0):
def run(robot, tau_p, tau_d, tau_i, n=100, speed=1.0):
x_trajectory = []
y_trajectory = []
# TODO: your code here
last_error = robot.y
sum_error = 0
for i in range(n):
steering = -tau_p*robot.y -tau_d*(robot.y-last_error) -tau_i*sum_error
last_error = robot.y
sum_error += last_error
robot.move(steering,speed)
x_trajectory.append(robot.x)
y_trajectory.append(robot.y)
return x_trajectory, y_trajectory
x_trajectory, y_trajectory = run(robot, 0.2, 3.0, 0.004)
n = len(x_trajectory)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8,8))
ax1.plot(x_trajectory, y_trajectory, 'g', label='PID controller')
ax1.plot(x_trajectory, np.zeros(n), 'r', label='reference')
# ----------------
# User Instructions
#
# Implement twiddle as shown in the previous two videos.
# Your accumulated error should be very small!
#
# You don't have to use the exact values as shown in the video
# play around with different values! This quiz isn't graded just see
# how low of an error you can get.
#
# Try to get your error below 1.0e-10 with as few iterations
# as possible (too many iterations will cause a timeout).
#
# No cheating!
# ------------
import random
import numpy as np
import matplotlib.pyplot as plt
# ------------------------------------------------
#
# this is the Robot class
#
class Robot(object):
def __init__(self, length=20.0):
"""
Creates robot and initializes location/orientation to 0, 0, 0.
"""
self.x = 0.0
self.y = 0.0
self.orientation = 0.0
self.length = length
self.steering_noise = 0.0
self.distance_noise = 0.0
self.steering_drift = 0.0
def set(self, x, y, orientation):
"""
Sets a robot coordinate.
"""
self.x = x
self.y = y
self.orientation = orientation % (2.0 * np.pi)
def set_noise(self, steering_noise, distance_noise):
"""
Sets the noise parameters.
"""
# makes it possible to change the noise parameters
# this is often useful in particle filters
self.steering_noise = steering_noise
self.distance_noise = distance_noise
def set_steering_drift(self, drift):
"""
Sets the systematical steering drift parameter
"""
self.steering_drift = drift
def move(self, steering, distance, tolerance=0.001, max_steering_angle=np.pi / 4.0):
"""
steering = front wheel steering angle, limited by max_steering_angle
distance = total distance driven, most be non-negative
"""
if steering > max_steering_angle:
steering = max_steering_angle
if steering < -max_steering_angle:
steering = -max_steering_angle
if distance < 0.0:
distance = 0.0
# apply noise
steering2 = random.gauss(steering, self.steering_noise)
distance2 = random.gauss(distance, self.distance_noise)
# apply steering drift
steering2 += self.steering_drift
# Execute motion
turn = np.tan(steering2) * distance2 / self.length
if abs(turn) < tolerance:
# approximate by straight line motion
self.x += distance2 * np.cos(self.orientation)
self.y += distance2 * np.sin(self.orientation)
self.orientation = (self.orientation + turn) % (2.0 * np.pi)
else:
# approximate bicycle model for motion
radius = distance2 / turn
cx = self.x - (np.sin(self.orientation) * radius)
cy = self.y + (np.cos(self.orientation) * radius)
self.orientation = (self.orientation + turn) % (2.0 * np.pi)
self.x = cx + (np.sin(self.orientation) * radius)
self.y = cy - (np.cos(self.orientation) * radius)
def __repr__(self):
return '[x=%.5f y=%.5f orient=%.5f]' % (self.x, self.y, self.orientation)
############## ADD / MODIFY CODE BELOW ####################
# ------------------------------------------------------------------------
#
# run - does a single control run
def make_robot():
"""
Resets the robot back to the initial position and drift.
You'll want to call this after you call `run`.
"""
robot = Robot()
robot.set(0.0, 1.0, 0.0)
robot.set_steering_drift(10.0 / 180.0 * np.pi)
return robot
# NOTE: We use params instead of tau_p, tau_d, tau_i
def run(robot, params, n=100, speed=1.0):
x_trajectory = []
y_trajectory = []
err = 0
prev_cte = robot.y
int_cte = 0
for i in range(2 * n):
cte = robot.y
diff_cte = cte - prev_cte
int_cte += cte
prev_cte = cte
steer = -params[0] * cte - params[1] * diff_cte - params[2] * int_cte
robot.move(steer, speed)
x_trajectory.append(robot.x)
y_trajectory.append(robot.y)
if i >= n:
err += cte ** 2
return x_trajectory, y_trajectory, err / n
# Make this tolerance bigger if you are timing out!
def twiddle(tol=0.2):
# Don't forget to call `make_robot` before every call of `run`!
p = [0.0, 0.0, 0.0]
dp = [1.0, 1.0, 1.0]
robot = make_robot()
x_trajectory, y_trajectory, best_err = run(robot, p)
# TODO: twiddle loop here
while sum(dp) > tol:
for i in range(3):
p[i] += dp[i]
robot = make_robot()
x_trajectory, y_trajectory, err = run(robot, p)
if err < best_err:
dp[i] *= 1.1
best_err = err
else:
p[i] -= 2*dp[i]
robot = make_robot()
x_trajectory, y_trajectory, err = run(robot, p)
if err < best_err:
dp[i] *=1.1
best_err = err
else:
p[i] += dp[i]
dp[i] *= 0.9
return p, best_err
params, err = twiddle()
print("Final twiddle error = {}".format(err))
robot = make_robot()
x_trajectory, y_trajectory, err = run(robot, params)
n = len(x_trajectory)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 8))
ax1.plot(x_trajectory, y_trajectory, 'g', label='Twiddle PID controller')
ax1.plot(x_trajectory, np.zeros(n), 'r', label='reference')
Quiz
Q1: Missing Parameters
-
- no τd here, because τd makes the curve smooth
-
- no problem
-
- converges to a none 0 value, and τI wants the final value to converge to 0
-
- the only one starting to move to +Y direction firstly, which means τp is a small value
NOTE that there's a system bias here.
Q2: Cyclic Smoothing
# -------------
# User Instructions
#
# Here you will be implementing a cyclic smoothing
# algorithm. This algorithm should not fix the end
# points (as you did in the unit quizzes). You
# should use the gradient descent equations that
# you used previously.
#
# Your function should return the newpath that it
# calculates.
#
# Feel free to use the provided solution_check function
# to test your code. You can find it at the bottom.
#
# --------------
# Testing Instructions
#
# To test your code, call the solution_check function with
# two arguments. The first argument should be the result of your
# smooth function. The second should be the corresponding answer.
# For example, calling
#
# solution_check(smooth(testpath1), answer1)
#
# should return True if your answer is correct and False if
# it is not.
from math import *
from copy import deepcopy
# Do not modify path inside your function.
path=[[0, 0],
[1, 0],
[2, 0],
[3, 0],
[4, 0],
[5, 0],
[6, 0],
[6, 1],
[6, 2],
[6, 3],
[5, 3],
[4, 3],
[3, 3],
[2, 3],
[1, 3],
[0, 3],
[0, 2],
[0, 1]]
############# ONLY ENTER CODE BELOW THIS LINE ##########
# ------------------------------------------------
# smooth coordinates
# If your code is timing out, make the tolerance parameter
# larger to decrease run time.
#
def smooth(path, weight_data = 0.1, weight_smooth = 0.1, tolerance = 0.00001):
#
# Enter code here
#
new_path = deepcopy(path)
length = len(path)
diff = tolerance + 1
while diff > tolerance:
diff = -float('inf')
for i,p in enumerate(new_path):
dp0 = weight_data*(path[i][0]-p[0]) + \
weight_smooth*(new_path[(i+1)%length][0]+new_path[i-1][0]-2*p[0])
dp1 = weight_data*(path[i][1]-p[1]) + \
weight_smooth*(new_path[(i+1)%length][1]+new_path[i-1][1]-2*p[1])
new_path[i][0] += dp0
new_path[i][1] += dp1
diff = max(dp0,dp1,diff)
return new_path
# thank you - EnTerr - for posting this on our discussion forum
#newpath = smooth(path)
#for i in range(len(path)):
# print '['+ ', '.join('%.3f'%x for x in path[i]) +'] -> ['+ ', '.join('%.3f'%x for x in newpath[i]) +']'
##### TESTING ######
# --------------------------------------------------
# check if two numbers are 'close enough,'used in
# solution_check function.
#
def close_enough(user_answer, true_answer, epsilon = 0.001):
if abs(user_answer - true_answer) > epsilon:
return False
return True
# --------------------------------------------------
# check your solution against our reference solution for
# a variety of test cases (given below)
#
def solution_check(newpath, answer):
if type(newpath) != type(answer):
print "Error. You do not return a list."
return False
if len(newpath) != len(answer):
print 'Error. Your newpath is not the correct length.'
return False
if len(newpath[0]) != len(answer[0]):
print 'Error. Your entries do not contain an (x, y) coordinate pair.'
return False
for i in range(len(newpath)):
for j in range(len(newpath[0])):
if not close_enough(newpath[i][j], answer[i][j]):
print 'Error, at least one of your entries is not correct.'
return False
print "Test case correct!"
return True
# --------------
# Testing Instructions
#
# To test your code, call the solution_check function with
# two arguments. The first argument should be the result of your
# smooth function. The second should be the corresponding answer.
# For example, calling
#
# solution_check(smooth(testpath1), answer1)
#
# should return True if your answer is correct and False if
# it is not.
testpath1 = [[0, 0],
[1, 0],
[2, 0],
[3, 0],
[4, 0],
[5, 0],
[6, 0],
[6, 1],
[6, 2],
[6, 3],
[5, 3],
[4, 3],
[3, 3],
[2, 3],
[1, 3],
[0, 3],
[0, 2],
[0, 1]]
answer1 = [[0.4705860385182691, 0.4235279620576893],
[1.1764695730296597, 0.16470408411716733],
[2.058823799247812, 0.07058633859438503],
[3.000001503542886, 0.04705708651959327],
[3.9411790099468273, 0.07058689299792453],
[4.8235326678889345, 0.16470511854183797],
[5.529415336860586, 0.4235293374365447],
[5.76470933698621, 1.1058829941330384],
[5.764708805535902, 1.8941189433780983],
[5.5294138118186265, 2.5764724018811056],
[4.823530348360371, 2.835296251305122],
[3.941176199414957, 2.929413985845729],
[2.9999985709076413, 2.952943245204772],
[2.0588211310939526, 2.9294134622132018],
[1.1764675231284938, 2.8352952720424938],
[0.4705848811030855, 2.5764710948028178],
[0.23529088056307781, 1.8941174802285707],
[0.23529138316655338, 1.1058815684272394]]
testpath2 = [[1, 0], # Move in the shape of a plus sign
[2, 0],
[2, 1],
[3, 1],
[3, 2],
[2, 2],
[2, 3],
[1, 3],
[1, 2],
[0, 2],
[0, 1],
[1, 1]]
answer2 = [[1.2222234770374059, 0.4444422843711052],
[1.7777807251383388, 0.4444432993123497],
[2.111114925633848, 0.8888894279539462],
[2.5555592020540376, 1.2222246475393077],
[2.5555580686154244, 1.7777817817879298],
[2.111111849558437, 2.1111159707965514],
[1.7777765871460525, 2.55556033483712],
[1.2222194640861452, 2.5555593592828543],
[0.8888853322565222, 2.111113321684573],
[0.44444105139827167, 1.777778212019149],
[0.44444210978390364, 1.2222211690821811],
[0.8888882042812255, 0.8888870211766268]]
solution_check(smooth(testpath1), answer1)
solution_check(smooth(testpath2), answer2)
Q3: Constrained Smoothing
def smooth(path, fix, weight_data = 0.0, weight_smooth = 0.1, tolerance = 0.00001):
#
# Enter code here.
# The weight for each of the two new equations should be 0.5 * weight_smooth
#
newpath = deepcopy(path)
point_num = len(path)
dim_num = len(path[0])
max_diff = tolerance + 1
while max_diff > tolerance:
max_diff = -float("inf")
for i in range(point_num):
if fix[i] == 1:
continue
for j in range(dim_num):
diff_ij = weight_smooth*(newpath[(i+1)%point_num][j] + newpath[(i-1)%point_num][j] - 2*newpath[i][j]) + \
(weight_smooth/2)*(2*newpath[(i+1)%point_num][j] - newpath[(i+2)%point_num][j] - newpath[i][j]) + \
(weight_smooth/2)*(2*newpath[(i-1)%point_num][j] - newpath[(i-2)%point_num][j] - newpath[i][j])
max_diff = max(max_diff,diff_ij)
newpath[i][j] += diff_ij
return newpath
Note:
- min(target); target = (path[i][j] - newpath[i][j])2 ~> 接近原始点,提供约束,避免所有点都集中在一起。没有fix的点的话有这一项,否则没有。
- min(target); target = (newpath[i+1][j] - newpath[i][j])2 + (newpath[i-1][j] - newpath[i][j])2 = B2 + C2 ~> 相邻点更接近
- min(target); target = (A-B)2 + (C-D)2 ~> 导数连续
- A = (newpath[i+2][j] - newpath[i+1][j]); B = (newpath[i+1][j] - newpath[i][j]); C = (newpath[i-1][j] - newpath[i][j]); D = (newpath[i-2][j] - newpath[i-1][j]);
- ~> gradient descent to minimize a target function
Q4: Racetrack Control
def cte(self, radius):
#
#
# Add code here
if radius < self.x and self.x < 3*radius:
if cos(self.orientation) > 0:
cte = self.y - 2*radius
else:
cte = - self.y
elif self.x <= radius:
cte = sqrt((self.x-radius)**2 + (self.y-radius)**2) - radius
else:
cte = sqrt((self.x-3*radius)**2 + (self.y-radius)**2) - radius
#
#
return cte
return:
[x=0.00000 y=26.00000 orient=1.57080]
[x=0.01365 y=26.99988 orient=1.54349]
[x=0.06592 y=27.99840 orient=1.49349]
[x=0.16804 y=28.99307 orient=1.44349]
[x=0.31973 y=29.98139 orient=1.39349]
[x=0.52064 y=30.96090 orient=1.34349]
[x=0.77025 y=31.92914 orient=1.29349]
[x=1.06793 y=32.88369 orient=1.24349]
......
[x=99.46649 y=19.51598 orient=4.49955]
[x=99.23092 y=18.54423 orient=4.44955]
[x=98.94707 y=17.58547 orient=4.39955]
[x=98.62399 y=16.63914 orient=4.36724]
[x=98.27383 y=15.70248 orient=4.34203]
[x=97.89398 y=14.77750 orient=4.30342]
[x=97.47355 y=13.87029 orient=4.25342]
[x=97.00830 y=12.98523 orient=4.20342]
[x=96.50351 y=12.12207 orient=4.16299]Final parameters: [10.0, 15.0, 0]
-> 0.00586850481278