LQR的横向控制与算法仿真实现

文章目录

    • 1. 引言
    • 2. 车辆运动学线性离散模型
    • 3. LQR求解
    • 4. 算法和仿真实现

1. 引言

在现代控制理论的领域中,线性二次型调节器(Linear Quadratic Regulator,简称LQR)被广泛认可为一种高效的优化控制方法。LQR的核心优势在于其能力,通过最小化一个定义良好的二次型代价函数,来设计出能够引导系统达到预定性能指标的控制策略。尽管LQR最初是为线性时不变系统(Linear Time-Invariant, LTI)设计的,但其在稳定性和性能优化方面的卓越表现,已经使得它在航空航天、机器人技术、汽车工业等多个高端技术领域得到了广泛应用。

在车辆横向控制的具体应用场景中,我们面临车辆运动学模型的非线性特性的挑战。为了克服这一难题,我们通常采用线性化技术,将非线性模型转化为线性近似模型,从而使得LQR方法得以应用。此外,为了适应计算机控制系统的实现需求,模型的离散化处理也成为了一个不可或缺的步骤。通过将连续时间模型转换为离散时间模型,我们可以有效地利用LQR算法进行控制设计,实现对车辆横向运动的精确控制。

2. 车辆运动学线性离散模型

在车辆运动学模型的线性化和离散化及代码实现中,我们详细介绍了单车模型的线性化和离散化,其离散线性化后的微分方程如下
X e ( k + 1 ) = ( T A + I ) A X e ( k ) + T B u e ( k ) = [ 1 0 − T v r s i n φ r 0 1 T v r c o s φ r 0 0 1 ] [ x − x r y − y r φ − φ r ] + [ T c o s φ r 0 T s i n φ r 0 T v r t a n δ f r L T v r L c o s 2 δ f r ] [ v − v r δ − δ r ] (1) \begin{align*} X_e(k+1)&=(TA+I)AX_e(k)+TBu_e(k)\\ &= \begin{bmatrix} 1 & 0 & -Tv_rsin\varphi_r\\ 0 & 1 & Tv_rcos\varphi_r\\ 0 & 0 & 1\\ \end{bmatrix} \begin{bmatrix} x-x_r\\ y-y_r\\ \varphi-\varphi_r\\ \end{bmatrix} + \begin{bmatrix} Tcos\varphi_r & 0 \\ Tsin\varphi_r & 0 \\ \frac{Tv_r tan\delta_{fr}}{L} & \frac{Tv_r}{Lcos^2\delta_{fr}} \\ \end{bmatrix} \begin{bmatrix} v-v_r\\ \delta-\delta_r\\ \end{bmatrix} \\ \end{align*} \tag{1} Xe(k+1)=(TA+I)AXe(k)+TBue(k)= 100010TvrsinφrTvrcosφr1 xxryyrφφr + TcosφrTsinφrLTvrtanδfr00Lcos2δfrTvr [vvrδδr](1)
其中 T T T为采样步长, I I I为3x3的单位矩阵。

这里的 ( T A + I ) A (TA+I)A (TA+I)A为该系统的控制矩阵, T B TB TB为输入矩阵, u e ( k ) u_e(k) ue(k)为输入控制量误差,状态 X e ( K + 1 ) X_e(K+1) Xe(K+1)为状态误差,在控制过程中,我们期望状态误差逐渐稳定趋近为0,因此,定义代价函数
J = ∑ k = 0 N − 1 ( x k T Q x k + u k T R u k ) + x N T F x N (2) J = \sum_{k=0}^{N-1} (x_k^T Q x_k + u_k^T R u_k) + x_N^T F x_N \tag{2} J=k=0N1(xkTQxk+ukTRuk)+xNTFxN(2)
其中:

  • x k x_k xk 是在离散时间步 k k k的系统状态。
  • u k u_k uk是在时间步 k k k的控制输入。
  • Q Q Q是状态权重 m × m m \times m m×m的半正定方阵,用于衡量状态的代价,通常将其设计为对角矩阵。
  • $R 是控制权重 是控制权重 是控制权重n \times n$的正定对称矩阵,用于衡量控制输入的代价,通常将其设计为对角矩阵。
  • F F F是末端状态权重 m × m m \times m m×m的半正定方阵,用于衡量最终状态的代价,通常将其设计为对角矩阵。
  • N N N是控制的总时间步数。

