123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809 |
- // Copyright 2018-2021 The Autoware Foundation
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- #include "mpc_lateral_controller/mpc.hpp"
- #include "interpolation/linear_interpolation.hpp"
- #include "motion_utils/trajectory/trajectory.hpp"
- #include "mpc_lateral_controller/mpc_utils.hpp"
- #include "tier4_autoware_utils/math/unit_conversion.hpp"
- #include <algorithm>
- #include <limits>
- namespace autoware::motion::control::mpc_lateral_controller
- {
- using tier4_autoware_utils::calcDistance2d;
- using tier4_autoware_utils::normalizeRadian;
- using tier4_autoware_utils::rad2deg;
- bool MPC::calculateMPC(
- const SteeringReport & current_steer, const Odometry & current_kinematics,
- AckermannLateralCommand & ctrl_cmd, Trajectory & predicted_trajectory,
- Float32MultiArrayStamped & diagnostic)
- {
- // since the reference trajectory does not take into account the current velocity of the ego
- // vehicle, it needs to calculate the trajectory velocity considering the longitudinal dynamics.
- const auto reference_trajectory =
- applyVelocityDynamicsFilter(m_reference_trajectory, current_kinematics);
- // get the necessary data
- const auto [success_data, mpc_data] =
- getData(reference_trajectory, current_steer, current_kinematics);
- if (!success_data) {
- return fail_warn_throttle("fail to get MPC Data. Stop MPC.");
- }
- // calculate initial state of the error dynamics
- const auto x0 = getInitialState(mpc_data);
- // apply time delay compensation to the initial state
- const auto [success_delay, x0_delayed] =
- updateStateForDelayCompensation(reference_trajectory, mpc_data.nearest_time, x0);
- if (!success_delay) {
- return fail_warn_throttle("delay compensation failed. Stop MPC.");
- }
- // resample reference trajectory with mpc sampling time
- const double mpc_start_time = mpc_data.nearest_time + m_param.input_delay;
- const double prediction_dt =
- getPredictionDeltaTime(mpc_start_time, reference_trajectory, current_kinematics);
- const auto [success_resample, mpc_resampled_ref_trajectory] =
- resampleMPCTrajectoryByTime(mpc_start_time, prediction_dt, reference_trajectory);
- if (!success_resample) {
- return fail_warn_throttle("trajectory resampling failed. Stop MPC.");
- }
- // generate mpc matrix : predict equation Xec = Aex * x0 + Bex * Uex + Wex
- const auto mpc_matrix = generateMPCMatrix(mpc_resampled_ref_trajectory, prediction_dt);
- // solve Optimization problem
- const auto [success_opt, Uex] = executeOptimization(
- mpc_matrix, x0_delayed, prediction_dt, mpc_resampled_ref_trajectory,
- current_kinematics.twist.twist.linear.x);
- if (!success_opt) {
- return fail_warn_throttle("optimization failed. Stop MPC.");
- }
- // apply filters for the input limitation and low pass filter
- const double u_saturated = std::clamp(Uex(0), -m_steer_lim, m_steer_lim);
- const double u_filtered = m_lpf_steering_cmd.filter(u_saturated);
- // set control command
- ctrl_cmd.steering_tire_angle = static_cast<float>(u_filtered);
- ctrl_cmd.steering_tire_rotation_rate = static_cast<float>(calcDesiredSteeringRate(
- mpc_matrix, x0_delayed, Uex, u_filtered, current_steer.steering_tire_angle, prediction_dt));
- // save the control command for the steering prediction
- m_steering_predictor->storeSteerCmd(u_filtered);
- // save input to buffer for delay compensation
- m_input_buffer.push_back(ctrl_cmd.steering_tire_angle);
- m_input_buffer.pop_front();
- // save previous input for the mpc rate limit
- m_raw_steer_cmd_pprev = m_raw_steer_cmd_prev;
- m_raw_steer_cmd_prev = Uex(0);
- // calculate predicted trajectory
- predicted_trajectory =
- calcPredictedTrajectory(mpc_resampled_ref_trajectory, mpc_matrix, x0_delayed, Uex);
- // prepare diagnostic message
- diagnostic =
- generateDiagData(reference_trajectory, mpc_data, mpc_matrix, ctrl_cmd, Uex, current_kinematics);
- return true;
- }
- Trajectory MPC::calcPredictedTrajectory(
- const MPCTrajectory & mpc_resampled_ref_trajectory, const MPCMatrix & mpc_matrix,
- const VectorXd & x0_delayed, const VectorXd & Uex) const
- {
- const VectorXd Xex = mpc_matrix.Aex * x0_delayed + mpc_matrix.Bex * Uex + mpc_matrix.Wex;
- MPCTrajectory mpc_predicted_traj;
- const auto & traj = mpc_resampled_ref_trajectory;
- for (int i = 0; i < m_param.prediction_horizon; ++i) {
- const int DIM_X = m_vehicle_model_ptr->getDimX();
- const double lat_error = Xex(i * DIM_X);
- const double yaw_error = Xex(i * DIM_X + 1);
- const double x = traj.x.at(i) - std::sin(traj.yaw.at(i)) * lat_error;
- const double y = traj.y.at(i) + std::cos(traj.yaw.at(i)) * lat_error;
- const double z = traj.z.at(i);
- const double yaw = traj.yaw.at(i) + yaw_error;
- const double vx = traj.vx.at(i);
- const double k = traj.k.at(i);
- const double smooth_k = traj.smooth_k.at(i);
- const double relative_time = traj.relative_time.at(i);
- mpc_predicted_traj.push_back(x, y, z, yaw, vx, k, smooth_k, relative_time);
- }
- return MPCUtils::convertToAutowareTrajectory(mpc_predicted_traj);
- }
- Float32MultiArrayStamped MPC::generateDiagData(
- const MPCTrajectory & reference_trajectory, const MPCData & mpc_data,
- const MPCMatrix & mpc_matrix, const AckermannLateralCommand & ctrl_cmd, const VectorXd & Uex,
- const Odometry & current_kinematics) const
- {
- Float32MultiArrayStamped diagnostic;
- // prepare diagnostic message
- const double nearest_k = reference_trajectory.k.at(mpc_data.nearest_idx);
- const double nearest_smooth_k = reference_trajectory.smooth_k.at(mpc_data.nearest_idx);
- const double wb = m_vehicle_model_ptr->getWheelbase();
- const double current_velocity = current_kinematics.twist.twist.linear.x;
- const double wz_predicted = current_velocity * std::tan(mpc_data.predicted_steer) / wb;
- const double wz_measured = current_velocity * std::tan(mpc_data.steer) / wb;
- const double wz_command = current_velocity * std::tan(ctrl_cmd.steering_tire_angle) / wb;
- typedef decltype(diagnostic.data)::value_type DiagnosticValueType;
- const auto append_diag = [&](const auto & val) -> void {
- diagnostic.data.push_back(static_cast<DiagnosticValueType>(val));
- };
- append_diag(ctrl_cmd.steering_tire_angle); // [0] final steering command (MPC + LPF)
- append_diag(Uex(0)); // [1] mpc calculation result
- append_diag(mpc_matrix.Uref_ex(0)); // [2] feed-forward steering value
- append_diag(std::atan(nearest_smooth_k * wb)); // [3] feed-forward steering value raw
- append_diag(mpc_data.steer); // [4] current steering angle
- append_diag(mpc_data.lateral_err); // [5] lateral error
- append_diag(tf2::getYaw(current_kinematics.pose.pose.orientation)); // [6] current_pose yaw
- append_diag(tf2::getYaw(mpc_data.nearest_pose.orientation)); // [7] nearest_pose yaw
- append_diag(mpc_data.yaw_err); // [8] yaw error
- append_diag(reference_trajectory.vx.at(mpc_data.nearest_idx)); // [9] reference velocity
- append_diag(current_velocity); // [10] measured velocity
- append_diag(wz_command); // [11] angular velocity from steer command
- append_diag(wz_measured); // [12] angular velocity from measured steer
- append_diag(current_velocity * nearest_smooth_k); // [13] angular velocity from path curvature
- append_diag(nearest_smooth_k); // [14] nearest path curvature (used for feed-forward)
- append_diag(nearest_k); // [15] nearest path curvature (not smoothed)
- append_diag(mpc_data.predicted_steer); // [16] predicted steer
- append_diag(wz_predicted); // [17] angular velocity from predicted steer
- return diagnostic;
- }
- void MPC::setReferenceTrajectory(
- const Trajectory & trajectory_msg, const TrajectoryFilteringParam & param,
- const Odometry & current_kinematics)
- {
- const size_t nearest_seg_idx = motion_utils::findFirstNearestSegmentIndexWithSoftConstraints(
- trajectory_msg.points, current_kinematics.pose.pose, ego_nearest_dist_threshold,
- ego_nearest_yaw_threshold);
- const double ego_offset_to_segment = motion_utils::calcLongitudinalOffsetToSegment(
- trajectory_msg.points, nearest_seg_idx, current_kinematics.pose.pose.position);
- const auto mpc_traj_raw = MPCUtils::convertToMPCTrajectory(trajectory_msg);
- // resampling
- const auto [success_resample, mpc_traj_resampled] = MPCUtils::resampleMPCTrajectoryByDistance(
- mpc_traj_raw, param.traj_resample_dist, nearest_seg_idx, ego_offset_to_segment);
- if (!success_resample) {
- warn_throttle("[setReferenceTrajectory] spline error when resampling by distance");
- return;
- }
- const auto is_forward_shift =
- motion_utils::isDrivingForward(mpc_traj_resampled.toTrajectoryPoints());
- // if driving direction is unknown, use previous value
- m_is_forward_shift = is_forward_shift ? is_forward_shift.get() : m_is_forward_shift;
- // path smoothing
- MPCTrajectory mpc_traj_smoothed = mpc_traj_resampled; // smooth filtered trajectory
- const int mpc_traj_resampled_size = static_cast<int>(mpc_traj_resampled.size());
- if (
- param.enable_path_smoothing && mpc_traj_resampled_size > 2 * param.path_filter_moving_ave_num) {
- using MoveAverageFilter::filt_vector;
- if (
- !filt_vector(param.path_filter_moving_ave_num, mpc_traj_smoothed.x) ||
- !filt_vector(param.path_filter_moving_ave_num, mpc_traj_smoothed.y) ||
- !filt_vector(param.path_filter_moving_ave_num, mpc_traj_smoothed.yaw) ||
- !filt_vector(param.path_filter_moving_ave_num, mpc_traj_smoothed.vx)) {
- RCLCPP_DEBUG(m_logger, "path callback: filtering error. stop filtering.");
- mpc_traj_smoothed = mpc_traj_resampled;
- }
- }
- /*
- * Extend terminal points
- * Note: The current MPC does not properly take into account the attitude angle at the end of the
- * path. By extending the end of the path in the attitude direction, the MPC can consider the
- * attitude angle well, resulting in improved control performance. If the trajectory is
- * well-defined considering the end point attitude angle, this feature is not necessary.
- */
- if (param.extend_trajectory_for_end_yaw_control) {
- MPCUtils::extendTrajectoryInYawDirection(
- mpc_traj_raw.yaw.back(), param.traj_resample_dist, m_is_forward_shift, mpc_traj_smoothed);
- }
- // calculate yaw angle
- MPCUtils::calcTrajectoryYawFromXY(mpc_traj_smoothed, m_is_forward_shift);
- MPCUtils::convertEulerAngleToMonotonic(mpc_traj_smoothed.yaw);
- // calculate curvature
- MPCUtils::calcTrajectoryCurvature(
- param.curvature_smoothing_num_traj, param.curvature_smoothing_num_ref_steer, mpc_traj_smoothed);
- // stop velocity at a terminal point
- mpc_traj_smoothed.vx.back() = 0.0;
- // add a extra point on back with extended time to make the mpc stable.
- auto last_point = mpc_traj_smoothed.back();
- last_point.relative_time += 100.0; // extra time to prevent mpc calc failure due to short time
- last_point.vx = 0.0; // stop velocity at a terminal point
- mpc_traj_smoothed.push_back(last_point);
- if (!mpc_traj_smoothed.size()) {
- RCLCPP_DEBUG(m_logger, "path callback: trajectory size is undesired.");
- return;
- }
- m_reference_trajectory = mpc_traj_smoothed;
- }
- void MPC::resetPrevResult(const SteeringReport & current_steer)
- {
- // Consider limit. The prev value larger than limitation brakes the optimization constraint and
- // results in optimization failure.
- const float steer_lim_f = static_cast<float>(m_steer_lim);
- m_raw_steer_cmd_prev = std::clamp(current_steer.steering_tire_angle, -steer_lim_f, steer_lim_f);
- m_raw_steer_cmd_pprev = std::clamp(current_steer.steering_tire_angle, -steer_lim_f, steer_lim_f);
- }
- std::pair<bool, MPCData> MPC::getData(
- const MPCTrajectory & traj, const SteeringReport & current_steer,
- const Odometry & current_kinematics)
- {
- const auto current_pose = current_kinematics.pose.pose;
- MPCData data;
- if (!MPCUtils::calcNearestPoseInterp(
- traj, current_pose, &(data.nearest_pose), &(data.nearest_idx), &(data.nearest_time),
- ego_nearest_dist_threshold, ego_nearest_yaw_threshold)) {
- warn_throttle("calculateMPC: error in calculating nearest pose. stop mpc.");
- return {false, MPCData{}};
- }
- // get data
- data.steer = static_cast<double>(current_steer.steering_tire_angle);
- data.lateral_err = MPCUtils::calcLateralError(current_pose, data.nearest_pose);
- data.yaw_err = normalizeRadian(
- tf2::getYaw(current_pose.orientation) - tf2::getYaw(data.nearest_pose.orientation));
- // get predicted steer
- data.predicted_steer = m_steering_predictor->calcSteerPrediction();
- // check error limit
- const double dist_err = calcDistance2d(current_pose, data.nearest_pose);
- if (dist_err > m_admissible_position_error) {
- warn_throttle("Too large position error: %fm > %fm", dist_err, m_admissible_position_error);
- return {false, MPCData{}};
- }
- // check yaw error limit
- if (std::fabs(data.yaw_err) > m_admissible_yaw_error_rad) {
- warn_throttle("Too large yaw error: %f > %f", data.yaw_err, m_admissible_yaw_error_rad);
- return {false, MPCData{}};
- }
- // check trajectory time length
- const double max_prediction_time =
- m_param.min_prediction_length / static_cast<double>(m_param.prediction_horizon - 1);
- auto end_time = data.nearest_time + m_param.input_delay + m_ctrl_period + max_prediction_time;
- if (end_time > traj.relative_time.back()) {
- warn_throttle("path is too short for prediction.");
- return {false, MPCData{}};
- }
- return {true, data};
- }
- std::pair<bool, MPCTrajectory> MPC::resampleMPCTrajectoryByTime(
- const double ts, const double prediction_dt, const MPCTrajectory & input) const
- {
- MPCTrajectory output;
- std::vector<double> mpc_time_v;
- for (double i = 0; i < static_cast<double>(m_param.prediction_horizon); ++i) {
- mpc_time_v.push_back(ts + i * prediction_dt);
- }
- if (!MPCUtils::linearInterpMPCTrajectory(input.relative_time, input, mpc_time_v, output)) {
- warn_throttle("calculateMPC: mpc resample error. stop mpc calculation. check code!");
- return {false, {}};
- }
- return {true, output};
- }
- VectorXd MPC::getInitialState(const MPCData & data)
- {
- const int DIM_X = m_vehicle_model_ptr->getDimX();
- VectorXd x0 = VectorXd::Zero(DIM_X);
- const auto & lat_err = data.lateral_err;
- const auto & steer = m_use_steer_prediction ? data.predicted_steer : data.steer;
- const auto & yaw_err = data.yaw_err;
- const auto vehicle_model = m_vehicle_model_ptr->modelName();
- if (vehicle_model == "kinematics") {
- x0 << lat_err, yaw_err, steer;
- } else if (vehicle_model == "kinematics_no_delay") {
- x0 << lat_err, yaw_err;
- } else if (vehicle_model == "dynamics") {
- double dlat = (lat_err - m_lateral_error_prev) / m_ctrl_period;
- double dyaw = (yaw_err - m_yaw_error_prev) / m_ctrl_period;
- m_lateral_error_prev = lat_err;
- m_yaw_error_prev = yaw_err;
- dlat = m_lpf_lateral_error.filter(dlat);
- dyaw = m_lpf_yaw_error.filter(dyaw);
- x0 << lat_err, dlat, yaw_err, dyaw;
- RCLCPP_DEBUG(m_logger, "(before lpf) dot_lat_err = %f, dot_yaw_err = %f", dlat, dyaw);
- RCLCPP_DEBUG(m_logger, "(after lpf) dot_lat_err = %f, dot_yaw_err = %f", dlat, dyaw);
- } else {
- RCLCPP_ERROR(m_logger, "vehicle_model_type is undefined");
- }
- return x0;
- }
- std::pair<bool, VectorXd> MPC::updateStateForDelayCompensation(
- const MPCTrajectory & traj, const double & start_time, const VectorXd & x0_orig)
- {
- const int DIM_X = m_vehicle_model_ptr->getDimX();
- const int DIM_U = m_vehicle_model_ptr->getDimU();
- const int DIM_Y = m_vehicle_model_ptr->getDimY();
- MatrixXd Ad(DIM_X, DIM_X);
- MatrixXd Bd(DIM_X, DIM_U);
- MatrixXd Wd(DIM_X, 1);
- MatrixXd Cd(DIM_Y, DIM_X);
- MatrixXd x_curr = x0_orig;
- double mpc_curr_time = start_time;
- for (size_t i = 0; i < m_input_buffer.size(); ++i) {
- double k, v = 0.0;
- try {
- k = interpolation::lerp(traj.relative_time, traj.k, mpc_curr_time);
- v = interpolation::lerp(traj.relative_time, traj.vx, mpc_curr_time);
- } catch (const std::exception & e) {
- RCLCPP_ERROR(m_logger, "mpc resample failed at delay compensation, stop mpc: %s", e.what());
- return {false, {}};
- }
- // get discrete state matrix A, B, C, W
- m_vehicle_model_ptr->setVelocity(v);
- m_vehicle_model_ptr->setCurvature(k);
- m_vehicle_model_ptr->calculateDiscreteMatrix(Ad, Bd, Cd, Wd, m_ctrl_period);
- MatrixXd ud = MatrixXd::Zero(DIM_U, 1);
- ud(0, 0) = m_input_buffer.at(i); // for steering input delay
- x_curr = Ad * x_curr + Bd * ud + Wd;
- mpc_curr_time += m_ctrl_period;
- }
- return {true, x_curr};
- }
- MPCTrajectory MPC::applyVelocityDynamicsFilter(
- const MPCTrajectory & input, const Odometry & current_kinematics) const
- {
- const auto autoware_traj = MPCUtils::convertToAutowareTrajectory(input);
- if (autoware_traj.points.empty()) {
- return input;
- }
- const size_t nearest_seg_idx = motion_utils::findFirstNearestSegmentIndexWithSoftConstraints(
- autoware_traj.points, current_kinematics.pose.pose, ego_nearest_dist_threshold,
- ego_nearest_yaw_threshold);
- MPCTrajectory output = input;
- MPCUtils::dynamicSmoothingVelocity(
- nearest_seg_idx, current_kinematics.twist.twist.linear.x, m_param.acceleration_limit,
- m_param.velocity_time_constant, output);
- auto last_point = output.back();
- last_point.relative_time += 100.0; // extra time to prevent mpc calc failure due to short time
- last_point.vx = 0.0; // stop velocity at a terminal point
- output.push_back(last_point);
- return output;
- }
- /*
- * predict equation: Xec = Aex * x0 + Bex * Uex + Wex
- * cost function: J = Xex' * Qex * Xex + (Uex - Uref)' * R1ex * (Uex - Uref_ex) + Uex' * R2ex * Uex
- * Qex = diag([Q,Q,...]), R1ex = diag([R,R,...])
- */
- MPCMatrix MPC::generateMPCMatrix(
- const MPCTrajectory & reference_trajectory, const double prediction_dt)
- {
- const int N = m_param.prediction_horizon;
- const double DT = prediction_dt;
- const int DIM_X = m_vehicle_model_ptr->getDimX();
- const int DIM_U = m_vehicle_model_ptr->getDimU();
- const int DIM_Y = m_vehicle_model_ptr->getDimY();
- MPCMatrix m;
- m.Aex = MatrixXd::Zero(DIM_X * N, DIM_X);
- m.Bex = MatrixXd::Zero(DIM_X * N, DIM_U * N);
- m.Wex = MatrixXd::Zero(DIM_X * N, 1);
- m.Cex = MatrixXd::Zero(DIM_Y * N, DIM_X * N);
- m.Qex = MatrixXd::Zero(DIM_Y * N, DIM_Y * N);
- m.R1ex = MatrixXd::Zero(DIM_U * N, DIM_U * N);
- m.R2ex = MatrixXd::Zero(DIM_U * N, DIM_U * N);
- m.Uref_ex = MatrixXd::Zero(DIM_U * N, 1);
- // weight matrix depends on the vehicle model
- MatrixXd Q = MatrixXd::Zero(DIM_Y, DIM_Y);
- MatrixXd R = MatrixXd::Zero(DIM_U, DIM_U);
- MatrixXd Q_adaptive = MatrixXd::Zero(DIM_Y, DIM_Y);
- MatrixXd R_adaptive = MatrixXd::Zero(DIM_U, DIM_U);
- MatrixXd Ad(DIM_X, DIM_X);
- MatrixXd Bd(DIM_X, DIM_U);
- MatrixXd Wd(DIM_X, 1);
- MatrixXd Cd(DIM_Y, DIM_X);
- MatrixXd Uref(DIM_U, 1);
- const double sign_vx = m_is_forward_shift ? 1 : -1;
- // predict dynamics for N times
- for (int i = 0; i < N; ++i) {
- const double ref_vx = reference_trajectory.vx.at(i);
- const double ref_vx_squared = ref_vx * ref_vx;
- const double ref_k = reference_trajectory.k.at(i) * sign_vx;
- const double ref_smooth_k = reference_trajectory.smooth_k.at(i) * sign_vx;
- // get discrete state matrix A, B, C, W
- m_vehicle_model_ptr->setVelocity(ref_vx);
- m_vehicle_model_ptr->setCurvature(ref_k);
- m_vehicle_model_ptr->calculateDiscreteMatrix(Ad, Bd, Cd, Wd, DT);
- Q = MatrixXd::Zero(DIM_Y, DIM_Y);
- R = MatrixXd::Zero(DIM_U, DIM_U);
- const auto mpc_weight = getWeight(ref_k);
- Q(0, 0) = mpc_weight.lat_error;
- Q(1, 1) = mpc_weight.heading_error;
- R(0, 0) = mpc_weight.steering_input;
- Q_adaptive = Q;
- R_adaptive = R;
- if (i == N - 1) {
- Q_adaptive(0, 0) = m_param.nominal_weight.terminal_lat_error;
- Q_adaptive(1, 1) = m_param.nominal_weight.terminal_heading_error;
- }
- Q_adaptive(1, 1) += ref_vx_squared * mpc_weight.heading_error_squared_vel;
- R_adaptive(0, 0) += ref_vx_squared * mpc_weight.steering_input_squared_vel;
- // update mpc matrix
- int idx_x_i = i * DIM_X;
- int idx_x_i_prev = (i - 1) * DIM_X;
- int idx_u_i = i * DIM_U;
- int idx_y_i = i * DIM_Y;
- if (i == 0) {
- m.Aex.block(0, 0, DIM_X, DIM_X) = Ad;
- m.Bex.block(0, 0, DIM_X, DIM_U) = Bd;
- m.Wex.block(0, 0, DIM_X, 1) = Wd;
- } else {
- m.Aex.block(idx_x_i, 0, DIM_X, DIM_X) = Ad * m.Aex.block(idx_x_i_prev, 0, DIM_X, DIM_X);
- for (int j = 0; j < i; ++j) {
- int idx_u_j = j * DIM_U;
- m.Bex.block(idx_x_i, idx_u_j, DIM_X, DIM_U) =
- Ad * m.Bex.block(idx_x_i_prev, idx_u_j, DIM_X, DIM_U);
- }
- m.Wex.block(idx_x_i, 0, DIM_X, 1) = Ad * m.Wex.block(idx_x_i_prev, 0, DIM_X, 1) + Wd;
- }
- m.Bex.block(idx_x_i, idx_u_i, DIM_X, DIM_U) = Bd;
- m.Cex.block(idx_y_i, idx_x_i, DIM_Y, DIM_X) = Cd;
- m.Qex.block(idx_y_i, idx_y_i, DIM_Y, DIM_Y) = Q_adaptive;
- m.R1ex.block(idx_u_i, idx_u_i, DIM_U, DIM_U) = R_adaptive;
- // get reference input (feed-forward)
- m_vehicle_model_ptr->setCurvature(ref_smooth_k);
- m_vehicle_model_ptr->calculateReferenceInput(Uref);
- if (std::fabs(Uref(0, 0)) < tier4_autoware_utils::deg2rad(m_param.zero_ff_steer_deg)) {
- Uref(0, 0) = 0.0; // ignore curvature noise
- }
- m.Uref_ex.block(i * DIM_U, 0, DIM_U, 1) = Uref;
- }
- // add lateral jerk : weight for (v * {u(i) - u(i-1)} )^2
- for (int i = 0; i < N - 1; ++i) {
- const double ref_vx = reference_trajectory.vx.at(i);
- const double ref_k = reference_trajectory.k.at(i) * sign_vx;
- const double j = ref_vx * ref_vx * getWeight(ref_k).lat_jerk / (DT * DT);
- const Eigen::Matrix2d J = (Eigen::Matrix2d() << j, -j, -j, j).finished();
- m.R2ex.block(i, i, 2, 2) += J;
- }
- addSteerWeightR(prediction_dt, m.R1ex);
- return m;
- }
- /*
- * solve quadratic optimization.
- * cost function: J = Xex' * Qex * Xex + (Uex - Uref)' * R1ex * (Uex - Uref_ex) + Uex' * R2ex * Uex
- * , Qex = diag([Q,Q,...]), R1ex = diag([R,R,...])
- * constraint matrix : lb < U < ub, lbA < A*U < ubA
- * current considered constraint
- * - steering limit
- * - steering rate limit
- *
- * (1)lb < u < ub && (2)lbA < Au < ubA --> (3)[lb, lbA] < [I, A]u < [ub, ubA]
- * (1)lb < u < ub ...
- * [-u_lim] < [ u0 ] < [u_lim]
- * [-u_lim] < [ u1 ] < [u_lim]
- * ~~~
- * [-u_lim] < [ uN ] < [u_lim] (*N... DIM_U)
- * (2)lbA < Au < ubA ...
- * [prev_u0 - au_lim*ctp] < [ u0 ] < [prev_u0 + au_lim*ctp] (*ctp ... ctrl_period)
- * [ -au_lim * dt ] < [u1 - u0] < [ au_lim * dt ]
- * [ -au_lim * dt ] < [u2 - u1] < [ au_lim * dt ]
- * ~~~
- * [ -au_lim * dt ] < [uN-uN-1] < [ au_lim * dt ] (*N... DIM_U)
- */
- std::pair<bool, VectorXd> MPC::executeOptimization(
- const MPCMatrix & m, const VectorXd & x0, const double prediction_dt, const MPCTrajectory & traj,
- const double current_velocity)
- {
- VectorXd Uex;
- if (!isValid(m)) {
- warn_throttle("model matrix is invalid. stop MPC.");
- return {false, {}};
- }
- const int DIM_U_N = m_param.prediction_horizon * m_vehicle_model_ptr->getDimU();
- // cost function: 1/2 * Uex' * H * Uex + f' * Uex, H = B' * C' * Q * C * B + R
- const MatrixXd CB = m.Cex * m.Bex;
- const MatrixXd QCB = m.Qex * CB;
- // MatrixXd H = CB.transpose() * QCB + m.R1ex + m.R2ex; // This calculation is heavy. looking for
- // a good way. //NOLINT
- MatrixXd H = MatrixXd::Zero(DIM_U_N, DIM_U_N);
- H.triangularView<Eigen::Upper>() = CB.transpose() * QCB;
- H.triangularView<Eigen::Upper>() += m.R1ex + m.R2ex;
- H.triangularView<Eigen::Lower>() = H.transpose();
- MatrixXd f = (m.Cex * (m.Aex * x0 + m.Wex)).transpose() * QCB - m.Uref_ex.transpose() * m.R1ex;
- addSteerWeightF(prediction_dt, f);
- MatrixXd A = MatrixXd::Identity(DIM_U_N, DIM_U_N);
- for (int i = 1; i < DIM_U_N; i++) {
- A(i, i - 1) = -1.0;
- }
- // steering angle limit
- VectorXd lb = VectorXd::Constant(DIM_U_N, -m_steer_lim); // min steering angle
- VectorXd ub = VectorXd::Constant(DIM_U_N, m_steer_lim); // max steering angle
- // steering angle rate limit
- VectorXd steer_rate_limits = calcSteerRateLimitOnTrajectory(traj, current_velocity);
- VectorXd ubA = steer_rate_limits * prediction_dt;
- VectorXd lbA = -steer_rate_limits * prediction_dt;
- ubA(0) = m_raw_steer_cmd_prev + steer_rate_limits(0) * m_ctrl_period;
- lbA(0) = m_raw_steer_cmd_prev - steer_rate_limits(0) * m_ctrl_period;
- auto t_start = std::chrono::system_clock::now();
- bool solve_result = m_qpsolver_ptr->solve(H, f.transpose(), A, lb, ub, lbA, ubA, Uex);
- auto t_end = std::chrono::system_clock::now();
- if (!solve_result) {
- warn_throttle("qp solver error");
- return {false, {}};
- }
- {
- auto t = std::chrono::duration_cast<std::chrono::milliseconds>(t_end - t_start).count();
- RCLCPP_DEBUG(m_logger, "qp solver calculation time = %ld [ms]", t);
- }
- if (Uex.array().isNaN().any()) {
- warn_throttle("model Uex includes NaN, stop MPC.");
- return {false, {}};
- }
- return {true, Uex};
- }
- void MPC::addSteerWeightR(const double prediction_dt, MatrixXd & R) const
- {
- const int N = m_param.prediction_horizon;
- const double DT = prediction_dt;
- // add steering rate : weight for (u(i) - u(i-1) / dt )^2
- {
- const double steer_rate_r = m_param.nominal_weight.steer_rate / (DT * DT);
- const Eigen::Matrix2d D = steer_rate_r * (Eigen::Matrix2d() << 1.0, -1.0, -1.0, 1.0).finished();
- for (int i = 0; i < N - 1; ++i) {
- R.block(i, i, 2, 2) += D;
- }
- if (N > 1) {
- // steer rate i = 0
- R(0, 0) += m_param.nominal_weight.steer_rate / (m_ctrl_period * m_ctrl_period);
- }
- }
- // add steering acceleration : weight for { (u(i+1) - 2*u(i) + u(i-1)) / dt^2 }^2
- {
- const double w = m_param.nominal_weight.steer_acc;
- const double steer_acc_r = w / std::pow(DT, 4);
- const double steer_acc_r_cp1 = w / (std::pow(DT, 3) * m_ctrl_period);
- const double steer_acc_r_cp2 = w / (std::pow(DT, 2) * std::pow(m_ctrl_period, 2));
- const double steer_acc_r_cp4 = w / std::pow(m_ctrl_period, 4);
- const Eigen::Matrix3d D =
- steer_acc_r *
- (Eigen::Matrix3d() << 1.0, -2.0, 1.0, -2.0, 4.0, -2.0, 1.0, -2.0, 1.0).finished();
- for (int i = 1; i < N - 1; ++i) {
- R.block(i - 1, i - 1, 3, 3) += D;
- }
- if (N > 1) {
- // steer acc i = 1
- R(0, 0) += steer_acc_r * 1.0 + steer_acc_r_cp2 * 1.0 + steer_acc_r_cp1 * 2.0;
- R(1, 0) += steer_acc_r * -1.0 + steer_acc_r_cp1 * -1.0;
- R(0, 1) += steer_acc_r * -1.0 + steer_acc_r_cp1 * -1.0;
- R(1, 1) += steer_acc_r * 1.0;
- // steer acc i = 0
- R(0, 0) += steer_acc_r_cp4 * 1.0;
- }
- }
- }
- void MPC::addSteerWeightF(const double prediction_dt, MatrixXd & f) const
- {
- if (f.rows() < 2) {
- return;
- }
- const double DT = prediction_dt;
- // steer rate for i = 0
- f(0, 0) += -2.0 * m_param.nominal_weight.steer_rate / (std::pow(DT, 2)) * 0.5;
- // const double steer_acc_r = m_param.weight_steer_acc / std::pow(DT, 4);
- const double steer_acc_r_cp1 =
- m_param.nominal_weight.steer_acc / (std::pow(DT, 3) * m_ctrl_period);
- const double steer_acc_r_cp2 =
- m_param.nominal_weight.steer_acc / (std::pow(DT, 2) * std::pow(m_ctrl_period, 2));
- const double steer_acc_r_cp4 = m_param.nominal_weight.steer_acc / std::pow(m_ctrl_period, 4);
- // steer acc i = 0
- f(0, 0) += ((-2.0 * m_raw_steer_cmd_prev + m_raw_steer_cmd_pprev) * steer_acc_r_cp4) * 0.5;
- // steer acc for i = 1
- f(0, 0) += (-2.0 * m_raw_steer_cmd_prev * (steer_acc_r_cp1 + steer_acc_r_cp2)) * 0.5;
- f(0, 1) += (2.0 * m_raw_steer_cmd_prev * steer_acc_r_cp1) * 0.5;
- }
- double MPC::getPredictionDeltaTime(
- const double start_time, const MPCTrajectory & input, const Odometry & current_kinematics) const
- {
- // Calculate the time min_prediction_length ahead from current_pose
- const auto autoware_traj = MPCUtils::convertToAutowareTrajectory(input);
- const size_t nearest_idx = motion_utils::findFirstNearestIndexWithSoftConstraints(
- autoware_traj.points, current_kinematics.pose.pose, ego_nearest_dist_threshold,
- ego_nearest_yaw_threshold);
- double sum_dist = 0;
- const double target_time = [&]() {
- const double t_ext = 100.0; // extra time to prevent mpc calculation failure due to short time
- for (size_t i = nearest_idx + 1; i < input.relative_time.size(); i++) {
- const double segment_dist = MPCUtils::calcDistance2d(input, i, i - 1);
- sum_dist += segment_dist;
- if (m_param.min_prediction_length < sum_dist) {
- const double prev_sum_dist = sum_dist - segment_dist;
- const double ratio = (m_param.min_prediction_length - prev_sum_dist) / segment_dist;
- const double relative_time_at_i = i == input.relative_time.size() - 1
- ? input.relative_time.at(i) - t_ext
- : input.relative_time.at(i);
- return input.relative_time.at(i - 1) +
- (relative_time_at_i - input.relative_time.at(i - 1)) * ratio;
- }
- }
- return input.relative_time.back() - t_ext;
- }();
- // Calculate delta time for min_prediction_length
- const double dt =
- (target_time - start_time) / static_cast<double>(m_param.prediction_horizon - 1);
- return std::max(dt, m_param.prediction_dt);
- }
- double MPC::calcDesiredSteeringRate(
- const MPCMatrix & mpc_matrix, const MatrixXd & x0, const MatrixXd & Uex, const double u_filtered,
- const float current_steer, const double predict_dt) const
- {
- if (m_vehicle_model_ptr->modelName() != "kinematics") {
- // not supported yet. Use old implementation.
- return (u_filtered - current_steer) / predict_dt;
- }
- // calculate predicted states to get the steering motion
- const auto & m = mpc_matrix;
- const MatrixXd Xex = m.Aex * x0 + m.Bex * Uex + m.Wex;
- const size_t STEER_IDX = 2; // for kinematics model
- const auto steer_0 = x0(STEER_IDX, 0);
- const auto steer_1 = Xex(STEER_IDX, 0);
- const auto steer_rate = (steer_1 - steer_0) / predict_dt;
- return steer_rate;
- }
- VectorXd MPC::calcSteerRateLimitOnTrajectory(
- const MPCTrajectory & trajectory, const double current_velocity) const
- {
- const auto interp = [&](const auto & steer_rate_limit_map, const auto & current) {
- std::vector<double> reference, limits;
- for (const auto & p : steer_rate_limit_map) {
- reference.push_back(p.first);
- limits.push_back(p.second);
- }
- // If the speed is out of range of the reference, apply zero-order hold.
- if (current <= reference.front()) {
- return limits.front();
- }
- if (current >= reference.back()) {
- return limits.back();
- }
- // Apply linear interpolation
- for (size_t i = 0; i < reference.size() - 1; ++i) {
- if (reference.at(i) <= current && current <= reference.at(i + 1)) {
- auto ratio =
- (current - reference.at(i)) / std::max(reference.at(i + 1) - reference.at(i), 1.0e-5);
- ratio = std::clamp(ratio, 0.0, 1.0);
- const auto interp = limits.at(i) + ratio * (limits.at(i + 1) - limits.at(i));
- return interp;
- }
- }
- std::cerr << "MPC::calcSteerRateLimitOnTrajectory() interpolation logic is broken. Command "
- "filter is not working. Please check the code."
- << std::endl;
- return reference.back();
- };
- // when the vehicle is stopped, no steering rate limit.
- const bool is_vehicle_stopped = std::fabs(current_velocity) < 0.01;
- if (is_vehicle_stopped) {
- return VectorXd::Zero(m_param.prediction_horizon);
- }
- // calculate steering rate limit
- VectorXd steer_rate_limits = VectorXd::Zero(m_param.prediction_horizon);
- for (int i = 0; i < m_param.prediction_horizon; ++i) {
- const auto limit_by_curvature = interp(m_steer_rate_lim_map_by_curvature, trajectory.k.at(i));
- const auto limit_by_velocity = interp(m_steer_rate_lim_map_by_velocity, trajectory.vx.at(i));
- steer_rate_limits(i) = std::min(limit_by_curvature, limit_by_velocity);
- }
- return steer_rate_limits;
- }
- bool MPC::isValid(const MPCMatrix & m) const
- {
- if (
- m.Aex.array().isNaN().any() || m.Bex.array().isNaN().any() || m.Cex.array().isNaN().any() ||
- m.Wex.array().isNaN().any() || m.Qex.array().isNaN().any() || m.R1ex.array().isNaN().any() ||
- m.R2ex.array().isNaN().any() || m.Uref_ex.array().isNaN().any()) {
- return false;
- }
- if (
- m.Aex.array().isInf().any() || m.Bex.array().isInf().any() || m.Cex.array().isInf().any() ||
- m.Wex.array().isInf().any() || m.Qex.array().isInf().any() || m.R1ex.array().isInf().any() ||
- m.R2ex.array().isInf().any() || m.Uref_ex.array().isInf().any()) {
- return false;
- }
- return true;
- }
- } // namespace autoware::motion::control::mpc_lateral_controller
|