自动驾驶控制与规划——Project 3: LQR车辆横向控制

news/2024/12/22 18:24:12 标签: 自动驾驶, 人工智能, 机器学习

目录

  • 零、任务介绍
  • 一、系统建模
    • 1.1 连续模型
    • 1.2 离散化
  • 二、算法
    • 2.1 离散时间LQR
    • 2.2 前馈控制
  • 三、代码实现
  • 四、效果展示

零、任务介绍

  1. 补全src/ros-bridge/carla_shenlan_projects/carla_shenlan_lqr_pid_controller/src/lqr_controller.cpp中的TODO部分,实现基于LQR的车辆横向控制。

一、系统建模

1.1 连续模型

在这里插入图片描述

选取系统状态变量为 x = [ e c g , e ˙ c g , e θ , e ˙ θ ] ⊤ x = [e_{cg}, \dot e_{cg}, e_\theta, \dot e_\theta]^\top x=[ecg,e˙cg,eθ,e˙θ],控制输入为 u = δ u = \delta u=δ。系统参考轨迹每个点可以给出位置、偏航角、速度、加速度、曲率,这些量可以表示为沿轨迹行驶路程 s s s的函数。车辆的横向误差动力学可以用如下的线性模型描述
x ˙ = A x + B 1 δ + B 2 r d e s \dot x = Ax + B_1\delta + B_2 r_{des} x˙=Ax+B1δ+B2rdes
其中
A = [ 0 1 0 0 0 − ( c f + c r ) m v c f + c r m l r c r − l f c f m v 0 0 0 1 0 l r c r − l f c f I z v l r c r − l f c f I z − ( l f 2 c f + l r 2 c r ) I z v ] A = \left[\begin{matrix} 0 & 1 & 0 & 0\\ 0 & -\frac{(c_f + c_r)}{mv} & \frac{c_f + c_r}{m} & \frac{l_r c_r - l_f c_f}{mv}\\ 0 & 0 & 0 & 1\\ 0 & \frac{l_r c_r - l_f c_f}{I_z v} & \frac{l_r c_r - l_f c_f}{I_z} & -\frac{(l_f^2 c_f + l_r^2 c_r)}{I_z v} \end{matrix}\right] A= 00001mv(cf+cr)0Izvlrcrlfcf0mcf+cr0Izlrcrlfcf0mvlrcrlfcf1Izv(lf2cf+lr2cr)

B 1 = [ 0 c f / m 0 l f c f / I z ] B_1 = \left[\begin{matrix} 0\\ c_f / m\\ 0\\ l_f c_f /I_z \end{matrix}\right] B1= 0cf/m0lfcf/Iz

B 2 = [ 0 ( l r c r − l f c f ) / ( m v ) − v 0 − ( l f 2 c f + l r 2 c r ) / ( I z v ) ] B_2 = \left[ \begin{matrix} 0\\ (l_r c_r - l_f c_f)/(mv) - v\\ 0\\ -(l_f^2 c_f + l_r^2 c_r)/(I_z v) \end{matrix} \right] B2= 0(lrcrlfcf)/(mv)v0(lf2cf+lr2cr)/(Izv)
这里实际上是假设在一小段时间内车辆纵向速度 v v v保持不变,然后在工作点线性化,参考轨迹的偏航角速度 r d e s r_{des} rdes可以由参考点速度 v d e s v_{des} vdes和参考点曲率 κ d e s \kappa_{des} κdes计算
r d e s = v d e s κ d e s r_{des} = v_{des} \kappa_{des} rdes=vdesκdes

1.2 离散化

实际实现时,需要将系统离散化,离散化可以采取前向欧拉法和零阶保持器精确离散化等方法。采样周期为 T T T,前向欧拉法得到的离散系统矩阵和输入矩阵为
A d = I + T A B d 1 = T B 1 B d 2 = T B 2 \begin{aligned} A_d &= I + TA \\ B_{d1} &= TB_1 \\ B_{d2} &= TB_2 \end{aligned} AdBd1Bd2=I+TA=TB1=TB2
为了达到更好的准确性和稳定性,可以使用零阶保持器进行精确离散化,精确离散化的计算公式如下:
A d = e A T B d 1 = ( ∫ 0 T e A τ d τ ) B 1 B d 2 = ( ∫ 0 T e A τ d τ ) B 2 \begin{aligned} A_d &= e^{AT}\\ B_{d1} &= (\int_0^T e^{A\tau} d\tau)B_1 \\ B_{d2} &= (\int_0^T e^{A\tau} d\tau)B_2 \end{aligned} AdBd1Bd2=eAT=(0TeAτdτ)B1=(0TeAτdτ)B2
矩阵指数可以用如下的拉普拉斯逆变换得到
e A t = L − 1 [ ( s I − A ) − 1 ] e^{At} = \mathcal L^{-1}[(sI - A)^{-1}] eAt=L1[(sIA)1]
本次project中的参数如下

