import numpy as np
def steady_state(matrix):
# Find eigenvalues and eigenvectors
eigenvalues, eigenvectors = np.linalg.eig(matrix.T) # Take the transpose of the matrix because we want left eigenvectors
# Find the eigenvector corresponding to eigenvalue 1
# (the real part is used to handle potential rounding errors)
steady_vector = eigenvectors[:, np.isclose(eigenvalues, 1, atol=1e-10)]
# Normalize the vector to get probabilities
steady_vector = steady_vector / np.sum(steady_vector)
return np.real(steady_vector) # Convert potential complex numbers to real numbers (imaginary parts will be close to 0)
# Example transition matrix
transition_matrix = np.array([[0.6,0.4],[0.7,0.3]])
print(steady_state(transition_matrix))
initial_prob_vector = np.array([1,0])
transition_matrix = np.array([[0.6,0.4],[0.7,0.3]])
time_points = np.arange(10)
markov_df = pd.DataFrame(index=time_points,columns=['A','E'])
current_prob = initial_prob_vector
for i in range(len(time_points)):
markov_df.loc[i] = current_prob
current_prob = np.matmul(current_prob, transition_matrix)
markov_df.plot(xlabel="Iteration",
ylabel="Fraction",
title="Evolution of State Makeup Over Time")
plt.show()
What happens if the initial state were [0,1] instead of [1,0]? This can be checked using the code provided, but it will end up converging to the same values.
Additionally, we are not restricted to just 2 states, our probability vector could have m columns for m possible states and our transition matrix would be mxm.
Markov chains can even be abstracted to infinite-state versions, although some properties change, which are beyond the scope of this textbook.
Furthermore, the concept can be extended to continuous time processes.
particles = np.zeros(5000)
fig, axes = plt.subplots(4,figsize=(16,12))
bins = [-31+2*i for i in range(0,31)]
for i in range(31):
if (i % 10 == 0):
axes[i//10].hist(particles, bins = bins,density=True,histtype='step')
axes[i//10].set(ylabel='Density',title=f't={i}')
dice_rolls = np.random.rand(5000)
movement = np.where(dice_rolls < 0.5,1,-1)
particles += movement
axes[-1].set(xlabel="x")
plt.show()
np.random.seed(1)
particles_x = np.zeros(5000)
particles_y = np.zeros(5000)
bins = [-31+2*i for i in range(0,31)]
fig, axes = plt.subplots(2,2,figsize=(16,12))
axes = axes.flatten()
j=0
for i in range(51):
if (i in [0,10,25,50]):
ax = axes[j].hist2d(particles_x,
particles_y, bins = bins,
density=True)
axes[j].set(ylabel='y',
title=f't={i}',
xlabel='x')
fig.colorbar(ax[3])
j+=1
dice_rolls = np.random.rand(5000)
movement = np.where(dice_rolls < 0.5,1,-1)
particles_x += movement
dice_rolls = np.random.rand(5000)
movement = np.where(dice_rolls < 0.5,1,-1)
particles_y += movement
plt.show()
particles = np.zeros(5000)
fig, axes = plt.subplots(4,sharex=True,figsize=(16,12))
bins = [-31+2*i for i in range(0,31)]
for i in range(31):
if (i % 10 == 0):
axes[i//10].hist(particles, bins = bins,density=True,histtype='step')
axes[i//10].set(ylabel='Density',title=f't={i}')
dice_rolls = np.random.rand(5000)
movement = np.where(dice_rolls < 0.75,1,-1) # changed from 0.5 to 0.75
particles += movement
axes[-1].set(xlabel="x")
plt.show()
B=np.zeros(3)
dt = 1e-5
np.random.seed(0)
brownian_df = pd.DataFrame(columns=['t',1,2,3])
brownian_df.loc[0] = [0,*B]
for i in range (1,100001):
dB = np.random.normal(size=3)*np.sqrt(dt)
B += dB
brownian_df.loc[i] = [i*dt,*B]
fig, ax = plt.subplots(figsize=(8,6))
ax.set_xlim(0,1)
ax.set_ylim(-3,3)
lineA, = ax.plot([],linewidth=0.75)
lineB, = ax.plot([],linewidth=0.75)
lineC, = ax.plot([],linewidth=0.75)
def animate(frame_num):
lineA.set_data((brownian_df.loc[:frame_num*1000]['t'],
brownian_df.loc[:frame_num*1000][1]))
lineB.set_data((brownian_df.loc[:frame_num*1000]['t'],
brownian_df.loc[:frame_num*1000][2]))
lineC.set_data((brownian_df.loc[:frame_num*1000]['t'],
brownian_df.loc[:frame_num*1000][3]))
plt.title(
f"Brownian Motion Simulation (t={frame_num*10}ms)")
return lineA,lineB
plt.ylabel("$B_t$")
plt.xlabel("Time (s)")
plt.legend(['1','2','3'])
anim = FuncAnimation(fig, animate, frames=100, interval=100)
anim.save('BrownianMotionSimulation.gif')
plt.show()
X=np.zeros(3)
dt = 1e-5
mu = 2
np.random.seed(0)
brownian_df = pd.DataFrame(columns=['t',1,2,3])
brownian_df.loc[0] = [0,*X]
for i in range (1,100001):
dB = np.random.normal(size=3)*np.sqrt(dt)
X += (mu*dt+dB)
brownian_df.loc[i] = [i*dt,*X]
fig, ax = plt.subplots(figsize=(8,6))
ax.set_xlim(0,1)
ax.set_ylim(-2,5)
lineA, = ax.plot([],linewidth=0.75)
lineB, = ax.plot([],linewidth=0.75)
lineC, = ax.plot([],linewidth=0.75)
def animate(frame_num):
lineA.set_data((brownian_df.loc[:frame_num*1000]['t'],
brownian_df.loc[:frame_num*1000][1]))
lineB.set_data((brownian_df.loc[:frame_num*1000]['t'],
brownian_df.loc[:frame_num*1000][2]))
lineC.set_data((brownian_df.loc[:frame_num*1000]['t'],
brownian_df.loc[:frame_num*1000][3]))
plt.title(
f"Brownian Motion w/ Const Drift Simulation (t={frame_num*10}ms)")
return lineA,lineB
plt.ylabel("$X_t$")
plt.xlabel("Time (s)")
plt.legend(['1','2','3'])
anim = FuncAnimation(fig, animate, frames=100, interval=100)
anim.save('BrownianMotionDriftSimulation.gif')
plt.show()
np.random.seed(0)
# Parameters
T = 10.0 # Total time
N = 10000 # Number of time steps
dt = T / N # Time step size
plt.figure(figsize=(9, 6))
for j in range(3):
# Initialize arrays
t = np.linspace(0, T, N+1)
W = np.zeros(N+1)
Y = np.zeros(N+1)
Y[0]=1
# Simulate the Bessel Process
for i in range(1, N+1):
dW = np.random.normal(0, np.sqrt(dt)) # Wiener process increment
mu = 1/Y[i-1] # Evaluate mu
sigma = 1 # Evaluate sigma (constant in this example)
# Update the process
Y[i] = Y[i-1] + mu * dt + sigma * dW
W[i] = W[i-1] + dW
# Plot Result
plt.plot(t, Y,linewidth=1)
plt.title('Besell Process $dY_t=1/(Y_t)dt+dW_t$')
plt.xlabel('Time (s)')
plt.ylabel('$Y_t$')
plt.ylim(-1,9)
plt.grid(True)
plt.legend([1,2,3])
plt.show()
B=0
dt = 1e-4
np.random.seed(2)
brownian_df = pd.DataFrame(columns=['t',1,2])
brownian_df.loc[0] = [0,B,B**2]
for i in range (1,100001):
dB = np.random.normal()*np.sqrt(dt)
B += dB
brownian_df.loc[i] = [i*dt,B,B**2]
fig, ax = plt.subplots(figsize=(8,6))
ax.set_xlim(0,10)
ax.set_ylim(-10,50)
lineA, = ax.plot([],linewidth=0.75)
lineB, = ax.plot([],linewidth=0.75)
def animate(frame_num):
lineA.set_data((brownian_df.loc[:frame_num*1000]['t'],
brownian_df.loc[:frame_num*1000][1]))
lineB.set_data((brownian_df.loc[:frame_num*1000]['t'],
brownian_df.loc[:frame_num*1000][2]))
plt.title(
f"Brownian Motion Simulation (t={frame_num//10}.{frame_num%10}s)")
return lineA,lineB
plt.ylabel("$y$")
plt.xlabel("Time (s)")
plt.legend(['$B_t$','$(B_t)^2$',])
anim = FuncAnimation(fig, animate, frames=100, interval=100)
anim.save('BrownianMotionSquared.gif')
plt.show()
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import Axes3D
%matplotlib notebook
# Set up the figure and axis
fig = plt.figure(figsize=(6,6))
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.axes.set_xlim3d(left=-5, right=5)
ax.axes.set_ylim3d(bottom=-5, top=5)
ax.axes.set_zlim3d(bottom=-5, top=5)
# Number of steps
T = 10
n = 360
dt = T/n
print(dt)
num_particles = 5
# Initialize arrays to hold the x, y, z coordinates of the particle
x = np.zeros((n,num_particles))
y = np.zeros((n,num_particles))
z = np.zeros((n,num_particles))
# print(x)
# Step size
step_size = np.sqrt(dt)/np.sqrt(3)
frame_multiplier = 3
frame_lag = 50
for i in range(1,n):
# Brownian motion
x[i] = x[i - 1] + np.random.normal(0, step_size,size=num_particles)
y[i] = y[i - 1] + np.random.normal(0, step_size,size=num_particles)
z[i] = z[i - 1] + np.random.normal(0, step_size,size=num_particles)
def update(num, x, y, z, plot,ax):
num *= frame_multiplier
# Update the plot
# for i in range(num_particles):
plt.cla()
ax.view_init(elev=10., azim=num)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.axes.set_xlim3d(left=-5, right=5)
ax.axes.set_ylim3d(bottom=-5, top=5)
ax.axes.set_zlim3d(bottom=-5, top=5)
plot = [ax.plot(x[max(0,num-frame_lag):num+1,i], y[max(0,num-frame_lag):num+1,i], z[max(0,num-frame_lag):num+1,i],linewidth=1)[0] for i in range(num_particles)]
# plt.title(f"t={num*dt:.2f}s\n ({x[num]:.4f},{y[num]:.4f},{z[num]:.4f})")
return plot
# Create an empty plot object
plot = [ax.plot(x[0:5,i], y[0:5,i], z[0:5,i])[0] for i in range(num_particles)]
# print(plot)
# Set up animation
plt.title("Brownian Motion 3D Animation")
ani = animation.FuncAnimation(fig, update, frames=n//frame_multiplier, fargs=(x, y, z, plot,ax), interval=dt*1000*frame_multiplier, blit=False)
ani.save('Brownian-Motion-3D.gif', fps=15,
#extra_args=['-vcodec', 'libx264']
)
plt.show()
def Inverse_Exp(p, lam):
return -1* np.log(1-p)/lam
def Get_Number_Jumps(exp_cum_sum,t):
return np.where(exp_cum_sum <= t, 1,0).sum()
np.random.seed(0)
dt = .001
uniform_dist = np.random.rand(500)
exp_sample = Inverse_Exp(uniform_dist, 1)
exp_sample
exp_cum_sum = exp_sample.cumsum()
exp_cum_sum
poisson_df = pd.DataFrame(columns=['t','X'])
for i in range(0,50001):
poisson_df.loc[i] = [i*dt,
Get_Number_Jumps(exp_cum_sum,i*dt)]
plt.figure(figsize=(9,6))
plt.scatter(poisson_df['t'],
poisson_df['X'],s=.5,marker='s')
plt.xlabel("Time (s)")
plt.ylabel("X")
plt.title("Poisson Process with $\lambda=1$")
plt.show()
np.random.seed(1)
sigma = 0.5
normal_jumps = np.random.normal(scale=sigma,size=500)
def Inverse_Exp(p, lam):
return -1* np.log(1-p)/lam
def Get_Number_Jumps(exp_cum_sum,t):
return np.dot(np.where(exp_cum_sum <= t, 1,0),normal_jumps)
dt = .01
uniform_dist = np.random.rand(500)
exp_sample = Inverse_Exp(uniform_dist, 1)
exp_sample
exp_cum_sum = exp_sample.cumsum()
exp_cum_sum
poisson_df = pd.DataFrame(columns=['t','X'])
for i in range(0,5001):
poisson_df.loc[i] = [i*dt,
Get_Number_Jumps(exp_cum_sum,i*dt)]
plt.figure(figsize=(9,6))
plt.scatter(poisson_df['t'],
poisson_df['X'],s=1.5,marker='s')
plt.plot(poisson_df['t'],
poisson_df['X'],alpha=0.5,
color='purple',linewidth='0.25')
plt.xlabel("Time (s)")
plt.ylabel("X")
plt.title(f"Compound Poisson Process with $\lambda=1$ and Jump$\sim N(0,{sigma**2})$")
plt.show()
np.random.seed(0)
# Parameters
T = 100.0 # Total time
N = 10000 # Number of time steps
dt = T / N # Time step size
mu = -0.75 # Drift coefficient
lambda_ = 0.01 # Jump frequency
jump_mean = 0.15 # Mean jump size
jump_std = 0.04 # Standard deviation of jump size
# Initialize arrays
t = np.linspace(0, T, N+1)
X = np.zeros(N+1)
# Simulate the jump process
for i in range(1, N+1):
dN = np.random.poisson(lambda_ * dt) # Poisson process increment
# Calculate the jump component
jump = np.random.normal(jump_mean, jump_std, dN).sum()
# Update the process
X[i] = X[i-1] + mu * X[i-1] * dt + jump
# Plot the jump process
plt.figure(figsize=(9, 6))
plt.plot(t, X)
plt.title('Jump Process')
plt.xlabel('Time')
plt.ylabel('$X_t$')
plt.grid(True)
plt.show()
np.random.seed(0)
# Parameters
T = 10.0 # Total time
N = 10000 # Number of time steps
dt = T / N # Time step size
mu = 0.00 # Drift coefficient
sigma = .2 # Volatility coefficient
lambda_ = 0.1 # Jump frequency
jump_mean = 0.1 # Mean jump size
jump_std = 0.3 # Standard deviation of jump size
# Initialize arrays
t = np.linspace(0, T, N+1)
W = np.zeros(N+1)
X = np.zeros(N+1)
# Simulate the jump diffusion process
for i in range(1, N+1):
dW = np.random.normal(0, np.sqrt(dt)) # Wiener process increment
dN = np.random.poisson(lambda_ * dt) # Poisson process increment
# Calculate the jump component
jump = np.random.normal(jump_mean, jump_std, dN).sum()
# Update the process
X[i] = X[i-1] + mu * X[i-1] * dt + sigma * X[i-1] * dW + jump
W[i] = W[i-1] + dW
# Plot the jump diffusion process
plt.figure(figsize=(9, 6))
plt.plot(t, X)
plt.title('Geometric BM with Jump Diffusion Process')
plt.xlabel('Time')
plt.ylabel('$X_t$')
plt.grid(True)
plt.show()