{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2 MPC\n",
    "This notebook contains the CVXPY implementation of a MPC\n",
    "本篇笔记包含了一个基于CVXPY的MPC实现\n",
    "This is the simplest one\n",
    "这是最简单的一个"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### MPC Problem formulation MPC问题的表述\n",
    "\n",
    "**Model Predictive Control** refers to the control approach of **numerically** solving a optimization problem at each time step.\n",
    "**模型预测控制**是指在每个时间步上**数值**地解决一个优化问题的控制方法。 \n",
    "\n",
    "The controller generates a control signal over a fixed lenght T (Horizon) at each time step.\n",
    "控制器在每个时间步上生成一个固定长度T（视野）的控制信号。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![mpc](img/mpc_block_diagram.png)\n",
    "\n",
    "![mpc](img/mpc_t.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Linear MPC Formulation 线性MPC表述\n",
    "\n",
    "Linear MPC makes use of the **LTI** (Linear time invariant) discrete state space model, wich represents a motion model used for Prediction.\n",
    "线性MPC使用**LTI**（线性时不变）离散状态空间模型，该模型表示用于预测的运动模型。\n",
    "\n",
    "$x_{t+1} = Ax_t + Bu_t$\n",
    "\n",
    "The LTI formulation means that **future states** are linearly related to the current state and actuator signal. Hence, the MPC seeks to find a **control policy** U over a finite lenght horizon.\n",
    "LTI表述意味着**未来状态**与当前状态和执行器信号之间是线性相关的。因此，MPC试图找到一个有限长度视野上的**控制策略**U。\n",
    "\n",
    "$U={u_{t|t}, u_{t+1|t}, ...,u_{t+T|t}}$\n",
    "\n",
    "The objective function used minimize (drive the state to 0) is:\n",
    "用于最小化（将状态驱动至0）的目标函数为：\n",
    "\n",
    "$\n",
    "\\begin{equation}\n",
    "\\begin{aligned}\n",
    "\\min_{} \\quad &  \\sum^{t+T-1}_{j=t} x^T_{j|t}Qx_{j|t} + u^T_{j|t}Ru_{j|t}\\\\\n",
    "\\textrm{s.t.} \\quad &  x(0) = x0\\\\\n",
    "  & x_{j+1|t} = Ax_{j|t}+Bu_{j|t}) \\quad  \\textrm{for} \\quad t<j<t+T-1    \\\\\n",
    "\\end{aligned}\n",
    "\\end{equation}\n",
    "$\n",
    "\n",
    "Other linear constrains may be applied,for instance on the control variable:\n",
    "其他线性约束也可以应用，比如对控制变量的约束\n",
    "\n",
    "$ U_{MIN} < u_{j|t} < U_{MAX}    \\quad  \\textrm{for} \\quad t<j<t+T-1 $\n",
    "\n",
    "The objective fuction accounts for quadratic error on deviation from 0 of the state and the control inputs sequences. Q and R are the **weight matrices** and are used to tune the response.\n",
    "目标函数考虑了状态偏离0的二次误差以及控制输入序列的二次误差。矩阵Q和R是权重矩阵，用于调整系统响应\n",
    "Because the goal is tracking a **reference signal** such as a trajectory, the objective function is rewritten as:\n",
    "由于目标是跟踪参考信号（例如轨迹），目标函数被重写为：\n",
    "\n",
    "$\n",
    "\\begin{equation}\n",
    "\\begin{aligned}\n",
    "\\min_{} \\quad &  \\sum^{t+T-1}_{j=t} \\delta x^T_{j|t}Q\\delta x_{j|t} + u^T_{j|t}Ru_{j|t}\n",
    "\\end{aligned}\n",
    "\\end{equation}\n",
    "$\n",
    "\n",
    "where the error w.r.t desired state is accounted for:\n",
    "其中考虑了相对于期望状态的误差：\n",
    "\n",
    "$ \\delta x = x_{j,t,ref} - x_{j,t} $"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Problem Formulation: Study case  研究案例问题表述\n",
    "\n",
    "In this case, the objective function to minimize is given by:\n",
    "在这种情况下，要最小化的目标函数为：\n",
    "\n",
    "https://borrelli.me.berkeley.edu/pdfpub/IV_KinematicMPC_jason.pdf\n",
    "\n",
    "$\n",
    "\\begin{equation}\n",
    "\\begin{aligned}\n",
    "\\min_{} \\quad &  \\sum^{t+T-1}_{j=t} (x_{j} - x_{j,ref})^TQ(x_{j} - x_{j,ref}) + \\sum^{t+T-1}_{j=t+1} u^T_{j}Ru_{j} + (u_{j} - u_{j-1})^TP(u_{j} - u_{j-1}) \\\\\n",
    "\\textrm{s.t.} \\quad &  x(0) = x0\\\\\n",
    "  & x_{j+1} = Ax_{j}+Bu_{j} \\quad  \\textrm{for} \\quad t<j<t+T-1  \\\\\n",
    "  & u_{MIN} < u_{j} < u_{MAX} \\quad  \\textrm{for} \\quad t<j<t+T-1 \\\\\n",
    "  & \\dot{u}_{MIN} < \\frac{(u_{j} - u_{j-1})}{ts} < \\dot{u}_{MAX} \\quad  \\textrm{for} \\quad t+1<j<t+T-1 \\\\\n",
    "\\end{aligned}\n",
    "\\end{equation}\n",
    "$\n",
    "\n",
    "\n",
    "Where R,P,Q are the cost matrices used to tune the response.\n",
    "R,P,Q是用于调整响应的成本矩阵。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## PRELIMINARIES 准备工作\n",
    "\n",
    "* linearized system dynamics 线性化系统动力学\n",
    "* function to represent the track 轨迹函数\n",
    "* function to represent the **reference trajectory** to track 跟踪的**参考轨迹**函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-10-23T06:09:14.320605Z",
     "start_time": "2024-10-23T06:09:13.499103Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from scipy.integrate import odeint\n",
    "from scipy.interpolate import interp1d\n",
    "import cvxpy as cp\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.style.use(\"ggplot\")\n",
    "\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-10-23T06:09:15.094392Z",
     "start_time": "2024-10-23T06:09:15.092291Z"
    }
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "Control problem statement.\n",
    "控制问题陈述。\n",
    "\"\"\"\n",
    "\n",
    "N = 4  # number of state variables (x, y, v, theta)\n",
    "M = 2  # number of control variables (a, delta)\n",
    "T = 20  # Prediction Horizon (time steps)\n",
    "DT = 0.2  # discretization step [s]\n",
    "\n",
    "\n",
    "def get_linear_model(x_bar, u_bar):\n",
    "    \"\"\"\n",
    "    Computes the LTI approximated state space model x' = Ax + Bu + C\n",
    "    计算LTI近似状态空间模型x' = Ax + Bu + C\n",
    "    \"\"\"\n",
    "\n",
    "    L = 0.3  # vehicle wheelbase\n",
    "\n",
    "    x = x_bar[0]\n",
    "    y = x_bar[1]\n",
    "    v = x_bar[2]\n",
    "    theta = x_bar[3]\n",
    "\n",
    "    a = u_bar[0]\n",
    "    delta = u_bar[1]\n",
    "\n",
    "    A = np.zeros((N, N))\n",
    "    A[0, 2] = np.cos(theta)\n",
    "    A[0, 3] = -v * np.sin(theta)\n",
    "    A[1, 2] = np.sin(theta)\n",
    "    A[1, 3] = v * np.cos(theta)\n",
    "    A[3, 2] = v * np.tan(delta) / L\n",
    "    A_lin = np.eye(N) + DT * A\n",
    "\n",
    "    B = np.zeros((N, M))\n",
    "    B[2, 0] = 1\n",
    "    B[3, 1] = v / (L * np.cos(delta) ** 2)\n",
    "    B_lin = DT * B\n",
    "\n",
    "    f_xu = np.array(\n",
    "        [v * np.cos(theta), v * np.sin(theta), a, v * np.tan(delta) / L]\n",
    "    ).reshape(N, 1)\n",
    "    C_lin = DT * (\n",
    "        f_xu - np.dot(A, x_bar.reshape(N, 1)) - np.dot(B, u_bar.reshape(M, 1))\n",
    "    )\n",
    "\n",
    "    return np.round(A_lin, 4), np.round(B_lin, 4), np.round(C_lin, 4)\n",
    "\n",
    "\n",
    "\"\"\"\n",
    "the ODE is used to update the simulation given the mpc results\n",
    "I use this insted of using the LTI twice\n",
    "ODE用于根据mpc结果更新仿真，而不是两次使用LTI\n",
    "\"\"\"\n",
    "\n",
    "\n",
    "def kinematics_model(x, t, u):\n",
    "    \"\"\"\n",
    "    Returns the set of ODE of the vehicle model.\n",
    "    返回车辆模型的ODE模型\n",
    "    \"\"\"\n",
    "\n",
    "    L = 0.3  # vehicle wheelbase\n",
    "    dxdt = x[2] * np.cos(x[3])\n",
    "    dydt = x[2] * np.sin(x[3])\n",
    "    dvdt = u[0]\n",
    "    dthetadt = x[2] * np.tan(u[1]) / L\n",
    "\n",
    "    dqdt = [dxdt, dydt, dvdt, dthetadt]\n",
    "\n",
    "    return dqdt\n",
    "\n",
    "\n",
    "def predict(x0, u):\n",
    "    \"\"\"\n",
    "    预测车辆的真实轨迹，使用ODE求解 \n",
    "    \"\"\"\n",
    "\n",
    "    x_ = np.zeros((N, T + 1))\n",
    "\n",
    "    x_[:, 0] = x0\n",
    "\n",
    "    # solve ODE\n",
    "    for t in range(1, T + 1):\n",
    "\n",
    "        tspan = [0, DT]\n",
    "        x_next = odeint(kinematics_model, x0, tspan, args=(u[:, t - 1],))\n",
    "\n",
    "        x0 = x_next[1]\n",
    "        x_[:, t] = x_next[1]\n",
    "\n",
    "    return x_\n",
    "\n",
    "\n",
    "def compute_path_from_wp(start_xp, start_yp, step=0.1):\n",
    "    \"\"\"\n",
    "    Computes a reference path given a set of waypoints\n",
    "    给定一组路径点，计算参考路径\n",
    "    \"\"\"\n",
    "\n",
    "    final_xp = []\n",
    "    final_yp = []\n",
    "    delta = step  # [m]\n",
    "\n",
    "    for idx in range(len(start_xp) - 1):\n",
    "        section_len = np.sum(\n",
    "            np.sqrt(\n",
    "                np.power(np.diff(start_xp[idx : idx + 2]), 2)\n",
    "                + np.power(np.diff(start_yp[idx : idx + 2]), 2)\n",
    "            )\n",
    "        )\n",
    "\n",
    "        interp_range = np.linspace(0, 1, np.floor(section_len / delta).astype(int))\n",
    "\n",
    "        fx = interp1d(np.linspace(0, 1, 2), start_xp[idx : idx + 2], kind=1)\n",
    "        fy = interp1d(np.linspace(0, 1, 2), start_yp[idx : idx + 2], kind=1)\n",
    "\n",
    "        final_xp = np.append(final_xp, fx(interp_range))\n",
    "        final_yp = np.append(final_yp, fy(interp_range))\n",
    "\n",
    "    dx = np.append(0, np.diff(final_xp))\n",
    "    dy = np.append(0, np.diff(final_yp))\n",
    "    theta = np.arctan2(dy, dx)\n",
    "\n",
    "    return np.vstack((final_xp, final_yp, theta))\n",
    "\n",
    "\n",
    "def get_nn_idx(state, path):\n",
    "    \"\"\"\n",
    "    Computes the index of the waypoint closest to vehicle\n",
    "    计算最接近车辆的路径点的索引\n",
    "    \"\"\"\n",
    "\n",
    "    dx = state[0] - path[0, :]\n",
    "    dy = state[1] - path[1, :]\n",
    "    dist = np.hypot(dx, dy)\n",
    "    nn_idx = np.argmin(dist)\n",
    "\n",
    "    try:\n",
    "        v = [\n",
    "            path[0, nn_idx + 1] - path[0, nn_idx],\n",
    "            path[1, nn_idx + 1] - path[1, nn_idx],\n",
    "        ]\n",
    "        v /= np.linalg.norm(v)\n",
    "\n",
    "        d = [path[0, nn_idx] - state[0], path[1, nn_idx] - state[1]]\n",
    "\n",
    "        if np.dot(d, v) > 0:\n",
    "            target_idx = nn_idx\n",
    "        else:\n",
    "            target_idx = nn_idx + 1\n",
    "\n",
    "    except IndexError as e:\n",
    "        target_idx = nn_idx\n",
    "\n",
    "    return target_idx\n",
    "\n",
    "\n",
    "def get_ref_trajectory(state, path, target_v):\n",
    "    \"\"\"\n",
    "    Adapted from pythonrobotics\n",
    "    获取参考轨迹\n",
    "    从当前位置开始，截取路径上的一段作为参考轨迹，其中v=固定的，参考的方向转角为0\n",
    "    \"\"\"\n",
    "    xref = np.zeros((N, T + 1))\n",
    "    dref = np.zeros((1, T + 1))\n",
    "\n",
    "    # sp = np.ones((1,T +1))*target_v #speed profile\n",
    "\n",
    "    ncourse = path.shape[1]\n",
    "\n",
    "    ind = get_nn_idx(state, path)\n",
    "\n",
    "    xref[0, 0] = path[0, ind]  # X\n",
    "    xref[1, 0] = path[1, ind]  # Y\n",
    "    xref[2, 0] = target_v  # sp[ind] #V\n",
    "    xref[3, 0] = path[2, ind]  # Theta\n",
    "    dref[0, 0] = 0.0  # steer operational point should be 0\n",
    "\n",
    "    dl = 0.05  # Waypoints spacing [m]\n",
    "    travel = 0.0\n",
    "\n",
    "    for i in range(T + 1):\n",
    "        travel += abs(target_v) * DT  # current V or target V?\n",
    "        dind = int(round(travel / dl))\n",
    "\n",
    "        if (ind + dind) < ncourse:\n",
    "            xref[0, i] = path[0, ind + dind]\n",
    "            xref[1, i] = path[1, ind + dind]\n",
    "            xref[2, i] = target_v  # sp[ind + dind]\n",
    "            xref[3, i] = path[2, ind + dind]\n",
    "            dref[0, i] = 0.0\n",
    "        else:\n",
    "            xref[0, i] = path[0, ncourse - 1]\n",
    "            xref[1, i] = path[1, ncourse - 1]\n",
    "            xref[2, i] = 0.0  # stop? #sp[ncourse - 1]\n",
    "            xref[3, i] = path[2, ncourse - 1]\n",
    "            dref[0, i] = 0.0\n",
    "\n",
    "    return xref, dref"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## MPC \n",
    "\n",
    "test single iteration\n",
    "测试单次迭代"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-10-23T08:23:39.847687Z",
     "start_time": "2024-10-23T08:23:39.780867Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "===============================================================================\n",
      "                                     CVXPY                                     \n",
      "                                     v1.5.3                                    \n",
      "===============================================================================\n",
      "(CVXPY) Oct 23 04:23:39 PM: Your problem has 124 variables, 166 constraints, and 0 parameters.\n",
      "(CVXPY) Oct 23 04:23:39 PM: It is compliant with the following grammars: DCP, DQCP\n",
      "(CVXPY) Oct 23 04:23:39 PM: (If you need to solve this problem multiple times, but with different data, consider using parameters.)\n",
      "(CVXPY) Oct 23 04:23:39 PM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.\n",
      "(CVXPY) Oct 23 04:23:39 PM: Your problem is compiled with the CPP canonicalization backend.\n",
      "-------------------------------------------------------------------------------\n",
      "                                  Compilation                                  \n",
      "-------------------------------------------------------------------------------\n",
      "(CVXPY) Oct 23 04:23:39 PM: Compiling problem (target solver=OSQP).\n",
      "(CVXPY) Oct 23 04:23:39 PM: Reduction chain: CvxAttr2Constr -> Qp2SymbolicQp -> QpMatrixStuffing -> OSQP\n",
      "(CVXPY) Oct 23 04:23:39 PM: Applying reduction CvxAttr2Constr\n",
      "(CVXPY) Oct 23 04:23:39 PM: Applying reduction Qp2SymbolicQp\n",
      "(CVXPY) Oct 23 04:23:39 PM: Applying reduction QpMatrixStuffing\n",
      "(CVXPY) Oct 23 04:23:39 PM: Applying reduction OSQP\n",
      "(CVXPY) Oct 23 04:23:39 PM: Finished problem compilation (took 2.905e-02 seconds).\n",
      "-------------------------------------------------------------------------------\n",
      "                                Numerical solver                               \n",
      "-------------------------------------------------------------------------------\n",
      "(CVXPY) Oct 23 04:23:39 PM: Invoking solver OSQP  to obtain a solution.\n",
      "-------------------------------------------------------------------------------\n",
      "                                    Summary                                    \n",
      "-------------------------------------------------------------------------------\n",
      "(CVXPY) Oct 23 04:23:39 PM: Problem status: optimal\n",
      "(CVXPY) Oct 23 04:23:39 PM: Optimal value: 5.630e+02\n",
      "(CVXPY) Oct 23 04:23:39 PM: Compilation took 2.905e-02 seconds\n",
      "(CVXPY) Oct 23 04:23:39 PM: Solver (including time spent in interface) took 3.982e-03 seconds\n",
      "CPU times: user 60.9 ms, sys: 5.23 ms, total: 66.1 ms\n",
      "Wall time: 62.7 ms\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:27: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  A[0, 2] = np.cos(theta)\n",
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:28: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  A[0, 3] = -v * np.sin(theta)\n",
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:29: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  A[1, 2] = np.sin(theta)\n",
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:30: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  A[1, 3] = v * np.cos(theta)\n",
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:31: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  A[3, 2] = v * np.tan(delta) / L\n",
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:36: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  B[3, 1] = v / (L * np.cos(delta) ** 2)\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "# 限制条件\n",
    "MAX_SPEED = 1.5  # m/s\n",
    "MAX_STEER = np.radians(30)  # rad\n",
    "MAX_ACC = 1.0\n",
    "REF_VEL = 1.0   # 目标路径参考速度\n",
    "\n",
    "#获取参考轨迹，线性插值，三个点[0,0],[3,0],[6,0]\n",
    "track = compute_path_from_wp([0, 3, 6], [0, 0, 0], 0.05)\n",
    "\n",
    "# Starting Condition 初始条件\n",
    "x0 = np.zeros(N)\n",
    "x0[0] = 0  # x\n",
    "x0[1] = -0.5  # y\n",
    "x0[2] = 0.0  # v\n",
    "x0[3] = np.radians(-80)  # yaw\n",
    "\n",
    "# starting guess 开始猜测\n",
    "u_bar = np.zeros((M, T))\n",
    "u_bar[0, :] = MAX_ACC / 2  # a\n",
    "u_bar[1, :] = 0.1  # delta\n",
    "\n",
    "# dynamics starting state w.r.t world frame 与世界坐标系相关的动力学起始状态\n",
    "x_bar = np.zeros((N, T + 1)) # 4x21\n",
    "x_bar[:, 0] = x0\n",
    "\n",
    "# prediction for linearization of costrains 用于约束线性化的预测\n",
    "# 这部分应用线性模型，得到预测的轨迹\n",
    "for t in range(1, T + 1):\n",
    "    xt = x_bar[:, t - 1].reshape(N, 1)\n",
    "    ut = u_bar[:, t - 1].reshape(M, 1)\n",
    "    A, B, C = get_linear_model(xt, ut) # 获取在t - 1时刻的线性近似模型\n",
    "    xt_plus_one = np.squeeze(np.dot(A, xt) + np.dot(B, ut) + C)\n",
    "    x_bar[:, t] = xt_plus_one  # 获取t时刻的状态\n",
    "\n",
    "# x_bar是根据猜测的u_bar获取的预测状态估计\n",
    "\n",
    "# CVXPY Linear MPC problem statement CVXPY线性MPC问题陈述\n",
    "x = cp.Variable((N, T + 1)) # 4x21维，状态向量\n",
    "u = cp.Variable((M, T)) # 2x20维，控制向量\n",
    "cost = 0\n",
    "constr = []\n",
    "\n",
    "# Cost Matrices\n",
    "Q = np.diag([10, 10, 10, 10])  # state error cost 状态误差成本\n",
    "Qf = np.diag([10, 10, 10, 10])  # state  final error cost 最终状态误差成本\n",
    "R = np.diag([10, 10])  # input cost 输入成本\n",
    "R_ = np.diag([10, 10])  # input rate of change cost 输入变化率成本\n",
    "\n",
    "# Get Reference_traj 获取参考轨迹，根据当前位置截取的路径上的一系列点，并赋值目标速度和转角\n",
    "# x_ref 表示参考状态，d_ref表示参考转角\n",
    "x_ref, d_ref = get_ref_trajectory(x_bar[:, 0], track, REF_VEL)\n",
    "\n",
    "# Prediction Horizon 预测视野\n",
    "for t in range(T):\n",
    "\n",
    "    # Tracking Error 跟踪误差\n",
    "    cost += cp.quad_form(x[:, t] - x_ref[:, t], Q)\n",
    "\n",
    "    # Actuation effort 执行努力\n",
    "    cost += cp.quad_form(u[:, t], R)\n",
    "\n",
    "    # Actuation rate of change 变化率\n",
    "    if t < (T - 1):\n",
    "        cost += cp.quad_form(u[:, t + 1] - u[:, t], R_)\n",
    "\n",
    "    # Kinrmatics Constrains (Linearized model) 运动学约束（线性化模型）\n",
    "    A, B, C = get_linear_model(x_bar[:, t], u_bar[:, t])\n",
    "    constr += [x[:, t + 1] == A @ x[:, t] + B @ u[:, t] + C.flatten()]\n",
    "\n",
    "# Final Point tracking 最终点跟踪\n",
    "cost += cp.quad_form(x[:, T] - x_ref[:, T], Qf)\n",
    "\n",
    "# sums problem objectives and concatenates constraints. 求和问题目标并连接约束。\n",
    "constr += [x[:, 0] == x_bar[:, 0]]  # starting condition 初始条件\n",
    "constr += [x[2, :] <= MAX_SPEED]  # max speed 最大速度\n",
    "constr += [x[2, :] >= 0.0]  # min_speed (not really needed) 最小速度（实际上不需要）\n",
    "constr += [cp.abs(u[0, :]) <= MAX_ACC]  # max acc 最大加速度\n",
    "constr += [cp.abs(u[1, :]) <= MAX_STEER]  # max steer 最大转向\n",
    "# for t in range(T):\n",
    "#     if t < (T - 1):\n",
    "#         constr += [cp.abs(u[0,t] - u[0,t-1])/DT <= MAX_ACC]     #max acc\n",
    "#         constr += [cp.abs(u[1,t] - u[1,t-1])/DT <= MAX_STEER]   #max steer\n",
    "\n",
    "prob = cp.Problem(cp.Minimize(cost), constr) # 构建问题\n",
    "solution = prob.solve(solver=cp.OSQP, verbose=True) # 求解"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-10-23T08:23:43.320874Z",
     "start_time": "2024-10-23T08:23:43.203207Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 640x480 with 4 Axes>",
      "image/png": "[encoded data, 44516 characters]"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "x_mpc = np.array(x.value[0, :]).flatten()\n",
    "y_mpc = np.array(x.value[1, :]).flatten()\n",
    "v_mpc = np.array(x.value[2, :]).flatten()\n",
    "theta_mpc = np.array(x.value[3, :]).flatten()\n",
    "a_mpc = np.array(u.value[0, :]).flatten()\n",
    "delta_mpc = np.array(u.value[1, :]).flatten()\n",
    "\n",
    "# simulate robot state trajectory for optimized U\n",
    "x_traj = predict(x0, np.vstack((a_mpc, delta_mpc)))\n",
    "\n",
    "# plt.figure(figsize=(15,10))\n",
    "# plot trajectory\n",
    "plt.subplot(2, 2, 1)\n",
    "plt.plot(track[0, :], track[1, :], \"b\")\n",
    "plt.plot(x_ref[0, :], x_ref[1, :], \"g+\")\n",
    "plt.plot(x_traj[0, :], x_traj[1, :])    #根据mpc优化后的a和delta，预测的轨迹\n",
    "plt.axis(\"equal\")\n",
    "plt.ylabel(\"y\")\n",
    "plt.xlabel(\"x\")\n",
    "\n",
    "# plot v(t)\n",
    "plt.subplot(2, 2, 3)\n",
    "plt.plot(a_mpc)\n",
    "plt.ylabel(\"a_in(t)\")\n",
    "# plt.xlabel('time')\n",
    "\n",
    "\n",
    "plt.subplot(2, 2, 2)\n",
    "plt.plot(theta_mpc) \n",
    "plt.ylabel(\"theta(t)\")  # 航向角\n",
    "\n",
    "plt.subplot(2, 2, 4)\n",
    "plt.plot(delta_mpc)\n",
    "plt.ylabel(\"d_in(t)\")   # 前轮转角\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "# 下图展示的结果并不准确\n",
    "# 这是因为在做约束条件中，xt+1 = Axt + But + C时，A，B，C是线性化的模型，且是基于猜测的x_bar和u_bar来线性化的\n",
    "# 所以需要通过滚动优化，获得更准确的猜测和状态猜测\n",
    "# 这里有一个问题，那是否要将这次计算的u，作为下一次的猜测基础？"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## full track demo "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-10-23T08:19:27.567266Z",
     "start_time": "2024-10-23T08:19:11.796354Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:27: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  A[0, 2] = np.cos(theta)\n",
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:28: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  A[0, 3] = -v * np.sin(theta)\n",
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:29: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  A[1, 2] = np.sin(theta)\n",
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:30: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  A[1, 3] = v * np.cos(theta)\n",
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:31: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  A[3, 2] = v * np.tan(delta) / L\n",
      "/var/folders/hd/8kg_jtmd6svgg_sc384pbcdm0000gn/T/ipykernel_12777/1770301921.py:36: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)\n",
      "  B[3, 1] = v / (L * np.cos(delta) ** 2)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CVXPY Optimization Time: Avrg: 0.0791s Max: 0.1699s Min: 0.0546s\n"
     ]
    }
   ],
   "source": [
    "track = compute_path_from_wp(\n",
    "    [0, 3, 4, 6, 10, 12, 14, 6, 1, 0], [0, 0, 2, 4, 3, 3, -2, -6, -2, -2], 0.05\n",
    ")\n",
    "\n",
    "# track = compute_path_from_wp([0,10,10,0],\n",
    "#                              [0,0,1,1],0.05)\n",
    "\n",
    "sim_duration = 200  # time steps\n",
    "opt_time = []\n",
    "\n",
    "x_sim = np.zeros((N, sim_duration))\n",
    "x_sim[0,0] = 0  # x\n",
    "x_sim[1,0] = -0.5  # y\n",
    "x_sim[2,0] = 0.0  # v\n",
    "x_sim[3,0] = np.radians(-60.0)  # yaw\n",
    "u_sim = np.zeros((M, sim_duration - 1))\n",
    "\n",
    "MAX_SPEED = 1.5  # m/s\n",
    "MAX_ACC = 1.0  # m/ss\n",
    "MAX_D_ACC = 1.0  # m/sss\n",
    "MAX_STEER = np.radians(30)  # rad\n",
    "MAX_D_STEER = np.radians(30)  # rad/s\n",
    "\n",
    "REF_VEL = 1.0  # m/s\n",
    "\n",
    "# Starting Condition\n",
    "x0 = np.zeros(N)\n",
    "x0 = x_sim[:, 0]\n",
    "\n",
    "# starting guess\n",
    "u_bar = np.zeros((M, T))\n",
    "u_bar[0, :] = MAX_ACC / 2  # a\n",
    "u_bar[1, :] = 0.0  # delta\n",
    "\n",
    "for sim_time in range(sim_duration - 1):\n",
    "\n",
    "    iter_start = time.time()\n",
    "\n",
    "    # dynamics starting state\n",
    "    # 获取当前时刻的状态，x_sim是通过ode计算出的真值\n",
    "    x_bar = np.zeros((N, T + 1))\n",
    "    x_bar[:, 0] = x_sim[:, sim_time]\n",
    "\n",
    "    # prediction for linearization of costrains\n",
    "    # 获取各参考点处的线性化模型参数\n",
    "    for t in range(1, T + 1):\n",
    "        xt = x_bar[:, t - 1].reshape(N, 1)\n",
    "        ut = u_bar[:, t - 1].reshape(M, 1)\n",
    "        A, B, C = get_linear_model(xt, ut)\n",
    "        xt_plus_one = np.squeeze(np.dot(A, xt) + np.dot(B, ut) + C)\n",
    "        x_bar[:, t] = xt_plus_one\n",
    "\n",
    "    # CVXPY Linear MPC problem statement\n",
    "    # 构建MPC问题和求解器\n",
    "    x = cp.Variable((N, T + 1))\n",
    "    u = cp.Variable((M, T))\n",
    "    cost = 0\n",
    "    constr = []\n",
    "\n",
    "    # Cost Matrices\n",
    "    Q = np.diag([20, 20, 10, 0])  # state error cost\n",
    "    Qf = np.diag([30, 30, 30, 0])  # state final error cost\n",
    "    R = np.diag([10, 10])  # input cost\n",
    "    R_ = np.diag([10, 10])  # input rate of change cost\n",
    "\n",
    "    # Get Reference_traj\n",
    "    x_ref, d_ref = get_ref_trajectory(x_bar[:, 0], track, REF_VEL)\n",
    "\n",
    "    # Prediction Horizon\n",
    "    for t in range(T):\n",
    "\n",
    "        # Tracking Error\n",
    "        cost += cp.quad_form(x[:, t] - x_ref[:, t], Q)\n",
    "\n",
    "        # Actuation effort\n",
    "        cost += cp.quad_form(u[:, t], R)\n",
    "\n",
    "        # Actuation rate of change\n",
    "        if t < (T - 1):\n",
    "            cost += cp.quad_form(u[:, t + 1] - u[:, t], R_)\n",
    "            constr += [\n",
    "                cp.abs(u[0, t + 1] - u[0, t]) / DT <= MAX_D_ACC\n",
    "            ]  # max acc rate of change\n",
    "            constr += [\n",
    "                cp.abs(u[1, t + 1] - u[1, t]) / DT <= MAX_D_STEER\n",
    "            ]  # max steer rate of change\n",
    "\n",
    "        # Kinrmatics Constrains (Linearized model)\n",
    "        A, B, C = get_linear_model(x_bar[:, t], u_bar[:, t])\n",
    "        constr += [x[:, t + 1] == A @ x[:, t] + B @ u[:, t] + C.flatten()]\n",
    "\n",
    "    # Final Point tracking\n",
    "    cost += cp.quad_form(x[:, T] - x_ref[:, T], Qf)\n",
    "\n",
    "    # sums problem objectives and concatenates constraints.\n",
    "    constr += [x[:, 0] == x_bar[:, 0]]  # starting condition\n",
    "    constr += [x[2, :] <= MAX_SPEED]  # max speed\n",
    "    constr += [x[2, :] >= 0.0]  # min_speed (not really needed)\n",
    "    constr += [cp.abs(u[0, :]) <= MAX_ACC]  # max acc\n",
    "    constr += [cp.abs(u[1, :]) <= MAX_STEER]  # max steer\n",
    "\n",
    "    # Solve\n",
    "    prob = cp.Problem(cp.Minimize(cost), constr)\n",
    "    solution = prob.solve(solver=cp.OSQP, verbose=False)\n",
    "\n",
    "    # retrieved optimized U and assign to u_bar to linearize in next step\n",
    "    # 将本次计算出的u_bar作为下次猜测的起点\n",
    "    u_bar = np.vstack(\n",
    "        (np.array(u.value[0, :]).flatten(), (np.array(u.value[1, :]).flatten()))\n",
    "    )\n",
    "    \n",
    "    # 本次的执行值\n",
    "    u_sim[:, sim_time] = u_bar[:, 0]\n",
    "\n",
    "    # Measure elpased time to get results from cvxpy\n",
    "    opt_time.append(time.time() - iter_start)\n",
    "\n",
    "    # 用ode模型仿真车辆运动\n",
    "    # move simulation to t+1\n",
    "    tspan = [0, DT]\n",
    "    x_sim[:, sim_time + 1] = odeint(\n",
    "        kinematics_model, x_sim[:, sim_time], tspan, args=(u_bar[:, 0],)\n",
    "    )[1]\n",
    "\n",
    "print(\n",
    "    \"CVXPY Optimization Time: Avrg: {:.4f}s Max: {:.4f}s Min: {:.4f}s\".format(\n",
    "        np.mean(opt_time), np.max(opt_time), np.min(opt_time)\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-10-23T08:19:28.719495Z",
     "start_time": "2024-10-23T08:19:28.541069Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 1500x1000 with 5 Axes>",
      "image/png": "[encoded data, 129212 characters]"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# plot trajectory\n",
    "grid = plt.GridSpec(4, 5)\n",
    "\n",
    "plt.figure(figsize=(15, 10))\n",
    "\n",
    "plt.subplot(grid[0:4, 0:4])\n",
    "plt.plot(track[0, 0:200], track[1, 0:200], \"b+\")\n",
    "plt.plot(x_sim[0, 0:50], x_sim[1, 0:50])\n",
    "plt.axis(\"equal\")\n",
    "plt.ylabel(\"y\")\n",
    "plt.xlabel(\"x\")\n",
    "\n",
    "plt.subplot(grid[0, 4])\n",
    "plt.plot(u_sim[0, :])\n",
    "plt.ylabel(\"a(t) [m/ss]\")\n",
    "\n",
    "plt.subplot(grid[1, 4])\n",
    "plt.plot(x_sim[2, :])\n",
    "plt.ylabel(\"v(t) [m/s]\")\n",
    "\n",
    "plt.subplot(grid[2, 4])\n",
    "plt.plot(np.degrees(u_sim[1, :]))\n",
    "plt.ylabel(\"delta(t) [rad]\")\n",
    "\n",
    "plt.subplot(grid[3, 4])\n",
    "plt.plot(x_sim[3, :])\n",
    "plt.ylabel(\"theta(t) [rad]\")\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [],
   "metadata": {
    "collapsed": false
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "name": "python3",
   "language": "python",
   "display_name": "Python 3 (ipykernel)"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