参数名称符号数值
前轮侧偏刚度(左右轮之和) c f c_f cf155494.663
后轮侧偏刚度(左右轮之和) c r c_r cr155494.663
前轮到质心的距离 l f l_f lf1.426
后轮到质心的距离 l r l_r lr1.426
整车质量 m m m1845.0
Z轴转动惯量 I z I_z Iz3751.76
控制周期 t s t_s ts0.01

使用mathematica计算矩阵拉普拉斯反变换

ts = 0.01;
cf = 155494.663;
cr = 155494.663;
mfl = 1845.0/4;
mfr = 1845.0/4;
mrl = 1845.0/4;
mrr = 1845.0/4;
mf = mfl + mfr;
mr = mrl + mrr;
m = mf + mr
wheelbase = 2.852;
lf = wheelbase * (1.0 - mf/m)
lr = wheelbase * (1.0 - mr /m)

iz = lf*lf*mf + lr*lr*mr

A = {
  {0, 1, 0, 0},
  {0, -(cf + cr)/(m*v), (cf + cr)/m, (lr*cr - lf*cf)/(m*v)},
  {0, 0, 0, 1},
  {0, (lr*cr - lf*cf)/(iz * v), (lr*cr - lf*cf)/
    iz, -(lf^2*cf + lr^2*cr)/(iz * v)}
  }
  InverseLaplaceTransform[Inverse[s*IdentityMatrix[4] - A], s, 0.01]

计算得到的矩阵 A d A_d Ad如下
A d = ( 1.0 v ( 0.00593268 − 0.00593268 e − 1.68558 / v ) v ( ( 0.00593268 e − 1.68558 / v − 0.00593268 ) v + 0.01 ) 168.558 ( e − 1.68558 / v ( 4.176213162156246 × 1 0 − 7 v + 3.519668608485906 × 1 0 − 7 ) − 4.176213162156246 × 1 0 − 7 v + 3.519668608485956 × 1 0 − 7 ) v 2 0. 1. e − 1.68558 / v v ( 1. − 1. e − 1.68558 / v ) 168.558 ( e − 1.68558 / v ( − 0.0000351967 v − 0.0000593268 ) + 0.0000351967 v ) v 0. 0. 1. e − 1.68558 / v ( − 0.00593268 v − 3.3723378595376876 × 1 0 − 18 ) + 0.00593268 v 0. 0. 0. e − 1.68558 / v ( 1. v + 2.842170943040401 × 1 0 − 16 ) v ) A_d = \left( \begin{array}{cccc} 1.0 & v \left(0.00593268 - 0.00593268 e^{-1.68558/v}\right) & v \left(\left(0.00593268 e^{-1.68558/v}-0.00593268\right) v + 0.01\right) & 168.558 \left(e^{-1.68558/v} \left(4.176213162156246 \times 10^{-7} v + 3.519668608485906 \times 10^{-7}\right) - 4.176213162156246 \times 10^{-7} v + 3.519668608485956 \times 10^{-7}\right) v^2 \\ 0. & 1. e^{-1.68558/v} & v \left(1. - 1. e^{-1.68558/v}\right) & 168.558 \left(e^{-1.68558/v} \left(-0.0000351967 v - 0.0000593268\right) + 0.0000351967 v\right) v \\ 0. & 0. & 1. & e^{-1.68558/v} \left(-0.00593268 v - 3.3723378595376876 \times 10^{-18}\right) + 0.00593268 v \\ 0. & 0. & 0. & \frac{e^{-1.68558/v} \left(1. v + 2.842170943040401 \times 10^{-16}\right)}{v} \end{array} \right) Ad= 1.00.0.0.v(0.005932680.00593268e1.68558/v)1.e1.68558/v0.0.v((0.00593268e1.68558/v0.00593268)v+0.01)v(1.1.e1.68558/v)1.0.168.558(e1.68558/v(4.176213162156246×107v+3.519668608485906×107)4.176213162156246×107v+3.519668608485956×107)v2168.558(e1.68558/v(0.0000351967v0.0000593268)+0.0000351967v)ve1.68558/v(0.00593268v3.3723378595376876×1018)+0.00593268vve1.68558/v(1.v+2.842170943040401×1016)
通过观察可以得到,矩阵 A A A不可逆,因此计算 B d B_d Bd时采用幂级数展开求 e A τ e^{A\tau} eAτ的积分,即
B d 1 = ∫ 0 T e A τ d τ B 1 = ∫ 0 T ( I + A τ + ( A τ ) 2 2 ! + ( A τ ) 3 3 ! + ⋯   ) d τ B 1 = ( T I + T 2 2 A + T 3 6 A 2 + ⋯   ) B 1 ≈ T B 1 \begin{aligned} B_{d1} &= \int_0^T e^{A\tau} d\tau B_1 \\ &= \int_0^T(I + A\tau + \frac{(A\tau)^2}{2!} + \frac{(A\tau)^3}{3!} + \cdots)d\tau B_1 \\ & = (TI + \frac{T^2}{2}A + \frac{T^3}{6}A^2 + \cdots) B_1 \\ &\approx TB_1 \end{aligned} Bd1=0TeAτdτB1=0T(I+Aτ+2!(Aτ)2+3!(Aτ)3+)dτB1=(TI+2T2A+6T3A2+)B1TB1
类似地有 B d 2 = T B 2 B_{d2} = TB_2 Bd2=TB2

