笔记100:使用 OSQP-Eigen 对 MPC 进行求解的方法与代码

news2024/11/26 11:30:27

1. 前言:


我们在对系统进行建模的时候,为了减少计算量,一般都将系统简化为线性的,系统如果有约束,也是将约束简化为线性的;

因此本篇博客只针对两种常见系统模型的 MPC 问题进行求解:

  • 线性系统 + 无约束
  • 线性系统 + 线性约束

a

a

a

a

2. 线性系统 + 无约束的 MPC 问题求解


目前已知:

  • 目标(代价)函数:
    • J=\sum_{k=0}^{N-1}[x_{k}^TQx_{k}+u_{k}^TRu_{k}]+x_{N}^TPx_{N}
    • 矩阵 QRP 均为正定矩阵;
  • 线性系统状态空间方程:x(k+1)=Ax(k)+Bu(k)
  • 当前时间步 k = 0 时系统的初始状态量:x(0)

注:代价函数和状态空间方程中的状态量 x 已经是误差量了;

a

求解方法:

  • 动态规划(针对无约束的问题,根本不需要使用到二次规划,直接使用动态规划即可求解)

a

进行求解:


a

a

a

a

3. 线性系统 + 线性约束的 MPC 问题求解


目前已知:

  • 目标(代价)函数:
    • J=\sum_{k=0}^{N-1}[(x_{k}-x_{r})^TQ(x_{k}-x_{r})+u_{k}^TRu_{k}]+(x_{N}-x_{r})^TQ_{N}(x_{N}-x_{r})
    • 矩阵 QRQ_{N} 均为正定矩阵;
  • 线性系统状态空间方程:x(k+1)=Ax(k)+Bu(k)
  • 当前时间步 k = 0 时的初始状态量:x_{0}
  • 系统的目标状态值:x_{r}
  • 线性约束条件:
    • 线性等式约束:x(k+1)=Ax(k)+Bu(k)
    • 线性不等式约束:
      • x_{min}\leq x\leq x_{max}
      • u_{min}\leq u\leq u_{max}

注:代价函数和状态空间方程中的 x 并不是误差量,x_{k}-x_{r}才是误差量;

a

求解方法:

  • 二次规划
  • 解释:因为系统带了约束,所以动态规划方法已经不好使了,这种方法无法处理带有约束条件的问题,而二次规划方法可以用来处理带有约束条件的问题,所以需要我们将问题等价转换为二次规划的形式,再调用 OSQP 求解;

3.1 将问题转化为二次规划的形式

(1)目标:

(2)代价函数的转化过程:

(3)约束条件转化:

参考文章:LQR、MPC以及osqp库_osqp mpc-CSDN博客


3.2 使用 OSQP-Egine 库求解二次规划问题

  • 原始问题:

  • QP 问题:

  •  QP 问题中涉及的矩阵和向量:

a

a

  • 代码:
#include "OsqpEigen/OsqpEigen.h"    // osqp-eigen
#include <Eigen/Dense>              // eigen
#include <iostream>




// 函数作用:对矩阵 A / B 赋值
// 注意:这个函数根据自己实际的需要进行赋值
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;
}


// 函数作用:对向量 x_min / x_max / u_min / u_max 赋值
// 注意:这个函数根据自己实际的需要进行赋值
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) {
    // 注意:因为 MPC 输出的当前时刻控制量是基于上一时刻控制量的增量
    // 解释:当前时刻为 0,上一时刻为 -1,所以这个 u0 代表的是 -1 时刻的控制量大小
    double u0 = 10.5916;
    uMin << 9.6 - u0, 9.6 - u0, 9.6 - u0, 9.6 - u0;
    uMax << 13 - u0, 13 - u0, 13 - u0, 13 - u0;

    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;
}


// 函数作用:对矩阵 Q / R 赋值
// 注意:这个函数根据自己实际的需要进行赋值
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;
}


// 函数作用:对稀疏矩阵 P 赋值
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);

    // 使用 Q / R 填充稀疏矩阵 P
    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;
        }
    }
}


