基于LQR的倒立摆控制——python代码——dlqr步骤推导

2023-05-16

推荐一个自动控制小车开源项目:本文结合老王自动驾驶控制算法第五讲的离散LQR进行学习复盘

Inverted Pendulum Control — PythonRobotics documentation

  • dlqr原理(老王的拉格朗日乘子法)

  • 由于函数较多,写了一个框架方便理解。

  •  代码如下代,内附官方和自己的注释:
"""
Inverted Pendulum LQR control
author: Trung Kien - letrungkien.k53.hut@gmail.com
"""

import math
import time

import matplotlib.pyplot as plt
import numpy as np
from numpy.linalg import inv, eig

# Model parameters

l_bar = 2.0  # length of bar
M = 1.0  # [kg]
m = 0.3  # [kg]
g = 9.8  # [m/s^2]

nx = 4  # number of state
nu = 1  # number of input
Q = np.diag([0.0, 1.0, 1.0, 0.0])  # state cost matrix。返回所给元素组成的对角矩阵
R = np.diag([0.01])  # input cost matrix

delta_t = 0.1  # time tick [s]
sim_time = 5.0  # simulation time [s]

show_animation = True #一个为真的变量

## 1
def main():
    # x=[x,dot_x,seta,dot_seita]
    x0 = np.array([
        [0.0],
        [0.0],
        [0.3],#倾斜角
        [0.0]
    ])

    # x = np.copy(x0)
    x = x0
    time = 0.0

    while sim_time > time:
        time += delta_t

        # calc control input
        u = lqr_control(x)#调用函数

        # simulate inverted pendulum cart
        x = simulation(x, u)#调用函数

        if show_animation:
            plt.clf()#Clear the current figure.清楚每一帧的动画
            print("X[0]:",x[0])
            # print("X:",x)
            px = float(x[0])#将一个字符串或数字转换为浮点数。输出位置
            theta = float(x[2])#输出角度
            plot_cart(px, theta)#调用函数
            plt.xlim([-5.0, 2.0])
            plt.pause(0.001)#暂停间隔

    print("Finish")
    print(f"x={float(x[0]):.2f} [m] , theta={math.degrees(x[2]):.2f} [deg]")
    if show_animation:
        plt.show()


## 2
def simulation(x, u):
    '''
    调整X
    '''
    A, B = get_model_matrix()#调用函数,得到AB矩阵
    
    x = A @ x + B @ u#矩阵乘法,必须声明numpy

    return x


## 3
def solve_DARE(A, B, Q, R, maxiter=150, eps=0.01):
    """
    Solve a discrete time_Algebraic Riccati equation (DARE)求得离散时间黎卡提方程的解 矩阵P
    """
    P = Q#初始化

    for i in range(maxiter):
        #矩阵P进行迭代,4×4的矩阵
        Pn = A.T @ P @ A - A.T @ P @ B @ inv(R + B.T @ P @ B) @ B.T @ P @ A + Q
        # print("Pn:",Pn)
        #矩阵P收敛时,退出迭代循环
        # print("abs(Pn - P).max:",abs(Pn - P).max(),i)
        # print("Pn - P:",Pn - P,i)
        if (abs(Pn - P)).max() < eps:
            break
        P = Pn

    return Pn#收敛的矩阵P



    # P = Q
    # for i in range(50):
    #     Pn = A.T @ P @ A - A.T @ P @ B @ inv(R + B.T @ P @ B ) @ B.T @ P @ A +Q 
    #     # print("Pn:",Pn)
    #     # print("P:",P)
    #     if (abs(Pn - P)).max() < 0.01:
    #         break
    # return Pn

## 4
def dlqr(A, B, Q, R):
    """
    Solve the discrete time lqr controller.
    x[k+1] = A x[k] + B u[k]
    cost = sum x[k].T*Q*x[k] + u[k].T*R*u[k]
    # ref Bertsekas, p.151
    """

    # first, try to solve the ricatti equation
    #先求解迭代后收敛的矩阵P
    P = solve_DARE(A, B, Q, R)

    # compute the LQR gain
    #计算矩阵K,u=-KX
    K = inv(B.T @ P @ B + R) @ (B.T @ P @ A)#u=-kx的k

    eigVals, eigVecs = eig(A - B @ K)#计算方阵的特征值和右特征向量

    return K, P, eigVals


## 5
def lqr_control(x):
    '''
    得到输入u
    '''
    A, B = get_model_matrix()
    start = time.time()
    K, _, _ = dlqr(A, B, Q, R)
    u = -K @ x
    elapsed_time = time.time() - start
    print(f"calc time:{elapsed_time:.6f} [sec]")
    return u#返回输入u


## 6
def get_numpy_array_from_matrix(x):
    """
    get build-in list from matrix,将多维数组降为一维
    """
    return np.array(x).flatten()#将多维数组降为一维