二、算法

2.1 离散时间LQR

离散时间LQR的最优控制律为
u ∗ ( k ) = − K x ( k ) u^*(k) = - K x(k) u(k)=Kx(k)
其中反馈增益 K K K
K = ( R + B d ⊤ P B d ) − 1 B d ⊤ P A d K = (R + B_d^\top P B_d)^{-1} B_d^\top P A_d K=(R+BdPBd)1BdPAd
其中正定矩阵P可以使用动态规划算法得出,迭代形式如下
P t − 1 = A d ⊤ P t A d − A d ⊤ P t B d ( R + B d ⊤ P t B d ) − 1 B d ⊤ P t A d + Q P_{t-1} = A_d^\top P_{t} A_d - A_d^\top P_{t} B_d (R + B_d^\top P_{t} B_d)^{-1}B_d^\top P_{t} A_d + Q Pt1=AdPtAdAdPtBd(R+BdPtBd)1BdPtAd+Q
此处的推导细节参考基础算法 - LQR - 离散时间有限边界

2.2 前馈控制

采用上述状态反馈控制律后,系统的模型可以改写成
x ˙ = ( A − B 1 K ) x + B 2 r d e s \dot x = (A - B_1 K) x + B_2 r_{des} x˙=(AB1K)x+B2rdes
闭环系统 A − B 1 K A - B_1K AB1K是渐进稳定的,但是由于 B 2 r d e s B_2 r_{des} B2rdes的存在,无法保证系统状态量收敛至0。因此需要在控制律中加入前馈项
δ = − K x + δ f f \delta = -Kx + \delta_{ff} δ=Kx+δff
前馈项的设计参考自动驾驶控制算法 —— 横向LQR控制+前馈控制
δ f f = L κ + K v a y − k 3 κ ( l r − l f m v x 2 2 c α r L ) \begin{aligned} \delta_{ff} = L \kappa + K_v a_y - k_3 \kappa (l_r - \frac{l_f mv_x^2}{2c_{\alpha r} L}) \end{aligned} δff=Lκ+Kvayk3κ(lr2cαrLlfmvx2)
其中
K v = l r m 2 c α f ( l f + l r ) − l f m 2 c α r ( l f + l r ) K_v = \frac{l_r m}{2 c_{\alpha f} (l_f + l_r)} - \frac{l_f m}{2 c_{\alpha r} (l_f + l_r)} Kv=2cαf(lf+lr)lrm2cαr(lf+lr)lfm

三、代码实现

计算控制量

