JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

State Space: Modern Control, Controllability & Observability

For two centuries control engineers lived in the world of one input, one output, and a single [[ee-transfer-function|transfer function]]. Then the space race demanded that one autopilot juggle pitch, roll, yaw, and thrust at once — and the old picture cracked. The fix was to stop thinking about a signal and start thinking about a **state vector**: a short list of numbers that captures everything the system remembers. In fourteen minutes you'll meet ẋ = Ax + Bu, discover that the eigenvalues of A are exactly the [[ee-poles-zeros|poles]] you already know, and learn the two questions — **can I steer it?** and **can I see it?** — that decide whether modern control is even possible.

Why one transfer function stopped being enough

In 1960 Rudolf Kálmán published a paper that quietly split control theory in two. Everything before it — Bode, Nyquist, the transfer function G(s) you met on rungs 3 and 7 — is now called classical control. It is elegant, intuitive, and built around a single relationship: one input wiggles, one output responds. But the systems engineers actually wanted to fly did not have one input and one output. A rocket has thrusters, gimbals, and fins; it has position, velocity, attitude, and angular rate. These quantities are tangled together — pitching the nose changes the velocity vector, which changes the aerodynamic load, which changes the pitch again. A single G(s) cannot hold that web.

Kálmán's move was to stop asking "what comes out when I push the input?" and start asking "what does the system need to remember?" The answer is a small set of numbers called the state. If you know the state right now, plus the inputs from now on, you can predict the entire future — you never need the past again. For an RLC circuit the state is the capacitor voltage and the inductor current. For a drone it is position, velocity, and orientation. Bundle those numbers into a column vector x, and a tangled multi-input, multi-output (MIMO) system collapses into one clean matrix equation.

The four matrices — and why A's eigenvalues are the poles you know

Here is the whole of modern control in two lines. The first, ẋ = Ax + Bu, is the state equation: it says how the state changes from instant to instant. The second, y = Cx + Du, is the output equation: it says what your sensors actually report. Read them like a story. The state x drifts on its own according to A (the internal dynamics), gets nudged by inputs u through B (where the actuators connect), and the world peeks at it through C (where the sensors connect). D is a rare direct feed-through and is usually zero.

   The state-space model — every linear system, one shape:

      ẋ = A x + B u        <- how the state evolves (the dynamics)
      y = C x + D u        <- what the sensors measure (the readout)

   x : state vector   (n×1)   the system's memory
   u : input vector   (m×1)   the knobs you can turn (actuators)
   y : output vector  (p×1)   the dials you can read (sensors)

   A : n×n   system matrix     internal coupling — the heart of it
   B : n×m   input matrix      where inputs push the state
   C : p×n   output matrix     which states the sensors see
   D : p×m   feed-through      usually 0

        u ──►[ B ]──►(+)──► ẋ ──[ ∫ ]──┬──► x ──►[ C ]──►(+)──► y
                      ▲                 │                  ▲
                      └──────[ A ]◄─────┘          [ D ]◄──┘
                         the feedback OF NATURE
One template for every linear system. The integrator block ∫ turns ẋ back into x — and the loop through A is the system's own internal feedback, the dynamics that exist whether or not you control them.

Notice what just happened to scale. If a robot arm has 6 joints, u is a 6-vector of motor torques, y is a 6-vector of joint angles, and A is a 12×12 matrix coupling positions to velocities. The equations do not get more complicated — they get bigger. That is the superpower: the same ẋ = Ax + Bu describes a thermostat, a quadcopter, a power grid, and a chemical reactor. Linear algebra, not a fresh derivation, does the heavy lifting. This is why MATLAB and Python's control libraries can hand you a controller for a 50-state model with the same three function calls you'd use for a toy.

And here is the moment the two worlds — classical and modern — turn out to be one world. Back on rung 3 you learned that the poles of a transfer function are the roots of its denominator, and that their location in the s-plane decides everything: left half-plane means stable, right half-plane means runaway, distance from the imaginary axis sets the speed of decay. In state space, those exact same numbers are the eigenvalues of the matrix A. Not a coincidence, not an analogy — literally the same complex numbers.

   Why eigenvalues = poles  (the one derivation worth doing)

   Take ẋ = A x  and look for a natural mode  x(t) = v · e^{λt}
   (v is a fixed direction, the whole vector just scales by e^{λt})

      ẋ = λ v e^{λt}        and        A x = A v e^{λt}

   Set them equal and cancel e^{λt}:

            λ v = A v        <-  the EIGENVALUE equation

   So each natural mode of the system IS an eigenvector v of A,
   and it decays/grows like e^{λt} where λ is its eigenvalue.

   The transfer function makes the same numbers explicit:

        G(s) = C (sI − A)^{-1} B + D
                       └────┬────┘
        poles = where  det(sI − A) = 0   =   eigenvalues of A

   STABLE  ⇔  every eigenvalue of A has  Re(λ) < 0
             (every pole in the left half-plane)
