Ho-Kalman Identification ExampleΒΆ

In [1]:
%load_ext autoreload
%autoreload 2
%matplotlib inline

import control as ctrl
import matplotlib.pyplot as plt
import scipy as sp
import scipy.linalg as la
import vibrationtesting as vt
import scipy.signal as sig

Considers a system

\[ \begin{align}\begin{aligned}\ddot{x} + 2 \dot{x} + 100 x = f(t)\\with a displacement sensor.\end{aligned}\end{align} \]

The state space representation is

\[\dot{\mathbf{z}} = A \mathbf{z} + B u\]
\[y = C \mathbf{z} + D u\]

where

\[\begin{split}A = \begin{bmatrix} 0&1\\ -100&-2 \end{bmatrix} ,\quad B = \begin{bmatrix}0\\1\end{bmatrix} ,\quad C = \begin{bmatrix}1&0\end{bmatrix} , \text{ and } D = [0]\end{split}\]
In [2]:
sample_freq = 1e3
A = sp.array([[0, 1],\
              [-100, 2]])
B = sp.array([[0], [1]])
C = sp.array([[1, 0]])
D = sp.array([[0]])
sys = ctrl.ss(A, B, C, D)

For this example, we will generate an impulse response for only 4 discrete times.

In [3]:
t = sp.linspace(0, 1, num = 6, endpoint = False)
dt = t[1]
t
Out[3]:
array([ 0.        ,  0.16666667,  0.33333333,  0.5       ,  0.66666667,
        0.83333333])

The impulse response for a second order underdamped system is known to be

\[h(t) = \frac{1}{\omega_d}e^{-\zeta \omega_n t}\sin\left(\omega_d t\right)\]
In [4]:
omega_n = sp.sqrt(100)
zeta = 2/(2*omega_n)
omega_d = omega_n * sp.sqrt(1 - zeta**2)
In [5]:
h = 1/omega_d * sp.exp(-zeta*omega_n*t)*sp.sin(omega_d*t)
h
Out[5]:
array([ 0.        ,  0.08474903, -0.01254052, -0.05886968,  0.01769677,
        0.03956333])
In [6]:
plt.plot(t,h,'.')
plt.axis([0,1,-0.1,0.1])
plt.grid()
../../_images/tutorial_Notebooks_Ho-Kalman-Demo_9_0.png
In [6]:
Hankel_0 = sp.vstack((h[0:-2],h[1:-1]))
Hankel_0 = Hankel_0.T
Hankel_0
Out[6]:
array([[ 0.        ,  0.08474903],
       [ 0.08474903, -0.01254052],
       [-0.01254052, -0.05886968],
       [-0.05886968,  0.01769677]])
In [7]:
h[1:-1]
Out[7]:
array([ 0.08474903, -0.01254052, -0.05886968,  0.01769677])
In [8]:
h[2:]
Out[8]:
array([-0.01254052, -0.05886968,  0.01769677,  0.03956333])
In [9]:
Hankel_1 = sp.vstack((h[1:-1],h[2:]))
Hankel_1 = Hankel_1.T
Hankel_1
Out[9]:
array([[ 0.08474903, -0.01254052],
       [-0.01254052, -0.05886968],
       [-0.05886968,  0.01769677],
       [ 0.01769677,  0.03956333]])
In [10]:
U, sig, Vt = la.svd(Hankel_0)
V = Vt.T
U = U[:,:2]
print(U)
V = V[:,:2]
print(V)
[[ 0.56941225  0.57615451]
 [-0.59214005  0.56070011]
 [-0.32038129 -0.49580091]
 [ 0.47169449 -0.32839431]]
[[-0.66563569  0.74627684]
 [ 0.74627684  0.66563569]]
In [11]:
sig = sp.diag(sig)
print(sig)
[[ 0.11107284  0.        ]
 [ 0.          0.0979112 ]]
In [12]:
A_d = la.inv(sp.sqrt(sig))@U.T@Hankel_1@V@la.inv(sp.sqrt(sig))
print(A_d)
[[-0.22322281  0.85303151]
 [-0.85967388  0.07525037]]
In [13]:
lam_d, vec = la.eig(A_d)
print(lam_d)
print(vec)
[-0.07398622+0.84324217j -0.07398622-0.84324217j]
[[ 0.12298924-0.69493489j  0.12298924+0.69493489j]
 [ 0.70847664+0.j          0.70847664-0.j        ]]
In [14]:
# This should be the same as A_d
print(A_d)
print(vec@sp.diag(lam_d)@la.inv(vec))
[[-0.22322281  0.85303151]
 [-0.85967388  0.07525037]]
[[-0.22322281 +0.00000000e+00j  0.85303151 +2.77555756e-17j]
 [-0.85967388 -6.93889390e-18j  0.07525037 +0.00000000e+00j]]
In [15]:
lam = sp.log(lam_d)/dt
lam
Out[15]:
array([-1.+9.94987437j, -1.-9.94987437j])
In [16]:
# These are the continuous time eigenvalues
print('The undamped natural frequency is {} rad/sec.'.format(abs(lam[0])))
print('The damping ratio is {}.'.format(-sp.real(lam[0])/abs(lam[0])))

The undamped natural frequency is 10.0 rad/sec.
The damping ratio is 0.09999999999999998.

The identified state matrix is

In [17]:
A = la.logm(A_d)/dt
print(A)
[[ -2.76092394  10.06538424]
 [-10.1437611    0.76092394]]

which is the system the result in a balanced realization form.

In [18]:
# The discrete input matrix is
B_d = sp.sqrt(sig)@V.T[:,0].T.reshape((2,1))
print(B_d)
[[-0.22184035]
 [ 0.23351573]]
In [19]:
# The continuous input matrix is
B = la.solve((A_d - sp.eye(2)), A) @ B_d
print(B)
[[-2.58036274]
 [-0.22677789]]
In [20]:
C = (U @ sp.sqrt(sig))[0,:]
print(C)
[ 0.18977139  0.18028315]
In [21]:
# Of course, D is
D = h[0]
D
Out[21]:
0.0