3. LQR求解

采用LQR算法进行控制率求解的步骤(推导过程详见LQR求解推导及代码实现)概括为:

  1. 确定迭代范围 N N N

  2. 设置迭代初始值 P N = F P_{N}=F PN=F,其中 Q f = Q Q_f=Q Qf=Q

  3. 循环迭代, 从后往前 k = N − 1 , … , 0 k=N-1, \ldots, 0 k=N1,,0
    K k = ( B T P k + 1 B + R ) − 1 B T P k + 1 A P k = ( A − B K k ) T P k + 1 ( A − B K k ) + Q + K k T R K k \begin{align*} K_{k}&=(B^TP_{k+1}B + R)^{-1}B^TP_{k+1}A\\ P_{k}&=(A-BK_{k})^T P_{k+1} (A-BK_{k}) + Q + K_{k}^T R K_{k} \end{align*} KkPk=(BTPk+1B+R)1BTPk+1A=(ABKk)TPk+1(ABKk)+Q+KkTRKk

    判断 K k K_k Kk K k + 1 K_{k+1} Kk+1每个对应元素的差值是否小于 ϵ \epsilon ϵ(这里 ϵ \epsilon ϵ代表迭代精度,一般是非常小的数字),如果都小于则跳出循环,此时的 K t K_t Kt即为最终的最优反馈矩阵,否则继续循环。

  4. 最终得优化的控制量 u t ∗ = − K t x t u_{t}^{*}=-K_{t} x_{t} ut=Ktxt

4. 算法和仿真实现

这里我们将权重矩阵 Q Q Q R R R F F F分别设为
Q = [ 8 0 0 0 8 0 0 0 8 ] R = [ 2 0 0 0 2 0 0 0 2 ] F = [ 10 0 0 0 10 0 0 0 10 ] (3) Q=\begin{bmatrix} 8 & 0 & 0\\ 0 & 8 & 0\\ 0 & 0 & 8\\ \end{bmatrix} \\ R=\begin{bmatrix} 2 & 0 & 0\\ 0 & 2 & 0\\ 0 & 0 & 2\\ \end{bmatrix} \\ F=\begin{bmatrix} 10 & 0 & 0\\ 0 & 10 & 0\\ 0 & 0 & 10\\ \end{bmatrix} \tag{3} Q= 800080008 R= 200020002 F= 100001000010 (3)
实际使用过程可以根据需要动态调整相关权重。其具体实现如下

kinematicsLQR.py

