```
# Alexander Hebert
# ECE 6390
# Computer Project #1
```

```
# Tested using Python v3.4 and IPython v2
```

```
# Import libraries
```

```
import numpy as np
```

```
import scipy
```

```
import sympy
```

```
from IPython.display import display
```

```
from sympy.interactive import printing
```

```
printing.init_printing(use_latex='mathjax')
```

```
from __future__ import division
```

```
np.set_printoptions(precision=4)
```

```
np.set_printoptions(suppress=True)
```

```
# Original system:
```

```
A = np.matrix(np.loadtxt('A.txt'))
```

```
A
```

    matrix([[   0.   ,    1.   ,    0.   ,    0.   ,    0.   ,    0.   ,
                0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ],
            [  -0.202,   -1.15 ,    0.   ,    0.   ,    0.   ,    0.   ,
                0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ],
            [   0.   ,    0.   ,    0.   ,    1.   ,    0.   ,    0.   ,
                0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ],
            [   0.   ,    0.   ,    0.   ,    0.   ,    1.   ,    0.   ,
                0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ],
            [   0.   ,    0.   ,   -2.36 ,  -13.6  ,  -12.8  ,    0.   ,
                0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ],
            [   0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ,
                1.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ],
            [   0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ,
                0.   ,    1.   ,    0.   ,    0.   ,    0.   ,    0.   ],
            [   0.   ,    0.   ,    0.   ,    0.   ,    0.   ,   -1.62 ,
               -9.4  ,   -9.15 ,    0.   ,    0.   ,    0.   ,    0.   ],
            [   0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ,
                0.   ,    0.   ,    0.   ,    1.   ,    0.   ,    0.   ],
            [   0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ,
                0.   ,    0.   ,    0.   ,    0.   ,    1.   ,    0.   ],
            [   0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ,
                0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    1.   ],
            [   0.   ,    0.   ,    0.   ,    0.   ,    0.   ,    0.   ,
                0.   ,    0.   , -188.   , -111.6  , -116.4  ,  -20.8  ]])

```
B = np.transpose(np.matrix(np.loadtxt('B.txt')))
```

```
B
```

    matrix([[ 0.    ,  0.    ],
            [ 1.0439,  4.1486],
            [ 0.    ,  0.    ],
            [ 0.    ,  0.    ],
            [-1.794 ,  2.6775],
            [ 0.    ,  0.    ],
            [ 0.    ,  0.    ],
            [ 1.0439,  4.1486],
            [ 0.    ,  0.    ],
            [ 0.    ,  0.    ],
            [ 0.    ,  0.    ],
            [-1.704 ,  2.6775]])

```
C = np.matrix(np.loadtxt('C.txt'))
```

```
C
```

    matrix([[  0.264,   0.806,  -1.42 , -15.   ,   0.   ,   0.   ,   0.   ,
               0.   ,   0.   ,   0.   ,   0.   ,   0.   ],
            [  0.   ,   0.   ,   0.   ,   0.   ,   0.   ,   4.9  ,   2.12 ,
               1.95 ,   9.35 ,  25.8  ,   7.14 ,   0.   ]])

```
D = np.zeros((2,2))
```

```
D
```

    array([[ 0.,  0.],
           [ 0.,  0.]])

```
# Compute eigenvalues/poles of A to determine system stability:
```

```
A_eigvals, M = np.linalg.eig(A)
```

```
A_eigvals
```

    array([ -0.2164+0.j    ,  -0.9336+0.j    , -11.6500+0.j    ,
            -0.2172+0.j    ,  -0.9328+0.j    ,  -8.0004+0.j    ,
            -0.2172+0.j    ,  -0.9325+0.j    , -10.9955+0.j    ,
            -9.0644+0.j    ,  -0.3700+1.3226j,  -0.3700-1.3226j])

```
# All eigenvalues/poles of A are in the LHP. Therefore the system is stable.
```

```
# Sort eigenvalues and eigenvectors in descending order:
```

```
idx = A_eigvals.argsort()[::-1]
```

```
A_eigvals = A_eigvals[idx]
```

```
A_eigvals
```

    array([ -0.2164+0.j    ,  -0.2172+0.j    ,  -0.2172+0.j    ,
            -0.3700+1.3226j,  -0.3700-1.3226j,  -0.9325+0.j    ,
            -0.9328+0.j    ,  -0.9336+0.j    ,  -8.0004+0.j    ,
            -9.0644+0.j    , -10.9955+0.j    , -11.6500+0.j    ])

```
M = M[:,idx]
```

```
# Compute linear transformation Tc to get controller-type block companion form.
```

```
gamma = 12/2
```

```
gamma
```

$$6.0$$

```
# gamma is an integer.
```

```
Bc = np.matrix([[0,0],
                [0,0],
                [0,0],
                [0,0],
                [0,0],
                [0,0],
                [0,0],
                [0,0],
                [0,0],
                [0,0],
                [1,0],
                [0,1]])
```

```
Bc
```

    matrix([[0, 0],
            [0, 0],
            [0, 0],
            [0, 0],
            [0, 0],
            [0, 0],
            [0, 0],
            [0, 0],
            [0, 0],
            [0, 0],
            [1, 0],
            [0, 1]])

```
Tc1 = (Bc.T)*np.linalg.inv(np.concatenate(
(B,A*B,(A**2)*B,(A**3)*B,(A**4)*B,(A**5)*B),1))
```

```
Tc1
```

    matrix([[  5.6689,   1.448 ,   0.0041,  -0.0025,  -0.0001, -45.3482,
             -17.2531,  -1.448 ,  -0.0337,   0.0162,   0.003 ,   0.0001],
            [  3.6078,   0.9215,   0.0274,   0.0006,  -0.    , -28.8598,
             -10.9799,  -0.9215,  -0.2282,  -0.0331,  -0.0003,   0.    ]])

```
Tc = np.concatenate(
(Tc1,Tc1*A,Tc1*(A**2),Tc1*(A**3),Tc1*(A**4),Tc1*(A**5)),0)
```

```
Tc
```

    matrix([[   5.6689,    1.448 ,    0.0041,   -0.0025,   -0.0001,  -45.3482,
              -17.2531,   -1.448 ,   -0.0337,    0.0162,    0.003 ,    0.0001],
            [   3.6078,    0.9215,    0.0274,    0.0006,   -0.    ,  -28.8598,
              -10.9799,   -0.9215,   -0.2282,   -0.0331,   -0.0003,    0.    ],
            [  -0.2925,    4.0037,    0.0001,    0.0049,   -0.0017,    2.3458,
              -31.7368,   -4.0037,   -0.0115,   -0.0405,    0.0091,    0.0018],
            [  -0.1861,    2.548 ,    0.0001,    0.0279,    0.001 ,    1.4929,
              -20.1975,   -2.548 ,   -0.0073,   -0.2326,   -0.0376,   -0.0011],
            [  -0.8087,   -4.8968,    0.004 ,    0.0234,    0.0268,    6.4861,
               39.981 ,    4.8974,   -0.3335,   -0.2095,   -0.247 ,   -0.0278],
            [  -0.5147,   -3.1164,   -0.0025,   -0.0141,    0.0146,    4.1278,
               25.4441,    3.1167,    0.2031,    0.1133,   -0.1068,   -0.0152],
            [   0.9891,    4.8225,   -0.0633,   -0.3608,   -0.32  ,   -7.9338,
              -39.5496,   -4.8303,    5.2337,    2.7733,    3.031 ,    0.3321],
            [   0.6295,    3.0691,   -0.0345,   -0.2013,   -0.2012,   -5.0491,
              -25.1696,   -3.0741,    2.8532,    1.8968,    1.8798,    0.2089],
            [  -0.9741,   -4.5568,    0.7551,    4.2881,    3.7347,    7.8251,
               37.4714,    4.648 ,  -62.4292,  -31.8253,  -35.8796,   -3.8761],
            [  -0.62  ,   -2.9   ,    0.4749,    2.7024,    2.3746,    4.98  ,
               23.847 ,    2.958 ,  -39.2657,  -20.4556,  -22.4145,   -2.4645],
            [   0.9205,    4.2661,   -8.8139,  -50.037 ,  -43.5162,   -7.5297,
              -35.866 ,   -5.0577,  728.7022,  370.1408,  419.3498,   44.7427],
            [   0.5858,    2.715 ,   -5.604 ,  -31.8193,  -27.6922,   -4.792 ,
              -22.8253,   -3.2188,  463.3185,  235.768 ,  266.4076,   28.8462]])

```
Tc_inv = np.linalg.inv(Tc)
```

```
Tc_inv
```

    matrix([[  76.9462,  -11.6521,  448.169 ,  -60.8902,  444.4161,  -26.8159,
               57.0493,   30.767 ,    1.0439,    4.1486,    0.    ,   -0.    ],
            [  -0.    ,    0.    ,   76.9462,  -11.6521,  448.169 ,  -60.8902,
              444.4161,  -26.8159,   57.0493,   30.767 ,    1.0439,    4.1486],
            [ -28.6997,   44.8675,  -15.4174,   22.8961,  -17.2783,   25.8555,
               -1.794 ,    2.6775,   -0.    ,    0.    ,   -0.    ,   -0.    ],
            [  -0.    ,   -0.    ,  -28.6997,   44.8675,  -15.4174,   22.8961,
              -17.2783,   25.8555,   -1.794 ,    2.6775,   -0.    ,    0.    ],
            [  -0.    ,   -0.    ,    0.    ,   -0.    ,  -28.6997,   44.8675,
              -15.4174,   22.8961,  -17.2783,   25.8555,   -1.794 ,    2.6775],
            [   9.5945,   -1.4529,   54.8332,   -7.4335,   48.6981,   -2.4218,
                1.0439,    4.1486,    0.    ,   -0.    ,    0.    ,   -0.    ],
            [  -0.    ,    0.    ,    9.5945,   -1.4529,   54.8332,   -7.4335,
               48.6981,   -2.4218,    1.0439,    4.1486,    0.    ,    0.    ],
            [  -0.    ,    0.    ,   -0.    ,    0.    ,    9.5945,   -1.4529,
               54.8332,   -7.4335,   48.6981,   -2.4218,    1.0439,    4.1486],
            [  -0.3452,    0.5424,   -1.9596,    3.0791,   -1.704 ,    2.6775,
               -0.    ,    0.    ,   -0.    ,    0.    ,   -0.    ,   -0.    ],
            [  -0.    ,    0.    ,   -0.3452,    0.5424,   -1.9596,    3.0791,
               -1.704 ,    2.6775,   -0.    ,   -0.    ,   -0.    ,   -0.    ],
            [   0.    ,   -0.    ,    0.    ,   -0.    ,   -0.3452,    0.5424,
               -1.9596,    3.0791,   -1.704 ,    2.6775,    0.    ,    0.    ],
            [  -0.    ,    0.    ,   -0.    ,    0.    ,    0.    ,   -0.    ,
               -0.3452,    0.5424,   -1.9596,    3.0791,   -1.704 ,    2.6775]])

```
Ac = Tc*A*Tc_inv
```

