Skip to content

Instantly share code, notes, and snippets.

@AakashKumarNain
Last active June 12, 2017 20:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AakashKumarNain/6aac829950475344aa35d69285a099c5 to your computer and use it in GitHub Desktop.
Save AakashKumarNain/6aac829950475344aa35d69285a099c5 to your computer and use it in GitHub Desktop.
MPC
#include <math.h>
#include <uWS/uWS.h>
#include <chrono>
#include <iostream>
#include <thread>
#include <vector>
#include "Eigen-3.3/Eigen/Core"
#include "Eigen-3.3/Eigen/QR"
#include "MPC.h"
#include "json.hpp"
#include <cppad/cppad.hpp>
#include <cppad/ipopt/solve.hpp>
using CppAD :: AD;
// for convenience
using json = nlohmann::json;
// For converting back and forth between radians and degrees.
constexpr double pi() { return M_PI; }
double deg2rad(double x) { return x * pi() / 180; }
double rad2deg(double x) { return x * 180 / pi(); }
// Checks if the SocketIO event has JSON data.
// If there is data the JSON object in string format will be returned,
// else the empty string "" will be returned.
string hasData(string s) {
auto found_null = s.find("null");
auto b1 = s.find_first_of("[");
auto b2 = s.rfind("}]");
if (found_null != string::npos) {
return "";
} else if (b1 != string::npos && b2 != string::npos) {
return s.substr(b1, b2 - b1 + 2);
}
return "";
}
// Evaluate a polynomial.
double polyeval(Eigen::VectorXd coeffs, double x) {
double result = 0.0;
for (int i = 0; i < coeffs.size(); i++) {
result += coeffs[i] * pow(x, i);
}
return result;
}
// Fit a polynomial.
// Adapted from
// https://github.com/JuliaMath/Polynomials.jl/blob/master/src/Polynomials.jl#L676-L716
Eigen::VectorXd polyfit(Eigen::VectorXd xvals, Eigen::VectorXd yvals,
int order) {
assert(xvals.size() == yvals.size());
assert(order >= 1 && order <= xvals.size() - 1);
Eigen::MatrixXd A(xvals.size(), order + 1);
for (int i = 0; i < xvals.size(); i++) {
A(i, 0) = 1.0;
}
for (int j = 0; j < xvals.size(); j++) {
for (int i = 0; i < order; i++) {
A(j, i + 1) = A(j, i) * xvals(j);
}
}
auto Q = A.householderQr();
auto result = Q.solve(yvals);
return result;
}
int main() {
uWS::Hub h;
// MPC is initialized here!
MPC mpc;
h.onMessage([&mpc](uWS::WebSocket<uWS::SERVER> ws, char *data, size_t length,
uWS::OpCode opCode) {
// "42" at the start of the message means there's a websocket message event.
// The 4 signifies a websocket message
// The 2 signifies a websocket event
string sdata = string(data).substr(0, length);
cout << sdata << endl;
if (sdata.size() > 2 && sdata[0] == '4' && sdata[1] == '2') {
string s = hasData(sdata);
if (s != "") {
auto j = json::parse(s);
string event = j[0].get<string>();
if (event == "telemetry") {
// j[1] is the data JSON object
vector<double> ptsx = j[1]["ptsx"];
vector<double> ptsy = j[1]["ptsy"];
double px = j[1]["x"];
double py = j[1]["y"];
double psi = j[1]["psi"];
double v = j[1]["speed"];
// convert to car centered coordinates
vector<double> temp_ptsx;
vector<double> temp_ptsy;
temp_ptsx.resize(ptsx.size());
temp_ptsy.resize(ptsy.size());
for (int i = 0; i < ptsx.size(); i++)
{
auto x = ptsx[i] - px;
auto y = ptsy[i] - py;
temp_ptsx[i] = x * CppAD::cos(psi) + y * CppAD::sin(psi);
temp_ptsy[i] = -x * CppAD::sin(psi) + y * CppAD::cos(psi);
}
// convert from vector<double> to Eigen::VectorXd to match input to polyfit
Eigen::VectorXd x (temp_ptsx.size());
Eigen::VectorXd y (temp_ptsy.size());
for (size_t i = 0; i < temp_ptsx.size(); i++)
{
x(i) = temp_ptsx[i];
y(i) = temp_ptsy[i];
}
Eigen::VectorXd coeffs;
coeffs = polyfit(x, y, 3);
// The cross track error is calculated by evaluating at polynomial at x, f(x)
// and subtracting y. x and y are at 0 to represent the car.
double cte = polyeval(coeffs, 0);
// Due to the sign starting at 0, the orientation error is -f'(x).
// derivative of coeffs[0] + coeffs[1] * x + coeffs[2] * x^2 + coeffs[3] * x^3 -> coeffs[1] when x = 0
double epsi = -atan(coeffs[1]);
// in the car centered coordinate, we have a new state:
Eigen::VectorXd new_state(6);
new_state << 0.0, 0.0, 0.0, v, cte, epsi;
// run solver to get the next state and the value of the actuators
vector<double> next_and_actuators = mpc.Solve(new_state, coeffs);
/*
* TODO: Calculate steeering angle and throttle using MPC.
*
* Both are in between [-1, 1].
*
*/
double steer_value;
double throttle_value;
steer_value = next_and_actuators[6];
throttle_value = next_and_actuators[7];
json msgJson;
// NOTE: Remember to divide by deg2rad(25) before you send the steering value back.
// Otherwise the values will be in between [-deg2rad(25), deg2rad(25] instead of [-1, 1].
msgJson["steering_angle"] = -steer_value;
msgJson["throttle"] = throttle_value;
//Display the MPC predicted trajectory
vector<double> mpc_x_vals;
vector<double> mpc_y_vals;
//.. add (x,y) points to list here, points are in reference to the vehicle's coordinate system
// the points in the simulator are connected by a Green line
msgJson["mpc_x"] = mpc_x_vals;
msgJson["mpc_y"] = mpc_y_vals;
//Display the waypoints/reference line
vector<double> next_x_vals;
vector<double> next_y_vals;
//.. add (x,y) points to list here, points are in reference to the vehicle's coordinate system
// the points in the simulator are connected by a Yellow line
msgJson["next_x"] = temp_ptsx;
msgJson["next_y"] = temp_ptsy;
auto msg = "42[\"steer\"," + msgJson.dump() + "]";
std::cout << msg << std::endl;
// Latency
// The purpose is to mimic real driving conditions where
// the car does actuate the commands instantly.
//
// Feel free to play around with this value but should be to drive
// around the track with 100ms latency.
//
// NOTE: REMEMBER TO SET THIS TO 100 MILLISECONDS BEFORE
// SUBMITTING.
this_thread::sleep_for(chrono::milliseconds(100));
ws.send(msg.data(), msg.length(), uWS::OpCode::TEXT);
}
} else {
// Manual driving
std::string msg = "42[\"manual\",{}]";
ws.send(msg.data(), msg.length(), uWS::OpCode::TEXT);
}
}
});
// We don't need this since we're not using HTTP but if it's removed the
// program
// doesn't compile :-(
h.onHttpRequest([](uWS::HttpResponse *res, uWS::HttpRequest req, char *data,
size_t, size_t) {
const std::string s = "<h1>Hello world!</h1>";
if (req.getUrl().valueLength == 1) {
res->end(s.data(), s.length());
} else {
// i guess this should be done more gracefully?
res->end(nullptr, 0);
}
});
h.onConnection([&h](uWS::WebSocket<uWS::SERVER> ws, uWS::HttpRequest req) {
std::cout << "Connected!!!" << std::endl;
});
h.onDisconnection([&h](uWS::WebSocket<uWS::SERVER> ws, int code,
char *message, size_t length) {
ws.close();
std::cout << "Disconnected" << std::endl;
});
int port = 4567;
if (h.listen(port)) {
std::cout << "Listening to port " << port << std::endl;
} else {
std::cerr << "Failed to listen to port" << std::endl;
return -1;
}
h.run();
}
#include "MPC.h"
#include <cppad/cppad.hpp>
#include <cppad/ipopt/solve.hpp>
#include "Eigen-3.3/Eigen/Core"
using CppAD::AD;
// TODO: Set the timestep length and duration
size_t N = 0;
double dt = 0;
// This value assumes the model presented in the classroom is used.
//
// It was obtained by measuring the radius formed by running the vehicle in the
// simulator around in a circle with a constant steering angle and velocity on a
// flat terrain.
//
// Lf was tuned until the the radius formed by the simulating the model
// presented in the classroom matched the previous radius.
//
// This is the length from front to CoG that has a similar radius.
const double Lf = 2.67;
//Set the reference cross track error, orientation error and the velocity
double ref_cte = 0;
double ref_epsi = 0;
double ref_velocity = 40;
//Define all the states(start and end) of the variables that are used by solver
size_t x_start = 0;
size_t y_start = x_start + N;
size_t psi_start = y_start + N;
size_t v_start = psi_start + N;
size_t cte_start = v_start + N;
size_t epsi_start = cte_start + N;
size_t delta_start = epsi_start + N;
size_t a_start = delta_start + N - 1;
class FG_eval {
public:
// Fitted polynomial coefficients
Eigen::VectorXd coeffs;
FG_eval(Eigen::VectorXd coeffs) { this->coeffs = coeffs; }
typedef CPPAD_TESTVECTOR(AD<double>) ADvector;
void operator()(ADvector& fg, const ADvector& vars) {
// TODO: implement MPC
// fg a vector of constraints, x is a vector of constraints.
// NOTE: You'll probably go back and forth between this function and
// the Solver function below.
fg[0] = 0;
// Cost based on the reference state.
for (int i = 0; i < N; i++)
{
fg[0] += CppAD::pow(vars[cte_start + i] - ref_cte, 2);
fg[0] += CppAD::pow(vars[epsi_start + i] - ref_epsi, 2);
fg[0] += CppAD::pow(vars[v_start + i] - ref_velocity, 2);
}
// Make sure to have minimum actuatir usage. Yay baby!!
for (int i = 0; i < N - 1; i++)
{
fg[0] += CppAD::pow(vars[delta_start + i], 2);
fg[0] += CppAD::pow(vars[a_start + i], 2);
}
// To many sequential actuations. What the hell!! Minimize it
for (int i = 0; i < N - 2; i++)
{
fg[0] += 100*CppAD::pow(vars[delta_start + i + 1] - vars[delta_start + i], 2);
fg[0] += 100*CppAD::pow(vars[a_start + i + 1] - vars[a_start + i], 2);
//fg[0] += 200*CppAD::pow(vars[delta_start + i + 1] - vars[delta_start + i], 2);
//fg[0] += 10*CppAD::pow(vars[a_start + i + 1] - vars[a_start + i], 2);
}
//Add some initial constraints
fg[1 + x_start] = vars[x_start];
fg[1 + y_start] = vars[y_start];
fg[1 + psi_start] = vars[psi_start];
fg[1 + v_start] = vars[v_start];
fg[1 + cte_start] = vars[cte_start];
fg[1 + epsi_start] = vars[epsi_start];
for (int i = 0; i < N - 1; i++)
{
// State at time t+1 .
AD<double> x1 = vars[x_start + i + 1];
AD<double> y1 = vars[y_start + i + 1];
AD<double> psi1 = vars[psi_start + i + 1];
AD<double> v1 = vars[v_start + i + 1];
AD<double> cte1 = vars[cte_start + i + 1];
AD<double> epsi1 = vars[epsi_start + i + 1];
// State at time t.
AD<double> x0 = vars[x_start + i];
AD<double> y0 = vars[y_start + i];
AD<double> psi0 = vars[psi_start + i];
AD<double> v0 = vars[v_start + i];
AD<double> cte0 = vars[cte_start + i];
AD<double> epsi0 = vars[epsi_start + i];
// We need to consider the actuation at time t only.
AD<double> delta0 = vars[delta_start + i];
AD<double> a0 = vars[a_start + i];
AD<double> f0 = coeffs[0] + coeffs[1] * x0 + coeffs[2] * CppAD::pow(x0,2) + coeffs[3] * CppAD::pow(x0,3);
AD<double> psides0 = CppAD::atan(coeffs[1] + 2 * coeffs[2] * x0 + 3 * coeffs[3] * CppAD::pow(x0,2));
//Implement the equations of the model as describe in the lessons
fg[2 + x_start + i] = x1 - (x0 + v0 * CppAD::cos(psi0) * dt); // x_[t+1] = x[t] + v[t] * cos(psi[t]) * dt
fg[2 + y_start + i] = y1 - (y0 + v0 * CppAD::sin(psi0) * dt); // y_[t+1] = y[t] + v[t] * sin(psi[t]) * dt
fg[2 + psi_start + i] = psi1 - (psi0 + v0 * delta0 / Lf * dt); // psi_[t+1] = psi[t] + v[t] / Lf * delta[t] * dt
fg[2 + v_start + i] = v1 - (v0 + a0 * dt);
fg[2 + cte_start + i] = cte1 - ((f0 - y0) + (v0 * CppAD::sin(epsi0) * dt));
fg[2 + epsi_start + i] = epsi1 - ((psi0 - psides0) + v0 * delta0 / Lf * dt);
}
}
};
//
// MPC class definition implementation.
//
MPC::MPC() {}
MPC::~MPC() {}
vector<double> MPC::Solve(Eigen::VectorXd state, Eigen::VectorXd coeffs) {
bool ok = true;
size_t i;
typedef CPPAD_TESTVECTOR(double) Dvector;
// TODO: Set the number of model variables (includes both states and inputs).
// For example: If the state is a 4 element vector, the actuators is a 2
// element vector and there are 10 timesteps. The number of variables is:
//
// 4 * 10 + 2 * 9
//elements of state vector
double x = state[0];
double y = state[1];
double psi = state[2];
double v = state[3];
double cte = state[4];
double epsi = state[5];
size_t n_vars = N*6 + (N-1)*2; //6 state elements and 2 actuators
// TODO: Set the number of constraints
size_t n_constraints = N*6;
// Initial value of the independent variables.
// SHOULD BE 0 besides initial state.
Dvector vars(n_vars);
for (int i = 0; i < n_vars; i++)
{
vars[i] = 0;
}
vars[x_start] = x;
vars[y_start] = y;
vars[psi_start] = psi;
vars[v_start] = v;
vars[cte_start] = cte;
vars[epsi_start] = epsi;
Dvector vars_lowerbound(n_vars);
Dvector vars_upperbound(n_vars);
// TODO: Set lower and upper limits for variables.
for (int i = 0; i < delta_start; i++)
{
vars_lowerbound[i] = -1.0e19;
vars_upperbound[i] = 1.0e19;
}
// Lower and upper limits for the constraints
// Should be 0 besides initial state.
Dvector constraints_lowerbound(n_constraints);
Dvector constraints_upperbound(n_constraints);
for (int i = 0; i < n_constraints; i++)
{
constraints_lowerbound[i] = 0;
constraints_upperbound[i] = 0;
}
// The upper and lower limits of delta are set to -25 and 25 degrees (values in radians).
for (int i = delta_start; i < a_start; i++)
{
vars_lowerbound[i] = -0.436332; //try multiplying LF
vars_upperbound[i] = 0.436332;
}
// Acceleration/decceleration upper and lower limits.
for (int i = a_start; i < n_vars; i++)
{
vars_lowerbound[i] = -1.0;
vars_upperbound[i] = 1.0;
}
constraints_lowerbound[x_start] = x;
constraints_lowerbound[y_start] = y;
constraints_lowerbound[psi_start] = psi;
constraints_lowerbound[v_start] = v;
constraints_lowerbound[cte_start] = cte;
constraints_lowerbound[epsi_start] = epsi;
constraints_upperbound[x_start] = x;
constraints_upperbound[y_start] = y;
constraints_upperbound[psi_start] = psi;
constraints_upperbound[v_start] = v;
constraints_upperbound[cte_start] = cte;
constraints_upperbound[epsi_start] = epsi;
// object that computes objective and constraints
FG_eval fg_eval(coeffs);
//
// NOTE: You don't have to worry about these options
//
// options for IPOPT solver
std::string options;
// Uncomment this if you'd like more print information
options += "Integer print_level 0\n";
// NOTE: Setting sparse to true allows the solver to take advantage
// of sparse routines, this makes the computation MUCH FASTER. If you
// can uncomment 1 of these and see if it makes a difference or not but
// if you uncomment both the computation time should go up in orders of
// magnitude.
options += "Sparse true forward\n";
options += "Sparse true reverse\n";
// NOTE: Currently the solver has a maximum time limit of 0.5 seconds.
// Change this as you see fit.
options += "Numeric max_cpu_time 0.5\n";
// place to return solution
CppAD::ipopt::solve_result<Dvector> solution;
// solve the problem
CppAD::ipopt::solve<Dvector, FG_eval>(
options, vars, vars_lowerbound, vars_upperbound, constraints_lowerbound,
constraints_upperbound, fg_eval, solution);
// Check some of the solution values
ok &= solution.status == CppAD::ipopt::solve_result<Dvector>::success;
// Cost
auto cost = solution.obj_value;
std::cout << "Cost " << cost << std::endl;
// TODO: Return the first actuator values. The variables can be accessed with
// `solution.x[i]`.
//
// {...} is shorthand for creating a vector, so auto x1 = {1.0,2.0}
// creates a 2 element double vector.
std::vector<double> result;
result.push_back(solution.x[delta_start]);
result.push_back(solution.x[a_start]);
for (int i = 0; i < N-1; ++i)
{
result.push_back(solution.x[x_start + i + 1]);
result.push_back(solution.x[y_start + i + 1]);
}
return result;
}
#ifndef MPC_H
#define MPC_H
#include <vector>
#include "Eigen-3.3/Eigen/Core"
using namespace std;
class MPC {
public:
MPC();
virtual ~MPC();
// Solve the model given an initial state and polynomial coefficients.
// Return the first actuatotions.
vector<double> Solve(Eigen::VectorXd state, Eigen::VectorXd coeffs);
};
#endif /* MPC_H */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment