首页 > 编程语言 >【一看就会】Autoware.universe的控制部分源码梳理【二】

【一看就会】Autoware.universe的控制部分源码梳理【二】

时间:2025-01-06 17:57:51浏览次数:3  
标签:const mpc universe Autoware traj 源码 param double steer

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

上一篇讲完纵向速度控制了,这一篇讲横向速度控制的mpc。


一、autoware_mpc_lateral_controller文件架构

这个文件夹里面的文件很多。
mpc原理倒是不难,但是实现起来不容易,我们先将这些文件给梳理清楚。

autoware_mpc_lateral_controller文件夹中有6个cpp文件,还有三个文件夹。
分别是:

以下是对 `autoware_mpc_lateral_controller` 文件夹中这些文件的一个可能的解释:


1. **lowpass_filter.cpp**:
    - 低通滤波器的实现。
    - 低通滤波器通常用于信号处理,在车辆控制中可能用于对某些传感器信号(如速度、位置、角度等)进行滤波,以去除高频噪声。例如,对车辆的侧向速度或转向角的测量值进行滤波,使信号更加平滑,避免因传感器噪声导致的控制不稳定。

2. **mpc.cpp**:
    - 实现模型预测控制(Model Predictive Control,MPC)算法的主要文件。
    - MPC 是一种先进的控制算法,它基于系统的动态模型,在每个控制周期内求解一个有限时间范围内的优化问题,以找到最优控制序列。在车辆控制中,可能会根据车辆的状态(位置、速度、姿态等)、参考轨迹以及车辆动力学模型,预测未来一段时间内的系统行为,并优化控制输入(如转向角、加速度等),使车辆尽可能地跟随参考轨迹。

3. **mpc_lateral_controller.cpp**:
    - 横向控制器的实现文件,使用 MPC 算法进行车辆的横向控制。
    - 它会调用 `mpc.cpp` 中的 MPC 算法核心,并结合车辆的横向动力学模型,根据车辆的当前状态和期望的横向轨迹(如期望的路径或车道中心线),计算出最优的转向角控制输入,使车辆能够稳定地保持在期望的横向位置。

4. **mpc_trajectory.cpp**:
    - 负责处理 MPC 所需的轨迹信息。
    - 包括存储和管理参考轨迹,将参考轨迹转换为 MPC 算法所需的格式,例如将离散的路径点转换为多项式曲线或其他函数形式,以便 MPC 算法使用。它可能还会对轨迹进行预处理,如平滑、插值或根据车辆当前位置截取合适的轨迹段。

5. **mpc_utils.cpp**:
    - 包含一些 MPC 算法的辅助函数或工具函数。
    - 这些函数可能包括矩阵运算、约束的生成、目标函数的构建等。例如,在 MPC 求解过程中需要构建优化问题,可能会涉及到矩阵的构建、求逆、乘法等操作,这些操作可能会封装在 `mpc_utils.cpp` 中。


6. **steering_predictor**:
    - 可能用于预测车辆的转向行为。
    - 基于车辆的动力学模型和当前状态,预测未来一段时间内的转向角变化,为 MPC 算法提供更多的信息,或者用于评估当前控制输入的效果,以便进行更好的控制决策。

7. **qp_solver**文件夹:
    - 二次规划(Quadratic Programming,QP)求解器的实现或接口文件。
    - 由于 MPC 问题通常可以转化为一个二次规划问题,该文件提供了求解二次规划问题的能力,求解器将接收 MPC 问题的目标函数和约束条件(以矩阵和向量的形式),并计算出最优解。

8. **steering_offset**文件夹:
    - 转向偏移的处理。
    - 车辆的转向系统可能存在机械偏移,该文件可能包含用于校准或补偿转向偏移的功能。例如,根据车辆的校准数据或传感器反馈,对计算出的转向角进行偏移补偿,以确保实际转向与期望转向相符。


9. **vehicle_model**文件夹:
    - 包含车辆的动力学模型。
    - 车辆的动力学模型是 MPC 算法的基础,可能是一个简单的运动学模型(如阿克曼转向模型)或更复杂的动力学模型(考虑车辆的质量、惯性、轮胎力等)。这个文件会提供车辆状态更新方程,描述车辆如何根据控制输入(如转向角和加速度)和当前状态(如速度、位置、姿态)随时间变化。


**调用关系**:
- 当需要进行车辆的横向控制时,首先会调用 `mpc_lateral_controller.cpp`,它会从 `mpc_trajectory.cpp` 获取参考轨迹,从传感器或其他模块获取车辆的当前状态信息。
- `mpc_lateral_controller.cpp` 会使用 `vehicle_model` 中的车辆动力学模型,结合 `mpc.cpp` 中的 MPC 算法,构建 MPC 优化问题。
- 在构建 MPC 优化问题时,可能会调用 `mpc_utils.cpp` 中的辅助函数。
- 构建好的优化问题将传递给 `qp_solver` 进行求解,得到最优的控制输入(如转向角)。
- 得到的控制输入可能会经过 `steering_offset` 进行偏移补偿,最终输出到车辆的执行机构。


其中主文件为mpc_lateral_controller.cpp,我们继续往下说。

二、mpc_lateral_controller.cpp

1.mpc_lateral_controller.cpp源码注释

// Copyright 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 "autoware/mpc_lateral_controller/mpc_lateral_controller.hpp"

#include "autoware/motion_utils/trajectory/trajectory.hpp"
#include "autoware/mpc_lateral_controller/qp_solver/qp_solver_osqp.hpp"
#include "autoware/mpc_lateral_controller/qp_solver/qp_solver_unconstraint_fast.hpp"
#include "autoware/mpc_lateral_controller/vehicle_model/vehicle_model_bicycle_dynamics.hpp"
#include "autoware/mpc_lateral_controller/vehicle_model/vehicle_model_bicycle_kinematics.hpp"
#include "autoware/mpc_lateral_controller/vehicle_model/vehicle_model_bicycle_kinematics_no_delay.hpp"
#include "autoware_vehicle_info_utils/vehicle_info_utils.hpp"
#include "tf2/utils.h"
#include "tf2_ros/create_timer_ros.h"

#include <algorithm>
#include <deque>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>

namespace autoware::motion::control::mpc_lateral_controller
{

MpcLateralController::MpcLateralController(
  rclcpp::Node & node, std::shared_ptr<diagnostic_updater::Updater> diag_updater)
: clock_(node.get_clock()), logger_(node.get_logger().get_child("lateral_controller"))
{
    // 定义 lambda 函数,用于从节点获取 int 类型的参数
    const auto dp_int = [&](const std::string & s) { return node.declare_parameter<int>(s); };
    // 定义 lambda 函数,用于从节点获取 bool 类型的参数
    const auto dp_bool = [&](const std::string & s) { return node.declare_parameter<bool>(s); };
    // 定义 lambda 函数,用于从节点获取 double 类型的参数
    const auto dp_double = [&](const std::string & s) { return node.declare_parameter<double>(s); };

    // 存储诊断更新器的指针
    diag_updater_ = diag_updater;

    // 创建一个 MPC 对象的唯一指针
    m_mpc = std::make_unique<MPC>(node);

    // 从节点获取控制周期并设置 MPC 的控制周期
    m_mpc->m_ctrl_period = node.get_parameter("ctrl_period").as_double();

    // 轨迹过滤参数
    auto & p_filt = m_trajectory_filtering_param;
    // 是否启用路径平滑
    p_filt.enable_path_smoothing = dp_bool("enable_path_smoothing");
    // 路径平滑的移动平均点数
    p_filt.path_filter_moving_ave_num = dp_int("path_filter_moving_ave_num");
    // 轨迹曲率平滑的点数
    p_filt.curvature_smoothing_num_traj = dp_int("curvature_smoothing_num_traj");
    // 参考转向曲率平滑的点数
    p_filt.curvature_smoothing_num_ref_steer = dp_int("curvature_smoothing_num_ref_steer");
    // 轨迹重采样的距离
    p_filt.traj_resample_dist = dp_double("traj_resample_dist");
    // 是否为最终偏航控制扩展轨迹
    p_filt.extend_trajectory_for_end_yaw_control = dp_bool("extend_trajectory_for_end_yaw_control");

    // MPC 允许的位置误差
    m_mpc->m_admissible_position_error = dp_double("admissible_position_error");
    // MPC 允许的偏航误差(弧度)
    m_mpc->m_admissible_yaw_error_rad = dp_double("admissible_yaw_error_rad");
    // 是否使用转向预测
    m_mpc->m_use_steer_prediction = dp_bool("use_steer_prediction");
    // 车辆模型的转向时间常数
    m_mpc->m_param.steer_tau = dp_double("vehicle_model_steer_tau");

    // 停止状态参数
    // 进入停止状态的自车速度阈值
    m_stop_state_entry_ego_speed = dp_double("stop_state_entry_ego_speed");
    // 进入停止状态的目标速度阈值
    m_stop_state_entry_target_speed = dp_double("stop_state_entry_target_speed");
    // 收敛的转向角度阈值(弧度)
    m_converged_steer_rad = dp_double("converged_steer_rad");
    // 是否保持转向控制直到收敛
    m_keep_steer_control_until_converged = dp_bool("keep_steer_control_until_converged");
    // 新轨迹的持续时间(秒)
    m_new_traj_duration_time = dp_double("new_traj_duration_time");            
    // 新轨迹的结束距离(米)
    m_new_traj_end_dist = dp_double("new_traj_end_dist");                      
    // MPC 收敛阈值(弧度/秒)
    m_mpc_converged_threshold_rps = dp_double("mpc_converged_threshold_rps");  

    // MPC 参数
    // 获取车辆信息
    const auto vehicle_info = autoware::vehicle_info_utils::VehicleInfoUtils(node).getVehicleInfo();
    // 车辆的轴距
    const double wheelbase = vehicle_info.wheel_base_m;
    constexpr double deg2rad = static_cast<double>(M_PI) / 180.0;
    // 车辆的最大转向角限制
    m_mpc->m_steer_lim = vehicle_info.max_steer_angle_rad;

    // 根据曲率的转向速率限制
    const auto steer_rate_lim_dps_list_by_curvature =
        node.declare_parameter<std::vector<double>>("steer_rate_lim_dps_list_by_curvature");
    const auto curvature_list_for_steer_rate_lim =
        node.declare_parameter<std::vector<double>>("curvature_list_for_steer_rate_lim");
    for (size_t i = 0; i < steer_rate_lim_dps_list_by_curvature.size(); ++i) {
        // 将曲率映射到转向速率限制
        m_mpc->m_steer_rate_lim_map_by_curvature.emplace_back(
            curvature_list_for_steer_rate_lim.at(i),
            steer_rate_lim_dps_list_by_curvature.at(i) * deg2rad);
    }

    // 根据速度的转向速率限制
    const auto steer_rate_lim_dps_list_by_velocity =
        node.declare_parameter<std::vector<double>>("steer_rate_lim_dps_list_by_velocity");
    const auto velocity_list_for_steer_rate_lim =
        node.declare_parameter<std::vector<double>>("velocity_list_for_steer_rate_lim");
    for (size_t i = 0; i < steer_rate_lim_dps_list_by_velocity.size(); ++i) {
        // 将速度映射到转向速率限制
        m_mpc->m_steer_rate_lim_map_by_velocity.emplace_back(
            velocity_list_for_steer_rate_lim.at(i), steer_rate_lim_dps_list_by_velocity.at(i) * deg2rad);
    }

    // 车辆模型设置
    // 根据轴距、转向限制、转向时间常数和节点创建车辆模型
    auto vehicle_model_ptr =
        createVehicleModel(wheelbase, m_mpc->m_steer_lim, m_mpc->m_param.steer_tau, node);
    // 将车辆模型设置到 MPC 对象中
    m_mpc->setVehicleModel(vehicle_model_ptr);

    // QP 求解器设置
    // 根据节点创建 QP 求解器接口
    auto qpsolver_ptr = createQPSolverInterface(node);
    // 将 QP 求解器设置到 MPC 对象中
    m_mpc->setQPSolver(qpsolver_ptr);

    // 延迟补偿
    {
        // 从参数中获取输入延迟
        const double delay_tmp = dp_double("input_delay");
        // 计算延迟步数,基于控制周期
        const double delay_step = std::round(delay_tmp / m_mpc->m_ctrl_period);
        // 将输入延迟调整为控制周期的倍数
        m_mpc->m_param.input_delay = delay_step * m_mpc->m_ctrl_period;
        // 初始化输入缓冲区,用于延迟补偿
        m_mpc->m_input_buffer = std::deque<double>(static_cast<size_t>(delay_step), 0.0);
    }

    // 转向偏移补偿
    // 是否启用自动转向偏移消除
    enable_auto_steering_offset_removal_ =
        dp_bool("steering_offset.enable_auto_steering_offset_removal");
    // 创建转向偏移估计器
    steering_offset_ = createSteerOffsetEstimator(wheelbase, node);

    // 初始化低通滤波器
    {
        // 转向低通滤波器的截止频率
        const double steering_lpf_cutoff_hz = dp_double("steering_lpf_cutoff_hz");
        // 误差导数低通滤波器的截止频率
        const double error_deriv_lpf_cutoff_hz = dp_double("error_deriv_lpf_cutoff_hz");
        m_mpc->initializeLowPassFilters(steering_lpf_cutoff_hz, error_deriv_lpf_cutoff_hz);
    }

    // 自车最近索引搜索
    const auto check_and_get_param = [&](const auto & param) {
        return node.has_parameter(param)? node.get_parameter(param).as_double() : dp_double(param);
    };
    // 自车最近距离的阈值
    m_ego_nearest_dist_threshold = check_and_get_param("ego_nearest_dist_threshold");
    // 自车最近偏航的阈值
    m_ego_nearest_yaw_threshold = check_and_get_param("ego_nearest_yaw_threshold");
    // 将阈值设置到 MPC 对象中
    m_mpc->ego_nearest_dist_threshold = m_ego_nearest_dist_threshold;
    m_mpc->ego_nearest_yaw_threshold = m_ego_nearest_yaw_threshold;

    // 是否使用延迟初始状态
    m_mpc->m_use_delayed_initial_state = dp_bool("use_delayed_initial_state");

    // 是否发布调试轨迹
    m_mpc->m_publish_debug_trajectories = dp_bool("publish_debug_trajectories");

    // 创建发布者用于发布预测轨迹、调试值和转向偏移
    m_pub_predicted_traj = node.create_publisher<Trajectory>("~/output/predicted_trajectory", 1);
    m_pub_debug_values =
        node.create_publisher<Float32MultiArrayStamped>("~/output/lateral_diagnostic", 1);
    m_pub_steer_offset = node.create_publisher<Float32Stamped>("~/output/estimated_steer_offset", 1);

    // 声明 MPC 参数
    declareMPCparameters(node);

    // 参数更新回调设置
    using std::placeholders::_1;
    m_set_param_res =
        node.add_on_set_parameters_callback(std::bind(&MpcLateralController::paramCallback, this, _1));

    // 初始化 MPC 的转向预测器
    m_mpc->initializeSteeringPredictor();

    // 为 MPC 设置日志记录器
    m_mpc->setLogger(logger_);
    // 为 MPC 设置时钟
    m_mpc->setClock(clock_);

    // 配置诊断
    setupDiag();
}

2.mpc_lateral_controller.cpp解释

1.使用MPC进行横向控制,构建了 m_mpc 指针,然后进行mpc的调用。
2.调用mpc_trajectory.cpp,管理目标路径
3.调用vehicle_model文件,管理车辆模型
4.调用qp_solver文件,构建求解器。

三、mpc.cpp

先不说注释的效果怎么样,deepseek至少能将一篇800多行的代码完整的给注释下来。

1.mpc.cpp代码注释

// 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 "autoware/mpc_lateral_controller/mpc.hpp"

#include "autoware/interpolation/linear_interpolation.hpp"
#include "autoware/motion_utils/trajectory/trajectory.hpp"
#include "autoware/mpc_lateral_controller/mpc_utils.hpp"
#include "autoware/universe_utils/math/unit_conversion.hpp"
#include "rclcpp/rclcpp.hpp"

#include <algorithm>
#include <limits>

namespace autoware::motion::control::mpc_lateral_controller
{
using autoware::universe_utils::calcDistance2d;
using autoware::universe_utils::normalizeRadian;
using autoware::universe_utils::rad2deg;

// MPC构造函数,初始化发布器
MPC::MPC(rclcpp::Node & node)
{
  // 创建用于发布预测轨迹的发布器
  m_debug_frenet_predicted_trajectory_pub = node.create_publisher<Trajectory>(
    "~/debug/predicted_trajectory_in_frenet_coordinate", rclcpp::QoS(1));
  // 创建用于发布重采样参考轨迹的发布器
  m_debug_resampled_reference_trajectory_pub =
    node.create_publisher<Trajectory>("~/debug/resampled_reference_trajectory", rclcpp::QoS(1));
}

// MPC核心计算函数
bool MPC::calculateMPC(
  const SteeringReport & current_steer, const Odometry & current_kinematics, Lateral & ctrl_cmd,
  Trajectory & predicted_trajectory, Float32MultiArrayStamped & diagnostic)
{
  // 由于参考轨迹没有考虑当前车辆的速度,因此需要根据纵向动力学计算轨迹速度
  const auto reference_trajectory =
    applyVelocityDynamicsFilter(m_reference_trajectory, current_kinematics);

  // 获取必要的数据
  const auto [success_data, mpc_data] =
    getData(reference_trajectory, current_steer, current_kinematics);
  if (!success_data) {
    return fail_warn_throttle("获取MPC数据失败,停止MPC。");
  }

  // 计算误差动力学的初始状态
  const auto x0 = getInitialState(mpc_data);

  // 对初始状态进行时间延迟补偿
  const auto [success_delay, x0_delayed] =
    updateStateForDelayCompensation(reference_trajectory, mpc_data.nearest_time, x0);
  if (!success_delay) {
    return fail_warn_throttle("延迟补偿失败,停止MPC。");
  }

  // 使用MPC采样时间重采样参考轨迹
  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("轨迹重采样失败,停止MPC。");
  }

