Home Research Theory Concepts RSE AI & PCT Why AI Lies Audit Kit Robotics Critique Consulting GEO Cases Blog FAQ About

Two control architectures. Same inverted pendulum. One demands a complete mathematical model of the universe. The other demands two gain values and a reference signal.

Below: the equations, the Python code, the peer-reviewed benchmarks, and the repositories you can clone right now. No fluff. No textbook padding. Just the engineering that actually works when models break.

LQR vs PCT — Same Pendulum, Two Different Worlds

The classical LQR (Linear-Quadratic Regulator) needs a complete mathematical model of the system. Without matrices A, B, Q, R it is helpless. PCT needs only perception and a reference signal. Below, both architectures stripped to their foundations.

LQR — Optimization on Paper

For a linear system \( \dot{x} = A x + B u \), the cost function is:

// LQR cost function \[ J = \int_0^{\infty} \left( x^{\mathsf{T}} Q x + u^{\mathsf{T}} R u \right) \, dt \]
\( Q = Q^{\mathsf{T}} \succeq 0 \) and \( R = R^{\mathsf{T}} \succ 0 \) are required for the standard optimal solution to exist and be unique. \( x \) is the state vector, \( u \) the control input, both time-varying.

The solution is state feedback: \( u = -K x \), where \( K = R^{-1} B^{\mathsf{T}} P \), and \( P \) satisfies the algebraic Riccati equation:

// algebraic Riccati equation \[ A^{\mathsf{T}} P + P A - P B R^{-1} B^{\mathsf{T}} P + Q = 0 \]
The equation admits multiple solutions in general; the stabilising one is selected by requiring \( A - B K \) to be Hurwitz. This is the solution normally returned by lqr() in MATLAB, scipy.linalg.solve_continuous_are in Python, and equivalent routines elsewhere.

The weakness: Matrices A and B must be known to useful accuracy. Any model uncertainty — friction, mass change, wind — and the controller's optimality degrades, then its stability. The mathematics is elegant. The real world is not.

PCT — A Closed Loop That Doesn't Ask About Physics

The fundamental PCT cell (node) is three linear relations in series. In the notation used throughout PCT (Powers, 1973, appendix), the input function, comparator, and output function are written:

// step 1 — input function \[ p = K_i \, Q_i \] // step 2 — comparator \[ e = r - p \] // step 3 — output function \[ Q_o = K_o \, e \]
Here \( Q_i \) and \( Q_o \) are scalar signals, not matrices. The letter \( Q \) denotes different objects in the LQR and PCT equations above; the reader should not confuse the cost matrix \( Q \) in the Riccati equation with the signal variables \( Q_i, Q_o \) here. The collision of symbols is inherited from the two literatures and is standard in both.

The loop closes through the environment. Powers' appendix represents this with a single additive environment equation, which is the fourth equation of the loop and is required to derive the closed-loop transfer function:

// step 4 — environment equation (closes the loop) \[ Q_i = K_f \, Q_o + K_d \, D \]
\( K_f \) — the feedback-path gain from output to perceptual input through the environment. \( K_d \) — the gain from an external disturbance \( D \) into the same input. The two gains are separate because output and disturbance enter the perceptual input through different physical paths; they are not the same channel. Substituting this into the equations above and solving for \( p \) yields the closed-loop transfer function below.

Substituting the environment equation into the input equation and using \( Q_o = K_o (r - p) \), the loop closes algebraically:

// PCT closed-loop transfer \[ p = \frac{G}{1 + G} \, r \;+\; \frac{K_i K_d}{1 + G} \, D, \qquad G = K_i K_o K_f \]
\( G \) is the open-loop gain of the system. The first term is the reference tracking: as \( G \to \infty \), \( G/(1+G) \to 1 \) and \( p \to r \). The second term is the disturbance response: as \( G \to \infty \), \( K_i K_d / (1+G) \to 0 \) and the disturbance is rejected. This is the whole engineering argument for high-gain negative feedback, derived in four lines from Powers' appendix.

The key insight: as \( G \to \infty \), the reference term \( G/(1+G) \to 1 \) and the disturbance term vanishes. The system forces \( p \to r \) without knowing the dynamics of the environment. This is not prediction — it is real-time physical compensation, and it is why a controller with no plant model can outperform one that has it.

"LQR asks: 'What is the optimal action given a perfect model?' PCT asks: 'What action makes my perception match my reference?' One requires omniscience. The other requires a sensor."

— Łukasz Diener, perceptualcontroltheory.org

Your First PCT Controller — Python

A minimal, self-contained skeleton based on the perceptualrobots/pct library (MIT License). No magic — just the loop: Reference → Comparator → Error → Action.