Seeking solutions of the shape v·e^{λt} turns the dynamics into the eigenvalue equation λv = Av. The denominator det(sI − A) of the transfer function is the characteristic polynomial of A — so its roots, the poles, are A's eigenvalues.

This unification is more than tidy bookkeeping. It tells you that stability in the modern view is a pure linear-algebra question: compute the eigenvalues of A, check that every one sits in the left half-plane. No Routh array, no Nyquist contour — one `eig(A)` call. It also tells you that when we design a controller, what we are really doing is moving eigenvalues. The poles were never sacred properties of the plant; they are just the eigenvalues of A, and as the next section shows, feedback lets us rewrite A.

Can I steer it? Can I see it? Controllability & observability

Before you design anything, two brutal yes/no questions decide whether the project is even possible. The first is [[ee-controllability-observability|controllability]]: using only the inputs you have, can you drive the state from any starting point to any target? Imagine a car with a steering wheel but no accelerator — you can change heading but never speed. The speed coordinate is uncontrollable: no amount of cleverness in software fixes a missing actuator. Mathematically, B and A must between them be able to push the state in every direction.

The second question is observability: from the sensor outputs alone, can you reconstruct the full internal state, including the parts you never measure directly? A spacecraft might carry a gyro that reads angular *rate* but no sensor for absolute *attitude*. Can you still know the attitude? If the rate and attitude are linked through A, then yes — by integrating what you see, the hidden state reveals itself. If they are not linked, the attitude is unobservable, a ghost the sensors can never catch. Observability asks whether C and A together leave any state invisible.

   The two tests — both are rank checks on a stacked matrix:

   CONTROLLABILITY (n states, can inputs reach everywhere?)

        ┌                                  ┐
   𝒞 =  │  B   AB   A²B  ...  A^{n-1}B      │      controllable
        └                                  ┘   ⇔  rank(𝒞) = n

   OBSERVABILITY (can outputs reveal every state?)

        ┌      C      ┐
        │     CA      │
   𝒪 =  │    CA²      │                       observable
        │     ⋮       │                    ⇔  rank(𝒪) = n
        │  CA^{n-1}   │
        └             ┘

   FULL RANK  =  green light.   RANK DEFICIENT  =  a mode you
   can't touch (uncontrollable) or can't see (unobservable).

   Beautiful duality:  (A, B) controllable  ⇔  (Aᵀ, Bᵀ) observable.
   Every steering theorem has a free seeing-theorem twin.
Both questions reduce to one number: the rank of a tall stacked matrix. Full rank n means every mode can be steered (or seen). The transpose symmetry between the two tests is control theory's elegant duality — solve one problem and you've solved both.

Pillar one: pole placement, putting the eigenvalues where you want

Now the payoff. In classical feedback you fed back the single output y and tuned a PID gain until the response looked acceptable — a one-dimensional dial you nudged by trial and error. In state space you feed back the entire state vector: u = −Kx, where K is a row of gains, one per state variable. Substitute it into the dynamics and watch what happens — the closed-loop system becomes ẋ = (A − BK)x. You have replaced A with A − BK, and you get to choose K.

   STATE FEEDBACK  →  re-write the system's eigenvalues

   Plant:        ẋ = A x + B u
   Control law:  u = −K x            (feed back ALL states, gain row K)

   Close the loop (substitute u):

        ẋ = A x + B(−K x) = (A − BK) x
                              └───┬───┘
                         NEW system matrix

   Its eigenvalues = the CLOSED-LOOP poles, and they are now
   yours to set:  pick desired poles → solve for K.

   ┌──────────────────────────────────────────────────────────┐
   │  POLE-PLACEMENT THEOREM                                   │
   │  If (A, B) is CONTROLLABLE, a gain K exists that places   │
   │  the eigenvalues of (A − BK) at ANY locations you choose. │
   └──────────────────────────────────────────────────────────┘

   In code:   K = place(A, B, desired_poles)     # MATLAB / python-control
State feedback literally edits the system matrix from A to A − BK. The pole-placement theorem promises that controllability buys you total authority over the closed-loop eigenvalues — and one library call computes the gains.

This is the theorem that makes the controllability test worth caring about. If the system is controllable, you can place the closed-loop poles anywhere in the s-plane — make a sluggish plant snappy, damp out an oscillation, stabilise something that was diverging. Want a settling time of 0.5 s with no overshoot? Translate that into a pair of desired pole locations, call `place(A, B, poles)`, and the gain K drops out. Where classical design tweaks one loop and prays, pole placement assigns every eigenvalue by name.

