import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
%matplotlib inline
# Laplacian operator
# dx is grid spacing, a is a 2d array
# roll shifts array by 1, in i or j directions
# return Laplacian as an array
def laplacian2D(a, dx):
return (- 4*a + np.roll(a,1,axis=0) + np.roll(a,-1,axis=0)\
+ np.roll(a,+1,axis=1) + np.roll(a,-1,axis=1)) / (dx**2)
# it is faster to use a whole array routine than to loop over all indices (in the notebook)
# Reaction parts of differential equations for
# three different types of different Reaction diffusion equations
# u,v are 2D arrays, alpha, beta could be numbers or arrays
# rdtype is a string specifying the model
def Reaction(u,v,alpha,beta,rdtype):
if (rdtype == 'FN'): # FitzHugh Nagumo model
Ru,Rv=R_FN(u,v,alpha,beta)
return Ru,Rv
if (rdtype == 'Br'): # Brusellator model
Ru,Rv= R_Br(u,v,alpha,beta)
return Ru,Rv
if (rdtype == 'GS'): # Gray-Scott model
Ru,Rv = R_GS(u,v,alpha,beta)
return Ru,Rv
print('invalid reaction type')
#The FitzHugh-Nagumo Reaction Diffusion Model
# examples: Du, Dv, alpha, beta = 1, 100, 0.5, 20, dt=0.0002
def R_FN(u,v,alpha,beta):
Ru = u - u**3 - v + alpha
Rv = (u - v)*beta
return Ru,Rv
# Brusselator Reaction Diffusion model
# example: Du, Dv, alpha, beta = 2, 22, 5, 9 # dt=0.005
def R_Br(u,v,alpha,beta):
Ru = alpha - (beta+1)*u + (u**2)*v
Rv = beta*u - (u**2)*v
return Ru,Rv
# solution to problem #3
# The Gray Scott Reaction Diffusion model
# alpha is feed rate for u, beta is kill or drain rate
# alpha kills v
def R_GS(u,v,alpha,beta):
Ru = -u*(v**2) + alpha*(1-u)
Rv = u*(v**2) -(alpha+ beta)*v
return Ru, Rv
# A different implimentation of the Laplacian operator
# solution to problem 4
def laplacian2D_conv(a, dx):
z = -a
# be careful with roll() axis=0 is probably up/down
# axis=1 is probably left/right
# + 1 is probably shifting in the opposite direction than you expect
# nothing here depends on these issues because of symmetry
roll_right = np.roll(a,+1,axis=0)
roll_left = np.roll(a,-1,axis=0)
roll_up = np.roll(a,+1,axis=1)
roll_down = np.roll(a,-1,axis=1)
roll_right_up = np.roll(roll_right,+1,axis=1)
roll_right_down = np.roll(roll_right,-1,axis=1)
roll_left_up = np.roll(roll_left, +1,axis=1)
roll_left_down = np.roll(roll_left, -1,axis=1)
z += 0.2*(roll_right + roll_left + roll_up + roll_down)
z += 0.05*(roll_right_up + roll_right_down + roll_left_up + roll_left_down)
return 4*z/(dx**2)
# without the factor of 4 here this would be 4 times weaker than the other Laplacian operator
# however sometimes people refer to the operation without the 4 as a Laplacian operator
# a class for initializing and computing and showing solutions to
# a 2D reaction diffusion equation
# in this version I show both u,v images
class Update_RD(object):
# do this when creating a class object, axis info is passed,
# along with parameters for the reaction diffusion equation
def __init__(self, fig, ax, alpha=5.0,beta=9.0,Du=2.,Dv=22.,\
n=100,dt=0.005,dx=1,rdtype='Br',nsteps=1,\
Lap_fun=laplacian2D):
self.success = 0
self.alpha = alpha # parameter for reaction
self.beta = beta # parameter for reaction
self.Du = Du # diffusion coefficient for u
self.Dv = Dv # diffusion coefficient for v
self.n = n # dimension of u,v arrays
self.rdtype = rdtype # type of reaction diffusion model, is a string
self.dx = dx # horizontal grid spacing
self.dt = dt # timestep
self.nsteps = nsteps # how many timesteps to take per display
self.Lap_fun = Lap_fun # which Laplacian operator to use
# initial conditions for u,v arrays
# I find that the Gray-Scott model needs these types of initial conditions
if (rdtype=='GS'):
self.u = np.zeros((n,n)) + 1.0
self.v = np.zeros((n,n))
for k in range(0,int(n/10)):
i = int(n*np.random.rand()) # randomly chose locations to set
j = int(n*np.random.rand())
self.v[i,j]=np.random.rand() # set to number in [0,1)
else: # whereas Brusellator and FitzhughNag models seem to work with random,
# near zero initial conditions
self.u = 0.05*np.random.rand(n, n) # initial u data uniform distribution
self.v = 0.05*np.random.rand(n, n) # initial v data
# axis info
self.line0, = ax[0].plot([], [], 'k-')
self.line1, = ax[1].plot([], [], 'k-')
self.fig = fig # passing this just in case we want it
self.ax = ax
self.ax[0].set_aspect('equal') # so square plots
self.ax[1].set_aspect('equal')
# show the u,v images initial conditions
im0=self.ax[0].imshow(self.u,cmap='jet')
im1=self.ax[1].imshow(self.v,cmap='hsv')
#self.fig.colorbar(im0, ax=ax[0],fraction=0.046, pad=0.04)
#self.fig.colorbar(im1, ax=ax[1],fraction=0.046, pad=0.04)
# I wish I could get the colorbar to work!
fac_u= self.Du*self.dt/self.dx**2 # relevant for checking cfl condition
fac_v= self.Dv*self.dt/self.dx**2
if (fac_u>1):
print('cfl warning Du', fac_u)
if (fac_v>1):
print('cfl warning Dv', fac_v)
# initialization for animation
def init(self):
lines = []
self.success = 0
self.line0.set_data([], [])
self.line1.set_data([], [])
return self.line0,self.line1,
def update_nsteps(self):
# do nsteps, update u,v arrays
for ii in range(self.nsteps):
# compute diffusive terms, with Laplacian of your choice!
du = self.Du*self.Lap_fun(self.u, self.dx)
dv = self.Dv*self.Lap_fun(self.v, self.dx)
# compute reation terms
Ru,Rv = Reaction(self.u,self.v,self.alpha,self.beta,self.rdtype)
# update u,v arrays
self.u = self.u + (du + Ru)*self.dt
self.v = self.v + (dv + Rv)*self.dt
def return_uv(self):
return self.u,self.v
# called each display step!
def __call__(self, i):
# This way the plot can continuously run ?
if i == 0:
return self.init()
self.success += 1 #otherwise
self.update_nsteps()
# do nsteps x timestep for each display
im0=self.ax[0].imshow(self.u,cmap='jet')
im1=self.ax[1].imshow(self.v,cmap='hsv')
#self.fig.colorbar(im0, ax=ax[0])
#self.fig.colorbar(im1, ax=ax[1])
# it would be nice to have colorbars, but they screw up in the animation
return self.line0, self.line1,
# set up the Brusselator model with some nice parameters
fig, ax = plt.subplots(1,2,figsize=(8,5))
ud_Br = Update_RD(fig,ax, alpha=5.0,beta=9,Du=2,Dv=22,\
n=100,dt=0.005,dx=1,rdtype='Br',nsteps=200)
anim_Br = animation.FuncAnimation(fig, ud_Br, frames=np.arange(50), init_func=ud_Br.init,
interval=100, blit=True)
# with matplotlib notebook this seems to run here, with matplotlib inline use HTML
# show the animation, this takes a few minutes to do the calculation
#HTML(anim_Br.to_html5_video())
HTML(anim_Br.to_jshtml())
# image on the left is u, that on the right is v