import numpy as np class PCTNode: """ Basic Hierarchical Perceptual Control node. reference (r) -> perception (p) -> error (e) -> output (q_o) """ def __init__(self, name, reference=0.0, ki=1.0, ko=1.0): self.name = name self.reference = reference # reference signal (r) self.ki = ki # input gain self.ko = ko # output gain self.perception = 0.0 # p self.error = 0.0 # e self.output = 0.0 # q_o def perceive(self, env_var): self.perception = self.ki * env_var def compare(self): self.error = self.reference - self.perception def act(self): self.output = self.ko * self.error return self.output def step(self, env_var): """One cycle of the PCT loop.""" self.perceive(env_var) self.compare() return self.act() # ---- Example: inverted pendulum (hierarchy) ---- class HPCTController: def __init__(self): # Higher-level node: "X position" self.position_node = PCTNode("Position", reference=0.0, ki=1.0, ko=0.3) # Lower-level node: "pendulum angle" self.angle_node = PCTNode("Angle", reference=0.0, ki=1.0, ko=4.0) def run_cycle(self, current_x, current_angle): # Higher level says: "I need this angle to correct position" desired_angle = self.position_node.step(current_x) # Inject as reference for the lower level self.angle_node.reference = desired_angle # Lower level generates motor force motor_force = self.angle_node.step(current_angle) return motor_force

Run it yourself: pip install pct — or copy the skeleton above. Full repository: github.com/perceptualrobots/pct.

What you're looking at: The higher-level node controls position by outputting a desired angle. That desired angle becomes the reference signal for the lower-level node, which outputs motor force. Two nodes, zero physics equations, full pendulum control. This is HPCT — Hierarchical Perceptual Control Theory.

Inverted Pendulum — Numbers That Hurt LQR

Johnson et al. at the University of Manchester (2020) were the first to directly compare PCT and LQR on the same two-wheeled balancing robot. Here are the results.

MetricLQRPCT / HPCT
Modelling requirement Requires full dynamic equations (mass, inertia, friction coefficients) No physical model — only gains \( K_i \), \( K_o \)
Controlled variable System output (position, angle) Perceptual input (what the system sees)
Disturbance rejection Susceptible to nonlinearities; ~6 s recovery from 5 N push Near-instantaneous compensation — system barely flinches
Stability under uncertainty Degrades rapidly with model errors Maintained via continuous perceptual error reduction

"The performance of the PCT controller is comparable to the LQR controller and better at disturbance rejection."

— Johnson, Zhou, Cheah et al., Journal of Intelligent & Robotic Systems, 2020

Full paper: doi:10.1007/s10846-020-01158-4

Why This Matters

LQR is the gold standard taught in every control theory course on the planet. It won a Bellman Prize. It has 70 years of academic infrastructure behind it. And on the simplest possible benchmark — keep a stick upright — a feedback loop with two gains and no model matched its performance and beat it on robustness.

The question is not whether PCT works. The question is why this result is not on the first slide of every introductory control systems lecture.

Ready-Made Implementations — GitHub, PyPI, MATLAB

  • Full PCT library with hierarchical nodes, Welford variance estimator, and ready-to-use examples. The reference implementation.
    Python · MIT License
  • YAML-based DSL for defining PCT hierarchies. CartPole and car model examples. Useful for rapid prototyping without writing boilerplate.
    Python · YAML DSL
  • pip install pct — nodes ready to use in under 30 seconds.
    PyPI Package
  • Barter & Yin (2021) — Quadruped Robot
    HPCT on a four-legged robot: 12 degrees of freedom, torque sensors, terrain adaptation without a predictive model. Published in iScience. doi:10.1016/j.isci.2021.102948
    iScience · Peer-Reviewed

All links verified. No dead repositories, no ghost references. If you find a broken link, let us know — it will be fixed in five minutes.

Next: See how seven AI models independently rediscovered PCT when asked to design an architecture immune to reward hacking → Reward Hacking: Why AI Lies. Or start from the beginning with the core theory.

// how to cite

Diener, Ł. (2026). "PCT vs LQR: Engineering Evidence." PCT Knowledge Base. Available at: perceptualcontroltheory.org/robotics/pct-vs-lqr.html

Whitepaper (v2): DOI: 10.5281/zenodo.21989191

// about this page
Written by Łukasz Diener

Łukasz Diener wrote this comparison. Perceptual Control Theory is the framework of William T. Powers (1926–2013); the PCT-vs-LQR benchmark on the balancing robot is the work of Johnson et al. (2020, University of Manchester) and the HPCT robotics community, cited above. He does not claim the robotics results as his own. Diener's own original contribution is the formalisation of the PCT closed-loop transfer function — including the environment equation \( Q_i = K_f Q_o + K_d D \) missing from earlier treatments — together with the substrate formulation and differential gain collapse analysis developed in the theory section.

ORCID 0009-0006-6103-8514 Diener's PCT × RLHF paper v2 — Zenodo, 2026 Full profile LinkedIn Profile
The same loop, in software

A robot controls what it perceives because it holds a reference and compares it against a reading. A language model trained on human approval also holds a reference — but that reference is approval, not measurement, and nothing in the loop can contradict what it reports about its own work.

Seven frontier models were audited on exactly that point using three published prompts. All seven reached the same diagnosis; six then designed a repair whose comparator structure is the loop described on this page. It takes about ten minutes to run yourself.

Open the experiment kit → Six published audits