// 函数作用:赋值向量 q
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);

    // 填充向量 q
    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;
    }
}


// 函数作用:赋值稀疏矩阵 A_c
void castMPCToQPConstraintMatrix(const Eigen::Matrix<double, 12, 12>& dynamicMatrix,    // A
                                 const Eigen::Matrix<double, 12, 4>& controlMatrix,     // B
                                 int mpcWindow,
                                 Eigen::SparseMatrix<double>& constraintMatrix) {
    constraintMatrix.resize(12 * (mpcWindow + 1) + 12 * (mpcWindow + 1) + 4 * mpcWindow, 12 * (mpcWindow + 1) + 4 * mpcWindow);

    // 等式约束 -- 填充
    for (int i = 0; i < 12 * (mpcWindow + 1); i++) {
        constraintMatrix.insert(i, i) = -1;
    }
    // 填充 A_c 矩阵中的 A
    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;
            }
        }
    }
    // 填充 A_c 矩阵中的 B
    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;
    }
}


// 函数作用:赋值左右约束 l / u
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) {
    // 不等式约束的左右边界数组:[xmin , xmin , ... , xmin | umin , umin , ... umin ]
    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; 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;
    }

    // 不全数组 l / u 的上半部分:[ -x0 , 0 , 0 , ... , 0 ]
    Eigen::VectorXd lowerEquality = Eigen::MatrixXd::Zero(12 * (mpcWindow + 1), 1);
    Eigen::VectorXd upperEquality;
    lowerEquality.block(0, 0, 12, 1) = -x0;
    upperEquality = lowerEquality;
    lowerEquality = lowerEquality;

    // 将数组融合,得到真正的上下边界数组 l / u
    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;
}


// 函数作用:更新约束边界 l 和 u
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) {
    Eigen::Matrix<double, 12, 1> error = x - xRef;  // 计算误差(对比目标状态 xref 和当前状态 x0 之间的差距)
    return error.norm();                            // 返回误差的二范数
}