import numpy as np
import math
from scipy.linalg import inv
from kinematic_bicycle_model import update_ABMatrixN = 200  # 迭代范围
EPS = 1e-4  # 迭代精度
Q = np.eye(3) * 8
R = np.eye(2) * 2
F = np.eye(3) * 10def cal_lqr_k(A, B, Q, R, F):"""计算LQR反馈矩阵KArgs:A : mxm状态矩阵AB : mxn状态矩阵BQ : Q是状态权重mxm的半正定方阵,用于衡量状态的代价,通常将其设计为对角矩阵。R : R是控制权重nxn的正定对称矩阵,用于衡量控制输入的代价,通常将其设计为对角矩阵。F : F是末端状态权重mxm的半正定方阵,用于衡量最终状态的代价,通常将其设计为对角矩阵。Returns:K : 反馈矩阵K"""# 设置迭代初始值P = F# 循环迭代for t in range(N):K_t = inv(B.T @ P @ B + R) @ B.T @ P @ AP_t = (A - B @ K_t).T @ P @ (A - B @ K_t) + Q + K_t.T @ R @ K_tif (abs(P_t - P).max() < EPS):breakP = P_treturn K_tdef normalize_angle(angle):a = math.fmod(angle + np.pi, 2 * np.pi)if a < 0.0:a += (2.0 * np.pi)return a - np.pidef calc_preparation(vehicle, ref_path):"""计算角度误差theta_e、横向误差er、曲率rk和索引index"""rx, ry, ref_yaw, ref_kappa = ref_path[:, 0], ref_path[:, 1], ref_path[:, 2], ref_path[:, 4]dx = [vehicle.x - icx for icx in rx]dy = [vehicle.y - icy for icy in ry]d = np.hypot(dx, dy)index = np.argmin(d)rk = ref_kappa[index]ryaw = ref_yaw[index]rdelta = math.atan2(vehicle.L * rk, 1)vec_nr = np.array([math.cos(ryaw + math.pi / 2.0),math.sin(ryaw + math.pi / 2.0)])vec_target_2_rear = np.array([vehicle.x - rx[index],vehicle.y - ry[index]])er = np.dot(vec_target_2_rear, vec_nr)theta_e = normalize_angle(vehicle.yaw - ryaw)return dx[index], dy[index], theta_e, er, rdelta, ryaw, indexdef LQRController(vehicle, ref_path):x_e, y_e, theta_e, er, rdelta, ryaw, index = calc_preparation(vehicle, ref_path)x = np.matrix([[x_e],[y_e],[theta_e]])A, B = update_ABMatrix(vehicle, rdelta, ryaw)K = cal_lqr_k(A, B, Q, R, F)u = -K @ xdelta_f = rdelta + u[1,0]return delta_f, index, er

kinematic_bicycle_model.py

