MPC自动驾驶横向控制算法实现 c++

参考博客:
(1)无人车系统(十一):轨迹跟踪模型预测控制(MPC)原理与python实现【40行代码】
(2)【自动驾驶】模型预测控制(MPC)实现轨迹跟踪
(3)自动驾驶——模型预测控制(MPC)理解与实践
(4)MPC算法学习(1)

0 前言

在这里插入图片描述
前面介绍的PID、Pure pursuit、Stanley都只是利用当前的系统误差来设计控制器。每个控制周期只选择一个目标路点作为跟踪对象,以上控制器只利用了模型进行向前一步预测。那么如果更远的未来,参考轨迹变化不是那么平缓,并且有很多弯度小(急)的部分,那么只利用一步预测很难对整条轨迹进行有效的跟踪。

为了让无人车的控制器更有远见,设计控制器必须得利用模型对未来状态进行多步预测。由此引入MPC

1 模型预测控制原理

1.1 核心思想

MPC最核心的思想就是利用三维的空间模型加上时间构成四维时空模型,然后在这个时空模型的基础上,求解最优控制器。MPC基于一段时间的时空模型,得到的控制输出也是系统在未来有限时间步的控制序列。因为理论构建的模型与系统真实模型有误差;从而,更远未来的控制输出对系统控制的价值很低,MPC仅执行输出序列的第一个控制输出
在这里插入图片描述

假设向前预测步长为 T T T,那么 T T T步的时空模型要比原始的空间模型大很多。MPC在每个控制周期都需要重新利用未来 T T T步的模型计算得到当前执行的控制指令。MPC利用了一个比原始空间模型大很多的模型仅仅只得到当前一步的最优控制器(和PID、Pure pursuit、Stanley做了同样的事)

MPC仅考虑未来几个时间步,一定程度上牺牲了最优性。

1.2 MPC优点

① 善于处理多输入多输出系统
② 可以处理约束,如安全性约束,上下阈值
③ MPC是一种向前考虑未来时间步的有限时域优化方法(一定的预测能力)
最优控制要求在整个时间优化,MPC采用了折中的策略,既不是像最优控制那样考虑整个时域,也不是仅仅考虑当前,而是考虑未来的有限时间域

1.3 MPC流程

模型预测控制在k时刻共有三步:
(1)获取系统当前的状态
(2)基于u(k),u(k+1),u(k+2),…,u(k+m)进行最优化处理
离散系统的代价函数:
J = ∑ k m − 1 E k T Q E k + u k T R u k + E N T F E N J=\sum_{k}^{m-1} E^T_kQE_k+u^T_kRu_k+E^T_NFE_N J=km1EkTQEk+ukTRuk+ENTFEN
E N E_N EN表示误差的终值,也是衡量优劣的标准
(3)只取u(k)作为控制输入施加在系统上


在下一时刻重复上面三步,在下一步进行预测时使用的就是下一步的状态值,我们将这样的方案称为滚动优化控制

1.4 MPC vs LQR
MPCLQR
研究对象线性和非线性系统均可线性系统
状态方程:离散化对非线性的状态方程进行线性化,对线性方程离散化对线性方程离散化
目标函数MPC目标函数一个是累计和LQR目标函数一个是积分
求解方法转化为二次规划问题,利用求解器进行求解,生成控制序列采用变分法,通过求解黎卡提方程进行逼近,最终获取控制序列
工作时域是求解预测时间段Np内的控制序列 ,并在下个周期后进行滚动优化求解预测时间段内的控制序列 ,只求解一次,每个周期下取对应的控制量
LQR和MPC的优缺点计算量大,实时性差,对于算力要求高,硬件成本较高① LQR不滚动优化,预测时间段内的控制序列只求解一次,没有考虑实际与规划的误差② LQR求解整个预测时域内的控制序列,而MPC可在更小的时间窗口中求解优化问题,获得次优解,可大大提升求解速度③ 无约束,假设对控制量无约束

2 MPC控制器设计

2.1 线性模型