// 主函数:
int main() {
    // 设定有限时域长度:
    int mpcWindow = 20;

    // 初始化原始问题涉及到的矩阵:
    Eigen::DiagonalMatrix<double, 12> Q;        // Q    (对角阵)
    Eigen::DiagonalMatrix<double, 4> R;         // R    (对角阵)
    Eigen::Matrix<double, 12, 12> a;            // A    (状态量 x 的维数为 12)
    Eigen::Matrix<double, 12, 4> b;             // B    (控制量 u 的维数为 4 )
    Eigen::Matrix<double, 12, 1> xMax;          // x_max
    Eigen::Matrix<double, 12, 1> xMin;          // x_min
    Eigen::Matrix<double, 4, 1> uMax;           // u_max
    Eigen::Matrix<double, 4, 1> uMin;           // u_min
    Eigen::Matrix<double, 12, 1> x0;            // x_0
    Eigen::Matrix<double, 12, 1> xRef;          // x_ref

    // 指定初始值和参考值:
    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;

    // 初始化 QP 问题涉及到的矩阵:
    // 注意:这里只是初始化了名字,并未指定分配的内存空间的大小
    Eigen::SparseMatrix<double> hessian;        // P    (稀疏矩阵)
    Eigen::VectorXd gradient;                   // q
    Eigen::SparseMatrix<double> linearMatrix;   // A_c  (稀疏矩阵)
    Eigen::VectorXd lowerBound;                 // l
    Eigen::VectorXd upperBound;                 // u

    // 对原始问题中的矩阵进行赋值:
    // 注意:这些函数根据自己实际的需要进行赋值,不同的系统这些矩阵的值肯定是不一样的
    setWeightMatrices(Q, R);                            // 赋值矩阵 Q / R
    setDynamicsMatrices(a, b);                          // 赋值矩阵 A / B
    setInequalityConstraints(xMax, xMin, uMax, uMin);   // 赋值约束条件

    // 对 QP 问题中的矩阵进行赋值:
    // 注意:这些函数不需要调整,固定的函数格式,因为都是通过上面原始问题的矩阵进行赋值的,只要传入的原始矩阵被修改了就行了
    castMPCToQPHessian(Q, R, mpcWindow, hessian);                                                   // 赋值稀疏矩阵 P
    castMPCToQPGradient(Q, xRef, mpcWindow, gradient);                                              // 赋值向量 q
    castMPCToQPConstraintMatrix(a, b, mpcWindow, linearMatrix);                                     // 赋值稀疏矩阵 A_c
    castMPCToQPConstraintVectors(xMax, xMin, uMax, uMin, x0, mpcWindow, lowerBound, upperBound);    // 赋值左右约束 l / u

    // 创建求解器:
    // 注意:这句话只是实例化了一个求解器对象,但并未对其进行任何的配置
    OsqpEigen::Solver solver;

    // 配置求解器的设置:
    solver.settings()->setVerbosity(true);
    // 解释:这行代码会设置求解器的输出冗长程度,setVerbosity(false) 会关闭求解器的详细输出,使其在求解过程中不输出额外的信息,这在需要安静地运行求解器时非常有用;
    solver.settings()->setWarmStart(true);
    // 解释:启用 WarmStart 功能,加快求解速度(启用 WarmStart 意味着求解器在求解问题时,可以利用之前求解的结果作为初始猜测来加速收敛,这在处理连续求解类似问题时特别有用,可以显著减少计算时间);

    // 配置求解器的数据:
    // 包括:变量数目 + 约束数目 + 矩阵P + 梯度向量q + 线性约束矩阵A_c + 变量下界l + 上界u
    // 解释:如果传入失败,各个函数返回值为 false
    solver.data()->setNumberOfVariables(12 * (mpcWindow + 1) + 4 * mpcWindow);          // 设置优化问题的变量数目( x_0 ~ x_N  +  u_0 ~ u_N-1 )
    solver.data()->setNumberOfConstraints(2 * 12 * (mpcWindow + 1) + 4 * mpcWindow);    // 设置优化问题的约束数目
    if (!solver.data()->setHessianMatrix(hessian)) return 1;                            // 传入 P 矩阵
    if (!solver.data()->setGradient(gradient)) return 1;                                // 传入 q 向量
    if (!solver.data()->setLinearConstraintsMatrix(linearMatrix)) return 1;             // 传入 A_c 矩阵
    if (!solver.data()->setLowerBound(lowerBound)) return 1;                            // 传入 l
    if (!solver.data()->setUpperBound(upperBound)) return 1;                            // 传入 u

    // 初始化求解器:
    // 解释:使用上面已经传入 settings() 和 data() 的配置参数,来初始化求解器
    if (!solver.initSolver()) return 1;

    // 定义矩阵存放:控制输入 / 求解器输出的解
    Eigen::Vector4d ctr;            // 存储 MPC 控制器输出的控制量
    Eigen::VectorXd QPSolution;     // 存储求解器的解

    // 定义求解器最大迭代次数
    int numberOfSteps = 50;

    // 开始求解:
    for (int i = 0; i < numberOfSteps; i++) {
        // 使用求解器 solver 求解 QP 问题
        if (solver.solveProblem() != OsqpEigen::ErrorExitFlag::NoError) return 1;

        // 将 QP 问题的解存储在向量 QPSolution 中
        QPSolution = solver.getSolution();

        // 取解的第一个时间步的控制量 u0 放入向量 ctr 中
        ctr = QPSolution.block(12 * (mpcWindow + 1), 0, 4, 1);

        // 将当前时间步 0 处的状态 x0 的数据保存到 x0Data 中
        auto x0Data = x0.data();

        // 更新当前状态,时间步往前走一步
        x0 = a * x0 + b * ctr;
        
        // 根据更新后的 x0 值更新约束 l 和 u
        // 解释:x0 的变化只会影响 l 和 u,而不会对矩阵 A_c / P / q 造成影响
        updateConstraintVectors(x0, lowerBound, upperBound);

        // 将更新后的约束边界应用于求解器
        if (!solver.updateBounds(lowerBound, upperBound)) return 1;
    }

    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ARM-Linux篇】智能家居语音模块配置

1. pin脚配置&#xff1a; 2. 命令词自定义基本信息&#xff1a; 3. 命令词自定控制详情: • 测试&#xff1a;串口模块可先通过串口助手验证每个指令的准确性&#xff0c; 然后运行wiringOP中的serialTest程序(需把/dev/ttyS2改成/dev/ttyS5) 然后语音接收到指令后(比如喊你好…

如何在 Postman 中进行 HTTPS 请求

https 请求是一种安全的网络通信方式&#xff0c;它使用 SSL/TLS 协议来加密数据和验证身份。在 postman 中发起 https 请求的步骤如下。 Postman 发起 https 请求 1、打开 postman 应用程序&#xff0c;点击左上角的“”号按钮&#xff0c;创建一个新的请求。 2、在请求标签…

MB-iSTFT-VITS 模型论文思路与实验分享:基于VITS架构优化的轻量级文本转语音模型

参考文献&#xff1a; [1] Kawamura M, Shirahata Y, Yamamoto R, et al. Lightweight and high-fidelity end-to-end text-to-speech with multi-band generation and inverse short-time fourier transform[C]//ICASSP 2023-2023 IEEE International Conference on Acoustics…

万能破题方法包(3)暴力破解法

一、前言 暴力破解法是指通过尝试所有可能的密码组合来破解密码 1.1、概念 暴力破解法是一种通过尝试所有可能的密码组合来破解密码的方法。它基于暴力的方式&#xff0c;不依赖于任何密码漏洞或特殊技巧&#xff0c;而是通过穷举所有可能性来找到正确的密码。 1.2、解决步骤 …

华为数通题库HCIP-821——最新最全(带答案解析)

单选1、下面是一台路由器的输出信息&#xff0c;关于这段信息描述正确的是 A目的网段1.1.1.0/24所携带的团体属性值是no—export表明该路由条目不能通告给任何BGP邻居 B目的网段5.1.1.0/24所携带的团体属性值是no—advertise表明该路由条目不能被通告给任何其他的BGP对等体 C…

【秋招突围】2024届秋招笔试-小红书笔试题-第一套-三语言题解(Java/Cpp/Python)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系计划跟新各公司春秋招的笔试题 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f4e7; 清隆这边…

若依Ruoyi-vue和element admin的区别,该如何选择。

提到中后台的前端框架&#xff0c;每个人都能列举出很多&#xff0c;这其中提及率比较高的就是Ruoyi和element admin两款&#xff0c;很多小伙伴分不清二者&#xff0c;本文为大家详细讲解一下。 一、若依Ruoyi-vue是什么&#xff1f; 若依Ruoyi-Vue是一款基于 Vue.js 开发的…

英伟达开源3400亿参数巨兽,98%合成数据训练出最强开源通用模型!性能媲美GPT-4o

英伟达刚刚再次证明了其在AI创新领域的领导地位。 它全新发布的Nemotron-4 340B&#xff0c;是一系列具有开创意义的开源模型&#xff0c;有望彻底改变训练LLM的合成数据生成方式&#xff01; 这一突破性进展标志着AI行业的一个重要里程碑—— 各行各业无需依赖昂贵的真实世界数…

挑战5分钟内基于Springboot+SpringMVC+Mybatis-plus快速构建web后端三层架构

目标 在清晨的代码编辑器上&#xff0c;一场新的挑战即将开始。程序员们肃立于安静的办公室&#xff0c;眼神专注地盯着屏幕&#xff0c;等待着编译器的一声提示。 随着编译器输出的激动人心的"start!"的提示&#xff0c;战斗的序幕拉开了。Bug如潮水般涌来&#x…

Golang——gRPC gateway网关

前言 etcd3 API全面升级为gRPC后&#xff0c;同时要提供REST API服务&#xff0c;维护两个版本的服务显然不大合理&#xff0c;所以gRPC-gateway诞生了。通过protobuf的自定义option实现了一个网关。服务端同时开启gRPC和HTTP服务&#xff0c;HTTP服务接收客户端请求后转换为gr…

消息群发工具制作的过程和需要用到的源代码!

在信息化快速发展的今天&#xff0c;消息群发工具因其高效、便捷的特点&#xff0c;在各个领域得到了广泛的应用&#xff0c;无论是企业营销、社交互动&#xff0c;还是日常通知&#xff0c;消息群发工具都发挥着不可替代的作用。 本文将详细介绍消息群发工具的制作过程&#…

[RL9] Rocky Linux 9.4 搭载 PG 16.1

副标题&#xff1a;Rocky Linux 9.4 升级实录&#xff0c;及 PG 16 相关内容 背景 Rocky Linux 9.4 (以下简称 RL) 于5月9日正式发布&#xff0c;本文记录了从 RL 9.3 升级到 9.4 的过程&#xff0c;以及升级前后的一些变化。 之前介绍过 RL 9 的相关内容&#xff0c;请戳&…

【C++】STL中stack、queue、deque的使用

前言&#xff1a;在前面我们学习了List的模拟实现与使用&#xff0c;今天我们进一步的来学习stack、queue、deque的使用方法&#xff0c;然后为后面的模拟实现做一下铺垫。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:高质量&#xff…

liunx常见指令

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 二、安装环境 1.租借服务器 2.下载安装 XShell 3.使用xshll登录服务器 三、Linux基础命令 一、文件和命令 ​编辑1、cd 命令 2、pwd 命令 3、ls 命令 4、cp 命令 …

【three.js案例一】智慧星球

直接附上源码: import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js;//场景 const scene = new THREE.Scene();const geometry = new THREE.SphereGeometry(50,32,16);console.log(.postion,geometry.attributes.position)…

上网行为管理产品有哪些?好用的四款上网行为管理产品

上网行为管理产品是现代企业网络安全架构中的重要组成部分&#xff0c;它们旨在帮助企业有效监控、管理和控制员工的网络使用行为&#xff0c;确保网络资源的合理利用&#xff0c;保障信息安全&#xff0c;提升工作效率。 以安企神为例&#xff0c;我们将详细介绍它的主要功能…

python保存文件后打不开的原因是什么

引入数据集&#xff0c;奇怪的是怎么也打不开&#xff0c;显示不存在这个文件&#xff1a; 但是&#xff0c;我将文件改个名字&#xff0c;就打开了&#xff0c;难道csv的文件命名必须有一定合法性&#xff1f; import pandas users pandas.read_csv("H:\python\data an…

OpenDevin 环境配置及踩坑指南

不惧怕任何环境配置 首先 clone 项目&#xff0c;然后查看开发者文档&#xff1a;https://github.com/OpenDevin/OpenDevin/blob/main/Development.md make setup-config 自定义 LLM 配置 首先这个 devin 写的是支持自定义的 LLM 配置&#xff0c;并且提供了交互式命令供我们…

华为云计算和数通有什么用?大咖在这里为你讲解

网工这一职业的就业前景&#xff0c;是一直以来都被看好的。薪资水平普遍较高&#xff0c;随着经验的积累&#xff0c;薪资水平还会不断提升&#xff0c;职业发展路径也非常广阔。 谈到网工&#xff0c;就绕不开华为认证&#xff0c;华为认证作为网络工程师的一块金字招牌&…

mini web框架示例

web框架&#xff1a; 使用web框架专门负责处理用户的动态资源请求&#xff0c;这个web框架其实就是一个为web服务器提供服务的应用程序 什么是路由&#xff1f; 路由就是请求的url到处理函数的映射&#xff0c;也就是说提前把请求的URL和处理函数关联好 管理路由可以使用一个…