Wonham’s pole assignment (1967)#

For a single input, controllability lets state feedback \(u = -Kx\) place the poles of \(A - BK\) anywhere, and \(K\) is unique (Ackermann’s formula). Murray Wonham (1967) proved that the same holds with several inputs: the closed-loop poles can be assigned arbitrarily if and only if \((A, B)\) is controllable, even when no single input could do it (state_feedback_poles()). A precompensator \(N\) then scales the reference so the output settles on it (feedforward_gain()).

The first plant is an inverted pendulum on a cart, unstable in open loop, driven by the force on the cart.

import matplotlib.pyplot as plt
import numpy as np

from engineeringkit.control_systems import (
    feedforward_gain,
    is_controllable,
    state_feedback_poles,
    state_space,
    step_response,
)

M, m, ell, g = 1.0, 0.2, 0.5, 9.81  # cart mass, bob mass, rod length, gravity
A = np.array([[0, 1, 0, 0], [0, 0, -m * g / M, 0], [0, 0, 0, 1], [0, 0, (M + m) * g / (M * ell), 0]])
B = np.array([[0.0], [1 / M], [0.0], [-1 / (M * ell)]])
C = np.array([[1.0, 0.0, 0.0, 0.0]])  # cart position
print("open-loop poles:", np.sort(np.linalg.eigvals(A).real).round(3).tolist())
assert is_controllable(A, B) and np.linalg.eigvals(A).real.max() > 0

desired = [-2.0, -2.5, -3.0, -3.5]
K = state_feedback_poles(A, B, desired)
N = feedforward_gain(A, B, C, K)
assert np.allclose(np.sort(np.linalg.eigvals(A - B @ K).real), sorted(desired))
t = np.linspace(0, 6, 600)
_, y_cart = step_response(state_space(A - B @ K, B * N, C, [[0.0]]), t)
_, y_angle = step_response(state_space(A - B @ K, B * N, [[0.0, 0.0, 1.0, 0.0]], [[0.0]]), t)
print(f"K = {K.round(3).tolist()}, N = {N:.3f}, final cart position {y_cart[-1]:.4f} m")
assert np.isclose(y_cart[-1], 1, atol=1e-3) and abs(y_angle[-1]) < 1e-3
open-loop poles: [-4.852, 0.0, 0.0, 4.852]
K = [[-2.676, -4.065, -35.485, -7.532]], N = -2.676, final cart position 0.9998 m

Two inputs that are each insufficient#

Two unstable modes, each driven by its own actuator. Neither input alone reaches both modes, but together they are controllable, so Wonham’s theorem promises any pole set, here a complex pair.

A2 = np.diag([1.0, 2.0])
B2 = np.eye(2)
assert not is_controllable(A2, B2[:, :1]) and not is_controllable(A2, B2[:, 1:])
assert is_controllable(A2, B2)
K2 = state_feedback_poles(A2, B2, [-1 + 2j, -1 - 2j])
assert np.allclose(np.sort_complex(np.linalg.eigvals(A2 - B2 @ K2)), [-1 - 2j, -1 + 2j])
print("two-input gain:", K2.round(3).tolist())

fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(7, 6))
ax1.plot(t, y_cart)
ax1.axhline(1, color="k", lw=0.8)
ax1.set(ylabel="cart position [m]", title=f"Poles placed at {desired}")
ax2.plot(t, np.degrees(y_angle), color="C1")
ax2.set(xlabel="time [s]", ylabel="pendulum angle [deg]")

plt.show()
Poles placed at [-2.0, -2.5, -3.0, -3.5]
two-input gain: [[2.0, -2.0], [2.0, 3.0]]

Total running time of the script: (0 minutes 0.029 seconds)

Gallery generated by Sphinx-Gallery