## 7
def get_model_matrix():
    '''
    更新离散过程中的矩阵A、B
    '''
    A = np.array([
        [0.0, 1.0, 0.0, 0.0],
        [0.0, 0.0, m * g / M, 0.0],
        [0.0, 0.0, 0.0, 1.0],
        [0.0, 0.0, g * (M + m) / (l_bar * M), 0.0]
    ])
    
    # A = np.eye(nx) + delta_t * A#单位矩阵+△t*A,收敛速度慢
    A = inv(np.eye(4) - A *1/2 * delta_t) @ (np.eye(4) + A *1/2 * delta_t)#收敛更快

    B = np.array([
        [0.0],
        [1.0 / M],
        [0.0],
        [1.0 / (l_bar * M)]
    ])
    B = delta_t * B
    
    return A, B


## 8
def flatten(a):
    """
    将多维数组降为一维
    """
    return np.array(a).flatten()


## 9
def plot_cart(xt, theta):#xt:小球位置
    """
    画图
    """
    cart_w = 1.0#马车宽
    cart_h = 0.5#马车高
    radius = 0.1#马车轮半径

    cx = np.array([-cart_w / 2.0, cart_w / 2.0, cart_w /2.0, -cart_w / 2.0, -cart_w / 2.0])#马车顶点x坐标矩阵,闭合图形的顶点
    cy = np.array([0.0, 0.0, cart_h, cart_h, 0.0])#马车顶点y坐标
    cy += radius * 2.0#加轮高

    cx = cx + xt#调整马车位置,以球心坐标为基点变化

    bx = np.array([0.0, l_bar * math.sin(-theta)])#球心的x坐标
    bx += xt
    by = np.array([cart_h, l_bar * math.cos(-theta) + cart_h])#球心的y坐标
    by += radius * 2.0

    ##画车轮的圆
    #np.arange返回一个有终点和起点的固定步长的排列,第一个参数为起点,第二个参数为终点,第三个参数为步长
    angles = np.arange(0.0, math.pi * 2.0, math.radians(3.0))#math.radians()返回弧度制。离散化
    #每一步的x、y随球的旋转弧度变化
    ox = np.array([radius * math.cos(a) for a in angles])#np.array()的作用就是按照一定要求将object转换为数组
    oy = np.array([radius * math.sin(a) for a in angles])
    #右轮
    rwx = np.copy(ox) + cart_w / 4.0 + xt#右轮位置 = 离散矩阵 + 0.25 + 球位置
    rwy = np.copy(oy) + radius#离散矩阵
    #左轮
    lwx = np.copy(ox) - cart_w / 4.0 + xt
    lwy = np.copy(oy) + radius

    ##画球
    wx = np.copy(ox) + bx[-1]
    wy = np.copy(oy) + by[-1]

    plt.plot(flatten(cx), flatten(cy), "-b")#马车
    plt.plot(flatten(bx), flatten(by), "-k")#摆杆
    plt.plot(flatten(rwx), flatten(rwy), "-k")#右轮
    plt.plot(flatten(lwx), flatten(lwy), "-k")#左球
    plt.plot(flatten(wx), flatten(wy), "-k")#球
    plt.title(f"x: {xt:.2f} , theta: {math.degrees(theta):.2f}")

    # for stopping simulation with the esc key.
    plt.gcf().canvas.mpl_connect(
        'key_release_event',
        lambda event: [exit(0) if event.key == 'escape' else None])

    plt.axis("equal")


if __name__ == '__main__':
    main()

自己根据老王的dlqr推导过程,仿着开源代码修改了自己的代码,由于开源代码有些地方较为冗长,自己在函数顺序方面做了改进,图像plot部分未做修改

import math
import time

import matplotlib.pyplot as plt
import numpy as np
from numpy.linalg import inv, eig

l_bar = 2.0  # length of bar
M = 1.0  # [kg]
m = 0.3  # [kg]
g = 9.8  # [m/s^2]

nx = 4  # number of state
nu = 1  # number of input
Q = np.diag([0.0, 1.0, 1.0, 0.0])  # state cost matrix。返回所给元素组成的对角矩阵
R = np.diag([0.01])  # input cost matrix

delta_t = 0.1  # time tick [s]
sim_time = 5.0  # simulation time [s]


show_animation = True #一个为真的变量

#主函数√
def  main():
    time = 0.0
    X0 = np.array([
        [0.0],
        [0.0],
        [0.3],
        [0.0]
    ])

    X = X0
    while sim_time > time:
        time += delta_t
        
        A,B = get_A_B()
        P = get_P(A,B,R,Q)
        K = get_K(A,B,R,P)
        u = get_u(K, X)
        X = A @ X + B @ u

        if show_animation:
            plt.clf()#Clear the current figure.清楚每一帧的动画
            px = float(X[0])#将一个字符串或数字转换为浮点数。输出位置
            theta = float(X[2])#输出角度
            plot_cart(px, theta)#调用函数
            plt.xlim([-5.0, 5.0])
            plt.pause(0.001)#暂停间隔
    print("Finish")
    print(f"x={float(X[0]):.2f} [m] , theta={math.degrees(X[2]):.2f} [deg]")
    if show_animation:
        plt.show()