import math
import numpy as npclass Vehicle:def __init__(self,x=0.0,y=0.0,yaw=0.0,v=0.0,dt=0.1,l=3.0):self.steer = 0self.x = xself.y = yself.yaw = yawself.v = vself.dt = dtself.L = l  # 轴距self.x_front = x + l * math.cos(yaw)self.y_front = y + l * math.sin(yaw)def update(self, a, delta, max_steer=np.pi):delta = np.clip(delta, -max_steer, max_steer)self.steer = deltaself.x = self.x + self.v * math.cos(self.yaw) * self.dtself.y = self.y + self.v * math.sin(self.yaw) * self.dtself.yaw = self.yaw + self.v / self.L * math.tan(delta) * self.dtself.v = self.v + a * self.dtself.x_front = self.x + self.L * math.cos(self.yaw)self.y_front = self.y + self.L * math.sin(self.yaw)class VehicleInfo:# Vehicle parameterL = 2.0  # 轴距W = 2.0  # 宽度LF = 2.8  # 后轴中心到车头距离LB = 0.8  # 后轴中心到车尾距离MAX_STEER = 0.6  # 最大前轮转角TR = 0.5  # 轮子半径TW = 0.5  # 轮子宽度WD = W  # 轮距LENGTH = LB + LF  # 车辆长度def draw_vehicle(x, y, yaw, steer, ax, vehicle_info=VehicleInfo, color='black'):vehicle_outline = np.array([[-vehicle_info.LB, vehicle_info.LF, vehicle_info.LF, -vehicle_info.LB, -vehicle_info.LB],[vehicle_info.W / 2, vehicle_info.W / 2, -vehicle_info.W / 2, -vehicle_info.W / 2, vehicle_info.W / 2]])wheel = np.array([[-vehicle_info.TR, vehicle_info.TR, vehicle_info.TR, -vehicle_info.TR, -vehicle_info.TR],[vehicle_info.TW / 2, vehicle_info.TW / 2, -vehicle_info.TW / 2, -vehicle_info.TW / 2,vehicle_info.TW / 2]])rr_wheel = wheel.copy()  # 右后轮rl_wheel = wheel.copy()  # 左后轮fr_wheel = wheel.copy()  # 右前轮fl_wheel = wheel.copy()  # 左前轮rr_wheel[1, :] += vehicle_info.WD / 2rl_wheel[1, :] -= vehicle_info.WD / 2# 方向盘旋转rot1 = np.array([[np.cos(steer), -np.sin(steer)],[np.sin(steer), np.cos(steer)]])# yaw旋转矩阵rot2 = np.array([[np.cos(yaw), -np.sin(yaw)],[np.sin(yaw), np.cos(yaw)]])fr_wheel = np.dot(rot1, fr_wheel)fl_wheel = np.dot(rot1, fl_wheel)fr_wheel += np.array([[vehicle_info.L], [-vehicle_info.WD / 2]])fl_wheel += np.array([[vehicle_info.L], [vehicle_info.WD / 2]])fr_wheel = np.dot(rot2, fr_wheel)fr_wheel[0, :] += xfr_wheel[1, :] += yfl_wheel = np.dot(rot2, fl_wheel)fl_wheel[0, :] += xfl_wheel[1, :] += yrr_wheel = np.dot(rot2, rr_wheel)rr_wheel[0, :] += xrr_wheel[1, :] += yrl_wheel = np.dot(rot2, rl_wheel)rl_wheel[0, :] += xrl_wheel[1, :] += yvehicle_outline = np.dot(rot2, vehicle_outline)vehicle_outline[0, :] += xvehicle_outline[1, :] += yax.plot(fr_wheel[0, :], fr_wheel[1, :], color)ax.plot(rr_wheel[0, :], rr_wheel[1, :], color)ax.plot(fl_wheel[0, :], fl_wheel[1, :], color)ax.plot(rl_wheel[0, :], rl_wheel[1, :], color)ax.plot(vehicle_outline[0, :], vehicle_outline[1, :], color)ax.axis('equal')def update_ABMatrix(vehicle, ref_delta, ref_yaw):"""计算离散线性车辆运动学模型状态矩阵A和输入矩阵Breturn: A, b"""A = np.matrix([[1.0, 0.0, -vehicle.v * vehicle.dt * math.sin(ref_yaw)],[0.0, 1.0, vehicle.v * vehicle.dt * math.cos(ref_yaw)],[0.0, 0.0, 1.0]])B = np.matrix([[vehicle.dt * math.cos(ref_yaw), 0],[vehicle.dt * math.sin(ref_yaw), 0],[vehicle.dt * math.tan(ref_delta) / vehicle.L,vehicle.v * vehicle.dt / (vehicle.L * math.cos(ref_delta) * math.cos(ref_delta))]])return A, Bdef update_ABMatrix1(vehicle, ref_delta, ref_yaw):"""将模型离散化后的状态空间表达Args:delta (_type_): 参考输入Returns:_type_: _description_"""A = np.matrix([[1.0, 0.0, -vehicle.v * vehicle.dt * math.sin(ref_yaw)],[0.0, 1.0, vehicle.v * vehicle.dt * math.cos(ref_yaw)],[0.0, 0.0, 1.0]])B = np.matrix([[vehicle.dt * math.cos(ref_yaw), 0],[vehicle.dt * math.sin(ref_yaw), 0],[vehicle.dt * math.tan(ref_delta) / vehicle.L,vehicle.v * vehicle.dt / (vehicle.L * math.cos(ref_delta) * math.cos(ref_delta))]])return A, B

path_generator.py

"""
路径轨迹生成器
"""import math
import numpy as npclass Path:def __init__(self):self.ref_line = self.design_reference_line()self.ref_yaw = self.cal_yaw()self.ref_s = self.cal_accumulated_s()self.ref_kappa = self.cal_kappa()def design_reference_line(self):rx = np.linspace(0, 50, 1000) + 5 # x坐标ry = 20 * np.sin(rx / 20.0) + 60  # y坐标return np.column_stack((rx, ry))def cal_yaw(self):yaw = []for i in range(len(self.ref_line)):if i == 0:yaw.append(math.atan2(self.ref_line[i + 1, 1] - self.ref_line[i, 1],self.ref_line[i + 1, 0] - self.ref_line[i, 0]))elif i == len(self.ref_line) - 1:yaw.append(math.atan2(self.ref_line[i, 1] - self.ref_line[i - 1, 1],self.ref_line[i, 0] - self.ref_line[i - 1, 0]))else:yaw.append(math.atan2(self.ref_line[i + 1, 1] - self.ref_line[i -1, 1],self.ref_line[i + 1, 0] - self.ref_line[i - 1, 0]))return yawdef cal_accumulated_s(self):s = []for i in range(len(self.ref_line)):if i == 0:s.append(0.0)else:s.append(math.sqrt((self.ref_line[i, 0] - self.ref_line[i-1, 0]) ** 2+ (self.ref_line[i, 1] - self.ref_line[i-1, 1]) ** 2))return sdef cal_kappa(self):# 计算曲线各点的切向量dp = np.gradient(self.ref_line.T, axis=1)# 计算曲线各点的二阶导数d2p = np.gradient(dp, axis=1)# 计算曲率kappa = (d2p[0] * dp[1] - d2p[1] * dp[0]) / ((dp[0] ** 2 + dp[1] ** 2) ** (3 / 2))return kappadef get_ref_line_info(self):return self.ref_line[:, 0], self.ref_line[:, 1], self.ref_yaw, self.ref_s, self.ref_kappa