  // 生成MPC矩阵:预测方程 Xec = Aex * x0 + Bex * Uex + Wex
  const auto mpc_matrix = generateMPCMatrix(mpc_resampled_ref_trajectory, prediction_dt);

  // 求解优化问题
  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("优化失败,停止MPC。");
  }

  // 对输入进行限幅和低通滤波
  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);

  // 设置控制命令
  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));

  // 保存控制命令用于转向预测
  m_steering_predictor->storeSteerCmd(u_filtered);

  // 将输入保存到缓冲区以进行延迟补偿
  m_input_buffer.push_back(ctrl_cmd.steering_tire_angle);
  m_input_buffer.pop_front();

  // 保存前一次输入用于MPC速率限制
  m_raw_steer_cmd_pprev = m_raw_steer_cmd_prev;
  m_raw_steer_cmd_prev = Uex(0);

  /* 计算预测轨迹 */
  Eigen::VectorXd initial_state = m_use_delayed_initial_state ? x0_delayed : x0;
  predicted_trajectory = calculatePredictedTrajectory(
    mpc_matrix, initial_state, Uex, mpc_resampled_ref_trajectory, prediction_dt, "world");

  // 发布不同坐标系下的预测轨迹用于调试
  if (m_publish_debug_trajectories) {
    // 计算并发布Frenet坐标系下的预测轨迹
    auto predicted_trajectory_frenet = calculatePredictedTrajectory(
      mpc_matrix, initial_state, Uex, mpc_resampled_ref_trajectory, prediction_dt, "frenet");
    predicted_trajectory_frenet.header.stamp = m_clock->now();
    predicted_trajectory_frenet.header.frame_id = "map";
    m_debug_frenet_predicted_trajectory_pub->publish(predicted_trajectory_frenet);
  }

  // 准备诊断消息
  diagnostic =
    generateDiagData(reference_trajectory, mpc_data, mpc_matrix, ctrl_cmd, Uex, current_kinematics);

  return true;
}

// 生成诊断数据
Float32MultiArrayStamped MPC::generateDiagData(
  const MPCTrajectory & reference_trajectory, const MPCData & mpc_data,
  const MPCMatrix & mpc_matrix, const Lateral & ctrl_cmd, const VectorXd & Uex,
  const Odometry & current_kinematics) const
{
  Float32MultiArrayStamped diagnostic;

  // 准备诊断消息
  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;
  const int iteration_num = m_qpsolver_ptr->getTakenIter();
  const double runtime = m_qpsolver_ptr->getRunTime();
  const double objective_value = m_qpsolver_ptr->getObjVal();

  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] 最终转向命令(MPC + 低通滤波)
  append_diag(Uex(0));                            // [1] MPC计算结果
  append_diag(mpc_matrix.Uref_ex(0));             // [2] 前馈转向值
  append_diag(std::atan(nearest_smooth_k * wb));  // [3] 原始前馈转向值
  append_diag(mpc_data.steer);                    // [4] 当前转向角
  append_diag(mpc_data.lateral_err);              // [5] 横向误差
  append_diag(tf2::getYaw(current_kinematics.pose.pose.orientation));  // [6] 当前姿态的偏航角
  append_diag(tf2::getYaw(mpc_data.nearest_pose.orientation));         // [7] 最近点的偏航角
  append_diag(mpc_data.yaw_err);                                       // [8] 偏航误差
  append_diag(reference_trajectory.vx.at(mpc_data.nearest_idx));       // [9] 参考速度
  append_diag(current_velocity);                                       // [10] 测量速度
  append_diag(wz_command);                           // [11] 转向命令产生的角速度
  append_diag(wz_measured);                          // [12] 测量转向产生的角速度
  append_diag(current_velocity * nearest_smooth_k);  // [13] 路径曲率产生的角速度
  append_diag(nearest_smooth_k);          // [14] 最近路径曲率(用于前馈)
  append_diag(nearest_k);                 // [15] 最近路径曲率(未平滑)
  append_diag(mpc_data.predicted_steer);  // [16] 预测转向
  append_diag(wz_predicted);              // [17] 预测转向产生的角速度
  append_diag(iteration_num);             // [18] 迭代次数
  append_diag(runtime);                   // [19] 最新问题的运行时间
  append_diag(objective_value);           // [20] 最新问题的目标值

  return diagnostic;
}

// 设置参考轨迹
void MPC::setReferenceTrajectory(
  const Trajectory & trajectory_msg, const TrajectoryFilteringParam & param,
  const Odometry & current_kinematics)
{
  const size_t nearest_seg_idx =
    autoware::motion_utils::findFirstNearestSegmentIndexWithSoftConstraints(
      trajectory_msg.points, current_kinematics.pose.pose, ego_nearest_dist_threshold,
      ego_nearest_yaw_threshold);
  const double ego_offset_to_segment = autoware::motion_utils::calcLongitudinalOffsetToSegment(
    trajectory_msg.points, nearest_seg_idx, current_kinematics.pose.pose.position);

  const auto mpc_traj_raw = MPCUtils::convertToMPCTrajectory(trajectory_msg);

  // 重采样
  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] 按距离重采样时样条错误");
    return;
  }

  const auto is_forward_shift =
    autoware::motion_utils::isDrivingForward(mpc_traj_resampled.toTrajectoryPoints());

  // 如果驾驶方向未知,使用之前的值
  m_is_forward_shift = is_forward_shift ? is_forward_shift.value() : m_is_forward_shift;

  // 路径平滑
  MPCTrajectory mpc_traj_smoothed = mpc_traj_resampled;  // 平滑滤波后的轨迹
  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, "路径回调:滤波错误,停止滤波。");
      mpc_traj_smoothed = mpc_traj_resampled;
    }
  }

  /*
   * 扩展终点
   * 注意:当前的MPC没有很好地考虑路径终点的姿态角。通过沿姿态方向扩展路径终点,MPC可以更好地考虑姿态角,从而提高控制性能。
   * 如果轨迹已经很好地考虑了终点姿态角,则不需要此功能。
   */
  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);
  }

  // 计算偏航角
  MPCUtils::calcTrajectoryYawFromXY(mpc_traj_smoothed, m_is_forward_shift);
  MPCUtils::convertEulerAngleToMonotonic(mpc_traj_smoothed.yaw);

  // 计算曲率
  MPCUtils::calcTrajectoryCurvature(
    param.curvature_smoothing_num_traj, param.curvature_smoothing_num_ref_steer, mpc_traj_smoothed);

  // 在终点停止速度
  mpc_traj_smoothed.vx.back() = 0.0;

  // 在末尾添加一个额外的时间点,以使MPC稳定
  auto last_point = mpc_traj_smoothed.back();
  last_point.relative_time += 100.0;  // 额外时间以防止由于时间过短导致MPC计算失败
  last_point.vx = 0.0;                // 在终点停止速度
  mpc_traj_smoothed.push_back(last_point);

  if (!mpc_traj_smoothed.size()) {
    RCLCPP_DEBUG(m_logger, "路径回调:轨迹大小不符合预期。");
    return;
  }

  m_reference_trajectory = mpc_traj_smoothed;
}

// 重置前一次结果
void MPC::resetPrevResult(const SteeringReport & current_steer)
{
  // 考虑限制。前一次值超过限制会破坏优化约束并导致优化失败。
  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);
}

// 获取MPC数据
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: 计算最近点时出错,停止MPC。");
    return {false, MPCData{}};
  }

  // 获取数据
  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));

  // 获取预测转向
  data.predicted_steer = m_steering_predictor->calcSteerPrediction();

  // 检查误差限制
  const double dist_err = calcDistance2d(current_pose, data.nearest_pose);
  if (dist_err > m_admissible_position_error) {
    warn_throttle("位置误差过大:%fm > %fm", dist_err, m_admissible_position_error);
    return {false, MPCData{}};
  }

  // 检查偏航误差限制
  if (std::fabs(data.yaw_err) > m_admissible_yaw_error_rad) {
    warn_throttle("偏航误差过大:%f > %f", data.yaw_err, m_admissible_yaw_error_rad);
    return {false, MPCData{}};
  }

  // 检查轨迹时间长度
  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("路径太短,无法进行预测。");
    return {false, MPCData{}};
  }
  return {true, data};
}

// 按时间重采样MPC轨迹
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重采样错误,停止MPC计算。检查代码!");
    return {false, {}};
  }
  // 发布重采样参考轨迹用于调试
  if (m_publish_debug_trajectories) {
    auto converted_output = MPCUtils::convertToAutowareTrajectory(output);
    converted_output.header.stamp = m_clock->now();
    converted_output.header.frame_id = "map";
    m_debug_resampled_reference_trajectory_pub->publish(converted_output);
  }
  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, "(低通滤波前) dot_lat_err = %f, dot_yaw_err = %f", dlat, dyaw);
    RCLCPP_DEBUG(m_logger, "(低通滤波后) dot_lat_err = %f, dot_yaw_err = %f", dlat, dyaw);
  } else {
    RCLCPP_ERROR(m_logger, "vehicle_model_type 未定义");
  }
  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);

  const double sign_vx = m_is_forward_shift ? 1 : -1;

  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 = autoware::interpolation::lerp(traj.relative_time, traj.k, mpc_curr_time) * sign_vx;
      v = autoware::interpolation::lerp(traj.relative_time, traj.vx, mpc_curr_time);
    } catch (const std::exception & e) {
      RCLCPP_ERROR(m_logger, "延迟补偿时MPC重采样失败,停止MPC:%s", e.what());
      return {false, {}};
    }

    // 获取离散状态矩阵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);  // 用于转向输入延迟
    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 =
    autoware::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;  // 额外时间以防止由于时间过短导致MPC计算失败
  last_point.vx = 0.0;                // 在终点停止速度
  output.push_back(last_point);
  return output;
}