#获取p矩阵√
def get_P(A,B,R,Q):
    P = Q
    # Pn = A.T @ P @ A - A.T @ P @ B @ inv(R + B.T @ P @ B ) @ B.T @ P @ A +Q 
    # print("Pn:",Pn)
    for i in range(150):
        Pn = A.T @ P @ A - A.T @ P @ B @ inv(R + B.T @ P @ B ) @ B.T @ P @ A +Q 
        # print("Pn:",Pn)
        # print("P:",P)
        if (abs(Pn - P)).max()< 0.01:
            break
        P = Pn

    return Pn
            

    
#获取A,B√√√
def get_A_B():
    A0 = np.array([
        [0.0, 1.0, 0.0, 0.0],
        [0.0, 0.0, m * g / M, 0.0],
        [0.0, 0.0, 0.0, 1.0],
        [0.0, 0.0, g * (M + m) / (l_bar * M), 0.0]
    ])
    
    B0 = np.array([
        [0.0],
        [1.0 / M],
        [0.0],
        [1.0 / (l_bar * M)]
    ])

    A = inv(np.eye(4) - A0 *1/2 * delta_t) @ (np.eye(4) + A0 *1/2 * delta_t)
    B = B0 * delta_t

    return A,B



#获取K矩阵
def get_K(A,B,R,P):
    K = inv(B.T @ P @ B + R) @(B.T @ P @ A)
    return K



# 获取输入u
def get_u(K, X):
    u = -1 * K @ X
    return u


## 8
def flatten(a):
    """
    将多维数组降为一维
    """
    return np.array(a).flatten()


def plot_cart(xt, theta):
    """
    画图
    """
    cart_w = 1.0
    cart_h = 0.5
    radius = 0.1

    cx = np.array([-cart_w / 2.0, cart_w / 2.0, cart_w /
                   2.0, -cart_w / 2.0, -cart_w / 2.0])
    cy = np.array([0.0, 0.0, cart_h, cart_h, 0.0])
    cy += radius * 2.0

    cx = cx + xt

    bx = np.array([0.0, l_bar * math.sin(-theta)])
    bx += xt
    by = np.array([cart_h, l_bar * math.cos(-theta) + cart_h])
    by += radius * 2.0

    angles = np.arange(0.0, math.pi * 2.0, math.radians(3.0))
    ox = np.array([radius * math.cos(a) for a in angles])
    oy = np.array([radius * math.sin(a) for a in angles])

    rwx = np.copy(ox) + cart_w / 4.0 + xt
    rwy = np.copy(oy) + radius
    lwx = np.copy(ox) - cart_w / 4.0 + xt
    lwy = np.copy(oy) + radius

    wx = np.copy(ox) + bx[-1]
    wy = np.copy(oy) + by[-1]

    plt.plot(flatten(cx), flatten(cy), "-b")
    plt.plot(flatten(bx), flatten(by), "-k")
    plt.plot(flatten(rwx), flatten(rwy), "-k")
    plt.plot(flatten(lwx), flatten(lwy), "-k")
    plt.plot(flatten(wx), flatten(wy), "-k")
    plt.title(f"x: {xt:.2f} , theta: {math.degrees(theta):.2f}")

    # for stopping simulation with the esc key.
    plt.gcf().canvas.mpl_connect(
        'key_release_event',
        lambda event: [exit(0) if event.key == 'escape' else None])

    plt.axis("equal")