```
Ac
```

    matrix([[  -0.    ,   -0.    ,    1.    ,   -0.    ,    0.    ,   -0.    ,
                0.    ,   -0.    ,    0.    ,    0.    ,   -0.    ,   -0.    ],
            [   0.    ,   -0.    ,    0.    ,    1.    ,   -0.    ,   -0.    ,
               -0.    ,    0.    ,   -0.    ,    0.    ,    0.    ,   -0.    ],
            [   0.    ,   -0.    ,   -0.    ,    0.    ,    1.    ,    0.    ,
                0.    ,   -0.    ,    0.    ,   -0.    ,    0.    ,    0.    ],
            [   0.    ,   -0.    ,    0.    ,   -0.    ,   -0.    ,    1.    ,
                0.    ,    0.    ,    0.    ,   -0.    ,    0.    ,    0.    ],
            [   0.    ,   -0.    ,    0.    ,   -0.    ,    0.    ,   -0.    ,
                1.    ,   -0.    ,    0.    ,   -0.    ,    0.    ,    0.    ],
            [  -0.    ,    0.    ,   -0.    ,    0.    ,   -0.    ,    0.    ,
               -0.    ,    1.    ,   -0.    ,   -0.    ,   -0.    ,   -0.    ],
            [   0.    ,   -0.    ,   -0.    ,    0.    ,   -0.    ,    0.    ,
               -0.    ,    0.    ,    1.    ,   -0.    ,   -0.    ,   -0.    ],
            [  -0.    ,    0.    ,   -0.    ,    0.    ,   -0.    ,   -0.    ,
                0.    ,   -0.    ,    0.    ,    1.    ,    0.    ,    0.    ],
            [   0.    ,   -0.    ,    0.    ,   -0.    ,   -0.    ,    0.    ,
               -0.    ,   -0.    ,    0.    ,   -0.    ,    1.    ,    0.    ],
            [   0.    ,   -0.    ,    0.    ,   -0.    ,    0.    ,   -0.    ,
                0.    ,   -0.    ,    0.    ,    0.    ,    0.    ,    1.    ],
            [ -31.5119,   43.5244, -219.7319,  275.8889, -428.7516,  406.4136,
             -442.4287,  304.3612, -239.2004,  155.6859,  -31.5415,   15.0711],
            [   4.1826,  -10.3846,   12.139 ,  -63.2263,  -56.5344,  -81.2714,
             -122.6727,  -55.9729,  -62.8001,  -41.4416,   -6.1042,  -12.3585]])

```
Bc_verify = Tc*B
```

```
Bc_verify
```

    matrix([[-0., -0.],
            [-0., -0.],
            [ 0.,  0.],
            [ 0.,  0.],
            [-0.,  0.],
            [-0.,  0.],
            [-0.,  0.],
            [ 0.,  0.],
            [-0.,  0.],
            [-0., -0.],
            [ 1.,  0.],
            [ 0.,  1.]])

```
Cc = C*Tc_inv
```

```
Cc
```

    matrix([[  61.0674,  -66.788 ,  632.7239, -730.992 ,  734.3456, -436.3131,
              634.9827, -405.1258,   73.1673,  -14.269 ,    0.8414,    3.3438],
            [  43.7857,   -2.0479,  261.7949,    3.2789,  304.622 ,   77.8893,
              157.3251,   91.7629,   85.0079,   23.1899,    2.0356,    8.0898]])

```
# With the controller-type block companion form, the right MFD is obtained.
```

```
A11 = -1*Ac[10:12,0:2]
```

```
A12 = -1*Ac[10:12,2:4]
```

```
A13 = -1*Ac[10:12,4:6]
```

```
A14 = -1*Ac[10:12,6:8]
```

```
A15 = -1*Ac[10:12,8:10]
```

```
A16 = -1*Ac[10:12,10:12]
```

```
A17 = np.identity(2)
```

```
A21 = Cc[0:2,0:2]
```

```
A22 = Cc[0:2,2:4]
```

```
A23 = Cc[0:2,4:6]
```

```
A24 = Cc[0:2,6:8]
```

```
A25 = Cc[0:2,8:10]
```

```
A26 = Cc[0:2,10:12]
```

```
# Model reduction using second Cauer method:
```

```
# order of 2nd Cauer reduced model
```

```
order = 2
```

```
n_end = gamma + 1
```

```
q = 1
```

```
for m in np.linspace(1,order*2,order*2):
    exec("H%d = (A%d%d)*np.linalg.inv(A%d%d)" %(m,m,1,m+1,1))
    exec("A%d%d = np.zeros((2,2))" %(m+1,n_end))

    for n in np.linspace(1,n_end-1,n_end-1):
        exec("A%d%d = (A%d%d) - (H%d)*(A%d%d)" %(m+2,n,m,n+1,m,m+1,n+1))

    if (np.equal(q,1)):
        q = 0
        n_end = n_end - 1

    q = q+1
```

```
H1
```

    matrix([[ 0.6577, -0.1977],
            [-0.1594,  0.1267]])

```
H2
```

    matrix([[-0.1178,  0.793 ],
            [ 0.6139,  2.3887]])

```
H3
```

    matrix([[-0.2786, -0.0307],
            [-0.0022, -0.8821]])

```
H4
```

    matrix([[ 3.0498, -0.6611],
            [-0.5713, -0.5007]])

```
A_hat = -1*np.concatenate((np.concatenate((H1*H2,H1*H4),1),
                          np.concatenate((H1*H2,(H1+H3)*H4),1)),0)
```

```
A_hat
```

    matrix([[ 0.1988, -0.0494, -2.1189,  0.3359],
            [-0.0966, -0.1764,  0.5585, -0.0419],
            [ 0.1988, -0.0494, -1.2867,  0.1363],
            [-0.0966, -0.1764,  0.0611, -0.4851]])

```
A_hat_eigvals, M_hat = np.linalg.eig(A_hat)
```

```
A_hat_eigvals
```

    array([-0.8617+0.j    , -0.4482+0.j    , -0.2197+0.0014j, -0.2197-0.0014j])

```
B_hat = np.concatenate((np.identity(2),np.identity(2)),0)
```

```
B_hat
```

    array([[ 1.,  0.],
           [ 0.,  1.],
           [ 1.,  0.],
           [ 0.,  1.]])

```
C_hat = np.concatenate((H2,H4),1)
```

```
C_hat
```

    matrix([[-0.1178,  0.793 ,  3.0498, -0.6611],
            [ 0.6139,  2.3887, -0.5713, -0.5007]])

```
np.savetxt('A_hat_cp1.csv', A_hat, delimiter=',')
```

```
np.savetxt('B_hat_cp1.csv', B_hat, delimiter=',')
```

```
np.savetxt('C_hat_cp1.csv', C_hat, delimiter=',')
```

```
# Model reduction using residualization:
```

```
# M computed previously (see above).
```

```
M
```

    matrix([[ 0.9774+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000-0.j    ,  0.0000+0.j    ,  0.0000+0.j    , -0.7309+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ],
            [-0.2115+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000-0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.6824+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    ,  0.9762+0.j    ,  0.0000+0.j    ,
              0.0000-0.j    ,  0.0000+0.j    , -0.6169+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    , -0.0073+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    , -0.2120+0.j    ,  0.0000+0.j    ,
              0.0000-0.j    ,  0.0000+0.j    ,  0.5755+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0855+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    ,  0.0460+0.j    ,  0.0000+0.j    ,
              0.0000-0.j    ,  0.0000+0.j    , -0.5368+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    , -0.9963+0.j    ],
            [ 0.0000+0.j    ,  0.9762+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000-0.j    , -0.6171+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
             -0.0155+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    , -0.2120+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000-0.j    ,  0.5755+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.1240+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0460+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000-0.j    , -0.5366+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
             -0.9922+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.2013+0.1884j,
              0.2013-0.1884j,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0013+0.j    , -0.0007+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    , -0.3237+0.1965j,
             -0.3237-0.1965j,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    , -0.0121+0.j    ,  0.0082+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    , -0.1401-0.5008j,
             -0.1401+0.5008j,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.1096+0.j    , -0.0906+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.7142+0.j    ,
              0.7142-0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    , -0.9939+0.j    ,  0.9959+0.j    ,  0.0000+0.j    ]])

```
M_inv = np.linalg.inv(M)
```

```
M_inv
```

    matrix([[   1.3317+0.j    ,    1.4264+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ],
            [   0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    1.3726+0.j    ,
                1.6436+0.j    ,    0.1840+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ],
            [   0.0000+0.j    ,    0.0000+0.j    ,    1.3606+0.j    ,
                1.5754+0.j    ,    0.1252+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ],
            [   0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.5332-1.9206j,
               -1.1348-1.1372j,   -0.2446-0.1703j,   -0.0125-0.0075j],
            [   0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.5332+1.9206j,
               -1.1348+1.1372j,   -0.2446+0.1703j,   -0.0125+0.0075j],
            [   0.0000-0.j    ,    0.0000-0.j    ,    0.0000-0.j    ,
                0.0000-0.j    ,    0.0000-0.j    ,    0.5568+0.j    ,
                2.6337+0.j    ,    0.3205+0.j    ,    0.0000-0.j    ,
                0.0000-0.j    ,    0.0000-0.j    ,    0.0000-0.j    ],
            [   0.0000-0.j    ,    0.0000-0.j    ,    0.5347+0.j    ,
                2.5079+0.j    ,    0.2113+0.j    ,    0.0000-0.j    ,
                0.0000-0.j    ,    0.0000-0.j    ,    0.0000-0.j    ,
                0.0000-0.j    ,    0.0000-0.j    ,    0.0000-0.j    ],
            [   0.4127+0.j    ,    1.9073+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ],
            [   0.0000-0.j    ,    0.0000-0.j    ,    0.0000-0.j    ,
                0.0000-0.j    ,    0.0000-0.j    ,   -0.2375-0.j    ,
               -1.3482-0.j    ,   -1.1727-0.j    ,    0.0000-0.j    ,
                0.0000-0.j    ,    0.0000-0.j    ,    0.0000-0.j    ],
            [  -0.0000+0.j    ,   -0.0000+0.j    ,   -0.0000+0.j    ,
               -0.0000+0.j    ,   -0.0000+0.j    ,   -0.0000+0.j    ,
               -0.0000+0.j    ,   -0.0000+0.j    ,  104.0605+0.j    ,
               50.2920+0.j    ,   58.8806+0.j    ,    5.0173+0.j    ],
            [   0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,    0.0000+0.j    ,
                0.0000+0.j    ,    0.0000+0.j    ,  103.0909+0.j    ,
               51.8208+0.j    ,   59.1157+0.j    ,    6.0294+0.j    ],
            [   0.0000-0.j    ,    0.0000-0.j    ,   -0.2252-0.j    ,
               -1.2786-0.j    ,   -1.1118-0.j    ,    0.0000-0.j    ,
                0.0000-0.j    ,    0.0000-0.j    ,    0.0000-0.j    ,
                0.0000-0.j    ,    0.0000-0.j    ,    0.0000-0.j    ]])

```
Ad = M_inv*A*M
```

```
Ad
```

    matrix([[ -0.2164+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,  -0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,  -0.2172+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,  -0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,   0.0000+0.j    ,  -0.2172+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
              -0.3700+1.3226j,  -0.0000-0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
              -0.0000-0.j    ,   0.0000+0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000-0.j    ,  -0.3700-1.3226j,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
              -0.0000+0.j    ,   0.0000-0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,  -0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,  -0.9325+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,  -0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,   0.0000+0.j    ,  -0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
              -0.9328+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,  -0.9336+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,  -0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,  -8.0004+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000-0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
              -9.0644-0.j    ,  -0.0000+0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000-0.j    ,  -0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000-0.j    , -10.9955+0.j    ,   0.0000+0.j    ],
            [  0.0000+0.j    ,   0.0000+0.j    ,  -0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    ,   0.0000+0.j    ,
               0.0000+0.j    ,   0.0000+0.j    , -11.6500+0.j    ]])

```
Bd = M_inv*B
```

```
Bd
```

    matrix([[  1.4890+0.j    ,   5.9176+0.j    ],
            [  0.1921+0.j    ,   0.7633+0.j    ],
            [ -0.2246+0.j    ,   0.3352+0.j    ],
            [  0.0212+0.0128j,  -0.0334-0.0202j],
            [  0.0212-0.0128j,  -0.0334+0.0202j],
            [  0.3346+0.j    ,   1.3296+0.j    ],
            [ -0.3791+0.j    ,   0.5658+0.j    ],
            [  1.9911+0.j    ,   7.9127+0.j    ],
            [ -1.2242+0.j    ,  -4.8651+0.j    ],
            [ -8.5494-0.j    ,  13.4338+0.j    ],
            [-10.2742-0.j    ,  16.1438+0.j    ],
            [  1.9946+0.j    ,  -2.9768+0.j    ]])

```
Cd = C*M
```

```
Cd
```

    matrix([[ 0.0876+0.j    ,  0.0000+0.j    ,  1.7937+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    , -7.7564+0.j    ,  0.3571+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    , -1.2724+0.j    ],
            [ 0.0000+0.j    ,  4.4237+0.j    ,  0.0000+0.j    , -7.4695+3.2554j,
             -7.4695-3.2554j, -2.8504+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
             -1.7478+0.j    ,  0.4833+0.j    , -0.4412+0.j    ,  0.0000+0.j    ]])