当模型是线性的,MPC求解可以转化为一个二次规划问题
线性模型: x k + 1 = A x k + B u k + C x_{k+1}=Ax_k+Bu_k+C xk+1=Axk+Buk+C
假定未来T步的控制输入已知,为 u k , u k + 1 , u k + 2 , . . . , u k + T u_k,u_{k+1},u_{k+2},...,u_{k+T} uk,uk+1,uk+2,...,uk+T,根据以上模型与输入,可以计算未来 T 步的状态:
在这里插入图片描述
矩阵向量形式: X = A x k ​ + B u + C X=Ax _k​ +B_u+C X=Axk+Bu+C
其中,
在这里插入图片描述
在这里插入图片描述

2.2 非线性模型

线性化过程损失了模型精度。
无人车几何运动学模型:
在这里插入图片描述
将以上连续的微分模型离散化成差分模型(差分间隔为 d t d_t dt):
在这里插入图片描述
假定无人车需要跟踪的轨迹由离散点{ ( x ˉ 1 , y ˉ 1 ) , ( x ˉ 2 , y ˉ 2 ) , . . . , ( x ˉ M , y ˉ M ) (\bar{x}_1,\bar{y}_1),(\bar{x}_2,\bar{y}_2),...,(\bar{x}_M,\bar{y}_M) (xˉ1,yˉ1),(xˉ2,yˉ2),...,(xˉM,yˉM)}通过三次曲线拟合而成,可表示成由x轴为自变量的函数: y = f ( x ) = c 0 x 3 + c 1 x 2 + c 2 x + c 3 y=f(x)=c_0x^3+c_1x^2+c_2x+c_3 y=f(x)=c0x3+c1x2+c2x+c3

在每一个预测步,可以根据无人车的 x k x_k xk y k y_k yk值计算横向跟踪误差 c t e k cte_k ctek与航向偏差 e p s i k epsi_k epsik
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3 约束

对于约束,一般分为Hard约束Soft约束。Hard约束是不可违背必须遵守的,在控制系统中,输入输出都可能会有约束限制,但是在设计时不建议将输入输出都给予Hard约束,因为这两个约束有可能会有重叠,导致优化器会产生不可行解。

建议输出采用Soft约束,而输入的话建议输入和输入参数变化率二者之间不要同时为Hard约束,可以一个Hard一个Soft。

3 MPC - 无人车轨迹跟踪

车辆的状态量偏差和控制量偏差:
x ~ = [ x ˙ − x ˙ r y ˙ − y ˙ r φ ˙ − φ ˙ r ] , u ~ = [ v − v r δ − δ r ] \tilde{\boldsymbol{x}}=\left[\begin{array}{c} \dot{x}-\dot{x}_{r} \\ \dot{y}-\dot{y}_{r} \\ \dot{\varphi}-\dot{\varphi}_{r} \end{array}\right], \tilde{\boldsymbol{u}}=\left[\begin{array}{c} v-v_{r} \\ \delta-\delta_{r} \end{array}\right] x~= x˙x˙ry˙y˙rφ˙φ˙r ,u~=[vvrδδr]

PID控制器轨迹跟随 实现博客里的运动学模型的离散状态空间方程:
x ~ ( k + 1 ) = [ 1 0 − T v r sin ⁡ φ r 0 1 T v r cos ⁡ φ r 0 0 1 ] x ~ ( k ) + [ T cos ⁡ φ r 0 T sin ⁡ φ r 0 T tan ⁡ δ r l T v r l cos ⁡ 2 δ r ] u ~ ( k ) \tilde{\boldsymbol{x}}(k+1)=\left[\begin{array}{ccc} 1 & 0 & -T v_{r} \sin \varphi_{r} \\ 0 & 1 & T v_{r} \cos \varphi_{r} \\ 0 & 0 & 1 \end{array}\right] \tilde{\boldsymbol{x}}(k)+\left[\begin{array}{cc} T \cos \varphi_{r} & 0 \\ T \sin \varphi_{r} & 0 \\ T \frac{\tan \delta_{r}}{l} & T \frac{v_{r}}{l \cos ^{2} \delta_{r}} \end{array}\right] \tilde{\boldsymbol{u}}(k) x~(k+1)= 100010TvrsinφrTvrcosφr1 x~(k)+ TcosφrTsinφrTltanδr00Tlcos2δrvr u~(k)