main.py

from kinematic_bicycle_model import Vehicle, VehicleInfo, draw_vehicle
from kinematicsLQR import LQRController
from path_generator import Path
import numpy as np
import matplotlib.pyplot as plt
import imageio.v2 as imageioMAX_SIMULATION_TIME = 200.0  # 程序最大运行时间200*dtdef main():# 设置跟踪轨迹rx, ry, ref_yaw, ref_s, ref_kappa = Path().get_ref_line_info()ref_path = np.column_stack((rx, ry, ref_yaw, ref_s, ref_kappa))# 假设车辆初始位置为(5,60),航向角yaw=0.0,速度为2m/s,时间周期dt为0.1秒vehicle = Vehicle(x=5.0,y=60.0,yaw=0.0,v=2.0,dt=0.1,l=VehicleInfo.L)time = 0.0  # 初始时间target_ind = 0# 记录车辆轨迹trajectory_x = []trajectory_y = []lat_err = []  # 记录横向误差i = 0image_list = []  # 存储图片plt.figure(1)last_idx = ref_path.shape[0] - 1  # 跟踪轨迹的最后一个点的索引while MAX_SIMULATION_TIME >= time and last_idx > target_ind:time += vehicle.dt  # 累加一次时间周期# rear_wheel_feedbackdelta_f, target_ind, e_y = LQRController(vehicle, ref_path)# 横向误差lat_err.append(e_y)# 更新车辆状态vehicle.update(0.0, delta_f, np.pi / 10)  # 由于假设纵向匀速运动,所以加速度a=0.0trajectory_x.append(vehicle.x)trajectory_y.append(vehicle.y)# 显示动图plt.cla()plt.plot(ref_path[:, 0], ref_path[:, 1], '-.b', linewidth=1.0)draw_vehicle(vehicle.x, vehicle.y, vehicle.yaw, vehicle.steer, plt)plt.plot(trajectory_x, trajectory_y, "-r", label="trajectory")plt.plot(ref_path[target_ind, 0], ref_path[target_ind, 1], "go", label="target")plt.axis("equal")plt.grid(True)plt.pause(0.001)#     plt.savefig("temp.png")#     i += 1#     if (i % 5) == 0:#         image_list.append(imageio.imread("temp.png"))## imageio.mimsave("display.gif", image_list, duration=0.1)plt.figure(2)plt.subplot(2, 1, 1)plt.plot(ref_path[:, 0], ref_path[:, 1], '-.b', linewidth=1.0)plt.plot(trajectory_x, trajectory_y, 'r')plt.title("actual tracking effect")plt.subplot(2, 1, 2)plt.plot(lat_err)plt.title("lateral error")plt.show()if __name__ == '__main__':main()

运行结果如下
在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/577378.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UE5数字孪生系列笔记(三)

C创建Pawn类玩家 创建一个GameMode蓝图用来加载我们自定义的游戏Mode新建一个Pawn的C&#xff0c;MyCharacter类作为玩家&#xff0c;新建一个相机组件与相机臂组件&#xff0c;box组件作为根组件 // Fill out your copyright notice in the Description page of Project Set…

安科瑞智慧安全用电综合解决方案