```
# Keep 6 eigenvalues/poles because 3rd and 4th are complex conjugate pair
```

```
# and need 1 more for gamma (ratio of states to inputs) to be an integer.
```

```
A11 = Ad[0:6,0:6]
```

```
A11
```

    matrix([[-0.2164+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    , -0.2172+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    , -0.2172+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    , -0.3700+1.3226j,
             -0.0000-0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000-0.j    ,
             -0.3700-1.3226j,  0.0000+0.j    ],
            [ 0.0000+0.j    , -0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    , -0.9325+0.j    ]])

```
A12 = Ad[0:6,6:12]
```

```
A12
```

    matrix([[ 0.+0.j, -0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j],
            [ 0.+0.j,  0.+0.j, -0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j],
            [ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j],
            [ 0.+0.j,  0.+0.j,  0.+0.j, -0.-0.j,  0.+0.j,  0.+0.j],
            [ 0.+0.j,  0.+0.j,  0.+0.j, -0.+0.j,  0.-0.j,  0.+0.j],
            [ 0.+0.j,  0.+0.j, -0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j]])

```
A21 = Ad[6:12,0:6]
```

```
A21
```

    matrix([[ 0.+0.j,  0.+0.j, -0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j],
            [ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j],
            [ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j, -0.+0.j],
            [ 0.+0.j,  0.+0.j,  0.+0.j,  0.-0.j,  0.+0.j,  0.+0.j],
            [ 0.+0.j,  0.+0.j,  0.+0.j,  0.-0.j, -0.+0.j,  0.+0.j],
            [ 0.+0.j,  0.+0.j, -0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j]])

```
A22 = Ad[6:12,6:12]
```

```
A22
```

    matrix([[ -0.9328+0.j,   0.0000+0.j,   0.0000+0.j,   0.0000+0.j,
               0.0000+0.j,   0.0000+0.j],
            [  0.0000+0.j,  -0.9336+0.j,   0.0000+0.j,   0.0000+0.j,
               0.0000+0.j,   0.0000+0.j],
            [  0.0000+0.j,   0.0000+0.j,  -8.0004+0.j,   0.0000+0.j,
               0.0000+0.j,   0.0000+0.j],
            [  0.0000+0.j,   0.0000+0.j,   0.0000+0.j,  -9.0644-0.j,
              -0.0000+0.j,   0.0000+0.j],
            [  0.0000+0.j,   0.0000+0.j,   0.0000+0.j,   0.0000-0.j,
             -10.9955+0.j,   0.0000+0.j],
            [  0.0000+0.j,   0.0000+0.j,   0.0000+0.j,   0.0000+0.j,
               0.0000+0.j, -11.6500+0.j]])

```
B1 = Bd[0:6,0:2]
```

```
B1
```

    matrix([[ 1.4890+0.j    ,  5.9176+0.j    ],
            [ 0.1921+0.j    ,  0.7633+0.j    ],
            [-0.2246+0.j    ,  0.3352+0.j    ],
            [ 0.0212+0.0128j, -0.0334-0.0202j],
            [ 0.0212-0.0128j, -0.0334+0.0202j],
            [ 0.3346+0.j    ,  1.3296+0.j    ]])

```
B2 = Bd[6:12,0:2]
```

```
B2
```

    matrix([[ -0.3791+0.j,   0.5658+0.j],
            [  1.9911+0.j,   7.9127+0.j],
            [ -1.2242+0.j,  -4.8651+0.j],
            [ -8.5494-0.j,  13.4338+0.j],
            [-10.2742-0.j,  16.1438+0.j],
            [  1.9946+0.j,  -2.9768+0.j]])

```
C1 = Cd[0:2,0:6]
```

```
C1
```

    matrix([[ 0.0876+0.j    ,  0.0000+0.j    ,  1.7937+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  4.4237+0.j    ,  0.0000+0.j    , -7.4695+3.2554j,
             -7.4695-3.2554j, -2.8504+0.j    ]])

```
C2 = Cd[0:2,6:12]
```

```
C2
```

    matrix([[-7.7564+0.j,  0.3571+0.j,  0.0000+0.j,  0.0000+0.j,  0.0000+0.j,
             -1.2724+0.j],
            [ 0.0000+0.j,  0.0000+0.j, -1.7478+0.j,  0.4833+0.j, -0.4412+0.j,
              0.0000+0.j]])

```
Ar = A11 - A12*np.linalg.inv(A22)*A21
```

```
Ar
```

    matrix([[-0.2164+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    , -0.2172+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    , -0.2172+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    , -0.3700+1.3226j,
             -0.0000-0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,  0.0000-0.j    ,
             -0.3700-1.3226j,  0.0000+0.j    ],
            [ 0.0000+0.j    , -0.0000+0.j    ,  0.0000+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    , -0.9325+0.j    ]])

```
Br = B1 - A12*np.linalg.inv(A22)*B2
```

```
Br
```

    matrix([[ 1.4890+0.j    ,  5.9176+0.j    ],
            [ 0.1921+0.j    ,  0.7633+0.j    ],
            [-0.2246+0.j    ,  0.3352+0.j    ],
            [ 0.0212+0.0128j, -0.0334-0.0202j],
            [ 0.0212-0.0128j, -0.0334+0.0202j],
            [ 0.3346+0.j    ,  1.3296+0.j    ]])

```
Cr = C1 - C2*np.linalg.inv(A22)*A21
```

```
Cr
```

    matrix([[ 0.0876+0.j    ,  0.0000+0.j    ,  1.7937+0.j    ,  0.0000+0.j    ,
              0.0000+0.j    ,  0.0000+0.j    ],
            [ 0.0000+0.j    ,  4.4237+0.j    ,  0.0000+0.j    , -7.4695+3.2554j,
             -7.4695-3.2554j, -2.8504+0.j    ]])

```
Dr = D - C2*np.linalg.inv(A22)*B2
```

```
Dr
```

    matrix([[ 3.6961+0.j, -1.3536+0.j],
            [ 0.2238-0.j,  1.1313+0.j]])

```
np.savetxt('Ar_cp1.csv', Ar, delimiter=',')
```

```
np.savetxt('Br_cp1.csv', Br, delimiter=',')
```

```
np.savetxt('Cr_cp1.csv', Cr, delimiter=',')
```

```
np.savetxt('Dr_cp1.csv', Dr, delimiter=',')
```

```
# The reduced model from residualization is 6x6.
```

```
# Therefore, the second Cauer method will be applied again.
```

```
# Apply second Cauer method to reduced model from residualization:
```

```
gamma = 6/2
```

```
gamma
```

$$3.0$$

```
# gamma is an integer.
```

```
Bc = np.matrix([[0,0],
                [0,0],
                [0,0],
                [0,0],
                [1,0],
                [0,1]])
```

```
Bc
```

    matrix([[0, 0],
            [0, 0],
            [0, 0],
            [0, 0],
            [1, 0],
            [0, 1]])

```
Tc1 = (Bc.T)*np.linalg.inv(np.concatenate(
(Br,Ar*Br,(Ar**2)*Br),1))
```

```
Tc1
```

    matrix([[  334.1623+0.j    , -2593.5049+0.j    ,    -1.8259+0.j    ,
                -6.4853+5.0194j,    -6.4853-5.0194j,     1.6533+0.j    ],
            [  212.6657+0.j    , -1650.5170-0.j    ,     0.4594-0.j    ,
                 1.6319-1.263j ,     1.6319+1.263j ,     1.0522-0.j    ]])

```
Tc = np.concatenate((Tc1,Tc1*Ar,Tc1*(Ar**2)),0)
```

```
Tc
```

    matrix([[  334.1623 +0.j    , -2593.5049 +0.j    ,    -1.8259 +0.j    ,
                -6.4853 +5.0194j,    -6.4853 -5.0194j,     1.6533 +0.j    ],
            [  212.6657 +0.j    , -1650.5170 -0.j    ,     0.4594 -0.j    ,
                 1.6319 -1.263j ,     1.6319 +1.263j ,     1.0522 -0.j    ],
            [  -72.2983 +0.j    ,   563.1846 -0.j    ,     0.3965 -0.j    ,
                -4.2390-10.435j ,    -4.2390+10.435j ,    -1.5417 -0.j    ],
            [  -46.0117 -0.j    ,   358.4129 +0.j    ,    -0.0998 +0.j    ,
                 1.0667 +2.6257j,     1.0667 -2.6257j,    -0.9811 +0.j    ],
            [   15.6422 +0.j    ,  -122.2966 +0.j    ,    -0.0861 +0.j    ,
                15.3701 -1.7453j,    15.3701 +1.7453j,     1.4376 +0.j    ],
            [    9.9549 +0.j    ,   -77.8301 -0.j    ,     0.0217 -0.j    ,
                -3.8675 +0.4392j,    -3.8675 -0.4392j,     0.9149 -0.j    ]])

```
Tc_inv = np.linalg.inv(Tc)
```

```
Tc_inv
```

    matrix([[ 0.3015-0.j    ,  1.1983+0.j    ,  1.7118-0.j    ,  6.8030+0.j    ,
              1.4890-0.j    ,  5.9176+0.j    ],
            [ 0.0387-0.j    ,  0.1540+0.j    ,  0.2207-0.j    ,  0.8769+0.j    ,
              0.1921-0.j    ,  0.7633+0.j    ],
            [-0.4183-0.j    ,  0.6537+0.j    , -0.1675+0.j    ,  0.2429-0.j    ,
             -0.2246+0.j    ,  0.3352+0.j    ],
            [-0.0020+0.0071j,  0.0031-0.0112j, -0.0045+0.0356j,  0.0071-0.056j ,
              0.0212+0.0128j, -0.0334-0.0202j],
            [-0.0020-0.0071j,  0.0031+0.0112j, -0.0045-0.0356j,  0.0071+0.056j ,
              0.0212-0.0128j, -0.0334+0.0202j],
            [ 0.0157-0.j    ,  0.0625+0.j    ,  0.1450-0.j    ,  0.5764+0.j    ,
              0.3346-0.j    ,  1.3296+0.j    ]])

```
Arc = Tc*Ar*Tc_inv
```

```
Arc
```

    matrix([[ 0.0000+0.j, -0.0000-0.j,  1.0000-0.j,  0.0000+0.j, -0.0000-0.j,
             -0.0000+0.j],
            [ 0.0000-0.j, -0.0000+0.j,  0.0000-0.j,  1.0000+0.j, -0.0000-0.j,
             -0.0000+0.j],
            [-0.0000+0.j,  0.0000-0.j,  0.0000+0.j,  0.0000-0.j,  1.0000+0.j,
              0.0000-0.j],
            [ 0.0000+0.j,  0.0000+0.j,  0.0000-0.j,  0.0000+0.j,  0.0000-0.j,
              1.0000-0.j],
            [-0.3060-0.j,  0.4119+0.j, -1.5948-0.j,  1.7969+0.j, -1.0731+0.j,
             -0.4603-0.j],
            [ 0.0660+0.j, -0.1475-0.j,  0.2878+0.j, -0.9034-0.j, -0.0737-0.j,
             -1.2502+0.j]])

```
Brc_verify = Tc*Br
```

```
Brc_verify
```

    matrix([[ 0.+0.j,  0.+0.j],
            [-0.-0.j, -0.+0.j],
            [-0.-0.j, -0.-0.j],
            [-0.+0.j, -0.+0.j],
            [ 1.+0.j, -0.-0.j],
            [-0.-0.j,  1.-0.j]])

```
Crc = Cr*Tc_inv
```

```
Crc
```

    matrix([[-0.7239-0.j,  1.2775+0.j, -0.1506+0.j,  1.0316-0.j, -0.2724+0.j,
              1.1196+0.j],
            [ 0.1098-0.j,  0.5297+0.j,  0.3981-0.j,  2.4950+0.j, -0.5048+0.j,
              0.2165+0.j]])

```
# With the controller-type block companion form, the right MFD is obtained.
```