/*
 * 预测方程: Xec = Aex * x0 + Bex * Uex + Wex
 * 代价函数: 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);

  // 权重矩阵取决于车辆模型
  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;

  // 预测N次动力学
  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;

    // 获取离散状态矩阵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;

    // 更新MPC矩阵
    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;

    // 获取参考输入(前馈)
    m_vehicle_model_ptr->setCurvature(ref_smooth_k);
    m_vehicle_model_ptr->calculateReferenceInput(Uref);
    if (std::fabs(Uref(0, 0)) < autoware::universe_utils::deg2rad(m_param.zero_ff_steer_deg)) {
      Uref(0, 0) = 0.0;  // 忽略曲率噪声
    }
    m.Uref_ex.block(i * DIM_U, 0, DIM_U, 1) = Uref;
  }

  // 添加横向加加速度:权重为 (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;
}

/*
 * 求解二次优化问题。
 * 代价函数: J = Xex' * Qex * Xex + (Uex - Uref)' * R1ex * (Uex - Uref_ex) + Uex' * R2ex * Uex
 *                , Qex = diag([Q,Q,...]), R1ex = diag([R,R,...])
 * 约束矩阵 : lb < U < ub, lbA < A*U < ubA
 * 当前考虑的约束
 *  - 转向限制
 *  - 转向速率限制
 *
 * (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("模型矩阵无效,停止MPC。");
    return {false, {}};
  }

  const int DIM_U_N = m_param.prediction_horizon * m_vehicle_model_ptr->getDimU();

  // 代价函数: 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; // 此计算较耗时,寻找更好的方法。  //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;
  }

  // 转向角限制
  VectorXd lb = VectorXd::Constant(DIM_U_N, -m_steer_lim);  // 最小转向角
  VectorXd ub = VectorXd::Constant(DIM_U_N, m_steer_lim);   // 最大转向角

  // 转向角速率限制
  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求解器错误");
    return {false, {}};
  }

  {
    auto t = std::chrono::duration_cast<std::chrono::milliseconds>(t_end - t_start).count();
    RCLCPP_DEBUG(m_logger, "QP求解器计算时间 = %ld [ms]", t);
  }

  if (Uex.array().isNaN().any()) {
    warn_throttle("模型Uex包含NaN,停止MPC。");
    return {false, {}};
  }
  return {true, Uex};
}

// 添加转向权重R
void MPC::addSteerWeightR(const double prediction_dt, MatrixXd & R) const
{
  const int N = m_param.prediction_horizon;
  const double DT = prediction_dt;

  // 添加转向速率:权重为 (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) {
      // 转向速率 i = 0
      R(0, 0) += m_param.nominal_weight.steer_rate / (m_ctrl_period * m_ctrl_period);
    }
  }

  // 添加转向加速度:权重为 { (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) {
      // 转向加速度 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;
      // 转向加速度 i = 0
      R(0, 0) += steer_acc_r_cp4 * 1.0;
    }
  }
}

// 添加转向权重F
void MPC::addSteerWeightF(const double prediction_dt, MatrixXd & f) const
{
  if (f.rows() < 2) {
    return;
  }

  const double DT = prediction_dt;

  // 转向速率 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);

  // 转向加速度 i = 0
  f(0, 0) += ((-2.0 * m_raw_steer_cmd_prev + m_raw_steer_cmd_pprev) * steer_acc_r_cp4) * 0.5;

  // 转向加速度 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
{
  // 计算从当前姿态向前min_prediction_length的时间
  const auto autoware_traj = MPCUtils::convertToAutowareTrajectory(input);
  const size_t nearest_idx = autoware::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;  // 额外时间以防止由于时间过短导致MPC计算失败
    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;
  }();

  // 计算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") {
    // 尚未支持,使用旧实现。
    return (u_filtered - current_steer) / predict_dt;
  }

  // 计算预测状态以获取转向运动
  const auto & m = mpc_matrix;
  const MatrixXd Xex = m.Aex * x0 + m.Bex * Uex + m.Wex;

  const size_t STEER_IDX = 2;  // 对于运动学模型

  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 (current <= reference.front()) {
      return limits.front();
    }
    if (current >= reference.back()) {
      return limits.back();
    }

    // 应用线性插值
    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() 插值逻辑错误。命令滤波器未工作。请检查代码。"
              << std::endl;
    return reference.back();
  };

  // 当车辆停止时,使用较大的转向速率限制以进行干转向。
  constexpr double steer_rate_lim = 100.0;
  const bool is_vehicle_stopped = std::fabs(current_velocity) < 0.01;
  if (is_vehicle_stopped) {
    return steer_rate_lim * VectorXd::Ones(m_param.prediction_horizon);
  }

  // 计算转向速率限制
  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;
}

// 计算预测轨迹
Trajectory MPC::calculatePredictedTrajectory(
  const MPCMatrix & mpc_matrix, const Eigen::MatrixXd & x0, const Eigen::MatrixXd & Uex,
  const MPCTrajectory & reference_trajectory, const double dt, const std::string & coordinate) const
{
  MPCTrajectory predicted_mpc_trajectory;

  if (coordinate == "world") {
    predicted_mpc_trajectory = m_vehicle_model_ptr->calculatePredictedTrajectoryInWorldCoordinate(
      mpc_matrix.Aex, mpc_matrix.Bex, mpc_matrix.Cex, mpc_matrix.Wex, x0, Uex, reference_trajectory,
      dt);
  } else if (coordinate == "frenet") {
    predicted_mpc_trajectory = m_vehicle_model_ptr->calculatePredictedTrajectoryInFrenetCoordinate(
      mpc_matrix.Aex, mpc_matrix.Bex, mpc_matrix.Cex, mpc_matrix.Wex, x0, Uex, reference_trajectory,
      dt);
  } else {
    throw std::invalid_argument("指定的坐标系无效。使用 'world' 或 'frenet'。");
  }

  // 不要超过参考轨迹
  const auto predicted_length = MPCUtils::calcMPCTrajectoryArcLength(reference_trajectory);
  const auto clipped_trajectory =
    MPCUtils::clipTrajectoryByLength(predicted_mpc_trajectory, predicted_length);

  const auto predicted_trajectory = MPCUtils::convertToAutowareTrajectory(clipped_trajectory);

  return predicted_trajectory;
}

// 检查MPC矩阵是否有效
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

deepseek还多了一段整理,我觉得不错,直接复制在下面:

主要功能模块的中文注释:

    MPC构造函数:初始化发布器,用于发布调试信息。

    calculateMPC:MPC的核心计算函数,负责计算控制命令和预测轨迹。

    generateDiagData:生成诊断数据,用于调试和监控。

    setReferenceTrajectory:设置参考轨迹,并进行重采样和平滑处理。

    resetPrevResult:重置前一次的结果,用于初始化或重置状态。

    getData:获取MPC计算所需的数据,包括当前状态和参考轨迹。

    resampleMPCTrajectoryByTime:按时间重采样MPC轨迹。

    getInitialState:获取误差动力学的初始状态。

    updateStateForDelayCompensation:更新状态以进行延迟补偿。

    applyVelocityDynamicsFilter:应用速度动力学滤波器。

    generateMPCMatrix:生成MPC矩阵,用于预测和控制。

    executeOptimization:执行二次优化,求解最优控制输入。

    addSteerWeightR:添加转向权重到R矩阵。

    addSteerWeightF:添加转向权重到f向量。

    getPredictionDeltaTime:获取预测时间步长。

    calcDesiredSteeringRate:计算期望的转向速率。

    calcSteerRateLimitOnTrajectory:计算轨迹上的转向速率限制。

    calculatePredictedTrajectory:计算预测轨迹。

    isValid:检查MPC矩阵是否有效。

四、vehicle_model

mpc算法中,车辆模型是很重要的一点,它决定了预测控制的精度和真实性。
在vehicle_model文件夹中,有四个cpp文件:
vehicle_model_bicycle_dynamics.cpp:带有动力学的自行车模型(Bicycle Dynamics Model)
vehicle_model_bicycle_kinematics.cpp:自行车运动学模型(Bicycle Kinematics Model)
vehicle_model_bicycle_kinematics_no_delay.cpp:无延迟的自行车运动学模型(Bicycle Kinematics No Delay Model),忽略了转向系统的延迟。
vehicle_model_interface.cpp:定义了车辆模型的通用接口,所有模型都继承自该类。

自行车模型可以看我之前写的博客。

1.vehicle_model_bicycle_dynamics.cpp

// 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 "autoware/mpc_lateral_controller/vehicle_model/vehicle_model_bicycle_dynamics.hpp"

#include <algorithm>

namespace autoware::motion::control::mpc_lateral_controller
{
// 构造函数,初始化动力学自行车模型的参数
DynamicsBicycleModel::DynamicsBicycleModel(
  const double wheelbase, const double mass_fl, const double mass_fr, const double mass_rl,
  const double mass_rr, const double cf, const double cr)
: VehicleModelInterface(/* dim_x */ 4, /* dim_u */ 1, /* dim_y */ 2, wheelbase)  // 状态维度4,输入维度1,输出维度2
{
  // 计算前后轴质量
  const double mass_front = mass_fl + mass_fr;
  const double mass_rear = mass_rl + mass_rr;

  // 计算总质量
  m_mass = mass_front + mass_rear;

  // 计算前后轴到质心的距离
  m_lf = m_wheelbase * (1.0 - mass_front / m_mass);  // 前轴到质心的距离
  m_lr = m_wheelbase * (1.0 - mass_rear / m_mass);   // 后轴到质心的距离

  // 计算绕Z轴的转动惯量
  m_iz = m_lf * m_lf * mass_front + m_lr * m_lr * mass_rear;

  // 设置前后轮的侧偏刚度
  m_cf = cf;  // 前轮侧偏刚度
  m_cr = cr;  // 后轮侧偏刚度
}

// 计算离散状态空间矩阵
void DynamicsBicycleModel::calculateDiscreteMatrix(
  Eigen::MatrixXd & a_d, Eigen::MatrixXd & b_d, Eigen::MatrixXd & c_d, Eigen::MatrixXd & w_d,
  const double dt)
{
  /*
   * 离散状态空间方程:
   * x[k+1] = a_d * x[k] + b_d * u + w_d
   */

  // 确保速度不为零,避免除以零错误
  const double vel = std::max(m_velocity, 0.01);

  // 初始化连续时间状态矩阵 A
  a_d = Eigen::MatrixXd::Zero(m_dim_x, m_dim_x);
  a_d(0, 1) = 1.0;  // 横向速度
  a_d(1, 1) = -(m_cf + m_cr) / (m_mass * vel);  // 横向加速度
  a_d(1, 2) = (m_cf + m_cr) / m_mass;  // 横向加速度对偏航角的影响
  a_d(1, 3) = (m_lr * m_cr - m_lf * m_cf) / (m_mass * vel);  // 横向加速度对偏航角速度的影响
  a_d(2, 3) = 1.0;  // 偏航角速度
  a_d(3, 1) = (m_lr * m_cr - m_lf * m_cf) / (m_iz * vel);  // 偏航角加速度对横向速度的影响
  a_d(3, 2) = (m_lf * m_cf - m_lr * m_cr) / m_iz;  // 偏航角加速度对偏航角的影响
  a_d(3, 3) = -(m_lf * m_lf * m_cf + m_lr * m_lr * m_cr) / (m_iz * vel);  // 偏航角加速度对偏航角速度的影响

  // 使用双线性离散化方法计算离散状态矩阵
  Eigen::MatrixXd I = Eigen::MatrixXd::Identity(m_dim_x, m_dim_x);
  Eigen::MatrixXd a_d_inverse = (I - dt * 0.5 * a_d).inverse();
  a_d = a_d_inverse * (I + dt * 0.5 * a_d);  // 双线性离散化

  // 初始化输入矩阵 B
  b_d = Eigen::MatrixXd::Zero(m_dim_x, m_dim_u);
  b_d(0, 0) = 0.0;  // 横向位置不受输入影响
  b_d(1, 0) = m_cf / m_mass;  // 输入对横向加速度的影响
  b_d(2, 0) = 0.0;  // 偏航角不受输入影响
  b_d(3, 0) = m_lf * m_cf / m_iz;  // 输入对偏航角加速度的影响

  // 初始化扰动矩阵 W
  w_d = Eigen::MatrixXd::Zero(m_dim_x, 1);
  w_d(0, 0) = 0.0;  // 横向位置无扰动
  w_d(1, 0) = (m_lr * m_cr - m_lf * m_cf) / (m_mass * vel) - vel;  // 横向加速度扰动
  w_d(2, 0) = 0.0;  // 偏航角无扰动
  w_d(3, 0) = -(m_lf * m_lf * m_cf + m_lr * m_lr * m_cr) / (m_iz * vel);  // 偏航角加速度扰动

  // 离散化输入矩阵 B 和扰动矩阵 W
  b_d = (a_d_inverse * dt) * b_d;
  w_d = (a_d_inverse * dt * m_curvature * vel) * w_d;

  // 初始化输出矩阵 C
  c_d = Eigen::MatrixXd::Zero(m_dim_y, m_dim_x);
  c_d(0, 0) = 1.0;  // 输出横向位置
  c_d(1, 2) = 1.0;  // 输出偏航角
}

// 计算参考输入(前馈控制)
void DynamicsBicycleModel::calculateReferenceInput(Eigen::MatrixXd & u_ref)
{
  const double vel = std::max(m_velocity, 0.01);  // 确保速度不为零
  const double Kv =
    m_lr * m_mass / (2 * m_cf * m_wheelbase) - m_lf * m_mass / (2 * m_cr * m_wheelbase);  // 转向增益
  u_ref(0, 0) = m_wheelbase * m_curvature + Kv * vel * vel * m_curvature;  // 参考转向角
}

// 在世界坐标系中计算预测轨迹(未实现)
MPCTrajectory DynamicsBicycleModel::calculatePredictedTrajectoryInWorldCoordinate(
  const Eigen::MatrixXd & a_d, const Eigen::MatrixXd & b_d,
  [[maybe_unused]] const Eigen::MatrixXd & c_d, const Eigen::MatrixXd & w_d,
  const Eigen::MatrixXd & x0, const Eigen::MatrixXd & Uex,
  const MPCTrajectory & reference_trajectory, [[maybe_unused]] const double dt) const
{
  // 输出错误信息:动力学模型不支持在世界坐标系中计算预测轨迹
  RCLCPP_ERROR(
    rclcpp::get_logger("control.trajectory_follower.lateral_controller"),
    "Predicted trajectory calculation in world coordinate is not supported in dynamic model. "
    "Calculate in the Frenet coordinate instead.");
  return calculatePredictedTrajectoryInFrenetCoordinate(
    a_d, b_d, c_d, w_d, x0, Uex, reference_trajectory, dt);
}

// 在 Frenet 坐标系中计算预测轨迹
MPCTrajectory DynamicsBicycleModel::calculatePredictedTrajectoryInFrenetCoordinate(
  const Eigen::MatrixXd & a_d, const Eigen::MatrixXd & b_d,
  [[maybe_unused]] const Eigen::MatrixXd & c_d, const Eigen::MatrixXd & w_d,
  const Eigen::MatrixXd & x0, const Eigen::MatrixXd & Uex,
  const MPCTrajectory & reference_trajectory, [[maybe_unused]] const double dt) const
{
  // 状态变量:x = [e, de, th, dth]
  // e      : 横向误差
  // de     : 横向误差的导数
  // th     : 偏航角误差
  // dth    : 偏航角误差的导数
  // steer  : 转向角(输入)

  // 计算预测状态
  Eigen::VectorXd Xex = a_d * x0 + b_d * Uex + w_d;
  MPCTrajectory mpc_predicted_trajectory;
  const auto DIM_X = getDimX();
  const auto & t = reference_trajectory;

  // 遍历参考轨迹,计算预测轨迹
  for (size_t i = 0; i < reference_trajectory.size(); ++i) {
    const auto lateral_error = Xex(i * DIM_X);  // 横向误差
    const auto yaw_error = Xex(i * DIM_X + 2);  // 偏航角误差
    const auto x = t.x.at(i) - std::sin(t.yaw.at(i)) * lateral_error;  // 世界坐标系中的 x 坐标
    const auto y = t.y.at(i) + std::cos(t.yaw.at(i)) * lateral_error;  // 世界坐标系中的 y 坐标
    const auto yaw = t.yaw.at(i) + yaw_error;  // 世界坐标系中的偏航角
    mpc_predicted_trajectory.push_back(
      x, y, t.z.at(i), yaw, t.vx.at(i), t.k.at(i), t.smooth_k.at(i), t.relative_time.at(i));
  }
  return mpc_predicted_trajectory;
}
}  // namespace autoware::motion::control::mpc_lateral_controller

2.vehicle_model_bicycle_kinematics.cpp

// 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 "autoware/mpc_lateral_controller/vehicle_model/vehicle_model_bicycle_kinematics.hpp"

#include <cmath>

