#!/usr/bin/env python
# coding: utf-8
# In[1]:
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
import sympy as sy
sy.init_printing()
# In[2]:
def round_expr(expr, num_digits):
return expr.xreplace({n : round(n, num_digits) for n in expr.atoms(sy.Number)})
# # Systems of First-Order Equations
# In time series analysis, we study difference equations by writing them into a linear system. For instance,
#
# $$
# 3y_{t+3} - 2y_{t+2} + 4y_{t+1} - y_t = 0
# $$
# We define
#
# $$
# \mathbf{x}_t =
# \left[
# \begin{matrix}
# y_t\\
# y_{t+1}\\
# y_{t+2}
# \end{matrix}
# \right], \qquad
# \mathbf{x}_{t+1} =
# \left[
# \begin{matrix}
# y_{t+1}\\
# y_{t+2}\\
# y_{t+3}
# \end{matrix}
# \right]
# $$
# Rerrange the difference equation for better visual shape,
#
# $$
# y_{t+3} = \frac{2}{3}y_{t+2} - \frac{4}{3}y_{t+1} + \frac{1}{3}y_{t}
# $$
# The difference equation can be rewritten as
#
# $$
# \mathbf{x}_{t+1} = A \mathbf{x}_{t}
# $$
# That is,
#
# $$
# \left[
# \begin{matrix}
# y_{t+1}\\
# y_{t+2}\\
# y_{t+3}
# \end{matrix}
# \right] =
# \left[
# \begin{matrix}
# 0 & 1 & 0\\
# 0 & 0 & 1\\
# \frac{1}{3} & -\frac{4}{3} & \frac{2}{3}
# \end{matrix}
# \right]
# \left[
# \begin{matrix}
# y_t\\
# y_{t+1}\\
# y_{t+2}
# \end{matrix}
# \right]
# $$
# In general, we make sure the difference equation look like:
#
# $$
# y_{t+k} = a_1y_{t+k-1} + a_2y_{t+k-2} + ... + a_ky_{t}
# $$
#
# then rewrite as $\mathbf{x}_{t+1} = A \mathbf{x}_{t}$, where
#
# $$
# \mathbf{x}_t =
# \left[
# \begin{matrix}
# y_{t}\\
# y_{t+1}\\
# \vdots\\
# y_{t+k-1}
# \end{matrix}
# \right], \quad
# \mathbf{x}_{t+1} =
# \left[
# \begin{matrix}
# y_{t+1}\\
# y_{t+2}\\
# \vdots\\
# y_{t+k}
# \end{matrix}
# \right]
# $$
# And also
#
# $$A=\left[\begin{array}{ccccc}
# 0 & 1 & 0 & \cdots & 0 \\
# 0 & 0 & 1 & & 0 \\
# \vdots & & & \ddots & \vdots \\
# 0 & 0 & 0 & & 1 \\
# a_{k} & a_{k-1} & a_{k-2} & \cdots & a_{1}
# \end{array}\right]$$
# # Markov Chains
# Markov chain is a type of stochastic process, commonly modeled by difference equation, we will be slightly touching the surface of this topic by walking through an example.
# Markov chain is also described by the first-order difference equation $\mathbf{x}_{t+1} = P\mathbf{x}_t$, where $\mathbf{x}_t$ is called state vector, $A$ is called stochastic matrix.
# Suppose there are 3 cities $A$, $B$ and $C$, the proportion of population migration among cities are constructed in the stochastic matrix below
# $$
# M =
# \left[
# \begin{matrix}
# .89 & .07 & .10\\
# .07 & .90 & .11\\
# .04 & .03 & .79
# \end{matrix}
# \right]
# $$
# For instance, the first column means that $89\%$ of population will stay in city $A$, $7\%$ will move to city $B$ and $4\%$ will migrate to city $C$. The first row means $7\%$ of city $B$'s population will immigrate into $A$, $10\%$ of city $C$'s population will immigrate into $A$.
# Suppose the initial population of 3 cities are $(593000, 230000, 709000)$, convert the entries into percentage of total population.
# In[3]:
x = np.array([593000, 230000, 709000])
x = x/np.sum(x);x
# Input the stochastic matrix
# In[4]:
M = np.array([[.89, .07, .1], [.07, .9, .11], [.04, .03, .79]])
# After the first period, the population proportion among cities are
# In[5]:
x1 = M@x
x1
# The second period
# In[6]:
x2 = M@x1
x2
# The third period
# In[7]:
x3 = M@x2
x3
# We can construct a loop till $\mathbf{x}_{100} = M\mathbf{x}_{99}$, then plot the dynamic path. Notice that the curve is flattening after 20 periods, and we call it convergence to steady-state.
# In[8]:
k = 100
X = np.zeros((k, 3))
X[0] = M@x
i = 0
while i+1 < 100:
X[i+1] = M@X[i]
i = i + 1
# In[9]:
fig, ax = plt.subplots(figsize = (12, 12))
la = ['City A', 'City B', 'City C']
s = '$%.3f$'
for i in [0, 1, 2]:
ax.plot(X[:, i], lw = 3, label = la[i] )
ax.text(x = 20, y = X[-1,i], s = s %X[-1,i], size = 16)
ax.axis([0, 20, 0, .6]) # No need to show more of x, it reaches steady-state around 20 periods
ax.legend(fontsize = 16)
ax.grid()
ax.set_title('Dynamics of Population Percentage')
plt.show()
# # Eigenvalue and -vector in Markov Chain
# If the $M$ in last example is diagonalizable, there will be $n$ linearly independent eigenvectors and corresponding eigenvalues, $\lambda_1$,...,$\lambda_n$. And eigenvalues can always be arranged so that $\left|\lambda_{1}\right| \geq\left|\lambda_{2}\right| \geq \cdots \geq\left|\lambda_{n}\right|$.
#
# Also, because any initial vector $\mathbb{x}_0 \in \mathbb{R}^n$, we can use the basis of eigenspace (eigenvectors) to represent all $\mathbf{x}$.
#
# $$
# \mathbf{x}_{0}=c_{1} \mathbf{v}_{1}+\cdots+c_{n} \mathbf{v}_{n}
# $$
# This is called eigenvector decomposition of $\mathbf{x}_0$. Multiply by $A$
#
# $$
# \begin{aligned}
# \mathbf{x}_{1}=A \mathbf{x}_{0} &=c_{1} A \mathbf{v}_{1}+\cdots+c_{n} A \mathbf{v}_{n} \\
# &=c_{1} \lambda_{1} \mathbf{v}_{1}+\cdots+c_{n} \lambda_{n} \mathbf{v}_{n}
# \end{aligned}
# $$
#
# In general, we have a formula for $\mathbf{x}_k$
#
# $$
# \mathbf{x}_{k}=c_{1}\left(\lambda_{1}\right)^{k} \mathbf{v}_{1}+\cdots+c_{n}\left(\lambda_{n}\right)^{k} \mathbf{v}_{n}
# $$
# Now we test if $M$ has $n$ linearly independent eigvectors.
# In[10]:
M = sy.Matrix([[.89, .07, .1], [.07, .9, .11], [.04, .03, .79]]);M
# In[11]:
M.is_diagonalizable()
# $M$ is diagonalizable, which also means that $M$ has $n$ linearly independent eigvectors.
# In[12]:
P, D = M.diagonalize()
P = round_expr(P,4); P # user-defined round function at the top of the notebook
# In[13]:
D = round_expr(D,4); D
# First we find the $\big[\mathbf{x}_0\big]_C$, i.e. $c_1, c_2, c_3$
# In[14]:
x0 = sy.Matrix([[.3870], [.1501], [0.4627]]);x0
# In[15]:
P_aug = P.row_join(x0)
P_aug_rref = round_expr(P_aug.rref()[0],4); P_aug_rref
# In[16]:
c = sy.zeros(3, 1)
for i in [0, 1, 2]:
c[i] = P_aug_rref[i, 3]
c = round_expr(c,4);c
# Now we can use the formula to compute $\mathbf{x}_{100}$, it is the same as we have plotted in the graph.
# In[17]:
x100 = c[0] * D[0, 0]**100 * P[:, 0]\
+ c[1] * D[1, 1]**100 * P[:, 1]\
+ c[2] * D[2, 2]**100 * P[:, 2]
x100 = round_expr(x100,4);x100
# This is close enough to the steady-state.
# # Fractal Pictures
# Here is an example of fractal geometry, illustrating how dynamic system and affine transformation can create fractal pictures.
# The algorithem is perform 4 types of affine transformation. The corresponding probabilites are $p_1, p_2, p_3, p_4$.
#
# $$
# \begin{array}{l}
# T_{1}\left(\left[\begin{array}{l}
# x \\
# y
# \end{array}\right]\right)=\left[\begin{array}{rr}
# 0.86 & 0.03 \\
# -0.03 & 0.86
# \end{array}\right]\left[\begin{array}{l}
# x \\
# y
# \end{array}\right]+\left[\begin{array}{l}
# 0 \\
# 1.5
# \end{array}\right], p_{1}=0.83 \\
# T_{2}\left(\left[\begin{array}{l}
# x \\
# y
# \end{array}\right]\right)=\left[\begin{array}{lr}
# 0.2 & -0.25 \\
# 0.21 & 0.23
# \end{array}\right]\left[\begin{array}{l}
# x \\
# y
# \end{array}\right]+\left[\begin{array}{l}
# 0 \\
# 1.5
# \end{array}\right], p_{2}=0.09 \\
# T_{3}\left(\left[\begin{array}{l}
# x \\
# y
# \end{array}\right]\right)=\left[\begin{array}{rr}
# -0.15 & 0.27 \\
# 0.25 & 0.26
# \end{array}\right]\left[\begin{array}{l}
# x \\
# y
# \end{array}\right]+\left[\begin{array}{l}
# 0 \\
# 0.45
# \end{array}\right], p_{3}=0.07 \\
# T_{4}\left(\left[\begin{array}{l}
# x \\
# y
# \end{array}\right]\right)=\left[\begin{array}{ll}
# 0 & 0 \\
# 0 & 0.17
# \end{array}\right]\left[\begin{array}{l}
# x \\
# y
# \end{array}\right]+\left[\begin{array}{l}
# 0 \\
# 0
# \end{array}\right], p_{4}=0.01
# \end{array}
# $$
# The codes below are self-explanatory.
# In[18]:
A = np.array([[[.86, .03],
[-.03, .86]],
[[.2, -.25],
[.21, .23]],
[[-.15, .27],
[.25, .26]],
[[0., 0.],
[0., .17]]])
a = np.array([[[0,1.5]],
[[0,1.5]],
[[0,0.45]],
[[0,0]]])
p1 = 1*np.ones(83)
p2 = 2*np.ones(9)
p3 = 3*np.ones(7)
p4 = 4*np.ones(1)
p = np.hstack((p1,p2,p3,p4))
k = 30000
fig, ax = plt.subplots(figsize = (5,8))
X = np.zeros((2,k))
for i in range(k-1):
n = np.random.randint(0, 100)
if p[n] == 1:
X[:,i+1] = A[0,:,:]@X[:,i]+a[0,:,:]
elif p[n] == 2:
X[:,i+1] = A[1,:,:]@X[:,i]+a[1,:,:]
elif p[n] == 3:
X[:,i+1] = A[2,:,:]@X[:,i]+a[2,:,:]
else:
X[:,i+1] = A[3,:,:]@X[:,i]+a[3,:,:]
ax.scatter(X[0,:],X[1,:], s = 1, color = 'g')
plt.show()