为了表示控制系统达到稳定控制所付出的代价,MPC控制的代价函数:
min ⁡ J ( U ) = ∑ k = 0 N − 1 ( x ~ ( k ) T Q x ~ ( k ) + u ~ ( k ) T R u ~ ( k ) ) + x ~ ( N ) T Q f x ~ ( N ) \min J(\boldsymbol{U})=\sum_{k=0}^{N-1}\left(\tilde{\boldsymbol{x}}(k)^{T} Q \tilde{\boldsymbol{x}}(k)+\tilde{\boldsymbol{u}}(k)^{T} R \tilde{\boldsymbol{u}}(k)\right)+\tilde{\boldsymbol{x}}(N)^{T} Q_{f} \tilde{\boldsymbol{x}}(N) minJ(U)=k=0N1(x~(k)TQx~(k)+u~(k)TRu~(k))+x~(N)TQfx~(N)

其中,
U = ( u 0 , u 1 , . . . , u N ) U=(u_0,u_1,...,u_N) U=(u0,u1,...,uN),并且矩阵 Q , Q f , R Q,Q_f,R Q,Qf,R为正定矩阵
Q Q Q:给定状态代价矩阵
Q f Q_f Qf:最终状态代价矩阵
R R R:输入代价矩阵
N N N:时间范围
R > 0 R>0 R>0:任何非0输入都增加 J J J的代价

对于 min ⁡ J ( U ) = ∑ k = 0 N − 1 ( x ~ ( k ) T Q x ~ ( k ) + u ~ ( k ) T R u ~ ( k ) ) + x ~ ( N ) T Q f x ~ ( N ) \min J(\boldsymbol{U})=\sum_{k=0}^{N-1}\left(\tilde{\boldsymbol{x}}(k)^{T} Q \tilde{\boldsymbol{x}}(k)+\tilde{\boldsymbol{u}}(k)^{T} R \tilde{\boldsymbol{u}}(k)\right)+\tilde{\boldsymbol{x}}(N)^{T} Q_{f} \tilde{\boldsymbol{x}}(N) minJ(U)=k=0N1(x~(k)TQx~(k)+u~(k)TRu~(k))+x~(N)TQfx~(N),需要服从的约束条件包括:
① 运动学模型约束 —— x ~ ( k + 1 ) = A x ~ ( k ) + B u ~ ( k ) \tilde{\boldsymbol{x}}(k+1)=\boldsymbol{A} \tilde{\boldsymbol{x}}(k)+\boldsymbol{B} \tilde{\boldsymbol{u}}(k) x~(k+1)=Ax~(k)+Bu~(k)
② 控制量约束 —— ∣ u ~ ( k ) ∣ ≤ u ~ max ⁡ |\tilde{\boldsymbol{u}}(k)| \leq \tilde{\boldsymbol{u}}_{\max } u~(k)u~max
③ 初始状态 —— x ~ ( 0 ) = x ~ 0 \tilde{\boldsymbol{x}}(0)=\tilde{\boldsymbol{x}}_{0} x~(0)=x~0

osqp-eigen官网给出的MPC例子
MPCExample.cpp
在这里插入图片描述

// osqp-eigen
#include "OsqpEigen/OsqpEigen.h"

// eigen
#include <Eigen/Dense>

#include <iostream>

void setDynamicsMatrices(Eigen::Matrix<double, 12, 12>& a, Eigen::Matrix<double, 12, 4>& b)
{
    a << 1., 0., 0., 0., 0., 0., 0.1, 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.1, 0., 0.,
        0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.1, 0., 0., 0., 0.0488, 0., 0., 1., 0., 0., 0.0016,
        0., 0., 0.0992, 0., 0., 0., -0.0488, 0., 0., 1., 0., 0., -0.0016, 0., 0., 0.0992, 0., 0.,
        0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.0992, 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0.,
        0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0.,
        0., 0., 0.9734, 0., 0., 0., 0., 0., 0.0488, 0., 0., 0.9846, 0., 0., 0., -0.9734, 0., 0., 0.,
        0., 0., -0.0488, 0., 0., 0.9846, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.9846;

    b << 0., -0.0726, 0., 0.0726, -0.0726, 0., 0.0726, 0., -0.0152, 0.0152, -0.0152, 0.0152, -0.,
        -0.0006, -0., 0.0006, 0.0006, 0., -0.0006, 0.0000, 0.0106, 0.0106, 0.0106, 0.0106, 0,
        -1.4512, 0., 1.4512, -1.4512, 0., 1.4512, 0., -0.3049, 0.3049, -0.3049, 0.3049, -0.,
        -0.0236, 0., 0.0236, 0.0236, 0., -0.0236, 0., 0.2107, 0.2107, 0.2107, 0.2107;
}

