= 101
N = 0.2 epsilon
Heat Diffusion
We will be conducting a simulation of two-dimensional heat diffusion in a multitude of ways. We will first set the initial parameters.
import numpy as np
from matplotlib import pyplot as plt
# construct initial condition: 1 unit of heat at midpoint
= np.zeros((N,N))
u0 int(N/2), int(N/2)] = 1.0
u0[
plt.imshow(u0) plt.show()
As we can see, we have a point in the very middle of the space with heat. From this point, the heat will dissipate outwards according to the diffusion equation in a circular fashion. Now let’s create some functions that will simulate this.
#initialize A
= np.zeros((N**2,N**2))
A
for k in range (N**2):
#create main diagonal
=-4
A[k,k]
#create 1st lower diagonal (placing zeros every Nth entry)
if k-1>=0:
if k%N==0:
-1]=0
A[k,kelse:
-1]=1
A[k,k
#create 1st upper diagnonal (placing zeros every Nth entry)
if k+1<=N**2-1:
if (k+1)%N==0:
+1]=0
A[k,kelse:
+1]=1
A[k,k
#create Nth upper and lower diagonals
if N+k<=N**2-1:
+k]=1
A[k,N+k,k]=1
A[N A
array([[-4., 1., 0., ..., 0., 0., 0.],
[ 1., -4., 1., ..., 0., 0., 0.],
[ 0., 1., -4., ..., 0., 0., 0.],
...,
[ 0., 0., 0., ..., -4., 1., 0.],
[ 0., 0., 0., ..., 1., -4., 1.],
[ 0., 0., 0., ..., 0., 1., -4.]])
def graph(sols_arr, row_max, col_max, interval):
'''
plots solutions gathered throughout the call
'''
#create fig and initialize variables
= plt.subplots(row_max,col_max,sharex=True,sharey=True)
fig,axarr
plt.xticks([])
plt.yticks([])False)
plt.grid(= 0
col = 0
row =0
i
#plot each updated heatmap in correct grid spot
for u in sols_arr:
+=1
i
axarr[row,col].imshow(u)f'Iteration {interval*i}')
axarr[row,col].set_xlabel(if col == col_max-1:
=0
col+=1
rowelse:
+=1 col
def heat_matmul(A, epsilon, N, start_mat, iterations, interval):
'''
Runs a heat diffusion simulation using matrix mulitplication and plots it
'''
#initialize values needed
=[]
sols= start_mat
u
#update the matrix of heat values over and over
for i in range(1,1+iterations):
= u + epsilon * (A @ u.flatten()).reshape((N, N))
u
#store solutions every so often for plotting
if i % interval == 0:
sols.append(u)
return u, sols
%%time
= heat_matmul(A, epsilon, N, u0, 2700, 300) u1, sols1
CPU times: user 4min 19s, sys: 4.71 s, total: 4min 24s
Wall time: 1min 51s
This took a whole 4 minutes to run! Let’s see if we can speed up the process.
3,3, 300) graph(sols1,
Luckily, we can significantly cut the time using sparse matrices. We know that A is a pentadiagonal matrix, so we can use dia matrix to significantly speed up the process. We run the code for 2700 iterations again, and we’ll compare the times.
from scipy.sparse import dia_matrix
= dia_matrix(A) A_dia_matrix
%%time
= heat_matmul(A_dia_matrix, epsilon, N, u0, 2700, 300) u2, sols2
CPU times: user 130 ms, sys: 1.61 ms, total: 132 ms
Wall time: 131 ms
This is impressive! Before, the total time was 4 minutes, and now the time to complete the simulation was only 132 milliseconds! We can see that the time complexity of the matrix multiplications was significantly reduced, allowing us to achieve this.
3, 3, 300) graph(sols2,
Now, we will use the python package numba to solve this. Instead of using matrix multiplication, we will use a for loop, since numba is best at optimizing these. Now let’s see how this method does compared to the previous two.
from numba import jit
@jit(nopython=True) #<--- THE BIG DIFFERENCE
def heat_explicit(A, epsilon, N, start_mat, iterations, interval):
'''
Runs a heat diffusion simulation using by hand matrix mulitplication and plots it
'''
#initialize values needed
=[]
sols= start_mat
u
#update the matrix of heat values over and over
for k in range(1,1+iterations):
#manually calculate matrix multiplication result (heat equation)
= np.zeros((N,N))
u_new for i in range(N):
for j in range(N):
=-4*u[i,j]
totif i>0:
+=u[i-1,j]
totif i+1<N:
+=u[i+1,j]
totif j>0:
+=u[i,j-1]
totif j+1<N:
+=u[i,j+1]
tot= u[i,j] + epsilon * (tot)
u_new[i,j]
#store solutions every so often for plotting
if k % interval == 0:
sols.append(u_new)
#update u and repeat
= u_new
u
return u, sols
We are just going to do a quick test run to make sure that the function is compiled before we actually run this. We do this because the compilation part takes a significant portion of time, and when we are comparing the times of our methods we wouldn’t want that to play a factor in our analysis when we’re just trying to look at the methods.
= heat_explicit(A, epsilon, N, u0, 10, 10) u3, sols3
%%time
= heat_explicit(A, epsilon, N, u0, 2700, 300) u4, sols4
CPU times: user 45.4 ms, sys: 484 µs, total: 45.9 ms
Wall time: 45.8 ms
This was less than 50 milliseconds! This is significantly faster than doing the computations using a sparse matrix, and the power of the numba module is truly put on display here. Graphed below is the visualization of the heat diffusion every 300 iterations.
3, 3, 300) graph(sols4,