if __name__ == '__main__':
    main()

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于LQR的倒立摆控制——python代码——dlqr步骤推导 的相关文章

  • 关于自制openmv的一些建议

    从接触到openmv开始一直都想制作一块自己的openmv xff0c 包括从硬件 xff0c 到烧录程序 最开始制作的版本是openmv3 xff0c 其实openmv3并不是属于自己制作 xff0c 而是下载的硬件电路城开源的openm
  • vue3创建文件报“组件名称应该总是由多个单词组成“Component name “index“ should always be multi-word

    在项目根目录下的 eslintrc js 文件中添加 vue multi word component names off 没有该文件就创建一个 module span class token punctuation span export
  • STM32和ARM的区别?

    下面先看一张图 xff1a 这张图是我在意大利与法国合资的意法半导体公司 xff08 ST xff0c 世界几大半导体公司之一 xff09 的官网上看到的 这说明 xff0c STM32是意法半导体公司的产品 意法半导体 xff08 ST
  • OrangePi 5 Docker下安装OpenWRT作软路由(同样适用于树莓派等设备)

    OrangePi5 Docker下安装OpenWRT作软路由 xff08 同样适用于树莓派等设备 xff09 说明 本文的软路由作为家中的二级路由 xff0c 用一根网线连接主路由的LAN口和二级路由的WAN口 xff08 当主路由使用配置
  • kubernetes-----k8s入门详解

    目录 docker的编排工具 k8s的介绍 k8s的特性 pod的分类 service 网络 通信 认证与存储 插件 docker的编排工具 docker的第一类编排工具 xff08 docker三剑客 xff09 docker compo
  • ROS机械臂开发:Moveit + Gazebo仿真/Gazebo配置

    一 ROS中的控制器插件 ros control的功能 xff1a ROS为开发者提供的机器人控制中间件 包含一系列控制器接口 传动装置接口 硬件接口 控制器工具箱等等 可以帮助机器人应用功能包快速落地 xff0c 提高开发效率 ros c
  • 匿名飞控openmv寻色块解读

    作者 xff1a 不会写代码的菜鸟 时间 xff1a 2019 7 26 源码 xff1a 匿名TI板飞控源码 43 openmvH4 说明 xff1a 限于本人水平有限 xff0c 并不能写的很详细 xff0c 还望各位能够补充
  • 校验和的计算方法

    实验要求 编写一个计算机程序用来计算一个文件的16位效验和 最快速的方法是用一个32位的整数来存放这个和 记住要处理进位 xff08 例如 xff0c 超过16位的那些位 xff09 xff0c 把它们加到效验和中 要求 xff1a 1 x
  • MT7621路由器芯片/处理器参数介绍

    MT7621路由器芯片包括一个880 MHz MIPS 1004Kc CPU双核 xff0c 一个5端口10 100 1000交换机 PHY和一个RGMII 嵌入式高性能cpu可以很容易地处理高级应用程序 如路由 安全和VoIP等 MT76
  • 谈谈你对事件的传递链和响应链的理解

    一 xff1a 响应者链 UIResponser包括了各种Touch message 的处理 xff0c 比如开始 xff0c 移动 xff0c 停止等等 常见的 UIResponser 有 UIView及子类 xff0c UIViCont
  • CMake 引入第三方库

    CMake 引入第三方库 在 CMake 中 xff0c 如何引入第三方库是一个常见的问题 在本文中 xff0c 我们将介绍 CMake 中引入第三方库的不同方法 xff0c 以及它们的优缺点 1 使用 find package 命令 在
  • u-boot的启动模式(面试常考)

    交互模式 uboot启动之后 xff0c 在倒计时减到0之前按任意键 xff0c uboot会进入到交互模式 xff0c 此时可以输入各种uboot命令 和uboot进行交互 自启动模式 uboot启动之后 xff0c 在倒计时减到0之前不
  • vins-fusion代码理解

    代码通读了一遍做些总结 xff0c 肯定有很多理解错了的地方 xff0c 清晰起见详细程序都放到引用链接里 从rosNodeTest cpp开始 main函数 ros span class token operator span span
  • vins博客的一部分1

    文章目录 imu callbackimg callback imu callback 从话题中读入各个数据的t x y z g y r xff0c 存放到acc和gry中 span class token comment 从话题读入 spa
  • vins博客的一部分2

    sync process 对两个imgBuf里的图像进行双目时间匹配 xff08 通过判断双目图像时间之差 lt 3ms xff09 xff0c 扔掉匹配不到的老帧 span class token keyword double span
  • vins博客的一部分3

    FeatureTracker trackImage 包含了 xff1a 帧间光流法 区域mask 检测特征点 左右目光流法匹配 计算像素速度 画图 跟踪上一帧的特征点 如果已经有特征点 xff0c 就直接进行LK追踪 xff0c 新的特征点
  • vins博客的一部分4

    processMeasurements 取出数据 将 featureBuf中 xff0c 最早帧的feature取出 xff1a feature 61 featureBuf front 节点的接收IMU的消息再imu callback中被放
  • vins博客的一部分5

    目录 initFirstIMUPose xff08 xff09 processIMU propagate initFirstIMUPose xff08 xff09 得到IUM的Z与重力对齐的旋转矩阵 xff1a IMU开始很大可能不是水平放
  • vins博客的一部分6

    processImage 输入是本帧的特征点 id cam id xyz uv vxvy 包含了检测关键帧 估计外部参数 初始化 状态估计 划窗等等 检测关键帧 选择margin帧 addFeatureCheckParallax 检测和上一
  • vins博客的一部分7

    目录 initFramePoseByPnP frame count Ps Rs tic ric triangulate frame count Ps Rs tic ric initFramePoseByPnP frame count Ps

随机推荐