Chapter 23: Pole Placement for Single-Input Systems
Lesson 3: Ackermann’s Formula – Concept and Application
This lesson derives Ackermann’s formula for continuous-time SISO pole placement, explains why controllability is the decisive algebraic condition, and shows how the formula converts a desired characteristic polynomial directly into the state-feedback gain \( K \) for \( u=-Kx \).
1. Problem Setting and Lesson Context
In the previous lesson, pole assignment was performed by transforming a controllable single-input system into controllable canonical form. Here we obtain the same feedback gain without explicitly writing the full coordinate transformation. Consider the continuous-time SISO system
\[ \dot{x}=Ax+Bu,\qquad x\in\mathbb{R}^{n},\quad B\in\mathbb{R}^{n\times 1}. \]
With full-state feedback \( u=-Kx \), the closed-loop matrix is
\[ A_{cl}=A-BK. \]
The pole placement objective is to choose \( K=[k_1\;k_2\;\cdots\;k_n] \) such that
\[ \det(sI-A+BK)=p_d(s), \]
where the desired monic characteristic polynomial is
\[ p_d(s)=s^n+\alpha_{n-1}s^{n-1}+\cdots+\alpha_1s+\alpha_0 =\prod_{i=1}^{n}(s-\lambda_i^{d}). \]
Ackermann’s formula is compact, but it is not merely a computational trick. It is a coordinate-free expression of the canonical-form pole assignment law through the controllability matrix.
2. Controllability Matrix and the Key Algebraic Object
For a single-input system, define the controllability matrix
\[ \mathcal{C}=[B\;AB\;A^2B\;\cdots\;A^{n-1}B]. \]
The system is controllable exactly when \( \operatorname{rank}(\mathcal{C})=n \). In that case \( \mathcal{C}^{-1} \) exists, and the last row selector
\[ e_n^T=[0\;0\;\cdots\;0\;1] \]
extracts the coordinate direction corresponding to the highest-order phase variable in controllable canonical coordinates. The desired matrix polynomial is obtained by substituting \( A \) into \( p_d(s) \):
\[ p_d(A)=A^n+\alpha_{n-1}A^{n-1}+\cdots+\alpha_1A+\alpha_0I. \]
The formula is then
\[ K=e_n^T\mathcal{C}^{-1}p_d(A). \]
flowchart TD
A["Given SISO pair A, B"] --> B["Build controllability matrix C"]
B --> C["Check rank(C) = n"]
C -->|"no"| D["Arbitrary pole placement impossible"]
C -->|"yes"| E["Choose desired poles"]
E --> F["Build desired polynomial p_d(s)"]
F --> G["Evaluate matrix polynomial p_d(A)"]
G --> H["Compute K = e_n^T inv(C) p_d(A)"]
H --> I["Verify eig(A - B K)"]
3. Derivation from Controllable Canonical Form
Let the open-loop characteristic polynomial of \( A \) be
\[ p_A(s)=\det(sI-A)=s^n+a_{n-1}s^{n-1}+\cdots+a_1s+a_0. \]
In controllable canonical form, the system has
\[ A_c=\begin{bmatrix} 0 & 1 & 0 & \cdots & 0\\ 0 & 0 & 1 & \cdots & 0\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 0 & 0 & 0 & \cdots & 1\\ -a_0 & -a_1 & -a_2 & \cdots & -a_{n-1} \end{bmatrix},\qquad B_c=\begin{bmatrix}0\\0\\\vdots\\0\\1\end{bmatrix}. \]
Applying \( u=-K_cz \), where \( K_c=[\kappa_0\;\kappa_1\;\cdots\;\kappa_{n-1}] \), changes only the last row:
\[ A_c-B_cK_c=\begin{bmatrix} 0 & 1 & 0 & \cdots & 0\\ 0 & 0 & 1 & \cdots & 0\\ \vdots & \vdots & \vdots & \ddots & \vdots\\ 0 & 0 & 0 & \cdots & 1\\ -(a_0+\kappa_0) & -(a_1+\kappa_1) & \cdots & -(a_{n-2}+\kappa_{n-2}) & -(a_{n-1}+\kappa_{n-1}) \end{bmatrix}. \]
Therefore the closed-loop characteristic polynomial is
\[ s^n+(a_{n-1}+\kappa_{n-1})s^{n-1}+\cdots+(a_1+\kappa_1)s+(a_0+\kappa_0). \]
Matching it with \( p_d(s) \) gives
\[ \begin{aligned} \kappa_i &= \alpha_i-a_i, \qquad i=0,1,\ldots,n-1. \end{aligned} \]
Ackermann’s formula packages this canonical-coordinate gain back into the original coordinates. If \( x=Tz \), then \( K=K_cT^{-1} \). The product \( e_n^T\mathcal{C}^{-1}p_d(A) \) is exactly that coordinate pullback.
4. Formal Statement and Proof
Theorem: Let \( (A,B) \) be a controllable SISO pair with \( A\in\mathbb{R}^{n\times n} \) and \( B\in\mathbb{R}^{n\times 1} \). For any monic polynomial \( p_d(s)=s^n+\alpha_{n-1}s^{n-1}+\cdots+\alpha_0 \), the feedback
\[ K=e_n^T\mathcal{C}^{-1}p_d(A) \]
satisfies \( \det(sI-(A-BK))=p_d(s) \).
Proof idea: Because \( (A,B) \) is controllable, there exists a nonsingular transformation \( T \) that maps the system to controllable canonical form. In canonical coordinates, coefficient matching uniquely gives the gain \( K_c \). Since closed-loop eigenvalues are invariant under similarity transformations,
\[ A-BK=T(A_c-B_cK_c)T^{-1}. \]
Hence \( A-BK \) and \( A_c-B_cK_c \) have the same characteristic polynomial. The algebraic identity
\[ K_cT^{-1}=e_n^T\mathcal{C}^{-1}p_d(A) \]
follows from the construction of the controllability transformation and the Cayley-Hamilton relation. Thus Ackermann’s expression produces the original-coordinate feedback that realizes \( p_d(s) \).
The proof also shows why the formula fails when \( \operatorname{rank}(\mathcal{C})<n \): the inverse \( \mathcal{C}^{-1} \) does not exist, and some modes are not affected by the input channel.
5. Worked Numerical Example
Consider
\[ A=\begin{bmatrix}0&1\\-2&-3\end{bmatrix},\qquad B=\begin{bmatrix}0\\1\end{bmatrix}. \]
The controllability matrix is
\[ \mathcal{C}=[B\;AB] =\begin{bmatrix}0&1\\1&-3\end{bmatrix},\qquad \det(\mathcal{C})=-1\neq0. \]
Choose desired poles \( -2\pm2j \), giving
\[ p_d(s)=(s+2-2j)(s+2+2j)=s^2+4s+8. \]
Hence
\[ p_d(A)=A^2+4A+8I. \]
Ackermann’s formula gives
\[ K=[0\;1]\mathcal{C}^{-1}(A^2+4A+8I)=[6\;1]. \]
Therefore
\[ A-BK=\begin{bmatrix}0&1\\-8&-4\end{bmatrix},\qquad \det(sI-A+BK)=s^2+4s+8. \]
flowchart TD
P["Desired poles: -2 plus/minus 2j"] --> Q["p_d(s) = s^2 + 4s + 8"]
Q --> R["p_d(A) = A^2 + 4A + 8I"]
R --> S["K = e2^T inv(C) p_d(A)"]
S --> T["K = [6, 1]"]
T --> U["eig(A - B K) = desired poles"]
6. Algorithmic Procedure and Numerical Warnings
The direct algorithm is:
- Build \( \mathcal{C}=[B\;AB\;\cdots\;A^{n-1}B] \).
- Check \( \operatorname{rank}(\mathcal{C})=n \).
- Compute \( p_d(s) \) from the desired poles.
- Evaluate \( p_d(A) \).
- Compute \( K=e_n^T\mathcal{C}^{-1}p_d(A) \).
- Verify \( \operatorname{eig}(A-BK) \).
Although Ackermann’s formula is exact in symbolic algebra, it can be numerically fragile. The inverse of \( \mathcal{C} \) may amplify roundoff when \( \kappa(\mathcal{C}) \) is large:
\[ \frac{\|\Delta K\|}{\|K\|}\lesssim \kappa(\mathcal{C})\left(\frac{\|\Delta\mathcal{C}\|}{\|\mathcal{C}\|} +\frac{\|\Delta p_d(A)\|}{\|p_d(A)\|}\right). \]
For high-order systems or poorly scaled states, robust algorithms such as Schur-based pole placement are usually preferred. Ackermann’s formula remains essential because it exposes the exact algebraic structure of SISO pole assignment.
7. Python Implementation
File: Chapter23_Lesson3.py
# Chapter23_Lesson3.py
# Ackermann's formula for SISO pole placement: u = -K x
# Requires: numpy. Optional comparison requires scipy.
import numpy as np
def controllability_matrix(A: np.ndarray, B: np.ndarray) -> np.ndarray:
"""Return C = [B, AB, ..., A^(n-1)B] for a single-input system."""
A = np.asarray(A, dtype=complex)
B = np.asarray(B, dtype=complex)
n = A.shape[0]
blocks = [B]
for k in range(1, n):
blocks.append(np.linalg.matrix_power(A, k) @ B)
return np.hstack(blocks)
def matrix_polynomial(A: np.ndarray, coefficients: np.ndarray) -> np.ndarray:
"""Evaluate monic polynomial p(A).
coefficients are [1, alpha_{n-1}, ..., alpha_0], as returned by np.poly.
p(A) = A^n + alpha_{n-1} A^(n-1) + ... + alpha_0 I.
"""
A = np.asarray(A, dtype=complex)
n = A.shape[0]
result = np.linalg.matrix_power(A, n)
for i, alpha in enumerate(coefficients[1:]):
power = n - 1 - i
term = np.eye(n, dtype=complex) if power == 0 else np.linalg.matrix_power(A, power)
result = result + alpha * term
return result
def ackermann_gain(A: np.ndarray, B: np.ndarray, desired_poles) -> np.ndarray:
"""Compute K from Ackermann's formula.
For x_dot = A x + B u and u = -K x, the closed-loop matrix is A - B K.
"""
A = np.asarray(A, dtype=complex)
B = np.asarray(B, dtype=complex).reshape((-1, 1))
n = A.shape[0]
if A.shape != (n, n) or B.shape != (n, 1):
raise ValueError("A must be n by n and B must be n by 1.")
Ctrb = controllability_matrix(A, B)
rank = np.linalg.matrix_rank(Ctrb)
if rank != n:
raise ValueError(f"System is not controllable: rank(C)={rank}, n={n}.")
p = np.poly(desired_poles) # [1, alpha_{n-1}, ..., alpha_0]
phi_A = matrix_polynomial(A, p)
e_n_T = np.zeros((1, n), dtype=complex)
e_n_T[0, -1] = 1.0
K = e_n_T @ np.linalg.inv(Ctrb) @ phi_A
return np.real_if_close(K)
def verify(A: np.ndarray, B: np.ndarray, K: np.ndarray):
Acl = A - B @ K
return np.linalg.eigvals(Acl), np.poly(Acl)
if __name__ == "__main__":
A = np.array([[0.0, 1.0],
[-2.0, -3.0]])
B = np.array([[0.0],
[1.0]])
desired = [-2 + 2j, -2 - 2j]
K = ackermann_gain(A, B, desired)
poles, char_poly = verify(A, B, K)
print("K =", K)
print("closed-loop poles =", poles)
print("closed-loop characteristic coefficients =", char_poly)
# Optional comparison with SciPy:
try:
from scipy.signal import place_poles
K_scipy = place_poles(A, B, desired).gain_matrix
print("SciPy place_poles K =", K_scipy)
except Exception as exc:
print("SciPy comparison skipped:", exc)
8. C++ Implementation
File: Chapter23_Lesson3.cpp
// Chapter23_Lesson3.cpp
// Ackermann's formula for SISO pole placement: u = -Kx
// Compile: g++ -std=c++17 Chapter23_Lesson3.cpp -O2 -o Chapter23_Lesson3
#include <cmath>
#include <complex>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <vector>
using C = std::complex<double>;
using Matrix = std::vector<std::vector<C>>;
Matrix zeros(int r, int c) {
return Matrix(r, std::vector<C>(c, C(0.0, 0.0)));
}
Matrix eye(int n) {
Matrix I = zeros(n, n);
for (int i = 0; i < n; ++i) I[i][i] = 1.0;
return I;
}
Matrix add(const Matrix& A, const Matrix& B) {
int r = (int)A.size(), c = (int)A[0].size();
Matrix R = zeros(r, c);
for (int i = 0; i < r; ++i)
for (int j = 0; j < c; ++j)
R[i][j] = A[i][j] + B[i][j];
return R;
}
Matrix scale(C a, const Matrix& A) {
int r = (int)A.size(), c = (int)A[0].size();
Matrix R = zeros(r, c);
for (int i = 0; i < r; ++i)
for (int j = 0; j < c; ++j)
R[i][j] = a * A[i][j];
return R;
}
Matrix mul(const Matrix& A, const Matrix& B) {
int r = (int)A.size(), m = (int)A[0].size(), c = (int)B[0].size();
Matrix R = zeros(r, c);
for (int i = 0; i < r; ++i)
for (int k = 0; k < m; ++k)
for (int j = 0; j < c; ++j)
R[i][j] += A[i][k] * B[k][j];
return R;
}
Matrix mpow(Matrix A, int p) {
int n = (int)A.size();
Matrix R = eye(n);
while (p > 0) {
if (p & 1) R = mul(R, A);
A = mul(A, A);
p >>= 1;
}
return R;
}
Matrix inverse(Matrix A) {
int n = (int)A.size();
Matrix Aug = zeros(n, 2 * n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) Aug[i][j] = A[i][j];
Aug[i][n + i] = 1.0;
}
for (int col = 0; col < n; ++col) {
int pivot = col;
for (int r = col + 1; r < n; ++r) {
if (std::abs(Aug[r][col]) > std::abs(Aug[pivot][col])) pivot = r;
}
if (std::abs(Aug[pivot][col]) < 1e-12) {
throw std::runtime_error("Matrix is singular or ill-conditioned.");
}
std::swap(Aug[pivot], Aug[col]);
C div = Aug[col][col];
for (int j = 0; j < 2 * n; ++j) Aug[col][j] /= div;
for (int r = 0; r < n; ++r) {
if (r == col) continue;
C factor = Aug[r][col];
for (int j = 0; j < 2 * n; ++j) Aug[r][j] -= factor * Aug[col][j];
}
}
Matrix Inv = zeros(n, n);
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
Inv[i][j] = Aug[i][n + j];
return Inv;
}
Matrix controllability(const Matrix& A, const Matrix& B) {
int n = (int)A.size();
Matrix Ctrb = zeros(n, n);
Matrix block = B;
for (int k = 0; k < n; ++k) {
for (int i = 0; i < n; ++i) Ctrb[i][k] = block[i][0];
block = mul(A, block);
}
return Ctrb;
}
std::vector<C> polynomialFromRoots(const std::vector<C>& roots) {
std::vector<C> coeff = {1.0};
for (C r : roots) {
std::vector<C> next(coeff.size() + 1, 0.0);
for (size_t i = 0; i < coeff.size(); ++i) {
next[i] += coeff[i];
next[i + 1] += -r * coeff[i];
}
coeff = next;
}
return coeff; // [1, alpha_{n-1}, ..., alpha_0]
}
Matrix matrixPolynomial(const Matrix& A, const std::vector<C>& coeff) {
int n = (int)A.size();
Matrix R = mpow(A, n);
for (int i = 1; i <= n; ++i) {
int power = n - i;
Matrix term = (power == 0) ? eye(n) : mpow(A, power);
R = add(R, scale(coeff[i], term));
}
return R;
}
Matrix ackermannGain(const Matrix& A, const Matrix& B, const std::vector<C>& desiredPoles) {
int n = (int)A.size();
Matrix Ctrb = controllability(A, B);
Matrix CtrbInv = inverse(Ctrb);
std::vector<C> p = polynomialFromRoots(desiredPoles);
Matrix phiA = matrixPolynomial(A, p);
Matrix eT = zeros(1, n);
eT[0][n - 1] = 1.0;
return mul(mul(eT, CtrbInv), phiA);
}
int main() {
Matrix A = {{0.0, 1.0}, {-2.0, -3.0}};
Matrix B = {{0.0}, {1.0}};
std::vector<C> desired = {C(-2.0, 2.0), C(-2.0, -2.0)};
Matrix K = ackermannGain(A, B, desired);
std::cout << std::fixed << std::setprecision(6);
std::cout << "K = [ ";
for (auto v : K[0]) std::cout << v.real() << " ";
std::cout << "]\n";
std::cout << "Expected for this example: K = [ 6 1 ]\n";
return 0;
}
9. Java Implementation
File: Chapter23_Lesson3.java
// Chapter23_Lesson3.java
// Ackermann's formula for SISO pole placement using basic matrix operations.
// Compile: javac Chapter23_Lesson3.java
// Run: java Chapter23_Lesson3
import java.util.Arrays;
public class Chapter23_Lesson3 {
static double[][] zeros(int r, int c) {
return new double[r][c];
}
static double[][] eye(int n) {
double[][] I = zeros(n, n);
for (int i = 0; i < n; i++) I[i][i] = 1.0;
return I;
}
static double[][] add(double[][] A, double[][] B) {
int r = A.length, c = A[0].length;
double[][] R = zeros(r, c);
for (int i = 0; i < r; i++)
for (int j = 0; j < c; j++)
R[i][j] = A[i][j] + B[i][j];
return R;
}
static double[][] scale(double a, double[][] A) {
int r = A.length, c = A[0].length;
double[][] R = zeros(r, c);
for (int i = 0; i < r; i++)
for (int j = 0; j < c; j++)
R[i][j] = a * A[i][j];
return R;
}
static double[][] mul(double[][] A, double[][] B) {
int r = A.length, m = A[0].length, c = B[0].length;
double[][] R = zeros(r, c);
for (int i = 0; i < r; i++)
for (int k = 0; k < m; k++)
for (int j = 0; j < c; j++)
R[i][j] += A[i][k] * B[k][j];
return R;
}
static double[][] mpow(double[][] A, int p) {
int n = A.length;
double[][] R = eye(n);
double[][] X = A;
while (p > 0) {
if ((p & 1) == 1) R = mul(R, X);
X = mul(X, X);
p >>= 1;
}
return R;
}
static double[][] inverse(double[][] A) {
int n = A.length;
double[][] aug = zeros(n, 2 * n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) aug[i][j] = A[i][j];
aug[i][n + i] = 1.0;
}
for (int col = 0; col < n; col++) {
int pivot = col;
for (int r = col + 1; r < n; r++)
if (Math.abs(aug[r][col]) > Math.abs(aug[pivot][col])) pivot = r;
if (Math.abs(aug[pivot][col]) < 1e-12)
throw new RuntimeException("Matrix is singular or ill-conditioned.");
double[] temp = aug[pivot]; aug[pivot] = aug[col]; aug[col] = temp;
double div = aug[col][col];
for (int j = 0; j < 2 * n; j++) aug[col][j] /= div;
for (int r = 0; r < n; r++) {
if (r == col) continue;
double factor = aug[r][col];
for (int j = 0; j < 2 * n; j++) aug[r][j] -= factor * aug[col][j];
}
}
double[][] inv = zeros(n, n);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
inv[i][j] = aug[i][n + j];
return inv;
}
static double[][] controllability(double[][] A, double[][] B) {
int n = A.length;
double[][] C = zeros(n, n);
double[][] block = B;
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) C[i][k] = block[i][0];
block = mul(A, block);
}
return C;
}
static double[][] matrixPolynomial(double[][] A, double[] coeff) {
int n = A.length;
double[][] R = mpow(A, n);
for (int i = 1; i <= n; i++) {
int power = n - i;
double[][] term = (power == 0) ? eye(n) : mpow(A, power);
R = add(R, scale(coeff[i], term));
}
return R;
}
static double[] ackermannGain(double[][] A, double[][] B, double[] desiredPoly) {
int n = A.length;
double[][] C = controllability(A, B);
double[][] Cinv = inverse(C);
double[][] phiA = matrixPolynomial(A, desiredPoly);
double[][] eT = zeros(1, n);
eT[0][n - 1] = 1.0;
double[][] K = mul(mul(eT, Cinv), phiA);
return K[0];
}
public static void main(String[] args) {
double[][] A = {{0.0, 1.0}, {-2.0, -3.0}};
double[][] B = {{0.0}, {1.0}};
// Desired poles -2 +/- 2i give p_d(s) = s^2 + 4s + 8.
double[] desiredPoly = {1.0, 4.0, 8.0};
double[] K = ackermannGain(A, B, desiredPoly);
System.out.println("K = " + Arrays.toString(K));
System.out.println("Expected for this example: K = [6.0, 1.0]");
}
}
10. MATLAB and Simulink Implementation
File: Chapter23_Lesson3.m
% Chapter23_Lesson3.m
% Ackermann's formula for SISO pole placement.
% The Control System Toolbox has acker(A,B,poles), but this script also
% implements the formula explicitly.
clear; clc;
A = [0 1; -2 -3];
B = [0; 1];
desired_poles = [-2+2i, -2-2i];
K = ackermann_gain(A, B, desired_poles);
disp('K from custom Ackermann formula:');
disp(K);
Acl = A - B*K;
disp('Closed-loop eigenvalues:');
disp(eig(Acl));
if exist('acker', 'file') == 2
disp('K from MATLAB acker:');
disp(acker(A, B, desired_poles));
end
% Simulink note:
% Use a State-Space block with A, B, C=eye(size(A)), D=zeros(size(B)).
% Feed the measured state vector x into a Gain block with gain -K, then feed
% that signal into the State-Space block input. Scope x and u to verify decay.
function Ctrb = controllability_matrix(A, B)
n = size(A, 1);
Ctrb = zeros(n, n);
for k = 1:n
Ctrb(:, k) = A^(k-1) * B;
end
end
function phiA = matrix_polynomial(A, coeff)
% coeff = [1 alpha_{n-1} ... alpha_0]
n = size(A, 1);
phiA = A^n;
for i = 2:length(coeff)
power = n - (i - 1);
if power == 0
term = eye(n);
else
term = A^power;
end
phiA = phiA + coeff(i) * term;
end
end
function K = ackermann_gain(A, B, desired_poles)
n = size(A, 1);
Ctrb = controllability_matrix(A, B);
if rank(Ctrb) ~= n
error('System is not controllable, so arbitrary pole placement is impossible.');
end
coeff = poly(desired_poles);
phiA = matrix_polynomial(A, coeff);
eT = zeros(1, n);
eT(end) = 1;
K = eT / Ctrb * phiA;
K = real(K);
end
For Simulink, use a State-Space block for \( \dot{x}=Ax+Bu \), connect the output state vector to a Gain block with gain \( -K \), and feed the result back to the input. Scopes on \( x(t) \) and \( u(t) \) verify the closed-loop decay and control magnitude.
11. Wolfram Mathematica Implementation
File: Chapter23_Lesson3.nb
Notebook[{
Cell["Chapter23_Lesson3.nb", "Title"],
Cell["Ackermann's formula for SISO pole placement", "Subtitle"],
Cell[BoxData[ToBoxes[
ClearAll[controllabilityMatrix, matrixPolynomial, ackermannGain];
controllabilityMatrix[A_, B_] := Module[{n = Length[A]},
Transpose[Table[Flatten[MatrixPower[A, k].B], {k, 0, n - 1}]]
];
matrixPolynomial[A_, coeff_] := Module[{n = Length[A]},
MatrixPower[A, n] + Sum[coeff[[i + 1]] If[n - i == 0, IdentityMatrix[n], MatrixPower[A, n - i]], {i, 1, n}]
];
ackermannGain[A_, B_, desiredPoles_] := Module[{n, Ctrb, coeff, phiA, eT},
n = Length[A];
Ctrb = controllabilityMatrix[A, B];
If[MatrixRank[Ctrb] != n, Print["System is not controllable."]; Abort[]];
coeff = CoefficientList[Expand[Times @@ (s - # & /@ desiredPoles)], s] // Reverse;
phiA = matrixPolynomial[A, coeff];
eT = UnitVector[n, n];
Chop[eT.Inverse[Ctrb].phiA]
];
]], "Input"],
Cell[BoxData[ToBoxes[
A = {{0, 1}, {-2, -3}};
B = {{0}, {1}};
desiredPoles = {-2 + 2 I, -2 - 2 I};
K = ackermannGain[A, B, desiredPoles]
]], "Input"],
Cell[BoxData[ToBoxes[
Eigenvalues[A - B.K]
]], "Input"]
}]
12. Problems and Solutions
Problem 1: For \( A=\begin{bmatrix}0&1\\-5&-2\end{bmatrix} \), \( B=\begin{bmatrix}0\\1\end{bmatrix} \), place the closed-loop poles at \( -3 \) and \( -4 \).
Solution: The desired polynomial is
\[ p_d(s)=(s+3)(s+4)=s^2+7s+12. \]
With \( K=[k_1\;k_2] \),
\[ A-BK=\begin{bmatrix}0&1\\-(5+k_1)&-(2+k_2)\end{bmatrix}. \]
Thus the closed-loop polynomial is \( s^2+(2+k_2)s+(5+k_1) \). Coefficient matching gives
\[ 2+k_2=7,\qquad 5+k_1=12,\qquad K=[7\;5]. \]
Problem 2: Show that arbitrary pole placement is impossible for \( A=\begin{bmatrix}1&0\\0&2\end{bmatrix} \), \( B=\begin{bmatrix}1\\0\end{bmatrix} \).
Solution:
\[ \mathcal{C}=[B\;AB] =\begin{bmatrix}1&1\\0&0\end{bmatrix},\qquad \operatorname{rank}(\mathcal{C})=1<2. \]
The second state is not influenced by the input. Therefore the pole at \( 2 \) is uncontrollable and cannot be moved by any state-feedback gain.
Problem 3: Prove that for a controllable second-order pair in companion form, Ackermann’s formula gives the same gain as coefficient matching.
Solution: Let
\[ A=\begin{bmatrix}0&1\\-a_0&-a_1\end{bmatrix},\qquad B=\begin{bmatrix}0\\1\end{bmatrix},\qquad p_d(s)=s^2+\alpha_1s+\alpha_0. \]
Coefficient matching gives \( K=[\alpha_0-a_0\;\alpha_1-a_1] \). Since
\[ \mathcal{C}=\begin{bmatrix}0&1\\1&-a_1\end{bmatrix},\qquad p_d(A)=A^2+\alpha_1A+ \alpha_0I, \]
direct multiplication of \( [0\;1]\mathcal{C}^{-1}p_d(A) \) yields exactly \( [\alpha_0-a_0\;\alpha_1-a_1] \).
Problem 4: Why should one avoid explicitly computing \( \mathcal{C}^{-1} \) for a high-order system?
Solution: The columns \( B,AB,\ldots,A^{n-1}B \) can become nearly linearly dependent, especially when \( A \) has widely separated modal scales. Then \( \mathcal{C} \) is ill-conditioned, and the gain becomes sensitive to roundoff. Numerically, solving a linear system or using Schur-based pole placement is better than forming the inverse explicitly.
13. Summary
Ackermann’s formula gives a direct SISO state-feedback gain \( K=e_n^T\mathcal{C}^{-1}p_d(A) \) for assigning the closed-loop poles of \( A-BK \). Its existence requires controllability, its derivation follows from controllable canonical form and coefficient matching, and its numerical use requires caution when the controllability matrix is poorly conditioned.
14. References
- Kalman, R.E. (1960). On the general theory of control systems. Proceedings of the First International Congress on Automatic Control, 481–492.
- Kalman, R.E. (1963). Mathematical description of linear dynamical systems. Journal of the Society for Industrial and Applied Mathematics, Series A: Control, 1(2), 152–192.
- Bass, R.W., & Gura, I. (1965). High-order system design via state-space considerations. Proceedings of the Joint Automatic Control Conference.
- Wonham, W.M. (1967). On pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, 12(6), 660–665.
- Ackermann, J. (1972). Der Entwurf linearer Regelungssysteme im Zustandsraum. Regelungstechnik und Prozessdatenverarbeitung, 20, 297–300.
- Heymann, M. (1968). Comments on pole assignment in multi-input controllable linear systems. IEEE Transactions on Automatic Control, 13(6), 748–749.
- Kautsky, J., Nichols, N.K., & Van Dooren, P. (1985). Robust pole assignment in linear state feedback. International Journal of Control, 41(5), 1129–1155.
- Tits, A.L., & Yang, Y. (1996). Globally convergent algorithms for robust pole assignment by state feedback. IEEE Transactions on Automatic Control, 41(10), 1432–1452.