void setInequalityConstraints(Eigen::Matrix<double, 12, 1>& xMax,
                              Eigen::Matrix<double, 12, 1>& xMin,
                              Eigen::Matrix<double, 4, 1>& uMax,
                              Eigen::Matrix<double, 4, 1>& uMin)
{
    double u0 = 10.5916;

    // input inequality constraints
    uMin << 9.6 - u0, 9.6 - u0, 9.6 - u0, 9.6 - u0;

    uMax << 13 - u0, 13 - u0, 13 - u0, 13 - u0;

    // state inequality constraints
    xMin << -M_PI / 6, -M_PI / 6, -OsqpEigen::INFTY, -OsqpEigen::INFTY, -OsqpEigen::INFTY, -1.,
        -OsqpEigen::INFTY, -OsqpEigen::INFTY, -OsqpEigen::INFTY, -OsqpEigen::INFTY,
        -OsqpEigen::INFTY, -OsqpEigen::INFTY;

    xMax << M_PI / 6, M_PI / 6, OsqpEigen::INFTY, OsqpEigen::INFTY, OsqpEigen::INFTY,
        OsqpEigen::INFTY, OsqpEigen::INFTY, OsqpEigen::INFTY, OsqpEigen::INFTY, OsqpEigen::INFTY,
        OsqpEigen::INFTY, OsqpEigen::INFTY;
}

void setWeightMatrices(Eigen::DiagonalMatrix<double, 12>& Q, Eigen::DiagonalMatrix<double, 4>& R)
{
    Q.diagonal() << 0, 0, 10., 10., 10., 10., 0, 0, 0, 5., 5., 5.;
    R.diagonal() << 0.1, 0.1, 0.1, 0.1;
}

void castMPCToQPHessian(const Eigen::DiagonalMatrix<double, 12>& Q,
                        const Eigen::DiagonalMatrix<double, 4>& R,
                        int mpcWindow,
                        Eigen::SparseMatrix<double>& hessianMatrix)
{

    hessianMatrix.resize(12 * (mpcWindow + 1) + 4 * mpcWindow,
                         12 * (mpcWindow + 1) + 4 * mpcWindow);

    // populate hessian matrix
    for (int i = 0; i < 12 * (mpcWindow + 1) + 4 * mpcWindow; i++)
    {
        if (i < 12 * (mpcWindow + 1))
        {
            int posQ = i % 12;
            float value = Q.diagonal()[posQ];
            if (value != 0)
                hessianMatrix.insert(i, i) = value;
        } else
        {
            int posR = i % 4;
            float value = R.diagonal()[posR];
            if (value != 0)
                hessianMatrix.insert(i, i) = value;
        }
    }
}

void castMPCToQPGradient(const Eigen::DiagonalMatrix<double, 12>& Q,
                         const Eigen::Matrix<double, 12, 1>& xRef,
                         int mpcWindow,
                         Eigen::VectorXd& gradient)
{

    Eigen::Matrix<double, 12, 1> Qx_ref;
    Qx_ref = Q * (-xRef);

    // populate the gradient vector
    gradient = Eigen::VectorXd::Zero(12 * (mpcWindow + 1) + 4 * mpcWindow, 1);
    for (int i = 0; i < 12 * (mpcWindow + 1); i++)
    {
        int posQ = i % 12;
        float value = Qx_ref(posQ, 0);
        gradient(i, 0) = value;
    }
}