bool LqrController::ComputeControlCommand(const VehicleState &localization, const TrajectoryData &planning_published_trajectory, ControlCmd &cmd) {
    // 规划轨迹
    trajectory_points_ = planning_published_trajectory.trajectory_points;
    /*
    A matrix (Gear Drive)
    [0.0,                               1.0,                            0.0,                                               0.0;
     0.0,            (-(c_f + c_r) / m) / v,                (c_f + c_r) / m,                   (l_r * c_r - l_f * c_f) / m / v;
     0.0,                               0.0,                            0.0,                                               1.0;
     0.0,   ((lr * cr - lf * cf) / i_z) / v,   (l_f * c_f - l_r * c_r) / i_z,   (-1.0 * (l_f^2 * c_f + l_r^2 * c_r) / i_z) / v;]
    */
    // TODO 01 配置状态矩阵A
    double v = max(localization.velocity, minimum_speed_protection_);    // 避免除0
    matrix_a_(1, 1) = matrix_a_coeff_(1, 1) / v;
    matrix_a_(1, 3) = matrix_a_coeff_(1, 3) / v;
    matrix_a_(3, 1) = matrix_a_coeff_(3, 1) / v;
    matrix_a_(3, 3) = matrix_a_coeff_(3, 3) / v;

    /*
    b = [0.0, c_f / m, 0.0, l_f * c_f / i_z]^T
    */
    // TODO 02 动力矩阵B
    matrix_bd_ = matrix_b_ * ts_;
    // cout << "matrix_bd_.row(): " << matrix_bd_.rows() << endl;
    // cout << "matrix_bd_.col(): " << matrix_bd_.cols() << endl;
    // Update state = [Lateral Error, Lateral Error Rate, Heading Error, Heading Error Rate]

    // TODO 03 计算横向误差并且更新状态向量x
    UpdateState(localization);

    // TODO 04 更新状态矩阵A并将状态矩阵A离散化
    UpdateMatrix(localization);

    // cout << "matrix_bd_.row(): " << matrix_bd_.rows() << endl;
    // cout << "matrix_bd_.col(): " << matrix_bd_.cols() << endl;

    // TODO 05 Solve Lqr Problem
    SolveLQRProblem(matrix_ad_, matrix_bd_, matrix_q_, matrix_r_, lqr_eps_, lqr_max_iteration_, &matrix_k_);

    // TODO 06 计算feedback, 根据反馈对计算状态变量(误差状态)的时候的符号的理解:K里面的值实际运算中全部为正值,steer = -K *
    // state,按照代码中采用的横向误差的计算方式,横向误差为正值的时候(state中的第一项为正),参考点位于车辆左侧,车辆应该向左转以减小误差,而根据试验,仿真器中,给正值的时候,车辆向右转,给负值的时候,车辆向左转。
    //   feedback = - K * state
    //   Convert vehicle steer angle from rad to degree and then to steer degrees
    //   then to 100% ratio
    std::cout << "matrix_k_: " << matrix_k_ << std::endl;
    cout << "matrix_k * matrix_state: " << (matrix_k_ * matrix_state_) << endl;
    double steer_angle_feedback = -(matrix_k_ * matrix_state_)(0, 0);

    // TODO 07 计算前馈控制,计算横向转角的反馈量
    double steer_angle_feedforward = 0.0;
    steer_angle_feedforward = ComputeFeedForward(localization, ref_curv_);
    double steer_angle = steer_angle_feedback - 0.9 * steer_angle_feedforward;
    cout << "steer_angle_feedforward: " << steer_angle_feedforward << endl;
    // 限制前轮最大转角,这里定义前轮最大转角位于 [-20度~20度]
    if (steer_angle >= atan2_to_PI(20.0)) {
        steer_angle = atan2_to_PI(20.0);
    } else if (steer_angle <= -atan2_to_PI(20.0)) {
        steer_angle = -atan2_to_PI(20.0);
    }
    // Set the steer commands

    cmd.steer_target = steer_angle;

    return true;
}

计算横向误差

