Instructor: Hasan A. Poonawala
Mechanical and Aerospace Engineering
University of Kentucky, Lexington, KY, USA
Topics:
Double Integrator Model
LQR Cost Function
Backward Riccati Recursion
Interactive Demo
A particle with unit mass moving in 1D under applied force :
Define state . Discrete dynamics with timestep :
Here and are the usual state-transition and input matrices — kept separate, unlike the augmented form in the generalized LQR.
Minimize over :
subject to .
| Symbol | Role | Requirement |
|---|---|---|
| State cost matrix | ||
| Control cost matrix | ||
| Terminal cost matrix |
We write (not ) for the state cost to avoid conflict with the Q-function used in the generalized formulation.
Define the cost-to-go from time :
Terminal condition:
Bellman recursion: for :
Assume . Expand and minimize over :
Substituting back: where
Finite Horizon LQR (Standard Form)
Inputs: , , , , , horizon
Backward pass — set ; for :
Forward pass — given , for :
Gains depend only on — not on . Cost scales quadratically with by linearity.
Set initial conditions and observe the optimal trajectory.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 580
from shiny import App, render, ui
import numpy as np
app_ui = ui.page_fluid(
ui.h4("Finite Horizon LQR — Double Integrator"),
ui.layout_sidebar(
ui.sidebar(
ui.h6("Initial State x₀"),
ui.input_slider("p0", "Position p₀", min=-5.0, max=5.0, value=3.0, step=0.1),
ui.input_slider("v0", "Velocity v₀", min=-5.0, max=5.0, value=0.0, step=0.1),
ui.hr(),
ui.h6("Horizon & Weights"),
ui.input_slider("T", "Horizon T", min=5, max=60, value=20, step=5),
ui.input_slider("wp", "State weight wp", min=0.1, max=10.0, value=1.0, step=0.1),
ui.input_slider("wv", "State weight wv", min=0.1, max=10.0, value=1.0, step=0.1),
ui.input_slider("r", "Control weight r", min=0.01, max=5.0, value=1.0, step=0.05),
ui.input_slider("wf", "Terminal weight wf",min=1.0, max=50.0, value=10.0,step=1.0),
width=280,
),
ui.output_plot("lqr_plot"),
),
)
def server(input, output, session):
@render.plot
def lqr_plot():
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
dt = 0.1
T = input.T()
wp, wv, r, wf = input.wp(), input.wv(), input.r(), input.wf()
x0 = np.array([input.p0(), input.v0()])
A = np.array([[1.0, dt], [0.0, 1.0]])
B = np.array([[0.5*dt**2], [dt]])
W = np.diag([wp, wv])
R = np.array([[r]])
Wf = wf * np.eye(2)
# --- Backward Riccati pass ---
P = Wf.copy()
Ks = []
for _ in range(T):
S = R + B.T @ P @ B
K = np.linalg.solve(S, B.T @ P @ A)
P = W + A.T @ P @ (A - B @ K)
Ks.append(K)
Ks.reverse()
# --- Forward pass ---
xs = np.zeros((T + 1, 2))
us = np.zeros(T)
xs[0] = x0
J = 0.0
for t in range(T):
u = -(Ks[t] @ xs[t])
us[t] = u[0]
xs[t+1] = A @ xs[t] + B.flatten() * us[t]
J += xs[t] @ W @ xs[t] + r * us[t]**2
J += xs[T] @ Wf @ xs[T]
# --- Plot ---
time = np.arange(T + 1) * dt
fig = plt.figure(figsize=(8, 6))
gs = gridspec.GridSpec(3, 1, hspace=0.55)
ax1 = fig.add_subplot(gs[0])
ax1.plot(time, xs[:, 0], 'b-o', markersize=3, lw=1.5)
ax1.axhline(0, color='k', lw=0.8, ls='--')
ax1.set_ylabel("Position $p$")
ax1.set_title(f"Optimal Trajectory (J = {J:.3f})", fontsize=10)
ax1.grid(True, alpha=0.3)
ax2 = fig.add_subplot(gs[1])
ax2.plot(time, xs[:, 1], 'g-o', markersize=3, lw=1.5)
ax2.axhline(0, color='k', lw=0.8, ls='--')
ax2.set_ylabel("Velocity $v$")
ax2.grid(True, alpha=0.3)
ax3 = fig.add_subplot(gs[2])
ax3.step(time[:-1], us, 'r-', where='post', lw=1.5)
ax3.axhline(0, color='k', lw=0.8, ls='--')
ax3.set_ylabel("Control $u$")
ax3.set_xlabel("Time (s)")
ax3.grid(True, alpha=0.3)
fig.tight_layout()
return fig
app = App(app_ui, server)Backward Riccati recursion — initialize ; repeat for :
Optimal control at runtime:
Trade-offs to explore:
| Change | Effect |
|---|---|
| Increase | Less aggressive control, slower convergence |
| Increase | State forced closer to 0 at |
| Increase | Smoother trajectories, gains converge toward |
| Large | Cost grows quadratically — gains unchanged |
See lqr.qmd for the generalized formulation with augmented cost matrices and the explicit Q-function, which extends to iLQR.
Optimal Control • ME 676 Home