void castMPCToQPConstraintMatrix(const Eigen::Matrix<double, 12, 12>& dynamicMatrix,
                                 const Eigen::Matrix<double, 12, 4>& controlMatrix,
                                 int mpcWindow,
                                 Eigen::SparseMatrix<double>& constraintMatrix)
{
    constraintMatrix.resize(12 * (mpcWindow + 1) + 12 * (mpcWindow + 1) + 4 * mpcWindow,
                            12 * (mpcWindow + 1) + 4 * mpcWindow);

    // populate linear constraint matrix
    for (int i = 0; i < 12 * (mpcWindow + 1); i++)
    {
        constraintMatrix.insert(i, i) = -1;
    }

    for (int i = 0; i < mpcWindow; i++)
        for (int j = 0; j < 12; j++)
            for (int k = 0; k < 12; k++)
            {
                float value = dynamicMatrix(j, k);
                if (value != 0)
                {
                    constraintMatrix.insert(12 * (i + 1) + j, 12 * i + k) = value;
                }
            }

    for (int i = 0; i < mpcWindow; i++)
        for (int j = 0; j < 12; j++)
            for (int k = 0; k < 4; k++)
            {
                float value = controlMatrix(j, k);
                if (value != 0)
                {
                    constraintMatrix.insert(12 * (i + 1) + j, 4 * i + k + 12 * (mpcWindow + 1))
                        = value;
                }
            }

    for (int i = 0; i < 12 * (mpcWindow + 1) + 4 * mpcWindow; i++)
    {
        constraintMatrix.insert(i + (mpcWindow + 1) * 12, i) = 1;
    }
}

void castMPCToQPConstraintVectors(const Eigen::Matrix<double, 12, 1>& xMax,
                                  const Eigen::Matrix<double, 12, 1>& xMin,
                                  const Eigen::Matrix<double, 4, 1>& uMax,
                                  const Eigen::Matrix<double, 4, 1>& uMin,
                                  const Eigen::Matrix<double, 12, 1>& x0,
                                  int mpcWindow,
                                  Eigen::VectorXd& lowerBound,
                                  Eigen::VectorXd& upperBound)
{
    // evaluate the lower and the upper inequality vectors
    Eigen::VectorXd lowerInequality
        = Eigen::MatrixXd::Zero(12 * (mpcWindow + 1) + 4 * mpcWindow, 1);
    Eigen::VectorXd upperInequality
        = Eigen::MatrixXd::Zero(12 * (mpcWindow + 1) + 4 * mpcWindow, 1);
    for (int i = 0; i < mpcWindow + 1; i++)
    {
        lowerInequality.block(12 * i, 0, 12, 1) = xMin;
        upperInequality.block(12 * i, 0, 12, 1) = xMax;
    }
    for (int i = 0; i < mpcWindow; i++)
    {
        lowerInequality.block(4 * i + 12 * (mpcWindow + 1), 0, 4, 1) = uMin;
        upperInequality.block(4 * i + 12 * (mpcWindow + 1), 0, 4, 1) = uMax;
    }

    // evaluate the lower and the upper equality vectors
    Eigen::VectorXd lowerEquality = Eigen::MatrixXd::Zero(12 * (mpcWindow + 1), 1);
    Eigen::VectorXd upperEquality;
    lowerEquality.block(0, 0, 12, 1) = -x0;
    upperEquality = lowerEquality;
    lowerEquality = lowerEquality;

    // merge inequality and equality vectors
    lowerBound = Eigen::MatrixXd::Zero(2 * 12 * (mpcWindow + 1) + 4 * mpcWindow, 1);
    lowerBound << lowerEquality, lowerInequality;

    upperBound = Eigen::MatrixXd::Zero(2 * 12 * (mpcWindow + 1) + 4 * mpcWindow, 1);
    upperBound << upperEquality, upperInequality;
}

void updateConstraintVectors(const Eigen::Matrix<double, 12, 1>& x0,
                             Eigen::VectorXd& lowerBound,
                             Eigen::VectorXd& upperBound)
{
    lowerBound.block(0, 0, 12, 1) = -x0;
    upperBound.block(0, 0, 12, 1) = -x0;
}