// TODO 03 计算误差
void LqrController::ComputeLateralErrors(const double x, const double y, const double theta, const double linear_v, const double angular_v, const double linear_a, LateralControlErrorPtr &lat_con_err) {
    // 轨迹上最近的点
    TrajectoryPoint target_point = QueryNearestPointByPosition(x, y);

    double dx = target_point.x - x;
    double dy = target_point.y - y;
    double theta_p = target_point.heading;

    double e_cg = cos(theta_p) * dy - sin(theta_p) * dx;
    double e_theta = NormalizeAngle(theta_p - theta);
    double diff_e_cg = linear_v * sin(e_theta);
    double diff_e_theta = angular_v - target_point.kappa * target_point.v;

    lat_con_err->heading_error = e_theta;
    lat_con_err->lateral_error = e_cg;
    lat_con_err->heading_error_rate = diff_e_theta;
    lat_con_err->lateral_error_rate = diff_e_cg;
}

更新状态矩阵并离散化

// TODO 04 更新状态矩阵A并将状态矩阵A离散化
void LqrController::UpdateMatrix(const VehicleState &vehicle_state) {
    double v = max(vehicle_state.velocity, minimum_speed_protection_);
    // 更新A矩阵
    matrix_a_(1, 1) = matrix_a_coeff_(1, 1) / v;
    matrix_a_(1, 3) = matrix_a_coeff_(1, 3) / v;
    matrix_a_(3, 1) = matrix_a_coeff_(3, 1) / v;
    matrix_a_(3, 3) = matrix_a_coeff_(3, 3) / v;
    // 更新A_d矩阵,前向欧拉法
    // matrix_ad_ = Matrix::Identity(basic_state_size_, basic_state_size_) + matrix_a_ * ts_;
    // 更新A_d矩阵,ZOH精确离散化
    // 计算指数项
    double exp_term = exp(-1.68558 / v);

    // 使用ZOH精确离散化
    matrix_ad_(0, 0) = 1.0;
    matrix_ad_(0, 1) = 0.00593268 * (1.0 - exp_term);
    matrix_ad_(0, 2) = 0.010000000000000142 + (-0.00593268 + 0.00593268 * exp_term) / v;
    matrix_ad_(0, 3) = 168.55790027100272 * (3.519668608485956e-7 + exp_term * (3.519668608485906e-7 + 4.176213162156246e-7 * v)) - 4.176213162156246e-7 * v;

    matrix_ad_(1, 0) = 0.0;
    matrix_ad_(1, 1) = 1.0 * exp_term;
    matrix_ad_(1, 2) = 168.55790027100272 * (exp_term * (-0.000059326795029614625 - 0.000035196686084859064 * v) + 0.000035196686084859064 * v);
    matrix_ad_(1, 3) = 0.0;

    matrix_ad_(2, 0) = 0.0;
    matrix_ad_(2, 1) = 0.0;
    matrix_ad_(2, 2) = 1.0;
    matrix_ad_(2, 3) = exp_term * (-0.00593268 * v - 3.3723378595376876e-18) + 0.00593268 * v;

    matrix_ad_(3, 0) = 0.0;
    matrix_ad_(3, 1) = 0.0;
    matrix_ad_(3, 2) = 0.0;
    matrix_ad_(3, 3) = exp_term * (1.0 * v + 2.842170943040401e-16) / v;
}

求解LQR问题

// TODO 05:求解LQR方程
void LqrController::SolveLQRProblem(const Matrix &A, const Matrix &B, const Matrix &Q, const Matrix &R, const double tolerance, const uint max_num_iteration, Matrix *ptr_K) {
    // 防止矩阵的维数出错导致后续的运算失败
    if (A.rows() != A.cols() || B.rows() != A.rows() || Q.rows() != Q.cols() || Q.rows() != A.rows() || R.rows() != R.cols() || R.rows() != B.cols()) {
        std::cout << "LQR solver: one or more matrices have incompatible dimensions." << std::endl;
        return;
    }

    Matrix P = Q;
    Matrix P_new = Q;
    Matrix At = A.transpose();
    Matrix Bt = B.transpose();

    for (int i = 0; i < max_num_iteration; i++) {
        P_new = At * P * A - At * P * B * (R + Bt * P * B).inverse() * Bt * P * A + Q;
        double diff = fabs((P_new - P).maxCoeff());
        P = P_new;
        if (diff < tolerance) {
            cout << "diff: " << diff << endl;
            *ptr_K = (R + Bt * P * B).inverse() * Bt * P * A;
            return;
        }
    }
    cout << "exceed max iteration" << endl;
}

计算前馈控制量

