12 #include "osqp_eigen/SparseMatrixHelper.hpp" 13 #include <nav_msgs/Path.h> 14 #include <geometry_msgs/Pose.h> 15 #include <geometry_msgs/PoseArray.h> 16 #include <geometry_msgs/Quaternion.h> 17 #include <geometry_msgs/Point.h> 18 #include <eigen3/Eigen/Dense> 19 #include <eigen3/Eigen/Sparse> 23 using Eigen::VectorXf;
24 using Eigen::VectorXd;
25 using Eigen::MatrixXf;
26 using Eigen::MatrixXd;
31 std::vector<geometry_msgs::Pose> MPCNode::adapt_path(
32 const std::vector<geometry_msgs::Pose> &orig_path,
33 const geometry_msgs::Point ¤t_position,
35 float &spatial_resolution,
36 float &time_resolution,
40 int size_path = orig_path.size();
41 float min_dist = 10000;
44 for (
int k = 0; k < size_path; k++) {
45 float d =
distance2(current_position, orig_path[k].position);
54 if (idx_path >= size_path - 1) {
55 return vector<geometry_msgs::Pose>(nbr_steps+1, orig_path[size_path-1]);
59 float path_length = 0;
61 for (
int k = idx_path; k < size_path-1; k++) {
62 path_length +=
distance(orig_path[k], orig_path[k+1]);
66 if (spatial_resolution*nbr_steps > path_length) {
67 float factor = path_length / (nbr_steps * spatial_resolution);
68 spatial_resolution *= factor;
69 time_resolution *= factor;
70 desired_speed *= factor;
74 vector<geometry_msgs::Pose> new_path(nbr_steps + 1);
75 new_path[0] = orig_path[idx_path];
77 for (
int k = 1; k <= nbr_steps; k++) {
79 float ds =
distance(new_path[k-1], orig_path[idx_path + 1]);
81 while (s + ds < spatial_resolution && idx_path+2 < size_path) {
84 ds =
distance(orig_path[idx_path], orig_path[idx_path+1]);
87 if (s + ds < spatial_resolution) {
90 ROS_WARN(
"[mpc_node] Unexpected path length");
93 new_path[k] = orig_path[size_path-1];
95 float t = (spatial_resolution-s) / ds;
96 new_path[k] =
interpolate(orig_path[idx_path], orig_path[idx_path+1], t);
104 template <
class VectorT>
105 bool MPCNode::fill_ref_pts(
107 const std::vector<geometry_msgs::Pose> &path,
109 float last_desired_speed,
114 if (N < 1 || n < 1 || m < 1 || path.size() != N+1) {
115 ROS_WARN(
"[mpc_node] Wrong input dimensions in fill_ref_pts:");
116 ROS_WARN(
"[mpc_node] path size: %d ; N+1=%d", (
int) path.size(), N+1);
120 X_ref = VectorT::Zero(n * (N+1));
121 U_ref = VectorT::Zero(m * (N+1));
123 for (
int k = 0; k <= N; k++) {
124 double roll, pitch, yaw;
125 to_euler(path[k].orientation, roll, pitch, yaw);
127 X_ref(k*n) = path[k].position.x;
128 X_ref(k*n + 1) = path[k].position.y;
129 X_ref(k*n + 2) = path[k].position.z;
130 X_ref(k*n + 3) = roll;
131 X_ref(k*n + 4) = pitch;
132 X_ref(k*n + 5) = yaw;
133 X_ref(k*n + 6) = desired_speed;
139 for (
int k = 0; k <= N; k++) {
140 U_ref(k*m) = desired_prop_speed;
147 template <
class VectorT>
148 void MPCNode::modulo_ref_state(VectorT &X_ref,
const VectorT &x0,
int n)
150 int N = X_ref.rows() /
n;
153 for (
int k = 0; k < 3; k++) {
155 while (abs(X_ref(3 + k) - x0(3 + k)) >= M_PI) {
156 if (X_ref(3 + k) - x0(3 + k) >= M_PI)
157 X_ref(3 + k) -= 2*M_PI;
159 X_ref(3 + k) += 2*M_PI;
163 for (
int l = 1; l < N; l++) {
164 while (abs(X_ref(l*n + 3 + k) - X_ref((l-1)*n + 3 + k)) >= M_PI) {
165 if (X_ref(l*n + 3 + k) - X_ref((l-1)*n + 3 + k) >= M_PI)
166 X_ref(l*n + 3 + k) -= 2*M_PI;
168 X_ref(l*n + 3 + k) += 2*M_PI;
175 template <
class VectorT,
class MatrixT>
176 void MPCNode::fill_ltv_G_H_D(
const VectorT &X_ref,
const VectorT &U_ref,
177 int N,
float dt,
float ds,
178 MatrixT &G, MatrixT &H, VectorT &D)
180 int n = X_ref.rows() / (N+1);
181 int m = U_ref.rows() / (N+1);
182 G = MatrixT::Zero(n*N, m*N);
183 H = MatrixT::Zero(n*N, n);
184 D = VectorT::Zero(n*N);
188 vector<MatrixT> Ad(N);
189 vector<MatrixT> Bd(N);
191 for (
int k = 0; k < N; k++) {
193 VectorT x_ref = X_ref.block(k*n, 0, n, 1);
194 VectorT u_ref = U_ref.block(k*m, 0, m, 1);
195 robot_model_.get_lin_discr_matrices(x_ref, u_ref, _Ad, _Bd, dt);
202 for (
int j = 0; j < N; j++) {
203 G.block(0, j*m, n, m) = Bd[j];
204 MatrixT prod = Bd[j];
206 for (
int i = j+1; i < N; i++) {
208 G.block(i*n, j*m, n, m) = prod;
213 H.block(0, 0, n, n) = Ad[0];
214 MatrixT prod = Ad[0];
216 for (
int i = 1; i < N; i++) {
218 H.block(i*n, 0, n, n) = prod;
224 for (
int k = 0; k < N; k++) {
230 for (
int l = 0; l <
n; l++)
231 x_k_ref[l] = X_ref(k*n + l);
232 for (
int l = 0; l < m; l++)
233 u_k_ref[l] = U_ref(k*m + l);
235 robot_model_.eval_ode(x_k_ref, u_k_ref, _f_k, 0.0);
238 for (
int l = 0; l <
n; l++)
242 delta = X_ref.block(k*n, 0, n, 1) + dt*f_k - X_ref.block((k+1)*n, 0, n, 1);
246 D.block(k*n, 0, n, 1) = delta;
248 D.block(k*n, 0, n, 1) = Ad[k]*D.block((k-1)*n, 0, n, 1) + delta;
253 template <
class MatrixT>
254 void MPCNode::fill_lti_G(
const MatrixT &Ad,
const MatrixT &Bd,
int N, MatrixT &G)
258 G = MatrixT::Zero(n*N, m*N);
261 for (
int k = 0; k < N; k++) {
265 for (
int l = 0; l < N-k; l++) {
266 G.block((k+l)*n, l*m, n, m) = M;
272 template <
class MatrixT>
273 void MPCNode::fill_lti_H(
const MatrixT &Ad,
int N, MatrixT &H)
277 MatrixT M = MatrixT::Identity(n, n);
279 for (
int k = 0; k < N; k++) {
281 H.block(k*n, 0, n, n) = M;
286 template <
class MatrixT>
287 void MPCNode::fill_L(
const MatrixT &
P,
const MatrixT &Q_x,
int N, MatrixT &L)
290 L = MatrixT::Zero(n*N, n*N);
292 for (
int k = 0; k < N-1; k++) {
293 L.block(k*n, k*n, n, n) = Q_x;
295 L.block((N-1)*n, (N-1)*n, n, n) =
P;
299 template <
class MatrixT>
300 void MPCNode::fill_M(
const MatrixT &R_u,
const MatrixT &R_delta,
int N, MatrixT &M)
303 M = MatrixT::Zero(m*N, m*N);
306 MatrixT A = 2*R_delta + R_u;
308 for (
int k = 0; k < N; k++) {
310 M.block(k*m, k*m, m, m) = A;
312 M.block(k*m, k*m, m, m) = R_delta + R_u;
316 MatrixT B = -R_delta;
318 for (
int k = 0; k < N-1; k++) {
319 M.block(k*m, (k+1)*m, m, m) = B;
320 M.block((k+1)*m, k*m, m, m) = B;
325 template <
class VectorT,
class MatrixT>
326 void MPCNode::fill_V(VectorT control_ref, VectorT last_control,
327 const MatrixT &R_delta,
int N, VectorT &V)
329 int m = R_delta.rows();
330 V = VectorT::Zero(m*N);
331 V.block(0, 0, m, 1) = 2*R_delta*(control_ref-last_control);
335 template <
class MatrixT>
336 void MPCNode::fill_lti_LG(
const MatrixT &G,
const MatrixT &
P,
const MatrixT &Q_x,
337 int n,
int m,
int N, MatrixT &LG)
339 LG = MatrixXf::Zero(n*N, m*N);
341 for (
int k = 0; k < N-1; k++) {
342 MatrixXf M = Q_x * G.block(k*n, 0, n, m);
344 for (
int l = 0; l < N-k-1; l++) {
345 LG.block((k+l)*n, l*m, n, m) = M;
348 for (
int l = 0; l < N; l++) {
349 LG.block((N-1)*n, l*m, n, m) = P * G.block((N-1)*n, l*m, n, m);
354 template <
class VectorT,
class MatrixT>
355 void MPCNode::fill_bounds_objs(
358 const VectorT &X0,
const VectorT &X_ref,
const VectorT &U_ref,
359 const MatrixT &G,
const MatrixT &H,
const VectorT &D,
360 VectorT &lb, VectorT &ub, MatrixT &Ab)
363 VectorT a = VectorT::Constant(N, -bounds.
delta_m);
364 VectorT b = VectorT::Constant(N, bounds.
delta_m);
365 MatrixT Kb = MatrixT::Zero(N, n*N);
367 for (
int k = 0; k < N; k++) {
368 Kb(k, n-1 + k*n) = 1;
372 int m = bounds.
input.size();
375 for (
int k = 0; k < m; k++) {
376 c(k) = -bounds.
input[k];
377 d(k) = bounds.
input[k];
382 lb = VectorT::Zero((m+1)*N);
383 ub = VectorT::Zero((m+1)*N);
384 Ab = MatrixT::Zero((m+1)*N, m*N);
389 for (
int k = 0; k < N; k++) {
390 delta(k) = HX0((k+1)*n - 1) + X_ref((k+2)*n - 1) + D((k+1)*n - 1);
393 lb.block(0, 0, N, 1) = a - delta;
394 ub.block(0, 0, N, 1) = b - delta;
396 for (
int k = 0; k < N; k++) {
397 lb.block(N + k*m, 0, m, 1) = c - U_ref.block((k+1)*m, 0, m, 1);
398 ub.block(N + k*m, 0, m, 1) = d - U_ref.block((k+1)*m, 0, m, 1);
402 for (
int i = 0; i < N; i++) {
403 for (
int j = 0; j < m*N; j++) {
404 KbG(i, j) = G((i+1)*n - 1, j);
407 Ab.block(0, 0, N, m*N) = KbG;
408 Ab.block(N, 0, m*N, m*N) = MatrixT::Identity(m*N, m*N);
412 template <
class VectorT,
class T>
413 bool MPCNode::solve_qp(
414 const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> &
P,
418 const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> &Ab,
421 typedef Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> MatrixT;
427 OSQPSettings *
settings = (OSQPSettings *)c_malloc(
sizeof(OSQPSettings));
428 OSQPData *data = (OSQPData *)c_malloc(
sizeof(OSQPData));
432 MatrixT P_tri = P.template triangularView<Eigen::Upper>();
433 Eigen::SparseMatrix<T> _P = P_tri.sparseView();
434 Eigen::SparseMatrix<T> _A = Ab.sparseView();
441 OsqpEigen::SparseMatrixHelper::createOsqpSparseMatrix(_P, data->P);
442 data->q =
const_cast<T*
>(q.data());
443 OsqpEigen::SparseMatrixHelper::createOsqpSparseMatrix(_A, data->A);
444 data->l =
const_cast<T*
>(lb.data());
445 data->u =
const_cast<T*
>(ub.data());
450 osqp_set_default_settings(settings);
451 settings->alpha = 1.0;
452 settings->verbose =
false;
456 c_int setup_flag = osqp_setup(&work, data, settings);
458 if (setup_flag != 0) {
459 ROS_WARN(
"[mpc_node] Setup of MPC problem failed");
460 string flag_string =
"";
462 switch (setup_flag) {
463 case OSQP_DATA_VALIDATION_ERROR: flag_string =
"OSQP_DATA_VALIDATION_ERROR";
break;
464 case OSQP_SETTINGS_VALIDATION_ERROR: flag_string =
"OSQP_SETTINGS_VALIDATION_ERROR";
break;
465 case OSQP_LINSYS_SOLVER_LOAD_ERROR: flag_string =
"OSQP_LINSYS_SOLVER_LOAD_ERROR";
break;
466 case OSQP_LINSYS_SOLVER_INIT_ERROR: flag_string =
"OSQP_LINSYS_SOLVER_INIT_ERROR";
break;
467 case OSQP_NONCVX_ERROR: flag_string =
"OSQP_NONCVX_ERROR";
break;
468 case OSQP_MEM_ALLOC_ERROR: flag_string =
"OSQP_MEM_ALLOC_ERROR";
break;
469 case OSQP_WORKSPACE_NOT_INIT_ERROR: flag_string =
"OSQP_WORKSPACE_NOT_INIT_ERROR";
break;
472 ROS_WARN_STREAM(
"[mpc_node] " << flag_string);
473 qp_warm_start_ =
false;
478 qp_warm_start_ =
false;
480 osqp_warm_start(work, qp_last_primal_.data(), qp_last_dual_.data());
484 solve_ret = osqp_solve(work);
486 if (solve_ret != 0 || work->info->status_val != OSQP_SOLVED) {
487 ROS_WARN(
"[mpc_node] Couldn't solve MPC problem");
488 string flag_string =
"";
490 switch (work->info->status_val) {
491 case OSQP_DUAL_INFEASIBLE_INACCURATE: flag_string =
"OSQP_DUAL_INFEASIBLE_INACCURATE";
break;
492 case OSQP_PRIMAL_INFEASIBLE_INACCURATE: flag_string =
"OSQP_PRIMAL_INFEASIBLE_INACCURATE";
break;
493 case OSQP_SOLVED_INACCURATE: flag_string =
"OSQP_SOLVED_INACCURATE";
break;
494 case OSQP_SOLVED: flag_string =
"OSQP_SOLVED";
break;
495 case OSQP_MAX_ITER_REACHED: flag_string =
"OSQP_MAX_ITER_REACHED";
break;
496 case OSQP_PRIMAL_INFEASIBLE: flag_string =
"OSQP_PRIMAL_INFEASIBLE";
break;
497 case OSQP_DUAL_INFEASIBLE: flag_string =
"OSQP_DUAL_INFEASIBLE";
break;
498 case OSQP_SIGINT: flag_string =
"OSQP_SIGINT";
break;
499 case OSQP_NON_CVX: flag_string =
"OSQP_NON_CVX";
break;
500 case OSQP_UNSOLVED: flag_string =
"OSQP_UNSOLVED";
break;
503 ROS_WARN_STREAM(
"[mpc_node] " << flag_string);
504 qp_warm_start_ =
false;
509 solution = VectorT(n);
510 qp_last_primal_ = VectorT(n);
511 qp_last_dual_ = VectorT(n);
513 for (
int k = 0; k <
n; k++) {
514 solution(k) = work->solution->x[k];
515 qp_last_primal_(k) = work->solution->x[k];
516 qp_last_dual_(k) = work->solution->y[k];
519 qp_warm_start_ =
true;
523 if (data->A) c_free(data->A);
524 if (data->P) c_free(data->P);
527 if (settings) c_free(settings);
531 bool MPCNode::compute_control(
532 float &desired_speed,
533 float last_desired_speed,
535 geometry_msgs::PoseArray &expected_traj)
537 if (path_.poses.size() < 2) {
538 ROS_WARN(
"[mpc_node] Path size < 2");
543 geometry_msgs::Point current_position;
544 current_position.x = state_[0];
545 current_position.y = state_[1];
546 current_position.z = state_[2];
548 int n = state_.size();
549 int m = last_control_.size();
552 int size_path = path_.poses.size();
553 vector<geometry_msgs::Pose> orig_path(size_path);
555 for (
int k = 0; k < size_path; k++) {
556 orig_path[k] = path_.poses[k].pose;
560 float spatial_resolution = desired_speed * time_horizon_ / nbr_steps_;
561 float time_resolution = time_horizon_ / nbr_steps_;
563 vector<geometry_msgs::Pose> new_path = adapt_path(orig_path, current_position,
564 nbr_steps_, spatial_resolution, time_resolution, desired_speed);
567 VectorXf x0(n), X0(n);
570 for (
int k = 0; k <
n; k++) x0(k) = state_[k];
571 for (
int k = 0; k < m; k++) u_m1(k) = last_control_[k];
575 VectorXf X_ref, U_ref;
577 if (!fill_ref_pts(N, n, m, new_path, desired_speed, last_desired_speed, robot_model_,
580 ROS_WARN(
"[mpc_node] Reference trajectory could not be filled");
584 modulo_ref_state(X_ref, x0, n);
586 X0 = x0 - X_ref.block(0, 0, n, 1);
587 VectorXf x0_ref = X_ref.block(0, 0, n, 1);
588 VectorXf u0_ref = U_ref.block(0, 0, m, 1);
597 fill_L(tuning_params_.P, tuning_params_.Q_x, N, L);
598 fill_M(tuning_params_.R_u, tuning_params_.R_delta, N, M);
599 fill_V(u0_ref, u_m1, tuning_params_.R_delta, N, V);
603 fill_ltv_G_H_D(X_ref, U_ref, N, time_resolution, spatial_resolution, G, H, D);
608 robot_model_.get_lin_discr_matrices(x0_ref, u0_ref, Ad, Bd, time_resolution);
610 fill_lti_G(Ad, Bd, N, G);
611 fill_lti_H(Ad, N, H);
612 fill_lti_LG(G, tuning_params_.P, tuning_params_.Q_x, n, m, N, LG);
613 D = VectorXf::Zero(n*N);
620 fill_bounds_objs(bounds_, n, N, X0, X_ref, U_ref, G, H, D, lb, ub, Ab);
623 MatrixXf
P = 2*(G.transpose()*LG + M);
624 VectorXf q = 2*LG.transpose()*(H*X0 + D) + V;
627 bool solved = solve_qp(P, q, lb, ub, Ab, solution);
634 for (
int k = 0; k < m; k++)
635 command[k] = solution(k) + U_ref(k);
638 VectorXf X = G*solution + H*X0 + D + X_ref.block(n, 0, n*N, 1);
639 expected_traj.poses.resize(N);
641 for (
int k = 0; k < N; k++) {
642 geometry_msgs::Pose pose;
643 pose.position.x = X(k*n);
644 pose.position.y = X(k*n + 1);
645 pose.position.z = X(k*n + 2);
646 to_quaternion(X(k*n + 3), X(k*n + 4), X(k*n + 5), pose.orientation);
648 expected_traj.poses[k] = pose;
Declaration of a node for Model Predictive Control of an underwater robot.
void to_quaternion(double roll, double pitch, double yaw, tf2::Quaternion &quat)
Converts Roll-Pitch-Yaw Euler angles to a quaternion.
geometry_msgs::Pose interpolate(const geometry_msgs::Pose &pose1, const geometry_msgs::Pose &pose2, float t)
Interpolates linearily two poses.
std::vector< double > state_type
Data type of the state.
Declaration of common functions.
std::vector< double > input
float distance(const geometry_msgs::Point &p1, const geometry_msgs::Point &p2)
Computes the the euclidean distance between two positions.
Bounds on the MPC problem.
Class defining the robot model.
std::vector< double > input_type
Data type of the input.
float distance2(const geometry_msgs::Point &p1, const geometry_msgs::Point &p2)
Computes the square of the euclidean distance between two positions.
double steady_propeller_speed(double speed) const
Computes the propeller speed in steady state.
void to_euler(const tf2::Quaternion &quat, double &roll, double &pitch, double &yaw)
Converts a quaternion to Roll-Pitch-Yaw Euler angles.