概述 智慧用电管理云平台是智慧城市建设的延伸成果&#xff0c;将电力物联网技术与云平台的大数据分析功能相结合&#xff0c;实现用电信息的可视化管理&#xff0c;可帮助用户实现安全用电&#xff0c;节约用电&#xff0c;可靠用电。平台支持web&#xff0c;app&#xff0c;微…

HarmonyOS 应用开发之进程模型

系统的进程模型如下图所示。 应用中&#xff08;同一Bundle名称&#xff09;的所有UIAbility、ServiceExtensionAbility和DataShareExtensionAbility均是运行在同一个独立进程&#xff08;主进程&#xff09;中&#xff0c;如下图中绿色部分的“Main Process”。应用中&#x…

stm32定时器中断函数回调函数

方式一&#xff1a;stm32定时器中断可以直接在硬件中断函数TIM3_IRQHandler执行。 在HAL库中可以注册回调函数&#xff0c;在定时器中断发生时调用注册的函数&#xff0c;这样可以统一接口&#xff0c;大大提高函数可读性&#xff0c;和硬件解耦提高程序可移植性。 使用过程如…

爬取b站音频和视频数据,未合成一个视频

一、首先找到含有音频和视频的url地址 打开一个视频&#xff0c;刷新后&#xff0c;找到这个包&#xff0c;里面有我们所需要的数据 访问这个数据包后&#xff0c;获取字符串数据&#xff0c;用正则提取&#xff0c;再转为json字符串方便提取。 二、获得标题和音频数据后&…

Python基础:标准库 -- pprint (数据美化输出)

1. pprint 库 官方文档 pprint --- 数据美化输出 — Python 3.12.2 文档 pprint — Data pretty printer — Python 3.12.2 documentation 2. 背景 处理JSON文件或复杂的嵌套数据时&#xff0c;使用普通的 print() 函数可能不足以有效地探索数据或调试应用程序。下面通过一…

PHP实现单列内容快速查重与去重

应用场景:excel一列内容比如身份证号&#xff0c;可能有重复的&#xff0c; 则用此工具快速查询那些重复及显示去重后内容。 使用&#xff1a;粘贴一列数据&#xff0c;然后提交发送。 <?php $tm "单列查重去重(粘贴Excel中1列内容查重)!";function tipx($str…

攻防世界-baby_web

题目信息 相关知识 使用bp进行抓包 解题过程 题目界面如下所示: 试图找index界面&#xff1a; 发现又跳转到http://61.147.171.105:51201/1.php页面&#xff0c;因此说明61.147.171.105:51201/index.php是存在的&#xff08;因为笔者试了&#xff0c;不存在的页面会直接报…

Linux 常用命令(1)

&#x1f607;作者介绍&#xff1a;一个有梦想、有理想、有目标的&#xff0c;且渴望能够学有所成的追梦人。 &#x1f386;学习格言&#xff1a;不读书的人,思想就会停止。——狄德罗 ⛪️个人主页&#xff1a;进入博主主页 &#x1f5fc;专栏系列&#xff1a;Linux 随笔集合 …

第十四届蓝桥杯JavaA组省赛真题 - 特殊日期

解题思路&#xff1a; 暴力秒了 public class Main {public static void main(String[] args) {int cnt 0;for (int i 1900; i < 9999; i) {for (int j 1; j < 12; j) {for (int k 1; k < days(i, j); k) {if (sum(i) sum(j) sum(k)) cnt;}}}System.out.print…

YOLOV8逐步分解(2)_DetectionTrainer类初始化过程

接上篇文章yolov8逐步分解(1)--默认参数&超参配置文件加载继续讲解。 1. 默认配置文件加载完成后&#xff0c;创建对象trainer时&#xff0c;需要从默认配置中获取类DetectionTrainer初始化所需的参数args&#xff0c;如下所示 def train(cfgDEFAULT_CFG, use_pythonFalse…

1.5-数组-059. 螺旋矩阵 II★★

59. 螺旋矩阵II ★★ 力扣题目链接&#xff0c;给你一个正整数 n &#xff0c;生成一个包含 1 到 n 2 n^2 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。1 < n < 20 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,…