3. State space representation

The “standard” or most commonly used state space representation is

\begin{align} \dot{x} &= Ax + Bu \\ y &= Cx + Du \end{align}

Take note that Seborg uses a slightly different version:

\begin{align} \dot{x} &= Ax + Bu + Ed\\ y &= Cx \end{align}

This second version can not represent pure gain systems as it effectively assumes \(D=0\). It is also possible to stack \(u\) and \(d\) from the bottom form into one input vector, so the \(E\) matrix really doesn’t add much. As you may infer, I prefer the top version and it is also the version used by most libraries.

[1]:
import numpy
import numpy.linalg

3.1. Converting between state space and transfer function forms

There is good support in various libraries for converting systems with numeric coefficients between transfer function and state space representation.

3.1.1. Scipy.signal

The scipy.signal library handles conversion between transfer function coefficients and state space matrices easily. Note that scipi.signal only handles SISO transfer functions.

[2]:
import scipy.signal
[3]:
G = scipy.signal.lti(1, [1, 1])
[4]:
G
[4]:
TransferFunctionContinuous(
array([1.]),
array([1., 1.]),
dt: None
)

This object allows us to access the numerator and denominator

[5]:
G.num, G.den
[5]:
(array([1.]), array([1., 1.]))

To convert to state space, we can use the .to_ss() method

[6]:
Gss = G.to_ss()
[7]:
Gss.A, Gss.B, Gss.C, Gss.D
[7]:
(array([[-1.]]), array([[1.]]), array([[1.]]), array([[0.]]))

We can build another object using the state space matrices instead of the Laplace form

[8]:
G2ss = scipy.signal.lti(Gss.A, Gss.B, Gss.C, Gss.D)
G2ss
[8]:
StateSpaceContinuous(
array([[-1.]]),
array([[1.]]),
array([[1.]]),
array([[0.]]),
dt: None
)

We can convert to transfer function form using .to_tf() (there is a small warning about bad coefficients, but the answer is reliable).

[9]:
G2 = G2ss.to_tf()
C:\Users\Admin\anaconda3\lib\site-packages\scipy\signal\filter_design.py:1630: BadCoefficients: Badly conditioned filter coefficients (numerator): the results may be meaningless
  warnings.warn("Badly conditioned filter coefficients (numerator): the "

We can now access the numerator and denominator again:

[10]:
G2.num, G2.den
[10]:
(array([1.]), array([1., 1.]))

Instead of building objects we can also use the functions in scipy.signal.lti_conversion:

[11]:
scipy.signal.lti_conversion.tf2ss(1, [1, 1])
[11]:
(array([[-1.]]), array([[1.]]), array([[1.]]), array([[0.]]))
[12]:
scipy.signal.lti_conversion.ss2tf(-1, 1, 1, 0)
[12]:
(array([[0., 1.]]), array([1., 1.]))

3.1.2. Control library

The control library (at least from version 0.8.0) does a good job with these conversions as well.

[13]:
import control
[14]:
Gtf = control.tf([1], [1, 1])
Gtf
[14]:
$$\frac{1}{s + 1}$$

In the control library we convert the system using ss (short for state space) to get a State Space representation:

[15]:
Gss = control.ss(Gtf)
Gss
[15]:
\[ \left( \begin{array}{rll|rll} -1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \hline 1\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}&0\phantom{.}&\hspace{-1em}&\hspace{-1em}\phantom{\cdot}\\ \end{array}\right) \]
[16]:
Gss.A
[16]:
array([[-1.]])

3.2. Symbolic conversion

It is easy to convert state space models to transfer functions since the Laplace transform is a linear operator:

\[\dot{x} = Ax + Bu \quad \therefore \quad sX(s) = AX(s) + BU(s) \quad X(s) = (sI - A)^{-1}BU(s)\]
\[y = Cx + Du \quad \therefore \quad Y(s) = CX(s) + DU(s) \quad Y(s) = \underbrace{(C(sI - A)^{-1}B + D)}_{G(s)}U(s)\]

This conversion is handled for symbolic matrices by tbcontrol.symbolic.ss2tf

[17]:
import sympy
[18]:
import tbcontrol
tbcontrol.expectversion('0.1.8')
import tbcontrol.symbolic
[19]:
s = sympy.symbols('s')
[20]:
A, B, C, D = [sympy.Matrix(m) for m in [G2ss.A, G2ss.B, G2ss.C, G2ss.D]]
[21]:
A, B, C, D
[21]:
(Matrix([[-1.0]]), Matrix([[1.0]]), Matrix([[1.0]]), Matrix([[0.0]]))
[22]:
G = tbcontrol.symbolic.ss2tf(A, B, C, D, s)
G
[22]:
$\displaystyle \left[\begin{matrix}\frac{1.0}{s + 1.0}\end{matrix}\right]$

Note that ss2tf returns a sympy Matrix. To get the SISO result, we need to index into the matrix:

[23]:
G[0, 0]
[23]:
$\displaystyle \frac{1.0}{s + 1.0}$

3.3. Analysis

Notice that the roots of the characteristic function correspond with the eigenvalues of the A matrix. The numerator and denominator of control transfer functions are stored as lists of lists to accomodate MIMO systems.

[24]:
Gtf.pole()
[24]:
array([-1.])
[25]:
numpy.roots(Gtf.den[0][0])
[25]:
array([-1.])
[26]:
numpy.linalg.eig(Gss.A)
[26]:
(array([-1.]), array([[1.]]))