double getErrorNorm(const Eigen::Matrix<double, 12, 1>& x, const Eigen::Matrix<double, 12, 1>& xRef)
{
    // evaluate the error
    Eigen::Matrix<double, 12, 1> error = x - xRef;

    // return the norm
    return error.norm();
}

int main()
{
    // set the preview window
    int mpcWindow = 20;

    // allocate the dynamics matrices
    Eigen::Matrix<double, 12, 12> a;
    Eigen::Matrix<double, 12, 4> b;

    // allocate the constraints vector
    Eigen::Matrix<double, 12, 1> xMax;
    Eigen::Matrix<double, 12, 1> xMin;
    Eigen::Matrix<double, 4, 1> uMax;
    Eigen::Matrix<double, 4, 1> uMin;

    // allocate the weight matrices
    Eigen::DiagonalMatrix<double, 12> Q;
    Eigen::DiagonalMatrix<double, 4> R;

    // allocate the initial and the reference state space
    Eigen::Matrix<double, 12, 1> x0;
    Eigen::Matrix<double, 12, 1> xRef;

    // allocate QP problem matrices and vectores
    Eigen::SparseMatrix<double> hessian;
    Eigen::VectorXd gradient;
    Eigen::SparseMatrix<double> linearMatrix;
    Eigen::VectorXd lowerBound;
    Eigen::VectorXd upperBound;

    // set the initial and the desired states
    x0 << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
    xRef << 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0;

    // set MPC problem quantities
    setDynamicsMatrices(a, b);
    setInequalityConstraints(xMax, xMin, uMax, uMin);
    setWeightMatrices(Q, R);

    // cast the MPC problem as QP problem
    castMPCToQPHessian(Q, R, mpcWindow, hessian);
    castMPCToQPGradient(Q, xRef, mpcWindow, gradient);
    castMPCToQPConstraintMatrix(a, b, mpcWindow, linearMatrix);
    castMPCToQPConstraintVectors(xMax, xMin, uMax, uMin, x0, mpcWindow, lowerBound, upperBound);

    // instantiate the solver
    OsqpEigen::Solver solver;

    // settings
    // solver.settings()->setVerbosity(false);
    solver.settings()->setWarmStart(true);

    // set the initial data of the QP solver
    solver.data()->setNumberOfVariables(12 * (mpcWindow + 1) + 4 * mpcWindow);
    solver.data()->setNumberOfConstraints(2 * 12 * (mpcWindow + 1) + 4 * mpcWindow);
    if (!solver.data()->setHessianMatrix(hessian))
        return 1;
    if (!solver.data()->setGradient(gradient))
        return 1;
    if (!solver.data()->setLinearConstraintsMatrix(linearMatrix))
        return 1;
    if (!solver.data()->setLowerBound(lowerBound))
        return 1;
    if (!solver.data()->setUpperBound(upperBound))
        return 1;

    // instantiate the solver
    if (!solver.initSolver())
        return 1;

    // controller input and QPSolution vector
    Eigen::Vector4d ctr;
    Eigen::VectorXd QPSolution;

    // number of iteration steps
    int numberOfSteps = 50;

    for (int i = 0; i < numberOfSteps; i++)
    {

        // solve the QP problem
        if (!solver.solve())
            return 1;

        // get the controller input
        QPSolution = solver.getSolution();
        ctr = QPSolution.block(12 * (mpcWindow + 1), 0, 4, 1);

        // save data into file
        auto x0Data = x0.data();

        // propagate the model
        x0 = a * x0 + b * ctr;

        // update the constraint bound
        updateConstraintVectors(x0, lowerBound, upperBound);
        if (!solver.updateBounds(lowerBound, upperBound))
            return 1;
    }
    return 0;
}

MPC.h

#pragma once

#include <iostream>
#include <vector>
#include <Eigen/Dense>
#include "KinematicModel.h"
#include "OsqpEigen/OsqpEigen.h"
#include <osqp/osqp.h>
#include "matplotlibcpp.h"

namespace plt = matplotlibcpp;
using namespace std;
using namespace Eigen;
using namespace OsqpEigen;

