Submission: group submission via Gradescope. Important: If any of the two team members have ROS Noetic installed, it is highly recommended to setup the code on this computer and not on the M1/M2 computer.
Demo: not required
Due time: 11:59pm, March 3, Monday
Files to submit:
Grading rubric:
+ 30% Clearly describe your approach and explain your code in the lab report and video.
+ 20% Plot the trajectories and discuss the results under different parameters.
+25% Pass the performance test on the Gradient Descent Path Smoothing Task.
+ 25% Pass all waypoints using 3rd order polynomial trajectories.
- 10% Penalty applies for each late day. No submission will be accepted beyond four (2) days from the due date.
All code submissions will be graded automatically by an autograder uploaded to Gradescope. The scripts will be tested on a Ubuntu cloud server using a similar ROS + Gazebo environment. The grading results will be available in a couple of minutes after submission.
Testing parameters are as follows.
Gradient Descent: The autograder will evaluate how closely the smoothed path adheres to the original waypoints and measure the overall smoothness using 3 test case trajectories along with one hidden test case.
Polynomial Timescaling:
The tolerance for distance error is set to 0.1m.
The autograder will check the waypoints for both paths and check for the tolerance.
The time limit is set to 400 seconds.
Sample Code for Gradient Descent
roscd ee144w25/src
touch gradient_descent.py
gedit gradient_descent.py
#!/usr/bin/env python3
from math import pi, sqrt, atan2, cos, sin
import numpy as np
from copy import deepcopy
import rospy
import tf
from std_msgs.msg import Empty
from nav_msgs.msg import Odometry
from geometry_msgs.msg import Twist, Pose2D
from std_srvs.srv import Empty as srvEmpty
class Turtlebot():
def __init__(self, path, num_path, wf, ws):
# rospy.init_node(node_name)
# rospy.loginfo("Press Ctrl + C to terminate")
self.vel_pub = rospy.Publisher("cmd_vel", Twist, queue_size=10)
self.rate = rospy.Rate(10)
# reset odometry to zero
self.reset_pub = rospy.Publisher("mobile_base/commands/reset_odometry", Empty, queue_size=10)
for i in range(10):
self.reset_pub.publish(Empty())
self.rate.sleep()
# set the path
self.path = path
self.wf = wf
self.ws = ws
# subscribe to odometry
self.pose = Pose2D()
self.logging_counter = 0
self.trajectory = list()
self.odom_sub = rospy.Subscriber("odom", Odometry, self.odom_callback)
#initialize your terms
try:
self.run()
except rospy.ROSInterruptException:
rospy.loginfo("Action terminated.")
finally:
# save trajectory into csv file
np.savetxt('trajectory'+str(num_path)+'.csv', np.array(self.trajectory), fmt='%f', delimiter=',')
self.reset_gazebo()
def run(self):
waypoints = self.path
while True:
if len(waypoints) == 1:
break
waypoints[0] = [self.pose.x, self.pose.y]
# use gradient descent for the optimal trajectory
# call self.wf and self.ws to get weight_follow and weight_smooth
waypoints =
self.move_to_point(waypoints[0], waypoints[1])
# update the path to optimize the rest of the trajectory
def move_to_point(self, current_waypoint, next_waypoint):
# generate gradient descent trajectory and move to current_waypoint
# next_waypoint is to help determine the velocity to pass current_waypoint
pass
def smooth(self, path, alpha=1, weight_follow = 0.5, weight_smooth = 0.1, tolerance = 0.000001):
newpath = deepcopy(path) # Make a copy of the original path
change = float('inf') # Large value
while change > tolerance: # Iterative update (repeat until convergence)
change = 0
#############Fill in your code here######################
##############Return smoothed path#######################
return newpath
def odom_callback(self, msg):
# get pose = (x, y, theta) from odometry topic
quarternion = [msg.pose.pose.orientation.x, msg.pose.pose.orientation.y,\
msg.pose.pose.orientation.z, msg.pose.pose.orientation.w]
(roll, pitch, yaw) = tf.transformations.euler_from_quaternion(quarternion)
self.pose.theta = yaw
self.pose.x = msg.pose.pose.position.x
self.pose.y = msg.pose.pose.position.y
# logging once every 100 times (Gazebo runs at 1000Hz; we save it at 10Hz)
self.logging_counter += 1
if self.logging_counter == 100:
self.logging_counter = 0
self.trajectory.append([self.pose.x, self.pose.y]) # save trajectory
rospy.loginfo("odom: x=" + str(self.pose.x) +\
"; y=" + str(self.pose.y) + "; theta=" + str(yaw))
def reset_gazebo(self):
rospy.wait_for_service('/gazebo/reset_world')
try:
reset_world = rospy.ServiceProxy('/gazebo/reset_world', srvEmpty)
reset_world()
print('resetting')
except rospy.ServiceException as e:
print('failed' % e)
def printpaths(self, path,newpath): # This will show the initial path before smoothing
for old,new in zip(path,newpath):
print ('['+ ', '.join('%.3f'%x for x in old) + \
'] -> ['+ ', '.join('%.3f'%x for x in new) +']')
def plot(self, path, newpath):
x = [x for [x, y] in path]
y = [y for [ax, y] in path]
# This plots the smooth path overlayed on the original
plt.plot(x, y, color='r', marker='o')
x2 = [x for [x, y] in newpath]
y2 = [y for [ax, y] in newpath]
plt.plot(x2, y2, color='b', marker='o')
plt.xlim(-1, 3)
plt.ylim(-1, 3)
plt.show()
if __name__ == '__main__':
#path1
path1 = [[0,0], [0,1], [0,2], [0,3], [0,4], [1,4], [2,4], [2,3], [2,2], [3,2], [4,2], [4,1], [4,0]]
#path2
path2 = [[0.5, 0], [0.5, -0.5], [1, -0.5], [1, 0], [1, 0.5],\
[1.5, 0.5], [1.5, 0], [1.5, -0.5], [1, -0.5], [1, 0],\
[1, 0.5], [0.5, 0.5], [0.5, 0], [0, 0], [0, 0]]
rospy.init_node('turtlebotmove')
rospy.loginfo("Press Ctrl + C to terminate")
whatever1 = Turtlebot(path1, 1, wf=0.1, ws=0.1)
# whatever2 = Turtlebot(path2, 2, wf=0.1, ws=0.1)