namespace autoware::motion::control::mpc_lateral_controller
{
// 构造函数,初始化自行车运动学模型
KinematicsBicycleModel::KinematicsBicycleModel(
  const double wheelbase, const double steer_lim, const double steer_tau)
: VehicleModelInterface(/* dim_x */ 3, /* dim_u */ 1, /* dim_y */ 2, wheelbase)
{
  m_steer_lim = steer_lim;  // 设置转向角限制
  m_steer_tau = steer_tau;  // 设置转向时间常数
}

// 计算离散状态空间矩阵
void KinematicsBicycleModel::calculateDiscreteMatrix(
  Eigen::MatrixXd & a_d, Eigen::MatrixXd & b_d, Eigen::MatrixXd & c_d, Eigen::MatrixXd & w_d,
  const double dt)
{
  auto sign = [](double x) { return (x > 0.0) - (x < 0.0); };  // 符号函数

  /* 在参考转向角 delta_r 附近线性化 */
  double delta_r = atan(m_wheelbase * m_curvature);  // 计算参考转向角
  if (std::abs(delta_r) >= m_steer_lim) {
    delta_r = m_steer_lim * static_cast<double>(sign(delta_r));  // 限制转向角在范围内
  }
  double cos_delta_r_squared_inv = 1 / (cos(delta_r) * cos(delta_r));  // 计算 cos^2(delta_r) 的倒数
  double velocity = m_velocity;  // 获取当前速度
  if (std::abs(m_velocity) < 1e-04) {
    velocity = 1e-04 * (m_velocity >= 0 ? 1 : -1);  // 避免速度为零
  }

  // 离散状态矩阵 A_d
  a_d << 0.0, velocity, 0.0, 0.0, 0.0, velocity / m_wheelbase * cos_delta_r_squared_inv, 0.0, 0.0,
    -1.0 / m_steer_tau;

  // 离散输入矩阵 B_d
  b_d << 0.0, 0.0, 1.0 / m_steer_tau;

  // 输出矩阵 C_d
  c_d << 1.0, 0.0, 0.0, 0.0, 1.0, 0.0;

  // 扰动矩阵 W_d
  w_d << 0.0,
    -velocity * m_curvature +
      velocity / m_wheelbase * (tan(delta_r) - delta_r * cos_delta_r_squared_inv),
    0.0;

  // 使用双线性变换对连续系统进行离散化
  Eigen::MatrixXd I = Eigen::MatrixXd::Identity(m_dim_x, m_dim_x);  // 单位矩阵
  const Eigen::MatrixXd i_dt2a_inv = (I - dt * 0.5 * a_d).inverse();  // 计算 (I - dt/2 * A_d)^-1
  a_d = i_dt2a_inv * (I + dt * 0.5 * a_d);  // 更新 A_d
  b_d = i_dt2a_inv * b_d * dt;  // 更新 B_d
  w_d = i_dt2a_inv * w_d * dt;  // 更新 W_d
}

// 计算参考输入(转向角)
void KinematicsBicycleModel::calculateReferenceInput(Eigen::MatrixXd & u_ref)
{
  u_ref(0, 0) = std::atan(m_wheelbase * m_curvature);  // 根据曲率计算参考转向角
}

// 在世界坐标系中计算预测轨迹
MPCTrajectory KinematicsBicycleModel::calculatePredictedTrajectoryInWorldCoordinate(
  [[maybe_unused]] const Eigen::MatrixXd & a_d, [[maybe_unused]] const Eigen::MatrixXd & b_d,
  [[maybe_unused]] const Eigen::MatrixXd & c_d, [[maybe_unused]] const Eigen::MatrixXd & w_d,
  const Eigen::MatrixXd & x0, const Eigen::MatrixXd & Uex,
  const MPCTrajectory & reference_trajectory, const double dt) const
{
  // 在世界坐标系中计算预测轨迹,因为 Frenet 坐标系中存在建模误差
  // 相对坐标系 x = [横向误差, 偏航误差, 转向角]
  // 世界坐标系 x = [x, y, 偏航角, 转向角]

  const auto & t = reference_trajectory;

  // 在世界坐标系中创建初始状态
  Eigen::VectorXd state_w = [&]() {
    Eigen::VectorXd state = Eigen::VectorXd::Zero(3);
    const auto lateral_error_0 = x0(0);  // 初始横向误差
    const auto yaw_error_0 = x0(1);  // 初始偏航误差
    state(0, 0) = t.x.at(0) - std::sin(t.yaw.at(0)) * lateral_error_0;  // 世界坐标系 x
    state(1, 0) = t.y.at(0) + std::cos(t.yaw.at(0)) * lateral_error_0;  // 世界坐标系 y
    state(2, 0) = t.yaw.at(0) + yaw_error_0;  // 世界坐标系偏航角
    return state;
  }();

  // 更新世界坐标系中的状态
  const auto updateState = [&](
                             const Eigen::VectorXd & state_w, const double & input, const double dt,
                             const double velocity) {
    const auto yaw = state_w(2);  // 当前偏航角

    Eigen::VectorXd dstate = Eigen::VectorXd::Zero(4);
    dstate(0) = velocity * std::cos(yaw);  // x 方向速度
    dstate(1) = velocity * std::sin(yaw);  // y 方向速度
    dstate(2) = velocity * std::tan(input) / m_wheelbase;  // 偏航角变化率

    // 注意:不要直接返回 "state_w + dstate * dt",因为 Eigen 的惰性求值会导致问题
    const Eigen::VectorXd next_state = state_w + dstate * dt;  // 计算下一时刻状态
    return next_state;
  };

  MPCTrajectory mpc_predicted_trajectory;  // 预测轨迹
  const auto DIM_U = getDimU();  // 输入维度

  // 遍历参考轨迹,更新状态并生成预测轨迹
  for (size_t i = 0; i < reference_trajectory.size(); ++i) {
    state_w = updateState(state_w, Uex(i * DIM_U, 0), dt, t.vx.at(i));  // 更新状态
    mpc_predicted_trajectory.push_back(
      state_w(0), state_w(1), t.z.at(i), state_w(2), t.vx.at(i), t.k.at(i), t.smooth_k.at(i),
      t.relative_time.at(i));  // 将状态添加到预测轨迹中
  }
  return mpc_predicted_trajectory;
}

// 在 Frenet 坐标系中计算预测轨迹
MPCTrajectory KinematicsBicycleModel::calculatePredictedTrajectoryInFrenetCoordinate(
  const Eigen::MatrixXd & a_d, const Eigen::MatrixXd & b_d,
  [[maybe_unused]] const Eigen::MatrixXd & c_d, const Eigen::MatrixXd & w_d,
  const Eigen::MatrixXd & x0, const Eigen::MatrixXd & Uex,
  const MPCTrajectory & reference_trajectory, [[maybe_unused]] const double dt) const
{
  // 相对坐标系 x = [横向误差, 偏航误差, 转向角]

  Eigen::VectorXd Xex = a_d * x0 + b_d * Uex + w_d;  // 计算预测状态
  MPCTrajectory mpc_predicted_trajectory;  // 预测轨迹
  const auto DIM_X = getDimX();  // 状态维度
  const auto & t = reference_trajectory;

  // 遍历参考轨迹,生成预测轨迹
  for (size_t i = 0; i < reference_trajectory.size(); ++i) {
    const auto lateral_error = Xex(i * DIM_X);  // 横向误差
    const auto yaw_error = Xex(i * DIM_X + 1);  // 偏航误差
    const auto x = t.x.at(i) - std::sin(t.yaw.at(i)) * lateral_error;  // 世界坐标系 x
    const auto y = t.y.at(i) + std::cos(t.yaw.at(i)) * lateral_error;  // 世界坐标系 y
    const auto yaw = t.yaw.at(i) + yaw_error;  // 世界坐标系偏航角
    mpc_predicted_trajectory.push_back(
      x, y, t.z.at(i), yaw, t.vx.at(i), t.k.at(i), t.smooth_k.at(i), t.relative_time.at(i));  // 添加到预测轨迹
  }
  return mpc_predicted_trajectory;
}
}  // namespace autoware::motion::control::mpc_lateral_controller

3.vehicle_model_bicycle_kinematics_no_delay.cpp

// 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 "autoware/mpc_lateral_controller/vehicle_model/vehicle_model_bicycle_kinematics_no_delay.hpp"

#include <cmath>

namespace autoware::motion::control::mpc_lateral_controller
{
// 构造函数,初始化无延迟运动学自行车模型的参数
KinematicsBicycleModelNoDelay::KinematicsBicycleModelNoDelay(
  const double wheelbase, const double steer_lim)
: VehicleModelInterface(/* dim_x */ 2, /* dim_u */ 1, /* dim_y */ 2, wheelbase)  // 状态维度2,输入维度1,输出维度2
{
  m_steer_lim = steer_lim;  // 设置转向角限制
}

// 计算离散状态空间矩阵
void KinematicsBicycleModelNoDelay::calculateDiscreteMatrix(
  Eigen::MatrixXd & a_d, Eigen::MatrixXd & b_d, Eigen::MatrixXd & c_d, Eigen::MatrixXd & w_d,
  const double dt)
{
  // 定义一个符号函数
  auto sign = [](double x) { return (x > 0.0) - (x < 0.0); };

  /* 在参考转向角 delta_r 附近线性化 */
  double delta_r = atan(m_wheelbase * m_curvature);  // 参考转向角
  if (std::abs(delta_r) >= m_steer_lim) {
    delta_r = m_steer_lim * static_cast<double>(sign(delta_r));  // 限制转向角范围
  }
  double cos_delta_r_squared_inv = 1 / (cos(delta_r) * cos(delta_r));  // 1 / cos^2(delta_r)

  // 设置连续时间状态矩阵 A
  a_d << 0.0, m_velocity, 0.0, 0.0;

  // 设置输入矩阵 B
  b_d << 0.0, m_velocity / m_wheelbase * cos_delta_r_squared_inv;

  // 设置输出矩阵 C
  c_d << 1.0, 0.0, 0.0, 1.0;

  // 设置扰动矩阵 W
  w_d << 0.0, -m_velocity / m_wheelbase * delta_r * cos_delta_r_squared_inv;

  // 使用双线性离散化方法对状态矩阵 A 和输入矩阵 B 进行离散化
  Eigen::MatrixXd I = Eigen::MatrixXd::Identity(m_dim_x, m_dim_x);
  const Eigen::MatrixXd i_dt2a_inv = (I - dt * 0.5 * a_d).inverse();
  a_d = i_dt2a_inv * (I + dt * 0.5 * a_d);
  b_d = i_dt2a_inv * b_d * dt;
  w_d = i_dt2a_inv * w_d * dt;
}

// 计算参考输入(前馈控制)
void KinematicsBicycleModelNoDelay::calculateReferenceInput(Eigen::MatrixXd & u_ref)
{
  u_ref(0, 0) = std::atan(m_wheelbase * m_curvature);  // 参考转向角 = arctan(wheelbase * curvature)
}

// 在世界坐标系中计算预测轨迹
MPCTrajectory KinematicsBicycleModelNoDelay::calculatePredictedTrajectoryInWorldCoordinate(
  [[maybe_unused]] const Eigen::MatrixXd & a_d, [[maybe_unused]] const Eigen::MatrixXd & b_d,
  [[maybe_unused]] const Eigen::MatrixXd & c_d, [[maybe_unused]] const Eigen::MatrixXd & w_d,
  const Eigen::MatrixXd & x0, const Eigen::MatrixXd & Uex,
  const MPCTrajectory & reference_trajectory, const double dt) const
{
  // 由于 Frenet 坐标系中存在建模误差,因此在世界坐标系中计算预测轨迹
  // 相对坐标系 x = [横向误差, 偏航角误差]
  // 世界坐标系 x = [x, y, yaw]

  const auto & t = reference_trajectory;

  // 创建世界坐标系中的初始状态
  Eigen::VectorXd state_w = [&]() {
    Eigen::VectorXd state = Eigen::VectorXd::Zero(3);
    const auto lateral_error_0 = x0(0);  // 初始横向误差
    const auto yaw_error_0 = x0(1);      // 初始偏航角误差
    state(0, 0) = t.x.at(0) - std::sin(t.yaw.at(0)) * lateral_error_0;  // 世界坐标系中的 x
    state(1, 0) = t.y.at(0) + std::cos(t.yaw.at(0)) * lateral_error_0;  // 世界坐标系中的 y
    state(2, 0) = t.yaw.at(0) + yaw_error_0;                            // 世界坐标系中的 yaw
    return state;
  }();

  // 更新世界坐标系中的状态
  const auto updateState = [&](
                             const Eigen::VectorXd & state_w, const Eigen::MatrixXd & input,
                             const double dt, const double velocity) {
    const auto yaw = state_w(2);  // 当前偏航角
    const auto steer = input(0);  // 当前转向角

    Eigen::VectorXd dstate = Eigen::VectorXd::Zero(3);
    dstate(0) = velocity * std::cos(yaw);  // x 方向速度
    dstate(1) = velocity * std::sin(yaw);  // y 方向速度
    dstate(2) = velocity * std::tan(steer) / m_wheelbase;  // 偏航角速度

    // 计算下一时刻的状态
    const Eigen::VectorXd next_state = state_w + dstate * dt;
    return next_state;
  };

  // 计算预测轨迹
  MPCTrajectory mpc_predicted_trajectory;
  const auto DIM_U = getDimU();
  for (size_t i = 0; i < t.size(); ++i) {
    state_w = updateState(state_w, Uex.block(i * DIM_U, 0, DIM_U, 1), dt, t.vx.at(i));  // 更新状态
    mpc_predicted_trajectory.push_back(
      state_w(0), state_w(1), t.z.at(i), state_w(2), t.vx.at(i), t.k.at(i), t.smooth_k.at(i),
      t.relative_time.at(i));  // 将状态添加到预测轨迹中
  }

  return mpc_predicted_trajectory;
}

// 在 Frenet 坐标系中计算预测轨迹
MPCTrajectory KinematicsBicycleModelNoDelay::calculatePredictedTrajectoryInFrenetCoordinate(
  const Eigen::MatrixXd & a_d, const Eigen::MatrixXd & b_d,
  [[maybe_unused]] const Eigen::MatrixXd & c_d, const Eigen::MatrixXd & w_d,
  const Eigen::MatrixXd & x0, const Eigen::MatrixXd & Uex,
  const MPCTrajectory & reference_trajectory, [[maybe_unused]] const double dt) const
{
  // 相对坐标系 x = [横向误差, 偏航角误差]

  // 计算预测状态
  Eigen::VectorXd Xex = a_d * x0 + b_d * Uex + w_d;
  MPCTrajectory mpc_predicted_trajectory;
  const auto DIM_X = getDimX();
  const auto & t = reference_trajectory;

  // 遍历参考轨迹,计算预测轨迹
  for (size_t i = 0; i < reference_trajectory.size(); ++i) {
    const auto lateral_error = Xex(i * DIM_X);  // 横向误差
    const auto yaw_error = Xex(i * DIM_X + 1);  // 偏航角误差
    const auto x = t.x.at(i) - std::sin(t.yaw.at(i)) * lateral_error;  // 世界坐标系中的 x
    const auto y = t.y.at(i) + std::cos(t.yaw.at(i)) * lateral_error;  // 世界坐标系中的 y
    const auto yaw = t.yaw.at(i) + yaw_error;  // 世界坐标系中的 yaw
    mpc_predicted_trajectory.push_back(
      x, y, t.z.at(i), yaw, t.vx.at(i), t.k.at(i), t.smooth_k.at(i), t.relative_time.at(i));  // 添加到预测轨迹
  }
  return mpc_predicted_trajectory;
}
}  // namespace autoware::motion::control::mpc_lateral_controller