namespace {
    const double MAX_STEER = M_PI / 6.0;
    const double MAX_VEL = 10.0;
}
class MPC {
private:
    int NX, NU, T;
    MatrixXd R = MatrixXd::Identity(NU, NU);
    MatrixXd Rd = MatrixXd::Identity(NU, NU);
    MatrixXd Q = MatrixXd::Identity(NX, NX);
    MatrixXd Qf = Q;
public:
    MPC(int nx, int nu, int t);
    vector<double> LMPC(MatrixXd xref, Vector3d x0, MatrixXd ref_delta, KinematicModel model);
};

MPC.cpp

#include "MPC.h"

MPC::MPC(int nx, int nu, int t) : NX(nx), NU(nu), T(t) {}

vector<double> MPC::LMPC(MatrixXd xref, Vector3d x0, MatrixXd ref_delta, KinematicModel model)
{
    int NX = xref.rows();      // 3
    int NU = ref_delta.rows(); // 2
    int T = xref.cols() - 1;  // Horizon length. // 2

    // Define optimization variables.
    MatrixXd x(NX, T + 1); // 3, 3
    MatrixXd u(NU, T); // 1, 2
    // Store A matrices.
    vector<MatrixXd> A_vec;
    // Store B matrices.
    vector<MatrixXd> B_vec;

    // Initialize A and B matrices.
    for (int t = 0; t < T; ++t) {
        auto state_space = model.stateSpace(ref_delta(1, t), xref(2, t));
        A_vec.push_back(state_space[0]);
        B_vec.push_back(state_space[1]);
    }

    // Define the optimization problem.
    VectorXd cost(T + 1); // t + 1
    // List of constraint indices.
    vector<vector<int>> constraints;
    for (int t = 0; t < T; ++t) {
        cost(t) = (u.col(t) - ref_delta.col(t)).transpose() * R * (u.col(t) - ref_delta.col(t));

        if (t != 0) {
            cost(t) += (x.col(t) - xref.col(t)).transpose() * Q * (x.col(t) - xref.col(t));
        }

        MatrixXd A = A_vec[t];
        MatrixXd B = B_vec[t];
        // 3 6  6 9
        constraints.push_back({(t + 1) * NX, (t + 1) * NX + NX});  // State constraints.
        // 0 2 2 4
        constraints.push_back({t * NU, t * NU + NU});  // Input constraints.

        x.col(t + 1) = A * x.col(t) + B * (u.col(t) - ref_delta.col(t));
    }
    
    // Final state cost.
    cost(T) = (x.col(T) - xref.col(T)).transpose() * Qf * (x.col(T) - xref.col(T));

    // Set initial state.
    x.col(0) = x0; // 3

    VectorXd lower_bound(T * NU); // 4
    VectorXd upper_bound(T * NU); // 4

    for (int t = 0; t < T; t++)
    {
        lower_bound.segment(t * NU, NU) << -MAX_VEL, -MAX_STEER; // (1:2) (3:4)
        upper_bound.segment(t * NU, NU) << MAX_VEL, MAX_STEER; // (1:2) (3:4)
    }

    // Solve the optimization problem.
    OsqpEigen::Solver solver;

   // solver.settings()->setVerbosity(false);
    solver.settings()->setWarmStart(true);

    // A: T * (NU + NX) x NX * (T + 1)
   // solver.data()->setNumberOfVariables(NX * (T + 1)); // 9
   // solver.data()->setNumberOfConstraints(T * (NU + NX));// 10

    solver.data()->setNumberOfVariables(NX * (T + 1)); //变量数n 9
    solver.data()->setNumberOfConstraints(T * (NU + NX));// 约束数m 10

    // P:NX * (T + 1) x NX * (T + 1)
    // Define the Hessian matrix dimension.
    int N = NX * (T + 1); // 9

    // Define and set the Hessian matrix.
    Eigen::SparseMatrix<double> P(N, N); // 9 x 9

    for (int t = 0; t < T; ++t) {
        for (int i = 0; i < NU; ++i) {
            P.coeffRef(t * NU + i, t * NU + i) = R(i, i);
        }
    }

    if (!solver.data()->setHessianMatrix(P)) return {};

    // Define the gradient vector (cost vector).
    VectorXd q(N); // 9 x 1

    for (int t = 0; t < T; ++t) {
        q.segment(t * NU, NU) = cost(t) * VectorXd::Ones(NU);
    }

    if (!solver.data()->setGradient(q)) return {};

    // Define the linear equality constraint matrix Aeq.
    int M = T * (NX + NU);  // Number of equality constraints. // M = 10
    Eigen::SparseMatrix<double> linearMatrix(M, N);  // 10 x 9
    for (int i = 0; i < M; i++)
    {
        for (int j = 0; j < N; j++)
        {
            if (i == j) linearMatrix.insert(i, j) = 1;
            else linearMatrix.insert(i, j) = 0;
        }
    }

    if (!solver.data()->setLinearConstraintsMatrix(linearMatrix)) return {};
    // Define the equality constraint vector beq.
    VectorXd beq(M); // 10 x 1
  //  for (int i = 0; i < M; i++)
  //  {
 //       beq(i) = 0;
 //   }

    // You should populate Aeq and beq based on your state dynamics.

    // Set lower and upper bounds for variables and constraints.
    if (!solver.data()->setLowerBound(lower_bound)) {
        cerr << "Error setting lower bound." << endl;
        return {};
    }

    if (!solver.data()->setUpperBound(upper_bound)) {
        cerr << "Error setting upper bound." << endl;
        return {};
    }

    // Initialize the solver.
    if (!solver.initSolver()) return {};

    // Solve the problem.
    if (!solver.solve()) {
        cerr << "Error solving the optimization problem." << endl;
        return {};
    }
    VectorXd optimal_solution = solver.getSolution();

    // Extract optimal control inputs.
    vector<double> optimal_input;

    for (int t = 0; t < T; ++t) {
        VectorXd u_t = optimal_solution.segment(t * NU, NU); // (1,2) (3,4)
        optimal_input.push_back(u_t(0));  // Extract the velocity input.
    }
    cout << optimal_input[1] << endl;
    return optimal_input;
}

