Skip to content

LQR

Deep Dive

LQR (Linear-Quadratic Regulator) is the principled answer to the question PID never solves: how do I pick my controller gains? Instead of hand-tuning knobs by trial and error, you write down a cost function that trades off how far the system is from its goal against how much control effort you spend — and LQR computes the optimal feedback gains that minimize that cost. It matters because it turns gain tuning into a design decision (what do I care about?) rather than guesswork.

Prerequisites

The setup

LQR starts from a linear system in state-space form:

ẋ = A·x + B·u

Here x is the state vector (everything you need to describe the system — e.g. position and velocity), u is the control input (what your actuators do), A describes how the state evolves on its own, and B describes how inputs push it. The controller is a full-state feedback law:

┌─────────────┐
r=0 │ │ u = -K·x ┌──────────────┐
──────►│ -K (gain) │ ─────────────────►│ plant │
│ │ │ ẋ = Ax + Bu │
└─────▲───────┘ └──────┬───────┘
│ │
│ x (full state) │
└──────────────────────────────────┘

In words: push every input proportional to how far the whole state is from zero. K is the gain matrix — for multi-input/multi-output (MIMO) systems it couples every input to every state.

The cost function

LQR finds K by minimizing a quadratic cost accumulated over time:

J = ∫ ( xᵀ·Q·x + uᵀ·R·u ) dt
  • Q — the state-error penalty: how much you care about being off-target. Big Q → tight, aggressive control that drives error to zero fast.
  • R — the control-effort penalty: how much you care about aggressive, energy-hungry, or hardware-stressing actuation. Big R → gentle, lazy control that conserves effort.

The Q/R ratio is the design knob. You don’t tune gains; you tune what you value, and the gains follow.

How K is found

LQR reduces to solving the Algebraic Riccati Equation (ARE) for a matrix P. You don’t derive it by hand — it’s a standard solver call. Once P is known:

K = R⁻¹·Bᵀ·P

Runnable example

A classic double integrator — a frictionless cart where the input is acceleration, state = [position, velocity].

import numpy as np
from scipy.linalg import solve_continuous_are
# Double integrator: state = [position, velocity], input = acceleration
A = np.array([[0.0, 1.0],
[0.0, 0.0]])
B = np.array([[0.0],
[1.0]])
# Q penalizes state error; R penalizes control effort.
# Increase Q (or shrink R) for tighter, more aggressive control.
Q = np.diag([1.0, 1.0])
R = np.array([[1.0]])
# Solve the Algebraic Riccati Equation for P, then form the LQR gain.
P = solve_continuous_are(A, B, Q, R)
K = np.linalg.inv(R) @ B.T @ P
print("LQR gain K =", K) # [[1.0, 1.732]]
print("closed-loop eigenvalues =",
np.linalg.eigvals(A - B @ K)) # both have negative real part → stable

Running this prints K = [[1.0, 1.732]], and the closed-loop poles eig(A - BK) land at -0.866 ± 0.5j — negative real parts, so the cart is driven smoothly back to the origin.

LQR vs PID

PID is hand-tuned and single-input/single-output (SISO): you twist three knobs per loop and accept the coupling between loops as a nuisance. LQR is optimal and natively MIMO — it handles coupled multi-axis systems in one shot. The trade-offs:

  • LQR requires a model (A, B). If you don’t have one, identify it first — see System Identification and State-Space Representation.
  • LQR assumes full state knowledge. When you can’t measure the whole state, pair it with a Kalman filter to estimate x; the combination is called LQG (Linear-Quadratic-Gaussian).

Sources