4.vehicle_model_interface.cpp

// 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 "autoware/mpc_lateral_controller/vehicle_model/vehicle_model_interface.hpp"

namespace autoware::motion::control::mpc_lateral_controller
{
// 构造函数,初始化车辆模型接口
VehicleModelInterface::VehicleModelInterface(int dim_x, int dim_u, int dim_y, double wheelbase)
: m_dim_x(dim_x), m_dim_u(dim_u), m_dim_y(dim_y), m_wheelbase(wheelbase)
{
}

// 获取状态向量的维度
int VehicleModelInterface::getDimX() const
{
  return m_dim_x;
}

// 获取输入向量的维度
int VehicleModelInterface::getDimU() const
{
  return m_dim_u;
}

// 获取输出向量的维度
int VehicleModelInterface::getDimY() const
{
  return m_dim_y;
}

// 获取车辆轴距
double VehicleModelInterface::getWheelbase() const
{
  return m_wheelbase;
}

// 设置车辆速度
void VehicleModelInterface::setVelocity(const double velocity)
{
  m_velocity = velocity;
}

// 设置路径曲率
void VehicleModelInterface::setCurvature(const double curvature)
{
  m_curvature = curvature;
}
}  // namespace autoware::motion::control::mpc_lateral_controller

五、qp_solver

二次规划(Quadratic Programming,QP)求解器的接口文件。
这部分就是俩接口,没啥讲的。
二次求解器我没整理过,等之后单独整理一版。
qp_solver文件夹下面有两个cpp文件:
qp_solver_osqp:这是一个基于 OSQP(Operator Splitting Quadratic Program) 的二次规划(QP)求解器。OSQP 是一个高效的、开源的二次规划求解器,专门用于求解凸二次规划问题。具体实现在autoware/osqp_interface/osqp_interface.cpp
qp_solver_unconstraint_fast:这是一个用于求解 无约束二次规划问题 的快速求解器。

求解器这部分先看看代码吧,之后我再单独出一篇说明一下如何修改。

1.qp_solver_osqp.cpp

// 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.

#ifndef AUTOWARE__MPC_LATERAL_CONTROLLER__QP_SOLVER__QP_SOLVER_OSQP_HPP_
#define AUTOWARE__MPC_LATERAL_CONTROLLER__QP_SOLVER__QP_SOLVER_OSQP_HPP_

#include "autoware/mpc_lateral_controller/qp_solver/qp_solver_interface.hpp"  // QP 求解器接口
#include "autoware/osqp_interface/osqp_interface.hpp"  // OSQP 接口
#include "rclcpp/rclcpp.hpp"  // ROS 2 的日志功能

namespace autoware::motion::control::mpc_lateral_controller
{

/// 使用 OSQP 库求解 QP 问题的类
class QPSolverOSQP : public QPSolverInterface
{
public:
  /**
   * @brief 构造函数
   * @param logger ROS 2 日志记录器
   */
  explicit QPSolverOSQP(const rclcpp::Logger & logger);

  /**
   * @brief 析构函数
   */
  virtual ~QPSolverOSQP() = default;

  /**
   * @brief 求解 QP 问题:最小化目标函数 j = u' * h_mat * u + f_vec' * u,无约束
   * @param [in] h_mat 目标函数中的参数矩阵
   * @param [in] f_vec 目标函数中的参数向量
   * @param [in] a 约束条件矩阵 lb_a < a*u < ub_a(此处未使用)
   * @param [in] lb 约束条件向量 lb < U < ub(此处未使用)
   * @param [in] ub 约束条件向量 lb < U < ub(此处未使用)
   * @param [in] lb_a 约束条件向量 lb_a < a*u < ub_a(此处未使用)
   * @param [in] ub_a 约束条件向量 lb_a < a*u < ub_a(此处未使用)
   * @param [out] u 最优解向量
   * @return 如果问题成功求解,返回 true
   */
  bool solve(
    const Eigen::MatrixXd & h_mat, const Eigen::MatrixXd & f_vec, const Eigen::MatrixXd & a,
    const Eigen::VectorXd & lb, const Eigen::VectorXd & ub, const Eigen::VectorXd & lb_a,
    const Eigen::VectorXd & ub_a, Eigen::VectorXd & u) override;

  /**
   * @brief 获取求解器迭代次数
   * @return 迭代次数
   */
  int64_t getTakenIter() const override { return osqpsolver_.getTakenIter(); }

  /**
   * @brief 获取求解器运行时间
   * @return 运行时间(秒)
   */
  double getRunTime() const override { return osqpsolver_.getRunTime(); }

  /**
   * @brief 获取目标函数的最优值
   * @return 目标函数的最优值
   */
  double getObjVal() const override { return osqpsolver_.getObjVal(); }

private:
  autoware::osqp_interface::OSQPInterface osqpsolver_;  // OSQP 求解器接口
  rclcpp::Logger logger_;  // ROS 2 日志记录器
};
}  // namespace autoware::motion::control::mpc_lateral_controller
#endif  // AUTOWARE__MPC_LATERAL_CONTROLLER__QP_SOLVER__QP_SOLVER_OSQP_HPP_

2.qp_solver_unconstraint_fast.cpp

// 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.

#ifndef AUTOWARE__MPC_LATERAL_CONTROLLER__QP_SOLVER__QP_SOLVER_UNCONSTRAINT_FAST_HPP_
#define AUTOWARE__MPC_LATERAL_CONTROLLER__QP_SOLVER__QP_SOLVER_UNCONSTRAINT_FAST_HPP_

#include "autoware/mpc_lateral_controller/qp_solver/qp_solver_interface.hpp"  // QP 求解器接口

namespace autoware::motion::control::mpc_lateral_controller
{

/**
 * 使用最小二乘法求解 QP 问题的类
 * 基于 Eigen 的标准 Cholesky 分解(LLT)实现
 */
class QPSolverEigenLeastSquareLLT : public QPSolverInterface
{
public:
  /**
   * @brief 构造函数
   */
  QPSolverEigenLeastSquareLLT();

  /**
   * @brief 析构函数
   */
  ~QPSolverEigenLeastSquareLLT() = default;

  /**
   * @brief 求解 QP 问题:最小化目标函数 j = u' * h_mat * u + f_vec' * u,无约束
   * @param [in] h_mat 目标函数中的参数矩阵
   * @param [in] f_vec 目标函数中的参数向量
   * @param [in] a 约束条件矩阵 lb_a < a*u < ub_a(此处未使用)
   * @param [in] lb 约束条件向量 lb < U < ub(此处未使用)
   * @param [in] ub 约束条件向量 lb < U < ub(此处未使用)
   * @param [in] lb_a 约束条件向量 lb_a < a*u < ub_a(此处未使用)
   * @param [in] ub_a 约束条件向量 lb_a < a*u < ub_a(此处未使用)
   * @param [out] u 最优解向量
   * @return 如果问题成功求解,返回 true
   */
  bool solve(
    const Eigen::MatrixXd & h_mat, const Eigen::MatrixXd & f_vec, const Eigen::MatrixXd & a,
    const Eigen::VectorXd & lb, const Eigen::VectorXd & ub, const Eigen::VectorXd & lb_a,
    const Eigen::VectorXd & ub_a, Eigen::VectorXd & u) override;

  /**
   * @brief 获取求解器迭代次数
   * @return 迭代次数(固定为 1,因为 LLT 分解是直接求解)
   */
  int64_t getTakenIter() const override { return 1; };

  // TODO(someone): 计算运行时间和目标函数值,否则基类默认返回 0
  // double getRunTime() const override { return 0.0; }
  // double getObjVal() const override { return 0.0; }

private:
  // 无私有成员变量
};
}  // namespace autoware::motion::control::mpc_lateral_controller
#endif  // AUTOWARE__MPC_LATERAL_CONTROLLER__QP_SOLVER__QP_SOLVER_UNCONSTRAINT_FAST_HPP_

六、mpc_trajectory.cpp

这个是处理目标路径的文件,将规划给的目标路径转换为 MPC 算法所需的格式。也没啥讲的。

// 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 "autoware/mpc_lateral_controller/mpc_trajectory.hpp"

namespace autoware::motion::control::mpc_lateral_controller
{
/**
 * @brief 向轨迹中添加一个点
 * @param xp x 坐标
 * @param yp y 坐标
 * @param zp z 坐标
 * @param yawp 偏航角
 * @param vxp 速度
 * @param kp 曲率
 * @param smooth_kp 平滑曲率
 * @param tp 相对时间
 */
void MPCTrajectory::push_back(
  const double & xp, const double & yp, const double & zp, const double & yawp, const double & vxp,
  const double & kp, const double & smooth_kp, const double & tp)
{
  x.push_back(xp);  // 添加 x 坐标
  y.push_back(yp);  // 添加 y 坐标
  z.push_back(zp);  // 添加 z 坐标
  yaw.push_back(yawp);  // 添加偏航角
  vx.push_back(vxp);  // 添加速度
  k.push_back(kp);  // 添加曲率
  smooth_k.push_back(smooth_kp);  // 添加平滑曲率
  relative_time.push_back(tp);  // 添加相对时间
}

/**
 * @brief 向轨迹中添加一个点
 * @param p 轨迹点对象
 */
void MPCTrajectory::push_back(const MPCTrajectoryPoint & p)
{
  x.push_back(p.x);  // 添加 x 坐标
  y.push_back(p.y);  // 添加 y 坐标
  z.push_back(p.z);  // 添加 z 坐标
  yaw.push_back(p.yaw);  // 添加偏航角
  vx.push_back(p.vx);  // 添加速度
  k.push_back(p.k);  // 添加曲率
  smooth_k.push_back(p.smooth_k);  // 添加平滑曲率
  relative_time.push_back(p.relative_time);  // 添加相对时间
}

/**
 * @brief 获取轨迹的最后一个点
 * @return 最后一个轨迹点
 */
MPCTrajectoryPoint MPCTrajectory::back()
{
  MPCTrajectoryPoint p;

  p.x = x.back();  // 获取最后一个点的 x 坐标
  p.y = y.back();  // 获取最后一个点的 y 坐标
  p.z = z.back();  // 获取最后一个点的 z 坐标
  p.yaw = yaw.back();  // 获取最后一个点的偏航角
  p.vx = vx.back();  // 获取最后一个点的速度
  p.k = k.back();  // 获取最后一个点的曲率
  p.smooth_k = smooth_k.back();  // 获取最后一个点的平滑曲率
  p.relative_time = relative_time.back();  // 获取最后一个点的相对时间

  return p;
}

/**
 * @brief 获取轨迹中指定索引的点
 * @param i 索引
 * @return 指定索引的轨迹点
 */
MPCTrajectoryPoint MPCTrajectory::at(const size_t i) const
{
  MPCTrajectoryPoint p;

  p.x = x.at(i);  // 获取第 i 个点的 x 坐标
  p.y = y.at(i);  // 获取第 i 个点的 y 坐标
  p.z = z.at(i);  // 获取第 i 个点的 z 坐标
  p.yaw = yaw.at(i);  // 获取第 i 个点的偏航角
  p.vx = vx.at(i);  // 获取第 i 个点的速度
  p.k = k.at(i);  // 获取第 i 个点的曲率
  p.smooth_k = smooth_k.at(i);  // 获取第 i 个点的平滑曲率
  p.relative_time = relative_time.at(i);  // 获取第 i 个点的相对时间

  return p;
}

/**
 * @brief 清空轨迹中的所有点
 */
void MPCTrajectory::clear()
{
  x.clear();  // 清空 x 坐标
  y.clear();  // 清空 y 坐标
  z.clear();  // 清空 z 坐标
  yaw.clear();  // 清空偏航角
  vx.clear();  // 清空速度
  k.clear();  // 清空曲率
  smooth_k.clear();  // 清空平滑曲率
  relative_time.clear();  // 清空相对时间
}

/**
 * @brief 获取轨迹中点的数量
 * @return 轨迹中点的数量
 */
size_t MPCTrajectory::size() const
{
  // 检查所有向量的长度是否一致
  if (
    x.size() == y.size() && x.size() == z.size() && x.size() == yaw.size() &&
    x.size() == vx.size() && x.size() == k.size() && x.size() == smooth_k.size() &&
    x.size() == relative_time.size()) {
    return x.size();  // 返回轨迹点的数量
  } else {
    std::cerr << "[MPC trajectory] trajectory size is inappropriate" << std::endl;  // 输出错误信息
    return 0;  // 返回 0 表示轨迹数据不一致
  }
}
}  // namespace autoware::motion::control::mpc_lateral_controller

七、mpc_utils.cpp

一些 MPC 算法的辅助函数或工具函数。计算曲率之类的。

以下是添加了详细汉语注释的代码:

```cpp
// 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 "autoware/mpc_lateral_controller/mpc_utils.hpp"

#include "autoware/interpolation/linear_interpolation.hpp"
#include "autoware/interpolation/spline_interpolation.hpp"
#include "autoware/motion_utils/trajectory/trajectory.hpp"
#include "autoware/universe_utils/geometry/geometry.hpp"
#include "autoware/universe_utils/math/normalization.hpp"

#include <algorithm>
#include <limits>
#include <string>
#include <vector>

namespace autoware::motion::control::mpc_lateral_controller
{
namespace
{
/**
 * @brief 计算目标点在前后两点连线上的纵向偏移量
 * @param p_front 前点
 * @param p_back 后点
 * @param p_target 目标点
 * @return 纵向偏移量
 */
double calcLongitudinalOffset(
  const geometry_msgs::msg::Point & p_front, const geometry_msgs::msg::Point & p_back,
  const geometry_msgs::msg::Point & p_target)
{
  const Eigen::Vector3d segment_vec{p_back.x - p_front.x, p_back.y - p_front.y, 0};  // 前后点向量
  const Eigen::Vector3d target_vec{p_target.x - p_front.x, p_target.y - p_front.y, 0};  // 目标点向量
  return segment_vec.dot(target_vec) / segment_vec.norm();  // 计算投影长度
}
}  // namespace

namespace MPCUtils
{
using autoware::universe_utils::calcDistance2d;
using autoware::universe_utils::createQuaternionFromYaw;
using autoware::universe_utils::normalizeRadian;

/**
 * @brief 计算轨迹中两个点之间的二维距离
 * @param trajectory 轨迹
 * @param idx1 第一个点的索引
 * @param idx2 第二个点的索引
 * @return 两点之间的距离
 */
double calcDistance2d(const MPCTrajectory & trajectory, const size_t idx1, const size_t idx2)
{
  const double dx = trajectory.x.at(idx1) - trajectory.x.at(idx2);  // x 方向差值
  const double dy = trajectory.y.at(idx1) - trajectory.y.at(idx2);  // y 方向差值
  return std::hypot(dx, dy);  // 计算欧几里得距离
}

/**
 * @brief 计算轨迹中两个点之间的三维距离
 * @param trajectory 轨迹
 * @param idx1 第一个点的索引
 * @param idx2 第二个点的索引
 * @return 两点之间的距离
 */
double calcDistance3d(const MPCTrajectory & trajectory, const size_t idx1, const size_t idx2)
{
  const double dx = trajectory.x.at(idx1) - trajectory.x.at(idx2);  // x 方向差值
  const double dy = trajectory.y.at(idx1) - trajectory.y.at(idx2);  // y 方向差值
  const double dz = trajectory.z.at(idx1) - trajectory.z.at(idx2);  // z 方向差值
  return std::hypot(dx, dy, dz);  // 计算欧几里得距离
}

/**
 * @brief 将欧拉角转换为单调递增的角度序列
 * @param angle_vector 角度向量
 */
void convertEulerAngleToMonotonic(std::vector<double> & angle_vector)
{
  for (uint i = 1; i < angle_vector.size(); ++i) {
    const double da = angle_vector.at(i) - angle_vector.at(i - 1);  // 角度差值
    angle_vector.at(i) = angle_vector.at(i - 1) + normalizeRadian(da);  // 归一化并累加
  }
}

/**
 * @brief 计算横向误差
 * @param ego_pose 自车位姿
 * @param ref_pose 参考位姿
 * @return 横向误差
 */
double calcLateralError(const Pose & ego_pose, const Pose & ref_pose)
{
  const double err_x = ego_pose.position.x - ref_pose.position.x;  // x 方向误差
  const double err_y = ego_pose.position.y - ref_pose.position.y;  // y 方向误差
  const double ref_yaw = tf2::getYaw(ref_pose.orientation);  // 参考位姿的偏航角
  const double lat_err = -std::sin(ref_yaw) * err_x + std::cos(ref_yaw) * err_y;  // 计算横向误差
  return lat_err;
}

/**
 * @brief 计算轨迹的弧长
 * @param trajectory 轨迹
 * @param arc_length 输出的弧长向量
 */
void calcMPCTrajectoryArcLength(const MPCTrajectory & trajectory, std::vector<double> & arc_length)
{
  double dist = 0.0;
  arc_length.clear();
  arc_length.push_back(dist);  // 初始弧长为 0
  for (uint i = 1; i < trajectory.size(); ++i) {
    dist += calcDistance2d(trajectory, i, i - 1);  // 累加两点之间的距离
    arc_length.push_back(dist);  // 存储当前弧长
  }
}

/**
 * @brief 计算轨迹的总弧长
 * @param trajectory 轨迹
 * @return 总弧长
 */
double calcMPCTrajectoryArcLength(const MPCTrajectory & trajectory)
{
  double length = 0.0;
  for (size_t i = 1; i < trajectory.size(); ++i) {
    length += calcDistance2d(trajectory, i, i - 1);  // 累加两点之间的距离
  }
  return length;
}

/**
 * @brief 按距离重新采样轨迹
 * @param input 输入轨迹
 * @param resample_interval_dist 重采样间隔距离
 * @param nearest_seg_idx 最近段的索引
 * @param ego_offset_to_segment 自车到最近段的偏移量
 * @return 重采样后的轨迹
 */
std::pair<bool, MPCTrajectory> resampleMPCTrajectoryByDistance(
  const MPCTrajectory & input, const double resample_interval_dist, const size_t nearest_seg_idx,
  const double ego_offset_to_segment)
{
  MPCTrajectory output;

  if (input.empty()) {
    return {true, output};  // 如果输入轨迹为空,直接返回
  }
  std::vector<double> input_arclength;
  calcMPCTrajectoryArcLength(input, input_arclength);  // 计算输入轨迹的弧长

  if (input_arclength.empty()) {
    return {false, output};  // 如果弧长计算失败,返回失败
  }

  std::vector<double> output_arclength;
  // 从当前位置向前和向后分别重采样
  for (double s = std::clamp(
         input_arclength.at(nearest_seg_idx) + ego_offset_to_segment, 0.0,
         input_arclength.back() - 1e-6);
       0 <= s; s -= resample_interval_dist) {
    output_arclength.push_back(s);  // 向后采样
  }
  std::reverse(output_arclength.begin(), output_arclength.end());  // 反转以保持顺序
  for (double s = std::max(input_arclength.at(nearest_seg_idx) + ego_offset_to_segment, 0.0) +
                  resample_interval_dist;
       s < input_arclength.back(); s += resample_interval_dist) {
    output_arclength.push_back(s);  // 向前采样
  }

  std::vector<double> input_yaw = input.yaw;
  convertEulerAngleToMonotonic(input_yaw);  // 将偏航角转换为单调递增序列

  // 使用线性插值和样条插值进行重采样
  const auto lerp_arc_length = [&](const auto & input_value) {
    return autoware::interpolation::lerp(input_arclength, input_value, output_arclength);
  };
  const auto spline_arc_length = [&](const auto & input_value) {
    return autoware::interpolation::spline(input_arclength, input_value, output_arclength);
  };

  output.x = spline_arc_length(input.x);  // 重采样 x 坐标
  output.y = spline_arc_length(input.y);  // 重采样 y 坐标
  output.z = spline_arc_length(input.z);  // 重采样 z 坐标
  output.yaw = spline_arc_length(input_yaw);  // 重采样偏航角
  output.vx = lerp_arc_length(input.vx);  // 重采样速度(必须线性插值)
  output.k = spline_arc_length(input.k);  // 重采样曲率
  output.smooth_k = spline_arc_length(input.smooth_k);  // 重采样平滑曲率
  output.relative_time = lerp_arc_length(input.relative_time);  // 重采样相对时间(必须线性插值)

  return {true, output};  // 返回重采样后的轨迹
}

/**
 * @brief 线性插值轨迹
 * @param in_index 输入索引
 * @param in_traj 输入轨迹
 * @param out_index 输出索引
 * @param out_traj 输出轨迹
 * @return 是否成功
 */
bool linearInterpMPCTrajectory(
  const std::vector<double> & in_index, const MPCTrajectory & in_traj,
  const std::vector<double> & out_index, MPCTrajectory & out_traj)
{
  if (in_traj.empty()) {
    out_traj = in_traj;  // 如果输入轨迹为空,直接返回
    return true;
  }

  std::vector<double> in_traj_yaw = in_traj.yaw;
  convertEulerAngleToMonotonic(in_traj_yaw);  // 将偏航角转换为单调递增序列

  // 使用线性插值进行重采样
  const auto lerp_arc_length = [&](const auto & input_value) {
    return autoware::interpolation::lerp(in_index, input_value, out_index);
  };

  try {
    out_traj.x = lerp_arc_length(in_traj.x);  // 重采样 x 坐标
    out_traj.y = lerp_arc_length(in_traj.y);  // 重采样 y 坐标
    out_traj.z = lerp_arc_length(in_traj.z);  // 重采样 z 坐标
    out_traj.yaw = lerp_arc_length(in_traj.yaw);  // 重采样偏航角
    out_traj.vx = lerp_arc_length(in_traj.vx);  // 重采样速度
    out_traj.k = lerp_arc_length(in_traj.k);  // 重采样曲率
    out_traj.smooth_k = lerp_arc_length(in_traj.smooth_k);  // 重采样平滑曲率
    out_traj.relative_time = lerp_arc_length(in_traj.relative_time);  // 重采样相对时间
  } catch (const std::exception & e) {
    std::cerr << "linearInterpMPCTrajectory error!: " << e.what() << std::endl;  // 输出错误信息
  }

  if (out_traj.empty()) {
    std::cerr << "[mpc util] linear interpolation error" << std::endl;  // 输出错误信息
    return false;
  }

  return true;  // 返回成功
}

/**
 * @brief 从 x, y 坐标计算轨迹的偏航角
 * @param traj 轨迹
 * @param is_forward_shift 是否向前偏移
 */
void calcTrajectoryYawFromXY(MPCTrajectory & traj, const bool is_forward_shift)
{
  if (traj.yaw.size() < 3) {  // 至少需要 3 个点来计算偏航角
    return;
  }
  if (traj.yaw.size() != traj.vx.size()) {
    RCLCPP_ERROR(rclcpp::get_logger("mpc_utils"), "trajectory size has no consistency.");
    return;
  }

  // 插值偏航角
  for (int i = 1; i < static_cast<int>(traj.yaw.size()) - 1; ++i) {
    const double dx = traj.x.at(i + 1) - traj.x.at(i - 1);  // x 方向差值
    const double dy = traj.y.at(i + 1) - traj.y.at(i - 1);  // y 方向差值
    traj.yaw.at(i) = is_forward_shift ? std::atan2(dy, dx) : std::atan2(dy, dx) + M_PI;  // 计算偏航角
  }
  if (traj.yaw.size() > 1) {
    traj.yaw.at(0) = traj.yaw.at(1);  // 第一个点的偏航角与第二个点相同
    traj.yaw.back() = traj.yaw.at(traj.yaw.size() - 2);  // 最后一个点的偏航角与倒数第二个点相同
  }
}

/**
 * @brief 计算轨迹的曲率
 * @param curvature_smoothing_num_traj 轨迹曲率平滑点数
 * @param curvature_smoothing_num_ref_steer 参考转向曲率平滑点数
 * @param traj 轨迹
 */
void calcTrajectoryCurvature(
  const int curvature_smoothing_num_traj, const int curvature_smoothing_num_ref_steer,
  MPCTrajectory & traj)
{
  traj.k = calcTrajectoryCurvature(curvature_smoothing_num_traj, traj);  // 计算轨迹曲率
  traj.smooth_k = calcTrajectoryCurvature(curvature_smoothing_num_ref_steer, traj);  // 计算平滑曲率
}

/**
 * @brief 计算轨迹的曲率
 * @param curvature_smoothing_num 曲率平滑点数
 * @param traj 轨迹
 * @return 曲率向量
 */
std::vector<double> calcTrajectoryCurvature(
  const int curvature_smoothing_num, const MPCTrajectory & traj)
{
  std::vector<double> curvature_vec(traj.x.size());

  /* 通过三点拟合圆计算曲率 */
  geometry_msgs::msg::Point p1, p2, p3;
  const int max_smoothing_num =
    static_cast<int>(std::floor(0.5 * (static_cast<double>(traj.x.size() - 1))));
  const size_t L = static_cast<size_t>(std::min(curvature_smoothing_num, max_smoothing_num));
  for (size_t i = L; i < traj.x.size() - L; ++i) {
    const size_t curr_idx = i;
    const size_t prev_idx = curr_idx - L;
    const size_t next_idx = curr_idx + L;
    p1.x = traj.x.at(prev_idx);
    p2.x = traj.x.at(curr_idx);
    p3.x = traj.x.at(next_idx);
    p1.y = traj.y.at(prev_idx);
    p2.y = traj.y.at(curr_idx);
    p3.y = traj.y.at(next_idx);
    try {
      curvature_vec.at(curr_idx) = autoware::universe_utils::calcCurvature(p1, p2, p3);  // 计算曲率
    } catch (...) {
      std::cerr << "[MPC] 2 points are too close to calculate curvature." << std::endl;  // 输出错误信息
      curvature_vec.at(curr_idx) = 0.0;  // 曲率设为 0
    }
  }

  /* 第一个和最后一个曲率从相邻点复制 */
  for (size_t i = 0; i < std::min(L, traj.x.size()); ++i) {
    curvature_vec.at(i) = curvature_vec.at(std::min(L, traj.x.size() - 1));
    curvature_vec.at(traj.x.size() - i - 1) =
      curvature_vec.at(std::max(traj.x.size() - L - 1, size_t(0)));
  }
  return curvature_vec;
}

/**
 * @brief 将 Trajectory 转换为 MPCTrajectory
 * @param input 输入轨迹
 * @return 转换后的 MPCTrajectory
 */
MPCTrajectory convertToMPCTrajectory(const Trajectory & input)
{
  MPCTrajectory output;
  for (const TrajectoryPoint & p : input.points) {
    const double x = p.pose.position.x;  // x 坐标
    const double y = p.pose.position.y;  // y 坐标
    const double z = p.pose.position.z;  // z 坐标
    const double yaw = tf2::getYaw(p.pose.orientation);  // 偏航角
    const double vx = p.longitudinal_velocity_mps;  // 速度
    const double k = 0.0;  // 曲率
    const double t = 0.0;  // 时间
    output.push_back(x, y, z, yaw, vx, k, k, t);  // 添加到 MPCTrajectory
  }
  calcMPCTrajectoryTime(output);  // 计算轨迹时间
  return output;
}

/**
 * @brief 将 MPCTrajectory 转换为 Autoware Trajectory
 * @param input 输入 MPCTrajectory
 * @return 转换后的 Autoware Trajectory
 */
Trajectory convertToAutowareTrajectory(const MPCTrajectory & input)
{
  Trajectory output;
  TrajectoryPoint p;
  for (size_t i = 0; i < input.size(); ++i) {
    p.pose.position.x = input.x.at(i);  // x 坐标
    p.pose.position.y = input.y.at(i);  // y 坐标
    p.pose.position.z = input.z.at(i);  // z 坐标
    p.pose.orientation = autoware::universe_utils::createQuaternionFromYaw(input.yaw.at(i));  // 偏航角
    p.longitudinal_velocity_mps =
      static_cast<decltype(p.longitudinal_velocity_mps)>(input.vx.at(i));  // 速度
    output.points.push_back(p);  // 添加到 Autoware Trajectory
    if (output.points.size() == output.points.max_size()) {
      break;  // 如果达到最大点数,停止添加
    }
  }
  return output;
}

/**
 * @brief 计算 MPCTrajectory 的时间
 * @param traj 轨迹
 * @return 是否成功
 */
bool calcMPCTrajectoryTime(MPCTrajectory & traj)
{
  constexpr auto min_dt = 1.0e-4;  // 最小时间间隔,避免时间重复
  double t = 0.0;
  traj.relative_time.clear();
  traj.relative_time.push_back(t);  // 初始时间为 0
  for (size_t i = 0; i < traj.x.size() - 1; ++i) {
    const double dist = calcDistance3d(traj, i, i + 1);  // 计算两点之间的距离
    const double v = std::max(std::fabs(traj.vx.at(i)), 0.1);  // 计算速度
    t += std::max(dist / v, min_dt);  // 计算时间间隔
    traj.relative_time.push_back(t);  // 存储当前时间
  }
  return true;
}

/**
 * @brief 动态平滑速度
 * @param start_seg_idx 起始段索引
 * @param start_vel 起始速度
 * @param acc_lim 加速度限制
 * @param tau 时间常数
 * @param traj 轨迹
 */
void dynamicSmoothingVelocity(
  const size_t start_seg_idx, const double start_vel, const double acc_lim, const double tau,
  MPCTrajectory & traj)
{
  double curr_v = start_vel;
  // 设置起始段的速度
  traj.vx.at(start_seg_idx) = start_vel;
  if (1 < traj.vx.size()) {
    traj.vx.at(start_seg_idx + 1) = start_vel;
  }

  // 动态平滑速度
  for (size_t i = start_seg_idx + 2; i < traj.size(); ++i) {
    const double ds = calcDistance2d(traj, i, i - 1);  // 计算两点之间的距离
    const double dt = ds / std::max(std::fabs(curr_v), std::numeric_limits<double>::epsilon());  // 计算时间间隔
    const double a = tau / std::max(tau + dt, std::numeric_limits<double>::epsilon());  // 计算平滑系数
    const double updated_v = a * curr_v + (1.0 - a) * traj.vx.at(i);  // 更新速度
    const double dv = std::max(-acc_lim * dt, std::min(acc_lim * dt, updated_v - curr_v));  // 限制速度变化
    curr_v = curr_v + dv;  // 更新当前速度
    traj.vx.at(i) = curr_v;  // 存储更新后的速度
  }
  calcMPCTrajectoryTime(traj);  // 重新计算轨迹时间
}

/**
 * @brief 计算最近位姿的插值
 * @param traj 轨迹
 * @param self_pose 自车位姿
 * @param nearest_pose 最近位姿
 * @param nearest_index 最近索引
 * @param nearest_time 最近时间
 * @param max_dist 最大距离
 * @param max_yaw 最大偏航角
 * @return 是否成功
 */
bool calcNearestPoseInterp(
  const MPCTrajectory & traj, const Pose & self_pose, Pose * nearest_pose, size_t * nearest_index,
  double * nearest_time, const double max_dist, const double max_yaw)
{
  if (traj.empty() || !nearest_pose || !nearest_index || !nearest_time) {
    return false;  // 如果输入无效,返回失败
  }

  const auto autoware_traj = convertToAutowareTrajectory(traj);  // 转换为 Autoware Trajectory
  if (autoware_traj.points.empty()) {
    const auto logger = rclcpp::get_logger("mpc_util");
    auto clock = rclcpp::Clock(RCL_ROS_TIME);
    RCLCPP_WARN_THROTTLE(logger, clock, 5000, "[calcNearestPoseInterp] input trajectory is empty");
    return false;  // 如果轨迹为空,返回失败
  }

  *nearest_index = autoware::motion_utils::findFirstNearestIndexWithSoftConstraints(
    autoware_traj.points, self_pose, max_dist, max_yaw);  // 找到最近点的索引
  const size_t traj_size = traj.size();

  if (traj.size() == 1) {
    nearest_pose->position.x = traj.x.at(*nearest_index);  // 最近点的 x 坐标
    nearest_pose->position.y = traj.y.at(*nearest_index);  // 最近点的 y 坐标
    nearest_pose->orientation = createQuaternionFromYaw(traj.yaw.at(*nearest_index));  // 最近点的偏航角
    *nearest_time = traj.relative_time.at(*nearest_index);  // 最近点的时间
    return true;
  }

  /* 获取第二个最近点的索引 */
  const auto [prev, next] = [&]() -> std::pair<size_t, size_t> {
    if (*nearest_index == 0) {
      return std::make_pair(0, 1);  // 如果最近点是第一个点,返回第一个和第二个点
    }
    if (*nearest_index == traj_size - 1) {
      return std::make_pair(traj_size - 2, traj_size - 1);  // 如果最近点是最后一个点,返回倒数第二个和最后一个点
    }

    geometry_msgs::msg::Point nearest_traj_point;
    nearest_traj_point.x = traj.x.at(*nearest_index);
    nearest_traj_point.y = traj.y.at(*nearest_index);
    geometry_msgs::msg::Point next_nearest_traj_point;
    next_nearest_traj_point.x = traj.x.at(*nearest_index + 1);
    next_nearest_traj_point.y = traj.y.at(*nearest_index + 1);

    const double signed_length =
      calcLongitudinalOffset(nearest_traj_point, next_nearest_traj_point, self_pose.position);  // 计算纵向偏移
    if (signed_length <= 0) {
      return std::make_pair(*nearest_index - 1, *nearest_index);  // 如果偏移为负,返回前一个点和最近点
    }
    return std::make_pair(*nearest_index, *nearest_index + 1);  // 否则返回最近点和下一个点
  }();

  geometry_msgs::msg::Point next_traj_point;
  next_traj_point.x = traj.x.at(next);
  next_traj_point.y = traj.y.at(next);
  geometry_msgs::msg::Point prev_traj_point;
  prev_traj_point.x = traj.x.at(prev);
  prev_traj_point.y = traj.y.at(prev);
  const double traj_seg_length =
    autoware::universe_utils::calcDistance2d(prev_traj_point, next_traj_point);  // 计算两点之间的距离
  /* 如果两点距离太近 */
  if (traj_seg_length < 1.0E-5) {
    nearest_pose->position.x = traj.x.at(*nearest_index);  // 最近点的 x 坐标
    nearest_pose->position.y = traj.y.at(*nearest_index);  // 最近点的 y 坐标
    nearest_pose->orientation = createQuaternionFromYaw(traj.yaw.at(*nearest_index));  // 最近点的偏航角
    *nearest_time = traj.relative_time.at(*nearest_index);  // 最近点的时间
    return true;
  }

  /* 线性插值 */
  const double ratio = std::clamp(
    calcLongitudinalOffset(prev_traj_point, next_traj_point, self_pose.position) / traj_seg_length,
    0.0, 1.0);  // 计算插值比例
  nearest_pose->position.x = (1 - ratio) * traj.x.at(prev) + ratio * traj.x.at(next);  // 插值 x 坐标
  nearest_pose->position.y = (1 - ratio) * traj.y.at(prev) + ratio * traj.y.at(next);  // 插值 y 坐标
  const double tmp_yaw_err = normalizeRadian(traj.yaw.at(prev) - traj.yaw.at(next));  // 计算偏航角误差
  const double nearest_yaw = normalizeRadian(traj.yaw.at(next) + (1 - ratio) * tmp_yaw_err);  // 插值偏航角
  nearest_pose->orientation = createQuaternionFromYaw(nearest_yaw);  // 设置偏航角
  *nearest_time = (1 - ratio) * traj.relative_time.at(prev) + ratio * traj.relative_time.at(next);  // 插值时间
  return true;
}

/**
 * @brief 计算停止距离
 * @param current_trajectory 当前轨迹
 * @param origin 起始点索引
 * @return 停止距离
 */
double calcStopDistance(const Trajectory & current_trajectory, const int origin)
{
  constexpr float zero_velocity = std::numeric_limits<float>::epsilon();  // 零速度阈值
  const float origin_velocity =
    current_trajectory.points.at(static_cast<size_t>(origin)).longitudinal_velocity_mps;  // 起始点速度
  double stop_dist = 0.0;

  // 向前搜索
  if (std::fabs(origin_velocity) > zero_velocity) {
    for (int i = origin + 1; i < static_cast<int>(current_trajectory.points.size()) - 1; ++i) {
      const auto & p0 = current_trajectory.points.at(i);
      const auto & p1 = current_trajectory.points.at(i - 1);
      stop_dist += calcDistance2d(p0, p1);  // 累加距离
      if (std::fabs(p0.longitudinal_velocity_mps) < zero_velocity) {
        break;  // 如果速度为零,停止搜索
      }
    }
    return stop_dist;
  }

  // 向后搜索
  for (int i = origin - 1; 0 < i; --i) {
    const auto & p0 = current_trajectory.points.at(i);
    const auto & p1 = current_trajectory.points.at(i + 1);
    if (std::fabs(p0.longitudinal_velocity_mps) > zero_velocity) {
      break;  // 如果速度不为零,停止搜索
    }
    stop_dist -= calcDistance2d(p0, p1);  // 累加距离
  }
  return stop_dist;
}

/**
 * @brief 在偏航方向上扩展轨迹
 * @param yaw 偏航角
 * @param interval 间隔
 * @param is_forward_shift 是否向前偏移
 * @param traj 轨迹
 */
void extendTrajectoryInYawDirection(
  const double yaw, const double interval, const bool is_forward_shift, MPCTrajectory & traj)
{
  // 设置终点偏航角
  traj.yaw.back() = yaw;

  // 获取终点位姿
  const auto autoware_traj = MPCUtils::convertToAutowareTrajectory(traj);
  auto extended_pose = autoware_traj.points.back().pose;

  constexpr double extend_dist = 10.0;  // 扩展距离
  constexpr double extend_vel = 10.0;  // 扩展速度
  const double x_offset = is_forward_shift ? interval : -interval;  // x 方向偏移
  const double dt = interval / extend_vel;  // 时间间隔
  const size_t num_extended_point = static_cast<size_t>(extend_dist / interval);  // 扩展点数
  for (size_t i = 0; i < num_extended_point; ++i) {
    extended_pose = autoware::universe_utils::calcOffsetPose(extended_pose, x_offset, 0.0, 0.0);  // 计算偏移位姿
    traj.push_back(
      extended_pose.position.x, extended_pose.position.y, extended_pose.position.z, traj.yaw.back(),
      extend_vel, traj.k.back(), traj.smooth_k.back(), traj.relative_time.back() + dt);  // 添加扩展点
  }
}

/**
 * @brief 按长度裁剪轨迹
 * @param trajectory 轨迹
 * @param length 长度
 * @return 裁剪后的轨迹
 */
MPCTrajectory clipTrajectoryByLength(const MPCTrajectory & trajectory, const double length)
{
  MPCTrajectory clipped_trajectory;
  clipped_trajectory.push_back(trajectory.at(0));  // 添加第一个点

  double current_length = 0.0;
  for (size_t i = 1; i < trajectory.size(); ++i) {
    current_length += calcDistance3d(trajectory, i, i - 1);  // 累加距离
    if (current_length > length) {
      break;  // 如果超过长度,停止添加
    }
    clipped_trajectory.push_back(trajectory.at(i));  // 添加当前点
  }

  return clipped_trajectory;  // 返回裁剪后的轨迹
}

}  // namespace MPCUtils
}  // namespace autoware::motion::control::mpc_lateral_controller

### 代码功能总结

1. **轨迹处理**:
   - 提供了对 MPC 轨迹的各种操作,包括计算距离、插值、平滑、曲率计算等。
   - 支持轨迹的重采样、扩展和裁剪。

2. **数学工具**:
   - 提供了计算偏航角、曲率、距离等数学工具函数。

3. **轨迹转换**:
   - 支持将 Autoware 的 `Trajectory` 转换为 MPC 的 `MPCTrajectory`,以及反向转换。

4. **动态平滑**:
   - 提供了动态平滑速度的功能,确保速度变化平滑且符合加速度限制。

5. **最近点计算**:
   - 提供了计算自车与轨迹最近点的功能,支持插值计算。

---

如果有进一步的问题或需要更详细的解释,欢迎继续讨论!

八、lowpass_filter.cpp

在mpc.cpp中,用到了低通滤波器对输入进行了滤波。

// 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 "autoware/mpc_lateral_controller/lowpass_filter.hpp"

#include <vector>

namespace autoware::motion::control::mpc_lateral_controller
{
/**
 * @brief Butterworth 二阶低通滤波器的构造函数
 * @param dt 采样时间间隔
 * @param f_cutoff_hz 截止频率(Hz)
 */
Butterworth2dFilter::Butterworth2dFilter(double dt, double f_cutoff_hz)
{
  initialize(dt, f_cutoff_hz);  // 初始化滤波器
}

/**
 * @brief Butterworth 二阶低通滤波器的析构函数
 */
Butterworth2dFilter::~Butterworth2dFilter()
{
}

/**
 * @brief 初始化 Butterworth 二阶低通滤波器
 * @param dt 采样时间间隔
 * @param f_cutoff_hz 截止频率(Hz)
 */
void Butterworth2dFilter::initialize(const double & dt, const double & f_cutoff_hz)
{
  m_y1 = 0.0;  // 上一次的输出
  m_y2 = 0.0;  // 上上次的输出
  m_u2 = 0.0;  // 上一次的输入
  m_u1 = 0.0;  // 上上次的输入

  // 使用双线性变换设计二阶 Butterworth 低通滤波器
  const double f_sampling_hz = 1.0 / dt;  // 采样频率
  const double xi = 1.0 / std::tan(M_PI * f_cutoff_hz / f_sampling_hz);  // 预扭曲频率
  const double xi_2 = xi * xi;  // xi 的平方
  const double q = std::sqrt(2);  // 二阶 Butterworth 滤波器的常数
  m_b0 = 1.0 / (1.0 + q * xi + xi_2);  // 滤波器系数 b0
  m_b1 = 2.0 * m_b0;  // 滤波器系数 b1
  m_b2 = m_b0;  // 滤波器系数 b2
  m_a1 = 2.0 * (xi_2 - 1.0) * m_b0;  // 滤波器系数 a1
  m_a2 = -(1.0 - q * xi + xi_2) * m_b0;  // 滤波器系数 a2
}

/**
 * @brief 对输入信号进行滤波
 * @param u0 当前输入信号
 * @return 滤波后的输出信号
 */
double Butterworth2dFilter::filter(const double & u0)
{
  // 使用差分方程计算滤波后的输出
  double y0 = m_b2 * m_u2 + m_b1 * m_u1 + m_b0 * u0 + m_a2 * m_y2 + m_a1 * m_y1;
  m_y2 = m_y1;  // 更新上上次的输出
  m_y1 = y0;  // 更新上一次的输出
  m_u2 = m_u1;  // 更新上上次的输入
  m_u1 = u0;  // 更新上一次的输入
  return y0;  // 返回滤波后的输出
}

/**
 * @brief 对向量中的信号进行滤波
 * @param t 输入信号向量
 * @param u 滤波后的输出信号向量
 */
void Butterworth2dFilter::filt_vector(const std::vector<double> & t, std::vector<double> & u) const
{
  u.resize(t.size());  // 调整输出向量的大小
  double y1 = t.at(0);  // 上一次的输出
  double y2 = t.at(0);  // 上上次的输出
  double u2 = t.at(0);  // 上上次的输入
  double u1 = t.at(0);  // 上一次的输入
  for (size_t i = 0; i < t.size(); ++i) {
    const double u0 = t.at(i);  // 当前输入信号
    const double y0 = m_b2 * u2 + m_b1 * u1 + m_b0 * u0 + m_a2 * y2 + m_a1 * y1;  // 计算滤波后的输出
    y2 = y1;  // 更新上上次的输出
    y1 = y0;  // 更新上一次的输出
    u2 = u1;  // 更新上上次的输入
    u1 = u0;  // 更新上一次的输入
    u.at(i) = y0;  // 存储滤波后的输出
  }
}

/**
 * @brief 对向量中的信号进行双向滤波(前向和后向)
 * @param t 输入信号向量
 * @param u 滤波后的输出信号向量
 */
void Butterworth2dFilter::filtfilt_vector(
  const std::vector<double> & t, std::vector<double> & u) const
{
  std::vector<double> t_fwd(t);  // 前向滤波结果
  std::vector<double> t_rev(t);  // 后向滤波结果

  // 前向滤波
  filt_vector(t, t_fwd);

  // 后向滤波
  std::reverse(t_rev.begin(), t_rev.end());  // 反转输入信号
  filt_vector(t, t_rev);  // 对反转后的信号进行滤波
  std::reverse(t_rev.begin(), t_rev.end());  // 反转滤波结果

  // 合并前向和后向滤波结果
  u.clear();
  for (size_t i = 0; i < t.size(); ++i) {
    u.push_back((t_fwd[i] + t_rev[i]) * 0.5);  // 取前向和后向滤波结果的平均值
  }
}

/**
 * @brief 获取滤波器的系数
 * @param coeffs 存储滤波器系数的向量
 */
void Butterworth2dFilter::getCoefficients(std::vector<double> & coeffs) const
{
  coeffs.clear();
  coeffs.push_back(m_a1);  // 系数 a1
  coeffs.push_back(m_a2);  // 系数 a2
  coeffs.push_back(m_b0);  // 系数 b0
  coeffs.push_back(m_b1);  // 系数 b1
  coeffs.push_back(m_b2);  // 系数 b2
}

namespace MoveAverageFilter
{
/**
 * @brief 对向量中的信号进行移动平均滤波
 * @param num 移动平均的窗口大小
 * @param u 输入信号向量
 * @return 是否成功
 */
bool filt_vector(const int num, std::vector<double> & u)
{
  if (static_cast<int>(u.size()) < num) {
    return false;  // 如果输入信号长度小于窗口大小,返回失败
  }
  std::vector<double> filtered_u(u);  // 存储滤波后的信号
  for (int i = 0; i < static_cast<int>(u.size()); ++i) {
    double tmp = 0.0;  // 临时变量,用于累加窗口内的信号
    int num_tmp = 0;  // 实际的窗口大小
    double count = 0;  // 窗口内的信号数量
    if ((i - num < 0) || (i + num > static_cast<int>(u.size()) - 1)) {
      num_tmp = std::min(i, static_cast<int>(u.size()) - i - 1);  // 调整窗口大小
    } else {
      num_tmp = num;  // 使用指定的窗口大小
    }

    for (int j = -num_tmp; j <= num_tmp; ++j) {
      int index = i + j;  // 计算窗口内的索引
      if (index >= 0 && index < static_cast<int>(u.size())) {
        tmp += u[static_cast<size_t>(index)];  // 累加窗口内的信号
        ++count;  // 增加信号数量
      }
    }
    filtered_u[static_cast<size_t>(i)] = tmp / count;  // 计算移动平均值
  }
  u = filtered_u;  // 更新输入信号
  return true;  // 返回成功
}
}  // namespace MoveAverageFilter
}  // namespace autoware::motion::control::mpc_lateral_controller

九、steering_predictor.cpp

在mpc.cpp中调用了这部分,进行转向预测。也没啥说的。

// Copyright 2023 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 "autoware/mpc_lateral_controller/steering_predictor.hpp"

namespace autoware::motion::control::mpc_lateral_controller
{

/**
 * @brief 构造函数,初始化转向预测器
 * @param steer_tau 转向系统的时间常数
 * @param steer_delay 转向系统的延迟时间
 */
SteeringPredictor::SteeringPredictor(const double steer_tau, const double steer_delay)
: m_steer_tau(steer_tau), m_input_delay(steer_delay)
{
}

/**
 * @brief 计算转向预测值
 * @return 预测的转向角度
 */
double SteeringPredictor::calcSteerPrediction()
{
  auto t_start = m_time_prev;  // 上一次计算的时间
  auto t_end = m_clock->now();  // 当前时间
  m_time_prev = t_end;  // 更新上一次计算的时间

  const double duration = (t_end - t_start).seconds();  // 计算时间间隔
  const double time_constant = m_steer_tau;  // 转向系统的时间常数

  // 计算初始响应(指数衰减部分)
  const double initial_response = std::exp(-duration / time_constant) * m_steer_prediction_prev;

  // 如果控制命令数量不足,直接返回初始响应
  if (m_ctrl_cmd_vec.size() <= 2) {
    setPrevResult(initial_response);  // 设置上一次的预测结果
    return initial_response;
  }

  // 计算预测的转向角度(初始响应 + 控制命令的贡献)
  const auto predicted_steering = initial_response + getSteerCmdSum(t_start, t_end, time_constant);
  setPrevResult(predicted_steering);  // 设置上一次的预测结果

  return predicted_steering;
}

/**
 * @brief 存储转向控制命令
 * @param steer 转向角度
 */
void SteeringPredictor::storeSteerCmd(const double steer)
{
  // 计算延迟后的时间
  const auto time_delayed = m_clock->now() + rclcpp::Duration::from_seconds(m_input_delay);
  Lateral cmd;
  cmd.stamp = time_delayed;  // 设置时间戳
  cmd.steering_tire_angle = static_cast<float>(steer);  // 设置转向角度

  // 存储控制命令
  m_ctrl_cmd_vec.emplace_back(cmd);

  // 如果控制命令数量不足,直接返回
  if (m_ctrl_cmd_vec.size() <= 2) {
    return;
  }

  // 删除过期的控制命令
  constexpr double store_time = 0.3;  // 存储时间阈值
  if ((time_delayed - m_ctrl_cmd_vec.at(1).stamp).seconds() > m_input_delay + store_time) {
    m_ctrl_cmd_vec.erase(m_ctrl_cmd_vec.begin());  // 删除最早的控制命令
  }
}

/**
 * @brief 计算控制命令的贡献
 * @param t_start 起始时间
 * @param t_end 结束时间
 * @param time_constant 时间常数
 * @return 控制命令的贡献
 */
double SteeringPredictor::getSteerCmdSum(
  const rclcpp::Time & t_start, const rclcpp::Time & t_end, const double time_constant) const
{
  // 如果控制命令数量不足,返回 0
  if (m_ctrl_cmd_vec.size() <= 2) {
    return 0.0;
  }

  // 找到第一个有效控制命令的索引
  size_t idx = 1;
  while (t_start > rclcpp::Time(m_ctrl_cmd_vec.at(idx).stamp)) {
    if ((idx + 1) >= m_ctrl_cmd_vec.size()) {
      return 0.0;  // 如果没有找到有效命令,返回 0
    }
    ++idx;
  }

  // 计算控制命令的贡献
  double steer_sum = 0.0;
  auto t = t_start;
  while (t_end > rclcpp::Time(m_ctrl_cmd_vec.at(idx).stamp)) {
    const double duration = (rclcpp::Time(m_ctrl_cmd_vec.at(idx).stamp) - t).seconds();  // 计算时间间隔
    t = rclcpp::Time(m_ctrl_cmd_vec.at(idx).stamp);  // 更新时间
    steer_sum += (1 - std::exp(-duration / time_constant)) *
                 static_cast<double>(m_ctrl_cmd_vec.at(idx - 1).steering_tire_angle);  // 累加贡献
    ++idx;
    if (idx >= m_ctrl_cmd_vec.size()) {
      break;  // 如果超出范围,退出循环
    }
  }

  // 计算最后一个时间段的贡献
  const double duration = (t_end - t).seconds();
  steer_sum += (1 - std::exp(-duration / time_constant)) *
               static_cast<double>(m_ctrl_cmd_vec.at(idx - 1).steering_tire_angle);

  return steer_sum;
}

/**
 * @brief 设置上一次的预测结果
 * @param steering 预测的转向角度
 */
void SteeringPredictor::setPrevResult(const double & steering)
{
  m_steer_prediction_prev = steering;  // 更新上一次的预测结果
}

}  // namespace autoware::motion::control::mpc_lateral_controller

十、steering_offset

转向偏移量是指车辆的实际转向角度与理论转向角度之间的差值。理论转向角度是根据车辆的运动学模型(如速度、角速度和轴距)计算得出的理想转向角度。

// Copyright 2018-2023 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 "autoware/mpc_lateral_controller/steering_offset/steering_offset.hpp"

#include <algorithm>
#include <iostream>
#include <numeric>

/**
 * @brief 构造函数,初始化转向偏移估计器
 * @param wheelbase 车辆轴距
 * @param average_num 用于计算平均偏移的样本数量
 * @param vel_thres 更新偏移的速度阈值
 * @param steer_thres 更新偏移的转向角度阈值
 * @param offset_limit 偏移量的限制范围
 */
SteeringOffsetEstimator::SteeringOffsetEstimator(
  double wheelbase, double average_num, double vel_thres, double steer_thres, double offset_limit)
: wheelbase_(wheelbase),  // 初始化轴距
  average_num_(average_num),  // 初始化样本数量
  update_vel_threshold_(vel_thres),  // 初始化速度阈值
  update_steer_threshold_(steer_thres),  // 初始化转向角度阈值
  offset_limit_(offset_limit),  // 初始化偏移量限制
  steering_offset_storage_(average_num, 0.0)  // 初始化偏移量存储容器
{
}

/**
 * @brief 更新转向偏移估计
 * @param twist 车辆的速度和角速度
 * @param steering 当前的转向角度
 */
void SteeringOffsetEstimator::updateOffset(
  const geometry_msgs::msg::Twist & twist, const double steering)
{
  // 判断是否满足更新偏移的条件
  const bool update_offset =
    (std::abs(twist.linear.x) > update_vel_threshold_ &&  // 速度大于阈值
     std::abs(steering) < update_steer_threshold_);  // 转向角度小于阈值

  if (!update_offset) return;  // 如果不满足条件,直接返回

  // 计算理论转向角度
  const auto steer_angvel = std::atan2(twist.angular.z * wheelbase_, twist.linear.x);
  // 计算转向偏移量
  const auto steer_offset = steering - steer_angvel;
  // 将偏移量存入存储容器
  steering_offset_storage_.push_back(steer_offset);
  // 如果存储的偏移量数量超过样本数量,移除最早的偏移量
  if (steering_offset_storage_.size() > average_num_) {
    steering_offset_storage_.pop_front();
  }

  // 计算平均偏移量
  steering_offset_ =
    std::accumulate(std::begin(steering_offset_storage_), std::end(steering_offset_storage_), 0.0) /
    std::size(steering_offset_storage_);
}

/**
 * @brief 获取当前的转向偏移量
 * @return 转向偏移量(限制在指定范围内)
 */
double SteeringOffsetEstimator::getOffset() const
{
  // 返回限制在 [-offset_limit_, offset_limit_] 范围内的偏移量
  return std::clamp(steering_offset_, -offset_limit_, offset_limit_);
}

总结

这部分讲了横向跟踪中mpc的相关内容,理解起来不难,就是各个部分很繁琐。

标签:const,mpc,universe,Autoware,traj,源码,param,double,steer
From: https://blog.csdn.net/weixin_48386130/article/details/144915804

相关文章

  • 【关注可白嫖源码】招聘数据爬取与人才特征可视化分析,怎么设计这个系统呢,不会的看过来
    设计一个招聘数据爬取与人才特征可视化分析系统,目的是通过收集各大招聘平台的数据,分析职位需求与人才特征,并进行可视化呈现,以帮助企业或求职者更好地理解市场需求和职业发展趋势。以下是详细的设计方案:1.系统架构设计数据爬取模块:使用Selenium、BeautifulSoup等工具对招......
  • SpringBoot喵喵宠物医院管理系统ti5f6(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表用户,部门分类,医生,宠物信息,日常健康,预约信息,就诊信息,诊断信息,预约统计,医院信息开题报告内容一、课题背景与意义随着现代生活节奏的加快,宠物已成为许多......
  • SpringBoot民宿管理系统l2548(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表客户,员工,客房类型,客房信息,客房预订,入住登记,退房登记,财务,换房登记开题报告内容一、选题背景与意义随着旅游业的快速发展,民宿作为一种新兴的住宿方式,因其......
  • springboot健康挑战社区小程序-计算机毕业设计源码84348
    目 录摘要1绪论1.1选题背景1.2选题意义1.3论文结构与章节安排2 健康挑战社区小程序系统分析2.1可行性分析2.1.1技术可行性分析2.1.2 经济可行性分析2.1.3法律可行性分析2.2系统功能分析2.2.1功能性分析2.2.2非功能性分析2.3 系统用例......
  • springboot毕业论文管理系统-计算机毕业设计源码95511
    摘要随着高等教育的发展和毕业论文在学术界的重要性日益凸显,毕业论文管理系统成为大学教育管理中不可或缺的一部分。本文设计并实现了一套高效的毕业论文管理系统。系统分为管理员、导师和学生三类用户,各自具有不同的权限和功能。管理员负责后台管理、用户管理和选题审核;导师......
  • springboot高校师生健康档案管理系统-计算机毕业设计源码69161
     摘要本文介绍了一款专为高校师生设计的健康档案管理系统。该系统集校园公告、健康资讯和健康知识管理于一体,利用先进的信息化技术为师生提供便捷、全面的健康信息服务。系统具备用户友好的交互界面,支持师生自主录入健康数据,实现健康档案的电子化管理。同时,系统强大的数据统......
  • springboot校园失物招领管理系统-计算机毕业设计源码70344
     目  录1绪论1.1研究背景1.2研究意义1.3论文结构与章节安排2 相关技术介绍2.1springboot框架2.2 Mysql数据库2.3Vue.js主要功能3 系统分析3.1可行性分析3.1.1技术可行性分析3.1.2 经济可行性分析3.1.3法律可行性分析3.2系统功能分析......
  • 【一看就会】autoware universe中有关control部分的参数设置
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、车辆模型1.mirror.param2.simulator_model.param3.vehicle_info.param二、控制相关参数1.pid.param.yaml2.mpc.param.yaml2.pure_pursuit.param.yaml总结前言autowareuniverse虽......
  • ThingsBoard - docker源码打包部署
    【ThingsBoard-docker源码打包部署-哔哩哔哩-如果觉得有用点下关注】 1、Docker-compose拉取镜像部署文档地址:ThingsBoard-源码编译打包部署-CSDN博客2、Dockerfile#使用thingsboard/openjdk17:bookworm-slim作为基础镜像FROMthingsboard/openjdk17:bookworm......
  • ssm远程健康数据管理系统f36p3(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景与意义随着科技的进步和人们健康意识的增强,远程健康管理逐渐成为现代医疗服务的重要组成部分。传统的健康数据管理方式存在诸多不便,尤......