// TODO 07 前馈控制,计算横向转角的反馈量
double LqrController::ComputeFeedForward(const VehicleState &localization, double ref_curvature) {
    if (isnan(ref_curvature)) {
        ref_curvature = 0;
    }
    const double K_v = lr_ * mass_ / (2 * cf_ * (lf_ + lr_));
    const double v = localization.velocity;
    double steer_angle_feedforward = ref_curvature * wheelbase_ 
    	+ K_v * v * v * ref_curvature 
    	- matrix_k_(0, 2) * ref_curvature * (lr_ - lf_ * mass_ * v * v / (2 * cr_ * wheelbase_));
    return steer_angle_feedforward;
}

四、效果展示

ZOH离散化(无前馈)
在这里插入图片描述
ZOH离散化(前馈)
在这里插入图片描述
前向欧拉法离散化(前馈)
在这里插入图片描述
通过观察可以发现

  1. 使用ZOH离散化后,转弯过后的横向振荡明显减少。
  2. 当轨迹的偏航角速度不为0时(转弯),未加入前馈时出现较大的横向误差,加入前馈后横向误差显著减小。

自动驾驶控制与规划——Project 3:LQR车辆横向控制


http://www.niftyadmin.cn/n/5795699.html

相关文章

Spring Boot 核心技术解析与应用实践

1.Spring Boot 需要独立的容器运行吗&#xff1f; Spring Boot 应用程序本身不需要独立的容器来运行&#xff0c;因为它可以被打包成一个包含所有依赖&#xff08;包括嵌入式HTTP服务器&#xff0c;如Tomcat、Jetty或Undertow&#xff09;的可执行JAR文件。这意味着你可以直接…

aiy【4】

那天下课&#xff0c;我目光呆滞地坐在位置上涂唇膏。 忽然又又想到他 到他位置旁笑他和朋友们打闹 上课铃响了 我的朋友&#xff08;男&#xff09;S小声急促的和我说&#xff1a;快&#xff01;快&#xff01;好机会&#xff01; 我红着脸吻了他的手 很快 S兴奋的叫了…

利用 Python 解决 “奇数之和” 问题

一、问题描述 在这个问题场景中&#xff0c;有着特定的时间和内存限制&#xff0c;每次测试时间限制为 2 秒&#xff0c;每个测试的内存限制为 256 MB。我们会获得两个整数 n 和 k&#xff0c;任务是判断 n 是否可以表示为 k 个不同的正奇数&#xff08;不能被 2 整除的整数&a…

ubuntu24.04使用opencv4

ubuntu24.04LTS自带opencv4.5代码实例 //opencv_example.cpp #include <opencv2/opencv.hpp> #include <iostream>int main() {// 读取图像cv::Mat img cv::imread("image.jpg", cv::IMREAD_COLOR);if (img.empty()) {std::cerr << "无法读…

【SH】在Ubuntu Server 24中基于Python Web应用的Flask Web开发(实现POST请求)学习笔记

文章目录 Flask开发环境搭建保持Flask运行Debug调试 路由和视图可变路由 请求和响应获取请求信息Request属性响应状态码常见状态码CookieSession 表单GET请求POST请求 Flask 在用户使用浏览器访问网页的过程中&#xff0c;浏览器首先会发送一个请求到服务器&#xff0c;服务器…

GIT与github的链接(同步本地与远程仓库)

1.官网下载GIT Git - 安装 Git 2.GIT生成密钥 2.1 打开gitbash配置邮箱与用户名&#xff08;非初次使用GIT跳过这一步&#xff09; git config --global user.name "你的用户名" git config --global user.email "你的邮箱" 2.2 生成ssh密匙 1&#xff…

MySQL通过日志恢复数据的步骤

试验环境&#xff1a;Windows Server2012 r2、MySql-8.0.27-winx64。 1、先检查MySQL有没有开启binlog日志 通过下面的SQL命令查看MySQL是否开启日志以及日志文件的位置&#xff1a; show variables like %log_bin% 执行结果如下图所示&#xff1a; 图中&#xff0c;log_bi…

重拾设计模式--备忘录模式

文章目录 备忘录模式&#xff08;Memento Pattern&#xff09;概述定义&#xff1a; 作用&#xff1a;实现状态的保存与恢复支持撤销 / 恢复操作 备忘录模式UML图备忘录模式的结构原发器&#xff08;Originator&#xff09;&#xff1a;备忘录&#xff08;Memento&#xff09;&…