From 5751829bab294019bb78917d91f280552bb549d2 Mon Sep 17 00:00:00 2001 From: Atsushi Sakai Date: Sat, 25 May 2024 23:31:54 +0900 Subject: [PATCH] Enhance lqr steering control docs (#1015) * enhance lqr steering control docs * enhance lqr steering control docs * enhance lqr steering control docs * enhance lqr steering control docs * improve lqr steering control docs --- .../lqr_steer_control/lqr_steer_control.py | 14 +-- docs/README.md | 2 +- .../lqr_steering_control_main.rst | 102 ++++++++++++++++++ .../lqr_steering_control_model.png | Bin 0 -> 17366 bytes 4 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 docs/modules/path_tracking/lqr_steering_control/lqr_steering_control_model.png diff --git a/PathTracking/lqr_steer_control/lqr_steer_control.py b/PathTracking/lqr_steer_control/lqr_steer_control.py index 4078ea56db9..461d6e37223 100644 --- a/PathTracking/lqr_steer_control/lqr_steer_control.py +++ b/PathTracking/lqr_steer_control/lqr_steer_control.py @@ -55,7 +55,7 @@ def update(state, a, delta): return state -def PIDControl(target, current): +def pid_control(target, current): a = Kp * (target - current) return a @@ -70,10 +70,11 @@ def solve_DARE(A, B, Q, R): solve a discrete time_Algebraic Riccati equation (DARE) """ X = Q - maxiter = 150 + Xn = Q + max_iter = 150 eps = 0.01 - for i in range(maxiter): + for i in range(max_iter): Xn = A.T @ X @ A - A.T @ X @ B @ \ la.inv(R + B.T @ X @ B) @ B.T @ X @ A + Q if (abs(Xn - X)).max() < eps: @@ -178,7 +179,7 @@ def closed_loop_prediction(cx, cy, cyaw, ck, speed_profile, goal): dl, target_ind, e, e_th = lqr_steering_control( state, cx, cy, cyaw, ck, e, e_th) - ai = PIDControl(speed_profile[target_ind], state.v) + ai = pid_control(speed_profile[target_ind], state.v) state = update(state, ai, dl) if abs(state.v) <= stop_speed: @@ -202,8 +203,9 @@ def closed_loop_prediction(cx, cy, cyaw, ck, speed_profile, goal): if target_ind % 1 == 0 and show_animation: plt.cla() # 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.gcf().canvas.mpl_connect( + 'key_release_event', + lambda event: [exit(0) if event.key == 'escape' else None]) plt.plot(cx, cy, "-r", label="course") plt.plot(x, y, "ob", label="trajectory") plt.plot(cx[target_ind], cy[target_ind], "xg", label="target") diff --git a/docs/README.md b/docs/README.md index 5c4145e8e38..fb7d4cc3bc7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ This folder contains documentation for the Python Robotics project. #### Install Sphinx and Theme ``` -pip install sphinx sphinx-autobuild sphinx-rtd-theme +pip install sphinx sphinx-autobuild sphinx-rtd-theme sphinx_rtd_dark_mode sphinx_copybutton sphinx_rtd_dark_mode ``` #### Building the Docs diff --git a/docs/modules/path_tracking/lqr_steering_control/lqr_steering_control_main.rst b/docs/modules/path_tracking/lqr_steering_control/lqr_steering_control_main.rst index bf6d6b5854c..b18e4728cd7 100644 --- a/docs/modules/path_tracking/lqr_steering_control/lqr_steering_control_main.rst +++ b/docs/modules/path_tracking/lqr_steering_control/lqr_steering_control_main.rst @@ -8,7 +8,109 @@ control. .. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/PathTracking/lqr_steer_control/animation.gif +Overview +~~~~~~~~ + +The LQR (Linear Quadratic Regulator) steering control model implemented in lqr_steer_control.py provides a simulation +for an autonomous vehicle to track a desired trajectory by adjusting steering angle based on feedback from the current state and the desired trajectory. +This model utilizes a combination of PID speed control and LQR steering control to achieve smooth and accurate trajectory tracking. + +Vehicle motion Model +~~~~~~~~~~~~~~~~~~~~~ + +The below figure shows the geometric model of the vehicle used in this simulation: + +.. image:: lqr_steering_control_model.png + :width: 600px + +The `e` and `theta` represent the lateral error and orientation error, respectively, with respect to the desired trajectory. +And :math:`\dot{e}` and :math:`\dot{\theta}` represent the rates of change of these errors. + +The :math:`e_t` and :math:`theta_t` are the updated values of `e` and `theta` at time `t`, respectively, and can be calculated using the following kinematic equations: + +.. math:: e_t = e_{t-1} + \dot{e}_{t-1} dt + +.. math:: \theta_t = \theta_{t-1} + \dot{\theta}_{t-1} dt + +Where `dt` is the time difference. + +The change rate of the `e` can be calculated as: + +.. math:: \dot{e}_t = V \sin(\theta_{t-1}) + +Where `V` is the vehicle speed. + +If the :math:`theta` is small, + +.. math:: \theta \approx 0 + +the :math:`\sin(\theta)` can be approximated as :math:`\theta`: + +.. math:: \sin(\theta) = \theta + +So, the change rate of the `e` can be approximated as: + +.. math:: \dot{e}_t = V \theta_{t-1} + +The change rate of the :math:`\theta` can be calculated as: + +.. math:: \dot{\theta}_t = \frac{V}{L} \tan(\delta) + +where `L` is the wheelbase of the vehicle and :math:`\delta` is the steering angle. + +If the :math:`\delta` is small, + +.. math:: \delta \approx 0 + +the :math:`\tan(\delta)` can be approximated as :math:`\delta`: + +.. math:: \tan(\delta) = \delta + +So, the change rate of the :math:`\theta` can be approximated as: + +.. math:: \dot{\theta}_t = \frac{V}{L} \delta + +The above equations can be used to update the state of the vehicle at each time step. + +To formulate the state-space representation of the vehicle dynamics as a linear model, +the state vector `x` and control input vector `u` are defined as follows: + +.. math:: x_t = [e_t, \dot{e}_t, \theta_t, \dot{\theta}_t]^T + +.. math:: u_t = \delta_t + +The state transition equation can be represented as: + +.. math:: x_{t+1} = A x_t + B u_t + +where: + +:math:`\begin{equation*} A = \begin{bmatrix} 1 & dt & 0 & 0\\ 0 & 0 & v & 0\\ 0 & 0 & 1 & dt\\ 0 & 0 & 0 & 0 \\ \end{bmatrix} \end{equation*}` + +:math:`\begin{equation*} B = \begin{bmatrix} 0\\ 0\\ 0\\ \frac{v}{L} \\ \end{bmatrix} \end{equation*}` + +LQR controller +~~~~~~~~~~~~~~~ + +The Linear Quadratic Regulator (LQR) controller is used to calculate the optimal control input `u` that minimizes the quadratic cost function: + +:math:`J = \sum_{t=0}^{N} (x_t^T Q x_t + u_t^T R u_t)` + +where `Q` and `R` are the weighting matrices for the state and control input, respectively. + +for the linear model: + +:math:`x_{t+1} = A x_t + B u_t` + +The optimal control input `u` can be calculated as: + +:math:`u_t = -K x_t` + +where `K` is the feedback gain matrix obtained by solving the Riccati equation. + References: ~~~~~~~~~~~ - `ApolloAuto/apollo: An open autonomous driving platform `_ +- `Linear Quadratic Regulator (LQR) `_ + diff --git a/docs/modules/path_tracking/lqr_steering_control/lqr_steering_control_model.png b/docs/modules/path_tracking/lqr_steering_control/lqr_steering_control_model.png new file mode 100644 index 0000000000000000000000000000000000000000..961dfcdc05cd4938c1ea7ebc79e1c8e0314e9dd4 GIT binary patch literal 17366 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 ShTuQyfRXff>lG92j{hIC6RZ#b literal 0 HcmV?d00001