```
Ar11 = Arc[4:6,0:2]
```

```
Ar11
```

    matrix([[-0.3060-0.j,  0.4119+0.j],
            [ 0.0660+0.j, -0.1475-0.j]])

```
Ar12 = Arc[4:6,2:4]
```

```
Ar12
```

    matrix([[-1.5948-0.j,  1.7969+0.j],
            [ 0.2878+0.j, -0.9034-0.j]])

```
Ar13 = Arc[4:6,4:6]
```

```
Ar13
```

    matrix([[-1.0731+0.j, -0.4603-0.j],
            [-0.0737-0.j, -1.2502+0.j]])

```
Ar14 = np.identity(2)
```

```
Ar14
```

    array([[ 1.,  0.],
           [ 0.,  1.]])

```
Ar21 = Crc[0:2,0:2]
```

```
Ar21
```

    matrix([[-0.7239-0.j,  1.2775+0.j],
            [ 0.1098-0.j,  0.5297+0.j]])

```
Ar22 = Crc[0:2,2:4]
```

```
Ar22
```

    matrix([[-0.1506+0.j,  1.0316-0.j],
            [ 0.3981-0.j,  2.4950+0.j]])

```
Ar23 = Crc[0:2,4:6]
```

```
Ar23
```

    matrix([[-0.2724+0.j,  1.1196+0.j],
            [-0.5048+0.j,  0.2165+0.j]])

```
# Model reduction using second Cauer method:
```

```
# order of 2nd Cauer reduced model
```

```
order = 2
```

```
order
```

$$2$$

```
n_end = gamma + 1
```

```
n_end
```

$$4.0$$

```
q = 1
```

```
q
```

$$1$$

```
for m in np.linspace(1,order*2,order*2):
    exec("Hr%d = (Ar%d%d)*np.linalg.inv(Ar%d%d)" %(m,m,1,m+1,1))
    exec("Ar%d%d = np.zeros((2,2))" %(m+1,n_end))

    for n in np.linspace(1,n_end-1,n_end-1):
        exec("Ar%d%d = (Ar%d%d) - (Hr%d)*(Ar%d%d)" %(m+2,n,m,n+1,m,m+1,n+1))

    if (np.equal(q,1)):
        q = 0
        n_end = n_end - 1

    q = q+1
```

```
Hr1
```

    matrix([[ 0.3959+0.j, -0.1770-0.j],
            [-0.0976-0.j, -0.0429+0.j]])

```
Hr2
```

    matrix([[ 0.2723-0.j, -1.1200+0.j],
            [-0.4715-0.j, -2.0022-0.j]])

```
Hr3
```

    matrix([[-15926.4016-0.j,      3.2082-0.j],
            [  5201.2473+0.j,     -0.4997+0.j]])

```
Hr4
```

    matrix([[-0.0000-0.j,  0.0002-0.j],
            [ 0.3372+0.j, -0.1000-0.j]])

```
Ar_hat = -1*np.concatenate((np.concatenate((Hr1*Hr2,Hr1*Hr4),1),
                           np.concatenate((Hr1*Hr2,(Hr1+Hr3)*Hr4),1)),0)
```

```
Ar_hat
```

    matrix([[-0.1913-0.j,  0.0890-0.j,  0.0597+0.j, -0.0178-0.j],
            [ 0.0064+0.j, -0.1953+0.j,  0.0145+0.j, -0.0043-0.j],
            [-0.1913-0.j,  0.0890-0.j, -1.6693-0.j,  2.9216+0.j],
            [ 0.0064+0.j, -0.1953+0.j,  0.3943+0.j, -0.9094-0.j]])

```
Ar_hat_eigvals, Mr_hat = np.linalg.eig(Ar_hat)
```

```
Ar_hat_eigvals
```

    array([-2.4262-0.j    , -0.1609-0.0736j, -0.1609+0.0736j, -0.2172+0.j    ])

```
Br_hat = np.concatenate((np.identity(2),np.identity(2)),0)
```

```
Br_hat
```

    array([[ 1.,  0.],
           [ 0.,  1.],
           [ 1.,  0.],
           [ 0.,  1.]])

```
Cr_hat = np.concatenate((Hr2,Hr4),1)
```

```
Cr_hat
```

    matrix([[ 0.2723-0.j, -1.1200+0.j, -0.0000-0.j,  0.0002-0.j],
            [-0.4715-0.j, -2.0022-0.j,  0.3372+0.j, -0.1000-0.j]])

```
np.savetxt('Ar_hat_cp1.csv', Ar_hat, delimiter=',')
```

```
np.savetxt('Br_hat_cp1.csv', Br_hat, delimiter=',')
```

```
np.savetxt('Cr_hat_cp1.csv', Cr_hat, delimiter=',')
```

```

```


```
import scipy
import numpy as np
import sympy
from sympy import *
```

```
ix, iy, iz = symbols('ix iy iz',real=True, constant = True)
hx, hy, hz = symbols('hx hy hz',real = True, constant = False)
```

```
h = Matrix([hx, hy, hz])
i = Matrix([ix, iy, iz])
tmp = 2.*(h.T*i)[0,0]
f = tmp*h - i
f = f.subs(ix,0).subs(iy,0).subs(iz,1)
f
```

    Matrix([
    [    2.0*hx*hz],
    [    2.0*hy*hz],
    [2.0*hz**2 - 1]])

```
nx, ny, nz, x, y, z = symbols('nx ny nz x y z')
```

```
tmp = sqrt(x*x + y*y + z*z)
n = Matrix([x, y, z])/tmp
n
```

    Matrix([
    [x/sqrt(x**2 + y**2 + z**2)],
    [y/sqrt(x**2 + y**2 + z**2)],
    [z/sqrt(x**2 + y**2 + z**2)]])

```
Jf = f.jacobian(Matrix([hx, hy, hz]))
Jf
```

    Matrix([
    [2.0*hz,      0, 2.0*hx],
    [     0, 2.0*hz, 2.0*hy],
    [     0,      0, 4.0*hz]])

```
Jf.det()
```

    16.0*hz**3

```
Jn = n.jacobian(Matrix([x, y, z]))
Jn
```

    Matrix([
    [-x**2/(x**2 + y**2 + z**2)**(3/2) + 1/sqrt(x**2 + y**2 + z**2),                               -x*y/(x**2 + y**2 + z**2)**(3/2),                               -x*z/(x**2 + y**2 + z**2)**(3/2)],
    [                              -x*y/(x**2 + y**2 + z**2)**(3/2), -y**2/(x**2 + y**2 + z**2)**(3/2) + 1/sqrt(x**2 + y**2 + z**2),                               -y*z/(x**2 + y**2 + z**2)**(3/2)],
    [                              -x*z/(x**2 + y**2 + z**2)**(3/2),                               -y*z/(x**2 + y**2 + z**2)**(3/2), -z**2/(x**2 + y**2 + z**2)**(3/2) + 1/sqrt(x**2 + y**2 + z**2)]])