Pillar two: the observer and the Kalman filter

Pole placement has a catch hiding in plain sight: the law u = −Kx needs the whole state x. But you rarely measure the whole state. A drone's IMU gives angles and rates, not the slow drift of its position estimate; a motor sensor gives shaft angle, not the load torque pushing back. If observability said the hidden states *can* be reconstructed, an observer is the machine that actually does it — it runs a copy of the model in software and continuously corrects its guess using the measurement error.

   THE OBSERVER  (a.k.a. estimator) — a model that watches itself

   Run a copy of the plant in software, state estimate x̂:

        x̂̇ = A x̂ + B u  +  L (y − C x̂)
             └─ predict ─┘   └──── correct ────┘
                            measured − expected output

   Estimation error  e = x − x̂  obeys:

        ė = (A − L C) e

   Choose the observer gain L so the eigenvalues of (A − LC)
   are FAST and stable  →  x̂ rushes toward the true x,
   no matter the starting guess.   (place() again — by duality!)

   ┌──────────────────────────────────────────────────────────┐
   │  SEPARATION PRINCIPLE                                     │
   │  Design K (control) and L (estimation) INDEPENDENTLY.    │
   │  Feed x̂ into the controller:  u = −K x̂.  The combined    │
   │  closed-loop poles = eig(A−BK) ∪ eig(A−LC). They don't   │
   │  interfere. Pick observer poles ~5–10× faster than       │
   │  the control poles, and you're done.                     │
   └──────────────────────────────────────────────────────────┘
The observer predicts with the model, then corrects with the measurement gap (y − Cx̂). Its error decays as eig(A − LC). The separation principle is the gift that lets you design controller and estimator on separate desks and bolt them together.

There is a deeper version of the observer that earned Kálmán a place in history: the Kalman filter. A plain observer assumes a perfect model and clean sensors. The real world has neither — process noise jostles the state, measurement noise corrupts y. The Kalman filter chooses the gain L *optimally*, weighing how much to trust the model against how much to trust each noisy sensor, given their statistics. It is the same predict-then-correct loop, but L is now recomputed to minimise the estimation error variance. That algorithm guided Apollo to the Moon, lives in every GPS receiver and phone, and fuses the accelerometer and gyro in the drone hovering outside your window.

Two views, one craft — and the next horizon

So which is right, the frequency-domain world of rung 7 or the state-space world you just met? Both — they are two windows onto the same room. The transfer-function view shines when you have one input and one output, when you only have measured frequency-response data, and when robustness margins (gain margin, phase margin) are what you must guarantee. The state-space view shines when channels are coupled (MIMO), when internal states matter, and when you want to assign performance by eigenvalue and let linear algebra scale to dozens of states. Seasoned engineers move between them fluidly: sketch intuition with Bode, then commit the design in state space.

   CLASSICAL (rung 7)              STATE SPACE (rung 8)
   ─────────────────────          ─────────────────────────
   G(s) transfer function   ↔     ẋ = Ax + Bu,  y = Cx + Du
   poles (denom. roots)     ≡     eigenvalues of A
   one input / one output         many inputs / many outputs
   Bode, Nyquist, margins         eig(), place(), rank tests
   loop-shaping by hand           pole placement + observer
   data-driven, robust            model-driven, scalable

   Same plant, two coordinate systems on the same truth.

   NEXT HORIZON ─────────────────────────────────────────►
   Pole placement asks "WHERE should the poles go?" — you
   still guess. OPTIMAL CONTROL (LQR) asks instead:

      minimise   J = ∫ ( xᵀQx + uᵀRu ) dt
                       └─error─┘ └─effort─┘

   Q penalises being off-target, R penalises control effort.
   Solve once (the Riccati equation) → the BEST K falls out,
   no pole-guessing. Pair it with a Kalman filter and you get
   LQG — the workhorse of aerospace & robotics control.
The two languages map term-for-term onto the same physics. And the natural next step — LQR — stops you from guessing pole locations: state a cost that trades tracking error against control effort, and the optimal gain K emerges from solving one matrix (Riccati) equation.

And that is the frontier this ladder points toward. Pole placement gives you authority but leaves you guessing where to aim; the Linear-Quadratic Regulator (LQR) removes the guess. You write down a cost — how much you hate being off-target (Q) versus how much you hate spending control effort (R) — and the mathematics hands you the single best gain K, automatically resolving the performance-versus-effort tension we flagged earlier. Marry LQR with the Kalman filter and you get LQG, the controller that lands rockets and flies airliners. You have arrived at the mastery frontier: from a single resistor-capacitor loop on rung 1 to the matrix machinery steering real machines today.