main.cpp

#include "MPC.h"
#include "Reference_path.h"
#include "KinematicModel.h"
#include "matplotlibcpp.h"
#include "pid_controller.h"

namespace plt = matplotlibcpp;

int main()
{
    parameters param;
    param.NX = 3;
    param.NU = 2;
    param.T = 2;
    param.L = 2.2;
    param.dt = 0.1;
    double Target_speed = 8.0 / 3.6;
    MPC mpc(param.NX, param.NU, param.T);

    Eigen::VectorXd x0(param.NX);

    x0 << 0.0, -3.0, 0.0; // x y yaw
    vector<double> robot_state = {0.0, -3.0, 0.0, 0.0};
    double dt = 0.1;
    double L = 2.2;
    ReferencePath reference_path;
    auto reference_trajectory = reference_path.calc_ref_trajectory(robot_state, param, 1.0);
    KinematicModel model(x0(0), x0(1), x0(2), 0.0, L, dt);
    PID_controller PID(3, 0.001, 30, Target_speed, 15.0 / 3.6, 0.0);

    std::vector<double> x_;
    std::vector<double> y_;

    for (int i = 0; i < param.T; i++)
    {
        Eigen::MatrixXd xref = reference_trajectory.xref;
        Eigen::VectorXd xref_i = xref.col(i); // 3 x 1
        Eigen::VectorXd ref_delta = reference_trajectory.dref.col(i); // 2 x 1

        std::vector<double> control_input = mpc.LMPC(xref_i, x0, ref_delta, model);
        cout << control_input[1] << endl;
        double a = PID.calOutput(model.v);

        model.updateState(a, control_input[1]);
        cout << "Speed: " << model.v << " m/s" << endl;

        x_.push_back(model.getState()[0]);
        y_.push_back(model.getState()[1]);

        const auto state = model.getState();
        x0 << state[0], state[1], state[2];

        //画参考轨迹
        plt::plot(reference_path.ref_x, reference_path.ref_y, "b--");
        plt::grid(true);
        plt::ylim(-5, 5);
        plt::plot(x_, y_, "r");
        plt::pause(0.01);
    }
    const char* filename = "./MPC.png";
    plt::save(filename);
    plt::show();
    return 0;
}

MPC.cpp有bug,有空时更正

  • 24
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值