```
Jn.subs(x, f[0,0]).subs(y, f[1,0]).subs(z, f[2,0])*Jf
```

    Matrix([
    [-2.0*hy*ix*(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) - 2.0*hz*ix*(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + (-(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 1/sqrt((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2))*(2.0*hx*ix*sign(hx*ix + hy*iy + hz*iz) + 2.0*Abs(hx*ix + hy*iy + hz*iz)),  2.0*hx*iy*(-(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 1/sqrt((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2))*sign(hx*ix + hy*iy + hz*iz) - 2.0*hz*iy*(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) - (2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*(2.0*hy*iy*sign(hx*ix + hy*iy + hz*iz) + 2.0*Abs(hx*ix + hy*iy + hz*iz))/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2),  2.0*hx*iz*(-(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 1/sqrt((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2))*sign(hx*ix + hy*iy + hz*iz) - 2.0*hy*iz*(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) - (2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*(2.0*hz*iz*sign(hx*ix + hy*iy + hz*iz) + 2.0*Abs(hx*ix + hy*iy + hz*iz))/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2)],
    [ 2.0*hy*ix*(-(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 1/sqrt((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2))*sign(hx*ix + hy*iy + hz*iz) - 2.0*hz*ix*(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) - (2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*(2.0*hx*ix*sign(hx*ix + hy*iy + hz*iz) + 2.0*Abs(hx*ix + hy*iy + hz*iz))/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2), -2.0*hx*iy*(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) - 2.0*hz*iy*(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + (-(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 1/sqrt((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2))*(2.0*hy*iy*sign(hx*ix + hy*iy + hz*iz) + 2.0*Abs(hx*ix + hy*iy + hz*iz)), -2.0*hx*iz*(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 2.0*hy*iz*(-(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 1/sqrt((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2))*sign(hx*ix + hy*iy + hz*iz) - (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*(2.0*hz*iz*sign(hx*ix + hy*iy + hz*iz) + 2.0*Abs(hx*ix + hy*iy + hz*iz))/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2)],
    [-2.0*hy*ix*(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 2.0*hz*ix*(-(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 1/sqrt((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2))*sign(hx*ix + hy*iy + hz*iz) - (2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*(2.0*hx*ix*sign(hx*ix + hy*iy + hz*iz) + 2.0*Abs(hx*ix + hy*iy + hz*iz))/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2), -2.0*hx*iy*(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 2.0*hz*iy*(-(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 1/sqrt((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2))*sign(hx*ix + hy*iy + hz*iz) - (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*(2.0*hy*iy*sign(hx*ix + hy*iy + hz*iz) + 2.0*Abs(hx*ix + hy*iy + hz*iz))/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2), -2.0*hx*iz*(2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) - 2.0*hy*iz*(2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)*(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)*sign(hx*ix + hy*iy + hz*iz)/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + (-(2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2/((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2)**(3/2) + 1/sqrt((2.0*hx*Abs(hx*ix + hy*iy + hz*iz) - ix)**2 + (2.0*hy*Abs(hx*ix + hy*iy + hz*iz) - iy)**2 + (2.0*hz*Abs(hx*ix + hy*iy + hz*iz) - iz)**2))*(2.0*hz*iz*sign(hx*ix + hy*iy + hz*iz) + 2.0*Abs(hx*ix + hy*iy + hz*iz))]])

```

```


# Vorticity-Streamfunction FD Python
## CH EN 6355 - Computational Fluid Dynamics
**Prof. Tony Saad (www.tsaad.net)
Department of Chemical Engineering
University of Utah**

## Synopsis
This notebook implements a finite difference solution of the 2D vorticity streamfunction approach for the constant density incompressible Navier-Stokes equations. These are given by
\begin{equation}
\frac{{\partial {\omega _z}}}{{\partial t}} =  - \frac{{\partial \psi }}{{\partial y}}\frac{{\partial {\omega _z}}}{{\partial x}} + \frac{{\partial \psi }}{{\partial x}}\frac{{\partial {\omega _z}}}{{\partial y}} + \nu {\nabla ^2}{\omega _z}
\end{equation}
\begin{equation}
\nabla ^2 \psi = -\omega_z
\end{equation}
along with $u =  \frac{\partial \psi}{\partial y}$ and $v =- \frac{\partial \psi}{\partial x}$.

The solution procedure consists of the following steps:
1. Initialize $\omega$ in the domain and set $\omega$ and $\psi$ appropriately at the boundaries
2. Compute the streamfunction by solving the Poisson equation for the streamfunction-vorticity relation
3. Solve the vorticity transport equation using an appropriate discretization scheme in time and space

For this notebook, we will use an FTCS scheme (Forward in Time, Central in Space). We have
\begin{equation}
\frac{{\partial {\omega _z}}}{{\partial t}} \approx \frac{{\omega _{i,j}^{n + 1} - \omega _{i,j}^n}}{{\Delta t}}
\end{equation}
\begin{equation}
- \frac{{\partial \psi }}{{\partial y}}\frac{{\partial {\omega _z}}}{{\partial x}} =  - \left( {\frac{{\psi _{i,j + 1}^n - \psi _{i,j - 1}^n}}{{2\Delta y}}} \right)\left( {\frac{{\omega _{i + 1,j}^n - \omega _{i - 1,j}^n}}{{2\Delta x}}} \right)
\end{equation}
\begin{equation}
\frac{{\partial \psi }}{{\partial x}}\frac{{\partial {\omega _z}}}{{\partial y}} = \left( {\frac{{\psi _{i + 1,j}^n - \psi _{i - 1,j}^n}}{{2\Delta x}}} \right)\left( {\frac{{\omega _{i,j + 1}^n - \omega _{i,j - 1}^n}}{{2\Delta y}}} \right)
\end{equation}
and
\begin{equation}
{\nabla ^2}{\omega _z} = \frac{{\omega _{i + 1,j}^n - 2\omega _{i,j}^n + \omega _{i - 1,j}^n}}{{\Delta {x^2}}} + \frac{{\omega _{i,j + 1}^n - 2\omega _{i,j}^n + \omega _{i,j - 1}^n}}{{\Delta {y^2}}}
\end{equation}

```python
import numpy as np
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import cm
plt.rcParams['animation.html'] = 'html5'
```

```python
nx = 21
ny = 21
lx = 1.0
ly = 1.0
dx = lx/(nx-1)
dy = ly/(ny-1)
```

```python
Ut = 5.0 # u top wall
Ub = 0.0 # u bottom wall
Vl = 0.0 # V left wall
Vr = 0.0 # V right wall
```

```python
ψ0 = np.zeros([ny,nx])
ω0 = np.zeros([ny,nx])

# apply boundary conditions
ω0[0,1:-1]  = -2.0*ψ0[1, 1:-1]/dy/dy + 2.0*Ub/dy     # vorticity on bottom wall (stationary)
ω0[-1,1:-1] = -2.0*ψ0[-2, 1:-1]/dy/dy - 2.0*Ut/dy # vorticity on top wall (moving at Uwall)
ω0[1:-1,-1] = -2.0*ψ0[1:-1,-2]/dx/dx # right wall
ω0[1:-1,0]  = -2.0*ψ0[1:-1,1]/dx/dx # left wall

# ω0[ny//2,:nx//2] = 1.0/dy
# ω0[ny//2-1,nx//2:] = 1/dy

t = 0.0
dt = 0.001

tol = 1e-3
maxIt = 30
β = 1.5
ν = 0.05
psi = []
psi.append(ψ0)
omega = []
omega.append(ω0)
# plt.contour(omega[0])
dt = min(0.25*dx*dx/ν, 4.0*ν/Ut/Ut)
tend = 4*dt
# print(ν*dt/dx/dx <= 0.25)
# print(Ut**2*dt/ν <= 4.0)
print('dt =', dt)
print('Reynods =', Ut*lx/ν)
```

    dt = 0.008
    Reynods = 100.0

```python
while t < tend:

    # first find ψ by solving the Poisson equation Δψ = -ω
    it = 0
    err = 1e3
    ψ = psi[-1].copy()
    ωn = omega[-1]
    while err > tol and it < maxIt:
        ψk = np.zeros_like(ψ)
        ψk[1:-1,1:-1] = ψ[1:-1,1:-1] # set interior values
        for i in range(1,nx-1):
            for j in range(1,ny-1):
                rhs = (dx*dy)**2 * ωn[j,i] + dx**2 * (ψ[j+1,i] + ψ[j-1,i]) + dy**2 * (ψ[j,i+1] + ψ[j,i-1])
                rhs *= β/2.0/(dx**2 + dy**2)
                ψ[j,i] = rhs + (1-β)*ψ[j,i]
        err = np.linalg.norm(ψ - ψk)
        it += 1
    psi.append(ψ)

    # declare new array for ω
    ω = np.zeros_like(ωn)

    Cx  = -(ψ[2:,1:-1] - ψ[:-2,1:-1])/2.0/dy * (ωn[1:-1,2:] - ωn[1:-1,:-2])/2.0/dx
    Cy  =  (ωn[2:,1:-1] - ωn[:-2,1:-1])/2.0/dy * (ψ[1:-1,2:] - ψ[1:-1,:-2])/2.0/dx
    Dxy =  (ωn[1:-1,2:] - 2.0*ωn[1:-1,1:-1] + ωn[1:-1,:-2])/dx/dx + (ωn[2:,1:-1] -2.0*ωn[1:-1,1:-1] + ωn[:-2,1:-1])/dy/dy
    rhs = Cx + Cy + ν*Dxy
    ω[1:-1,1:-1] = ωn[1:-1,1:-1] + dt*rhs

    # apply boundary conditions
    ω[0,1:-1]  = -2.0*ψ[1, 1:-1]/dy/dy + 2.0*Ub/dy     # vorticity on bottom wall (stationary)
    ω[-1,1:-1] = -2.0*ψ[-2, 1:-1]/dy/dy - 2.0*Ut/dy # vorticity on top wall (moving at Uwall)
    ω[1:-1,-1] = -2.0*ψ[1:-1,-2]/dx/dx # right wall
    ω[1:-1,0]  = -2.0*ψ[1:-1,1]/dx/dx # left wall

    omega.append(ω)
    t += dt
```

```python
solution = psi[-1]
plt.contour(solution,levels=20)
plt.colorbar()
```

    <matplotlib.colorbar.Colorbar at 0x121985668>

## Calculate the velocity vector field
The next step is to calculate the velocity vector field and double check that we are divergence free

```python
ψ = psi[-1]
u = np.zeros([ny,nx])
v = np.zeros([ny,nx])

u[1:-1,1:-1] = (ψ[2:,1:-1] - ψ[:-2,1:-1])/2.0/dy
v[1:-1,1:-1] = -(ψ[1:-1, 2:] - ψ[1:-1,:-2])/2.0/dx

# now will in the boundary values - they are all zero except at the top boundary where u = Ut
u[-1,1:-1] = Ut
divu = (u[1:-1,2:] - u[1:-1,:-2])/2.0/dx + (v[2:,1:-1] - v[:-2,1:-1])/2.0/dy
print(np.linalg.norm(divu.ravel()))

# note the size of the velocity vector field - it does not have points at the boundaries.
```

## Create some beautiful plots

```python
x = np.linspace(0,1,nx)
y = np.linspace(0,1,ny)
xx,yy = np.meshgrid(x,y)
nn = 1
plt.quiver(xx[::nn,::nn],yy[::nn,::nn],u[::nn,::nn],v[::nn,::nn])
# plt.quiver(xx[1:-1:nn,1:-1:nn],yy[1:-1:nn,1:-1:nn],u[::nn,::nn],v[::nn,::nn])
# plt.axis('equal')
```

```python
fig = plt.figure(figsize=[6,6],dpi=600)
ax = fig.add_subplot(111)
ax.contourf(xx, yy, np.sqrt(u*u + v*v), levels = 100, cmap=plt.cm.jet)
ax.streamplot(xx,yy,u, v, color=np.sqrt(u*u + v*v),density=1.1,cmap=plt.cm.autumn,linewidth=1.5)
# ax.set_xlim([xx[0,0],xx[0,-1]])
# ax.set_ylim([0,1])
# ax.set_aspect(1)
```

```python
x = np.linspace(0,1,nx)
y = np.linspace(0,1,ny)
xx,yy = np.meshgrid(x,y)
nn = 3

fig = plt.figure(figsize=(6.1,5),facecolor='w',dpi=150)
ax = fig.add_subplot(111)

ims = []
# levs = np.linspace(-1,1,20)
i = 0
t = 0.0
for sol in psi:
    if (i%10==0):
        u = (sol[2:,1:-1] - sol[:-2,1:-1])/2.0/dy
        v = -(sol[1:-1, 2:] - sol[1:-1,:-2])/2.0/dx
        im = ax.contourf(xx[1:-1,1:-1], yy[1:-1,1:-1], np.sqrt(u*u + v*v), levels = 100, cmap=plt.cm.jet)
#         im = ax.streamplot(xx[1:-1,1:-1],yy[1:-1,1:-1],u,v, color=abs(u*u + v*v),cmap=plt.cm.autumn,linewidth=1.0)
#         im = plt.contourf(solution,cmap=cm.jet,levels=100)
        ims.append(im.collections)
    i+=1
    t += dt

ax.set_xlim([0,1])
ax.set_ylim([0,1])
ax.set_aspect(1)

# cbar = plt.colorbar()
# plt.clim(-10,10)
# cbar.set_ticks(np.linspace(0,10,5))

ani = animation.ArtistAnimation(fig, ims, interval=35, blit=True,repeat_delay=1000)

ani
```

```python
divu = (u[1:-1, 2:] - u[1:-1,:-2])/2.0/dx + (v[2:,1:-1] - v[:-2,1:-1])/2.0/dy
```

```python
plt.imshow(divu)
plt.colorbar()
```

```python
import urllib
import requests
from IPython.core.display import HTML
def css_styling():
    styles = requests.get("https://raw.githubusercontent.com/saadtony/NumericalMethods/master/styles/custom.css")
    return HTML(styles.text)
css_styling()

```

```python

```

```python

```

```python

```

```python

```

Vorticity-Streamfunction FDM - lid-driven cavity.ipynb


```python
from sympy import *
init_printing(use_latex='mathjax')
Re,r,G,rho,eta,v_x,tau_xx,L_x,lam,tau,k,x = symbols('Re r G rho eta v_x tau_xx L_x lambda tau k x', positive=True)
v0,p0,tau_xx0 = symbols('v0 p0 tau_xx0')
```

```python
K   = r*G                                               # bulk modulus from modulus ratio r
rho = solve(Re - rho*sqrt((K+2*G)/rho)*L_x/eta,rho)[0]  # density from Reynolds number Re
V_p = sqrt((K+2*G)/rho)                                 # velocity scale - primary wave velocity
```

```python
fun    = v0*exp(-lam*tau*V_p/L_x)*sin(pi*k*x/L_x)
v      = fun
p      = fun
tau_xx = fun
```

```python
eq1 = 1/K*diff(p,tau) + diff(v,x)                       # mass balance
eq2 = rho*diff(v,tau) + diff(p-tau_xx,x)                # momentum balance
eq3 = 1/G*diff(tau_xx,tau) + 1/eta*tau_xx - 2*diff(v,x) # Maxwell rheological model
```

```python
disp    = diff(diff(eq2,tau)/G + diff(eq3,x) + eq2/eta - diff(eq1,x)*K/G,tau)*eta/rho - diff(eq1,x)*K/rho
disp    = expand(disp/fun)
disp    = expand(simplify(disp/disp.coeff(lam**3)))
```

```python
sol     = solve(disp,lam)
disc    = discriminant(disp,lam)
r_opt   = solve(discriminant(disc,Re),r)[0]
Re_opt  = solve(disc.subs(r,r_opt),Re)[0].subs(k,1)
r_opt,Re_opt
```

$\displaystyle \left( \frac{1}{4}, \  \frac{9 \sqrt{3} \pi}{4}\right)$

```python
from sympy import maximum, lambdify
import numpy as np
import warnings
warnings.filterwarnings('ignore')
lam      = [re(s.subs(k,1)) for s in sol]
lamf     = lambdify([Re,r],lam,"numpy")
Re1      = np.linspace(float(Re_opt)/2,float(Re_opt)*3/2,1000)
r1       = np.linspace(0,2*float(r_opt),1000)
[Re2,r2] = np.meshgrid(Re1,r1)
lam2     = np.stack(lamf(Re2,r2),axis=0)
lam2     = np.nanmin(lam2,axis=0)
```

```python
%matplotlib notebook
from matplotlib import pyplot as plt
from matplotlib import cm
plt.rcParams.update({"text.usetex": True, "font.size": 14})
f   = plt.figure(figsize=(7,5))
ax  = plt.subplot()
pos = ax.contourf(Re2,r2,lam2,17,cmap=cm.cool)
ax.contour(Re2,r2,lam2,17,linewidths=1,colors='k')
ax.axhline(y=r_opt.evalf(),ls='--',c='w')
ax.axvline(x=Re_opt.evalf(),ls='--',c='w')
ax.set_xlabel("$Re$"); ax.set_ylabel("$r$")
ax.set_xticks([float(Re_opt)/2,float(Re_opt),float(Re_opt)*3/2])
ax.set_yticks([0,float(r_opt),2*float(r_opt)])
ax.set_xticklabels(["$9\sqrt{3}\pi/8$","$9\sqrt{3}\pi/4$","$9\sqrt{3}\pi/2$"])
ax.set_yticklabels(["$0$","$1/4$","$1/2$"])
ax.set_aspect(1.0/ax.get_data_ratio(), adjustable='box')
cbar = f.colorbar(pos,ax=ax)
cbar.set_label("$\mathrm{min}\{\Re(\lambda_k)\}$")
```

    <IPython.core.display.Javascript object>

```python

```


# Internal Coordinates and the B-matrix
## Defining bonds
Assuming a reasonable initial geometrical input, the identification of which atoms are bonded may, in most cases, be simply achieved using standard covalent radii.  Here we do this for ammonium.

```python
import psi4
import numpy as np
import sys,os
from itertools import combinations, permutations
from math import sqrt
sys.path.append('os.getcwd()')
from opt_helper import covRadii, v3d, stre, bend, intcosMisc
```

```python
mol = psi4.geometry("""
 1 1
 N  0.000000  0.0       0.0
 H  1.584222  0.0       1.12022
 H  0.0      -1.58422  -1.12022
 H -1.584222  0.0       1.12022
 H  0.0       1.58422  -1.12022
 unit au
""")
mol.update_geometry()
xyzGeom = np.array(mol.geometry())
print("Starting Geometry for Ammonium")
print(xyzGeom)

# Get atomic numbers and covalent radii
Natom = mol.natom()
Z = [int(mol.Z(i)) for i in range(Natom)]

# Connectivity matrix indicates which atoms are bonded.
C = np.zeros( (Natom, Natom), bool )
for i,j in combinations( range(Natom), 2):
  R = v3d.dist( xyzGeom[i], xyzGeom[j])
  Rcov = (covRadii.R[Z[i]] + covRadii.R[Z[j]])/0.52917720859
  if R < 1.3 * Rcov:
    C[i,j] = C[j,i] = True

print("Connectivity matrix for Ammonium")
print(C)
```

In practice, the factor 1.3 may be increased as necessary to ensure bonds connect all the atoms together.  This is done in so-called 'supermolecule' approaches, even when multiple molecules are present.

## Determining internal coordinates automatically
Once the connectivity is determined, the chemically relevant bonds, angles, and dihedral angles can be determined from the connected atoms.  A stretching coordinate is added for every two bonded atoms, and a bending coordinate is added for all unique A-B-C sequences of connected atoms. For this example of ammonium, there are no dihedral angles.  However, if present, any bonded sequence A-B-C-D is taken to define a dihedral coordinate that is added to the set of optimization coordinates.  (If linear or near-linear bends are present, then 2 orthogonal bends are used to describe the situation, and dihedral coordinates which contain these linear bends must be avoided.  This complication is not further explored here.)

```python
intcos = []
# Add stretches by connectivity.
for i,j in combinations( range(Natom), 2 ):
  if C[i,j]:
    s = stre.STRE(i, j)
    if s not in intcos:
      intcos.append(s)

# Add bends by connectivity.
for i,j in permutations( range(Natom), 2):
  if C[i,j]:
    for k in range(i+1,Natom):  # make i<k
      if C[j,k]:
        (check, val) = v3d.angle(xyzGeom[i], xyzGeom[j], xyzGeom[k])
        if not check: continue
        b = bend.BEND(i, j, k)
        if b not in intcos:
          intcos.append(b)

# print our internal coordinate set with values
for i in intcos:
    print("%10s = %10.5f" % (i, i.q(xyzGeom)))
```

## Redundant Coordinates
These simple internal coordinates have many advantages. They tend to produce a strongly diagonal energy second derivative matrix (Hessian), and they have force constants which are readily guessable, i.e., transferable from one molecule to another. In these two respects, they are vastly superior to Cartesian coordinates.

For many years, the primary difficulty was that the simple procedure demonstrated here generates too many coordinates. Non-linear molecules have $3N-6$ internal degrees of freedom (where $N$ is the number of atoms), but including all the internal coordinates defined by the bond connectivity will generally produce more than that.

For ammonium, we see that we have 10 coordinates, while the ion has only 9 internal degrees of freedom. Various alternative coordinate systems were explored, for example "delocalized coordinates" [J.Baker, A. Kessi, and B. Delley, _J. Chem. Phys._, 105, 192 (1996)], and "natural internal coordinates" [G. Fogarasi, X. Zhou, P.W. Taylor, and P. Pulay, _J. Am. Chem. Soc._, 114, 8191 (1992)].  However, an elegant and robust solution turned out to be simply to work with a redundant set and to make the necessary mathematical adjustments.  This approach was advanced by P. Pulay and G. Fogarasi [_J. Chem. Phys._, 96, 2856 (1992)].

## The B matrix and coordinate transformations

The Wilson __B__-matrix [see the classic text by E.B. Wilson, J.C. Decius, and P.C. Cross, _Molecular Vibrations_, Dover (1955)] is the key to transformations between internal and Cartesian coordinates.  A __B__-matrix element is defined as the derivative of an internal coordinate value with respect to a Cartesian coordinate.

$$\textbf {B}_{ij} = \frac{\delta q_i}{\delta x_j}$$

The __B__-matrix therefore has dimensions of (Number of internals) by (Number of Cartesians).  A row of the __B__-matrix (corresponding to a particular internal coordinate) may also be interpreted as the collection of Cartesian unit vectors for each atom along which displacing would result in a maximal increase in the value of the internal coordinate.  (These unit vectors are sometimes called "_s_-vectors".)  Formulas for these __B__-matrix components as well as their derivatives are provided in a 2002 paper of V. Bakken and T. Helgaker [_J. Chem. Phys._, 117, 9160 (2002)], albeit not without some errors in the dihedral derivative __B__-matrix formulas.

Lets take a look at the first row of the __B__-matrix for ammonium:

```python
Bmat = intcosMisc.Bmat(intcos, xyzGeom)
print('B matrix - first row')
print(Bmat[0])
```

Notice that only the first 6 elements corresponding to the Cartesian coordinates of the first two atoms are non-zero. Since the first internal is R(1,2), only the first 2 atoms are relevant to its definition.  The B-matrix, therefore, is tremendously sparse, when the internal coordinates are restricted to stretches, bends, and dihedrals. (For Cartesian coordinates themselves, the __B__-matrix is simply the unit matrix.)

For a diatomic, the __B__-matrix consists of a single row, and the components are simply two vectors pointing outward as shown below.

```python
H2mol = psi4.geometry("""
H
H 1 0.8
""")
H2intcos = [ stre.STRE(0,1) ]
H2mol.update_geometry()
H2xyzGeom = np.array(H2mol.geometry())
H2Bmat = intcosMisc.Bmat(H2intcos, H2xyzGeom)
print("B matrix for hydrogen molecule:")
print(H2Bmat)

```

The __B__-matrix is immediately useful for transforming small displacements in Cartesian coordinates into displacements in internal coordinates.

$$\textbf {B}_{ij} \delta x_j = \delta q_i$$

or in matrix-vector form

$$\textbf {B} \Delta x = \Delta q $$

However, since the __B__-matrix depends upon the geometry, this transformation is exact only for infinitesimal displacements.

## Transformations between internal and Cartesian forces
The __B__-matrix readily facilitates the transformation of the energy gradient into Cartesian coordinates from internal coordinates.

$$ \frac{\delta E}{\delta q_i} \frac{\delta q_i}{\delta x_j} = \frac{\delta E}{\delta x_j}$$
or
$$ \textbf{B}^T g_q = g_x $$

The reverse transformation is of equal practical importance. Quantum chemistry codes typically provide an energy gradient in Cartesian coordinates.  This gradient must be transformed into internal coordinates, in preparation for whatever step-determining algorithm (Newton-Raphson, etc.) will be used. This transformation is more complicated because 1) we need to invert the non-square B-matrix; and 2) the B-matrix is often redundant, i.e., the rows are linearly dependent.

The left, generalized inverse of $\textbf {B}^T$ we will call the $\textbf A^T$ matrix.  $\textbf A$ has dimension (Num. of Cartesians) by (Num. of internals) and is defined by

$$ \textbf A^T = (\textbf{B} \textbf{u} \textbf {B}^T)^{-1} \textbf {B} \textbf{u}$$

where __u__ is an arbitrary matrix of dimension (Num. of Cartesians) by (Num. of Cartesians).

That $\textbf A^T$ is the left inverse of $\textbf {B}^T$ may be readily seen by the following:

\begin{align}
 \textbf{B}^T g_q &= g_x \\
 \textbf{A}^T \textbf{B}^T g_q &= \textbf{A}^T g_x \\
 [ (\textbf{B} \textbf{u} \textbf{B}^T )^{-1} \textbf{B} \textbf{u} ] \textbf{B}^T g_q &= \textbf{A}^T g_x  \\
(\textbf{B} \textbf{u} \textbf{B}^T )^{-1} ( \textbf{B} \textbf{u} \textbf{B}^T ) g_q &= \textbf{A}^T g_x  \\
 g_q  &= (\textbf{B} \textbf{u} \textbf{B}^T )^{-1} \textbf{B} \textbf{u} g_x
\end{align}

The first step in geometry optimization after the internal coordinates have been determined and a Cartesian energy derivative has been computed is to use this expression to compute the gradient in internal coordinates.  (One can instead think in terms of "forces" - the only difference is a minus sign.)

For transforming energy derivatives, the choice of $\textbf{u}$ is irrelevant.  In some spectroscopic applications, it is comprised of inverse atomic masses on its diagonal.  For a thorough exposition on geometric derivative transformaions see the paper by W.D. Allen and A.G. Csaszar [_J. Chem. Phys._, 98, 2983 (1993)].

If $\textbf{u}$ is chosen to be the unit matrix, then the transformation reduces to

$$ g_q  = (\textbf{B} \textbf{B}^T )^{-1} \textbf{B} g_x $$

The matrix $\textbf{B} \textbf{B}^T$ will be symmetric.  If redundant coordinates are present, then the generalized inverse (or pseudo-inverse) must be computed.  The matrix is diagonalized, then the non-zero eigenvalues on the diagonal are inverted, and the matrix is transformed back. Below is an example of the transformation of the gradient from Cartesians to internals for ammonium.  The symmMatInv() function shows how this generalized inverse can be performed.

```python
mol = psi4.geometry("""
1 1
 N  0.000000  0.0       0.0
 H  1.584222  0.0       1.12022
 H  0.0      -1.58422  -1.12022
 H -1.584222  0.0       1.12022
 H  0.0       1.58422  -1.12022
 unit au
""")

mol.update_geometry()
psi4.set_options({'basis': 'dz'})
psi4gradientMatrix = psi4.gradient('scf')
gx = np.reshape( np.array(psi4gradientMatrix), (3*Natom))
# Removing some noise
gx[:] = [0 if abs(x) < 1.0e-16 else x for x in gx]
print("Gradient in Cartesian coordinates")
print(gx)

# Function to return a generalized inverse
def symmMatInv(A):
    dim = A.shape[0]
    det = 1.0

    evals, evects = np.linalg.eigh(A)
    evects = evects.T
    for i in range(dim):
        det *= evals[i]

    diagInv = np.zeros( (dim,dim), float)
    for i in range(dim):
        if abs(evals[i]) > 1.0e-10:
            diagInv[i,i] = 1.0/evals[i]

    # A^-1 = P^t D^-1 P
    tmpMat = np.dot(diagInv, evects)
    AInv = np.dot(evects.T, tmpMat)
    return AInv

B = intcosMisc.Bmat(intcos, xyzGeom)
temp_arr = np.dot(B,gx.T)
G    = np.dot(B, B.T)
Ginv = symmMatInv(G)
gq = np.dot(Ginv,temp_arr.T)

print("Gradient in Internal coordinates.")
print(gq )
```

Lets try the reverse transformation and see if we return to the original gradient.

```python
gx2 = np.dot(B.T,gq)
print("Gradient in Cartesian coordinates (from internals).")
# Removing some noise
gx2[:] = [0 if abs(x) < 1.0e-16 else x for x in gx2]
print(gx2)
```

For information on the transformations of energy second derivatives, see the separate tutorial file on Hessians.


# Week 5: Dynamical Systems & Differential Equations

# Tutorial 2
# [insert your name]

**Important reminders**: Before starting, click "File -> Save a copy in Drive". Produce a pdf for submission by "File -> Print" and then choose "Save to PDF".

To complete this tutorial, you should have watched Videos 5.1, 5.2, and 5.3.

```
# @markdown Imports

# Imports
import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets  # interactive display
from scipy import optimize as opt
```

```
# @markdown Helper functions
import numpy
from numpy.linalg import inv, eig
from math import ceil
from matplotlib import pyplot, ticker, get_backend, rc
from mpl_toolkits.mplot3d import Axes3D
from itertools import cycle

%config InlineBackend.figure_format = 'retina'
plt.style.use("https://raw.githubusercontent.com/NeuromatchAcademy/course-content/master/nma.mplstyle")

classic = 'k'

def fitzhugh_nagumo_euler(init_V, init_w, I, dt=0.01, T=100):

    # Initialize variables
    t = np.arange(0, T, dt)
    V = np.zeros_like(t)
    V[0] = init_V

    w = np.zeros_like(t)
    w[0] = init_w

    # Step through time
    for n in range(0, len(t)-1):

      dV_dt = V[n] - V[n]**3 - w[n] + I
      dw_dt = 0.1*(V[n] -0.5*w[n]+1)

      V[n+1] = V[n] + dV_dt *dt
      w[n+1] = w[n] + dw_dt *dt

    return V, w, t

```

# Introducing the FitzHugh-Nagumo Model

We will be working with a neuron model today which is defined by a system of nonlinear differential equations. This model, the FitzHugh-Nagumo model, is similar in flavor to the better known Hodgkin-Huxley model, but is easier to analyze because it consists of two differential equations while the Hodgkin-Huxley model consists of four.  We will define the model as:

$$\begin{align}
\frac{dV}{dt} &= V - V^3 - w + I \\
\frac{dw}{dt} &= 0.1(V - 0.5w + 1)\\
\end{align}
$$

I saw that we will define it this way as this is slightly different (less constants) than other implementations you will see. $V$ here is the voltage of the neuron (without units, don't expect to equate to reality in mV) and $I$ is the input current. $w$ doesn't have a strict biological equivalent but it represents a recovery variable (as in, it helps the voltage recover after an action potential).

# Exercise 1: Finding nullclines & equilibrium points

Nullclines are curves in the phase plane space (w vs V) where one of the derivatives equals 0. The equilibrium points will occur at the intersections of the nullclines.

## A) Plotting the nullclines

Complete the function below to plot the nullclines on w vs V space (so we'll plot them as a function of V). We will want to vary I later so keep it general.

Hint: define both nullclines as w = ... since we're plotting as a function of V

```
def plot_nullclines(I, ax):

    # Range of V we want to look at
    V = np.arange(-2, 2, .01)

    # Plot a nullcline for dV/dt = 0
    ax.plot(V, ..., 'b', label='dV/dt=0')

    # Plot a nullcline for dw/dt = 0
    ax.plot(V, ..., 'g', label='dw/dt=0')

    ax.legend()

fig, ax = plt.subplots()
plot_nullclines(I=1, ax=ax)
ax.set(xlabel='V', ylabel='w');
```

Try different values of I to make the nullcline plot. What do you notice about the intersection of the nullclines with larger I?

### Answer

Fill in your text answer here so it is green. Use

  for line breaks

## Finding the equilibrium points

We can see from the plot above that there is one fixed point. Instead of finding this analytically, which is a bit annoying, we'll use the magic of computers. Specifically, we'll use `scipy.optimize.fsolve`. This function finds the roots (where an equation or set of equations equals 0) by numerical methods. It needs an initial guess, or starting estimate, in the neighborhood of the fixed point to work well. The initial guess doesn't have to be very exact though - in fact we'll use the same initial guess for every I.

I give you the code to compute the fixed point below.

```
def diff_eqs(x, I):
    V, w = x
    return [V - V**3 - w + I, .1*(V - 0.5*w+1)]

def get_fixed_point(I):
  initial_guess = [-1, -1]
  fixed_point = opt.fsolve(diff_eqs, initial_guess, I)
  return fixed_point

V_fp, w_fp = get_fixed_point(I=0)
print(V_fp)
print(w_fp)
```

# Exercise 2: Analyzing dynamics with zero current

We want to look at what happens in our system when I=0 (zero external current). Take a second and think - from a neuroscience perspective, what would you predict would happen? How would V evolve from some initial condition?

## A) Linearization of the fixed point

We are going to look at a linearization around the fixed point, or the dynamics of an associated homogeneous system at that point. To do this, we compute the Jacobian (the matrix of partial derivatives) and analyze the eigenvalues of the Jacobian at that fixed point. We can classify the fixed point based on those eigenvalues according to how they govern the dynamics of a homogenous system $ \frac{dX}{dt} = Jx$.

We first need to compute the Jacobian for the system as a whole. We will then plug in the fixed point at I=0 to get the Jacobian at that fixed point. I tell you what the Jacobian is in the hidden cell below but you are welcome to not look and derive it yourself if you have the time!

```
# @markdown Look at this cell for the Jacobian

# V_fp is the voltage at the fixed point
# J = np.array([[1-3*V_fp**2, -1], [0.1, -0.05]])
```

Plug in the voltage at the fixed point into the Jacobian when I=0 (find with `get_fixed_point`) and get the eigenvalues of the resulting matrix.

```
V_fp = ...
J = ...
# Get eigvals
...
```

## B) Classifying the fixed point

Based on the eigenvalues of the Jacobian when I=0, how would you classify the fixed point? What do you expect the trajectory from any initial point to look like?

Look at Video 5.3 at 24:05 for classifications with real, distinct eigenvalues, 27:03 for complex eigenvalues, and 28:29 for repeated eigenvalues.

Fill in your text answer here so it is green. Use

  for line breaks

## C) Plotting trajectories

I have implemented a Euler integration method for this model, `fitzhugh_nagumo_euler` in the Helper Functions. It takes inputs init_V, init_w, and I. Plot a couple trajectories in phase plane space for I=0.

```
# You'll want to use:
# V, w, t = fitzhugh_nagumo_euler(init_V, init_w, I)

fig, ax = plt.subplots()
ax.set_ylim([-2, 2])
plot_nullclines(I, ax=ax)

# Plot trajectories here

```

Plot V over time for one of the initial conditions.

```
# your code here
```

# Exercise 3: Analyzing dynamics with some current

We will repeat the steps of Exercise 2 but now we will look at different I.

## A) I = 1

Calculate the eigenvalues of the Jacobian for the fixed point when I = 1. What dynamic around the fixed point would you expect?

```
# Your code

# Get fixed point
# Get Jacobian
# Get eigenvalues

```

Fill in your text answer here so it is green. Use

  for line breaks

## B) I = 1.5

Calculate the eigenvalues of the Jacobian for the fixed point when I = 1.5. What dynamic around the fixed point would you expect?

```
# Your code

# Get fixed point
# Get Jacobian
# Get eigenvalues

```

Fill in your text answer here so it is green. Use

  for line breaks

## C) Interactive Demo

You can change I below to see the nullclines with the trajectory through phase plane space, and a plot of t vs V for the initial condition V = -2, w = -0.5.

i) What is happening (neuron-wise) when I=1?

ii) What is happening (neuron-wise) when I=1.5?

Fill in your text answer here so it is green. Use

  for line breaks

```
# @markdown Execute this cell to enable widget

def plot_fitzhugh_nagumo(I):

  fig, axes = plt.subplots(1, 2)

  # Plot nullclines
  plot_nullclines(I, axes[0])

  # Plot a few trajectories
  init_conds = [[-2, -.5]]
  for init_cond in init_conds:
    V, w, t = fitzhugh_nagumo_euler(init_cond[0], init_cond[1], I)

    axes[0].plot(V[0], w[0], 'or')
    axes[0].plot(V, w, 'r')
  axes[0].set(xlabel='V', ylabel='w')

  axes[1].plot(t, V)
  axes[1].set(xlabel='Time', ylabel='Voltage')

_ = widgets.interact(plot_fitzhugh_nagumo, I=(-1, 4, .01))
```

# Extra info

We've barely scratched the surface of examining this model. It can replicate lots of interesting phenomenon: http://www.scholarpedia.org/article/FitzHugh-Nagumo_model Explained phenomena section. Can you replicate some of those?


CompEcon Toolbox:

DemDdp03

Asset replacement model with maintenance

Randall Romero Aguilar, PhD

This demo is based on the original Matlab demo accompanying the  Computational Economics and Finance 2001 textbook by Mario Miranda and Paul Fackler.

Last updated: 2020-Sep-09

## About

Consider the preceding example, but suppose that the productivity of the asset may be
enhanced by performing annual service maintenance. Specifically, at the beginning of each
year, a manufacturer must decide whether to replace the asset with a new one or, if he elects
to keep the asset, whether to service it. An asset that is $a$ years old and has been serviced $s$ times yields a profit contribution $p(a, s)$ up to an age of $n$ years, at which point the asset becomes unsafe and must be replaced by law. The cost of a new asset is $c$, and the cost of servicing an asset is $k$. What replacement-maintenance policy maximizes profits?

## Initial tasks

```python
if 'google.colab' in str(get_ipython()):
    print("This notebook is running on Google Colab. Installing the compecon package.")
    !pip install compecon
```

```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from compecon import DDPmodel, gridmake, getindex
```

## Model Parameters
Assume a maximum asset age of 5 years, asset replacement cost $c = 75$, cost of servicing $k = 10$, and annual discount factor $\delta = 0.9$.

```python
maxage  = 5
repcost = 75
mancost = 10
delta   = 0.9
```

### State Space

This is an infinite horizon, deterministic model with time $t$ measured in years. The state
variables

$a \in \{1, 2, 3, \dots, n\}$

$s \in \{0, 1, 2, \dots, n − 1\}$

are the age of the asset in years and the number of servicings it has undergone, respectively.

```python
s1 = np.arange(1, 1 + maxage)   # asset age
s2 = s1 - 1                     # servicings
S  = gridmake(s1,s2)            # combined state grid
S1, S2 = S
n = S1.size                     # total number of states
```

Here, the set of possible asset ages and servicings are generated individually, and then a two-dimensional state grid is constructed by forming their Cartesian product using the CompEcon routine gridmake.

### Action Space
The action variable

$x \in \{\text{no action}, \text{service}, \text{replace}\}$

is the hold-replacement-maintenance decision.

```python
X = np.array(['no action', 'service', 'replace'])  # vector of actions
m = len(X)                               # number of actions
```

### Reward Function
The reward function is
\begin{equation}
f (a, s, x) =
\begin{cases}
p(a, s),         &x = \text{no action}\\
p(0, 0) − c,     &x = \text{service}\\
p(a, s + 1) − k, &x = \text{replace}
\end{cases}
\end{equation}

Assuming a profit contribution $p(a) =
50 − 2.5a −2.5a^2$ that is a function of the asset age $a$ in years.

Here, the rows of the reward matrix, which
correspond to the three admissible decisions (no action, service, replace), are computed
individually.

```python
f = np.zeros((m, n))
q = 50 - 2.5 * S1 - 2.5 * S1 ** 2
f[0] = q * np.minimum(1, 1 - (S1 - S2) / maxage)
f[1] = q * np.minimum(1, 1 - (S1 - S2 - 1) / maxage) - mancost
f[2] = 50 - repcost
```

### State Transition Function
The state transition function is
\begin{equation}
g(a, s, x) =
\begin{cases}
(a + 1, s),     &x = \text{no action}\\
    (1, 0),     &x = \text{service}\\
(a + 1, s + 1), &x = \text{replace}
\end{cases}
\end{equation}

Here, the routine ```getindex``` is used to find the index of the following period’s state.

```python
g = np.empty_like(f)
g[0] = getindex(np.c_[S1 + 1, S2], S)
g[1] = getindex(np.c_[S1 + 1, S2 + 1], S)
g[2] = getindex(np.c_[1, 0], S)
```

# Model Structure

The value of asset of age $a$ that has undergone $s$ servicings satisfies the Bellman equation
\begin{equation}
V(a,s) = \max\{p(a,s) + \delta V(a+1,s),\quad p(a,s+1)−k + \delta V(a+1,s+1),
\quad p(0,0) − c + \delta V(1,0)\}
\end{equation}

where we set $p(n, s) = −\infty$ for all $s$ to enforce replacement of an asset of age $n$. The
Bellman equation asserts that if the manufacturer replaces an asset of age $a$ with servicings
$s$, he earns $p(0,0) − c$ over the coming year and begins the subsequent year with an asset
worth $V(1,0)$; if he services the asset, he earns $p(a, s + 1) − k$ over the coming year
and begins the subsequent year with an asset worth $V(a + 1, s + 1)$. As with the previous
example, the value $V(a, s)$ measures not only the current and future net earnings of the
asset, but also the net earnings of all future assets that replace it.

To solve and simulate this model, use the CompEcon class ```DDPmodel```.

```python
model = DDPmodel(f, g, delta)
model.solve()

```

## Analysis

### Simulate Model
The paths are computed by performing q deterministic simulation of 12 years in duration using the ```simulate()``` method.

```python
sinit = 0
nyrs = 12
t = np.arange(nyrs + 1)
spath, xpath = model.simulate(sinit, nyrs)
```

```python
pd.Categorical(X[xpath], categories=X)
```

```python
simul = pd.DataFrame({
    'Year': t,
    'Age of Asset': S1[spath],
    'Number of Servicings': S2[spath]}).set_index('Year')

simul['Action'] = pd.Categorical(X[xpath], categories=X)
simul
```

### Plot State Paths (Age and Servicings) and Action Path
The asset is replaced every four years, and is serviced twice, at the beginning of the second and third years of operation.

```python
fig, axs = plt.subplots(3, 1, sharex=True, figsize=[9,9])
simul['Age of Asset'].plot(marker='o', ax=axs[0])
axs[0].set(title='Age of Asset')

simul['Number of Servicings'].plot(marker='o', ax=axs[1])
axs[1].set(title='Number of Servicings')

simul['Action'].cat.codes.plot(marker='o', ax=axs[2])
axs[2].set(title='Action Path', yticks=range(3), yticklabels=X);
```

notebooks/ddp/03 Asset replacement model with maintenance.ipynb


```python
# Import necessary resources
import os
import sys
import inspect
import numpy as np
import torch
import torch.optim as optim
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = [14, 14]

currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0, os.path.join(parentdir, 'scripts'))

import models as m
import utils
```

# First-Order ODEs

Note that a first-order ODE must be able to be written in the form

\begin{align}
\frac{d}{dx}\Psi + p(x)\Psi = g(x)
\end{align}

Where both $p(x)$ and $g(x)$ are continuous functions.

## Example I
Consider the first-order ODE:
\begin{align}
\frac{d}{dx}\Psi + (x+\frac{1 + 3 x^2}{1 + x+ x^3})\Psi = x^3 + 2x +x^2\frac{1 + 3x^2}{1 + x + x^3},\quad \Psi(0) = 1 \quad and \quad x\in [0,1]
\end{align}
Solve for $\Psi(x)$

### Step 1: Define DE and Initial Condition

```python
# Define Differential Equation
# Initial condition
f_0 = 1

# differential equation
def g(x):
    return x**3 + 2*x + (x**2)*((1+3*x**2)/(1+x+x**3))

def p(x):
    return x + ((1+3*(x**2))/(1+x+x**3))
```

### Step 2: Train the NN

```python
learning_rate = 0.003
epochs = 1000
display_step = 100

# Define NN
neural_network = m.MLP()

# Create optimizer
optimizer = optim.Adam(neural_network.parameters(), lr=learning_rate)

# Run the training steps
for epoch in range(epochs):
    optimizer.zero_grad()

    # Compute the loss
    loss = utils.first_order_loss_with_ic(neural_network, p, g, f_0)

    # Backpropagate
    loss.backward()

    # Adjust model parameters accordingly
    optimizer.step()

    if epoch % display_step == 0:
        print("epoch: {}, loss: {}".format(epoch, loss.item()))
```

    epoch: 0, loss: 7.20961332321167
    epoch: 100, loss: 1.1301414966583252
    epoch: 200, loss: 0.04078889638185501
    epoch: 300, loss: 0.03270101174712181
    epoch: 400, loss: 0.026999561116099358
    epoch: 500, loss: 0.027257751673460007
    epoch: 600, loss: 0.024393832311034203
    epoch: 700, loss: 0.021256979554891586
    epoch: 800, loss: 0.016167668625712395
    epoch: 900, loss: 0.013225913047790527

### Step 3: Visualize Result

Solving the DE by hand gives the exact result of $\Psi_a(x) = \frac{e^{\frac{-x^2}{2}}}{1+x+x^3} + x^2$. We will plot this against the trained trial solution.

```python
# Calculate
x = torch.linspace(0, 1, 100, requires_grad=True).unsqueeze(1)
y_approx = (f_0 + x * neural_network(x)).squeeze(0).detach().numpy().transpose()[0]
y_exact = ((torch.exp(-(x**2)/2)/(1+x+x**3))+x**2).squeeze(0).detach().numpy().transpose()[0]
x = x.squeeze(0).detach().numpy().transpose()[0]

fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
# Plot the analytical solution with the approximation
ax1.plot(x, y_approx, label='NN Approximation')
ax1.plot(x, y_exact, label='Exact Solution')
ax1.legend()

# Plot the residual
ax2.plot(x, y_exact-y_approx, label='Residual')
ax2.legend()

ax2.set_xlabel('x', fontsize=18)
ax1.set_ylabel("$\Psi(x)$", fontsize=18)
ax2.set_ylabel("Error", fontsize=18)
```

## Example II: More Complicated
Consider the first-order ODE:
\begin{align}
\frac{d}{dx}\Psi + \frac{1}{5}\Psi = e^{-\frac{x}{5}}cos(x),\quad \Psi(0) = 0 \quad and \quad x\in [0,10]
\end{align}
Solve for $\Psi(x)$

### Step 1: Define DE and Initial Condition

```python
# Define sample problem
# Initial condition
f_0 = 0

# differential equation
def g(x):
    return torch.exp(-x/5)*torch.cos(x)

def p(x):
    return 1/5

# Domain
domain = (0, 6)
```

### Step 2: Train the NN

```python
learning_rate = 0.003
epochs = 2000
display_step = 500

# Define NN
neural_network = m.MLP()

# Create optimizer
optimizer = optim.Adam(neural_network.parameters(), lr=learning_rate)

for epoch in range(epochs):
    optimizer.zero_grad()
    loss = utils.first_order_loss_with_ic(neural_network, p, g, f_0, domain[0], domain[1], 20)
    loss.backward()
    optimizer.step()
    # print(loss.item())
    if epoch % display_step == 0:
        print("epoch: {}, loss: {}".format(epoch, loss.item()))
```

    epoch: 0, loss: 8.864093780517578
    epoch: 500, loss: 0.0424497127532959
    epoch: 1000, loss: 0.027749208733439445
    epoch: 1500, loss: 0.014023992232978344

### Step 3: Visualize Result

Solving the DE by hand gives the exact result of $\Psi_a(x) = e^{-\frac{x}{5}}sin(x)$. We will plot this against the trained trial solution.

```python
x = torch.linspace(domain[0], domain[1], 100, requires_grad=True).unsqueeze(1)
y_approx = (f_0 + x * neural_network(x)).squeeze(0).detach().numpy().transpose()[0]
y_exact = (torch.exp(-x/5)*torch.sin(x)).squeeze(0).detach().numpy().transpose()[0]
x = x.squeeze(0).detach().numpy().transpose()[0]

fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
ax1.plot(x, y_approx, label='NN Approximation')
ax1.plot(x, y_exact, label='Exact Solution')
ax1.legend()
ax2.plot(x, y_exact-y_approx, label='Residual')
ax2.legend()

ax2.set_xlabel('x', fontsize=18)
ax1.set_ylabel("$\Psi(x)$", fontsize=18)
ax2.set_ylabel("Error", fontsize=18)
```

# Second-Order ODEs

Note that a second-order ODE must be able to be written in the form

\begin{align}
\frac{d^2}{dx^2}\Psi + a(x)\frac{d}{dx}\Psi + b(x)\Psi = g(x)
\end{align}

Where $a(x)$, $b(x)$, and $g(x)$ are continuous functions.

## Example III
The method described will be used to approximate the solution $\Psi(x)$ to the following second-order ODE:

\begin{align}
\frac{d^2}{dx^2}\Psi + \frac{1}{5}\frac{d}{dx}\Psi + \Psi= -\frac{1}{5}e^{-\frac{x}{5}}cos(x), \quad \Psi(0) = 0, \; \frac{d}{dx}\Psi(0)=1 \quad over \quad x\in [0,2]
\end{align}

```python
# Define sample problem
# Initial condition
f_0 = 0
f_prime_0 = 1

# differential equation
def g(x):
    result = -(1.0/5.0)*torch.ones_like(x, requires_grad=True)*torch.exp(-x/5.0)*torch.cos(x)
#     print(result.requires_grad)
    return result

def a(x):
    result =(1.0/5.0)*torch.ones_like(x, requires_grad=True)
#     print(result.requires_grad)
    return result

def b(x):
    return torch.ones_like(x, requires_grad=True)

domain = (0, 2)
```

```python
learning_rate = 0.0005
epochs = 2000
display_step = 100

# Define NN
neural_network = m.MLP()

# Create optimizer
optimizer = optim.Adam(neural_network.parameters(), lr=learning_rate)

for epoch in range(epochs):
    optimizer.zero_grad()
    loss = utils.second_order_loss_with_ic(neural_network, a, b, g, f_0, f_prime_0, domain[0], domain[1], 9)
    loss.backward()
    optimizer.step()

    if epoch % display_step == 0:
        print("epoch: {}, loss: {}".format(epoch, loss.item()))
```

    epoch: 0, loss: 4.61708402633667
    epoch: 100, loss: 0.5152031183242798
    epoch: 200, loss: 0.15065863728523254
    epoch: 300, loss: 0.7601860761642456
    epoch: 400, loss: 0.14682689309120178
    epoch: 500, loss: 0.1366061419248581
    epoch: 600, loss: 0.19627341628074646
    epoch: 700, loss: 0.2039232701063156
    epoch: 800, loss: 0.1131320670247078
    epoch: 900, loss: 0.05045817419886589
    epoch: 1000, loss: 0.023914627730846405
    epoch: 1100, loss: 0.3955507278442383
    epoch: 1200, loss: 0.2409828007221222
    epoch: 1300, loss: 0.1470753401517868
    epoch: 1400, loss: 0.06681714951992035
    epoch: 1500, loss: 0.1300501674413681
    epoch: 1600, loss: 0.1405242532491684
    epoch: 1700, loss: 0.19207489490509033
    epoch: 1800, loss: 0.10985593497753143
    epoch: 1900, loss: 0.13339689373970032

```python
x = torch.linspace(domain[0], domain[1], 100, requires_grad=True).unsqueeze(1)
y_approx = (f_0 + x*f_prime_0 + x**2 * neural_network(x)).squeeze(0).detach().numpy().transpose()[0]
y_exact = (torch.exp(-x/5)*torch.sin(x)).squeeze(0).detach().numpy().transpose()[0]
x = x.squeeze(0).detach().numpy().transpose()[0]

fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
ax1.plot(x, y_approx, label='NN Approximation')
ax1.plot(x, y_exact, label='Exact Solution')
ax1.legend()
ax2.plot(x, y_exact-y_approx, label='Residual')
ax2.legend()

ax2.set_xlabel('x', fontsize=18)
ax1.set_ylabel("$\Psi(x)$", fontsize=18)
ax2.set_ylabel("Error", fontsize=18)
```

```python

```