├── .gitignore ├── wavepacket_1D.py ├── mandelbrotzoom.py ├── wavepacket_2D.py ├── fouriercontour.py ├── README.md ├── ballsincircle.py ├── softbody.py └── stringart.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /wavepacket_1D.py: -------------------------------------------------------------------------------- 1 | import matplotlib as mpl 2 | import matplotlib.pyplot as plt 3 | from matplotlib.patches import Rectangle 4 | import numpy as N 5 | import scipy.linalg as SLA 6 | import os 7 | 8 | os.system('mkdir frames') 9 | 10 | mpl.rcParams['figure.figsize'] = 16/9*6,6 11 | 12 | ymin = -2 13 | xmin = ymin*16/9 14 | xmax = abs(xmin);ymax = abs(ymin) 15 | 16 | NN = 2000 # number of x coordinates 17 | pwp = 40 # momentum 18 | sig = .15 # width of wave packet 19 | Vmax = pwp**2/2*1. # height of tunneling barrier 20 | noframes = 200 # number of frames 21 | 22 | dx = (2*xmax-2*xmin)/NN 23 | xvec = N.arange(xmin*2,xmax*2,dx) 24 | b = -1/2/dx**2 25 | 26 | # finite difference Laplacian 27 | diag = -N.diagflat(2*b*N.ones(NN)) 28 | offdiag = N.diagflat(b*N.ones(NN-1),1) 29 | D2 = diag+offdiag+offdiag.transpose() 30 | 31 | # add potential to diagonal 32 | pot = N.zeros(NN) 33 | for ii in range(NN): 34 | if (ii>int(.495*NN) and ii0: 63 | plt.fill(xvec,pot/abs(b)*.5,linewidth=1,color='gray') 64 | 65 | else: 66 | plt.fill(xvec,pot/abs(b)*.5,linewidth=1,color='black') 67 | 68 | plt.plot(xvec,5*N.abs(Psi)**2,linewidth=1,color='red') 69 | plt.savefig('frames/fig'+f"{tt:04d}.png", 70 | bbox_inches='tight',pad_inches=0) 71 | plt.clf() 72 | plt.close("fig") 73 | -------------------------------------------------------------------------------- /mandelbrotzoom.py: -------------------------------------------------------------------------------- 1 | import numpy as N 2 | import matplotlib as mpl 3 | import matplotlib.pyplot as plt 4 | from matplotlib.colors import LinearSegmentedColormap 5 | import os,time 6 | import warnings 7 | warnings.filterwarnings("ignore", category=RuntimeWarning, message="overflow encountered") 8 | warnings.filterwarnings("ignore", category=RuntimeWarning, message="invalid value encountered") 9 | 10 | os.system('mkdir frames') 11 | 12 | # customized colormap 13 | mycolor = LinearSegmentedColormap.from_list('mycmap',['gray','black']) 14 | 15 | # set image resolution for frames to be saved 16 | yr = 10 17 | xr = int(yr*16/9) 18 | mpl.rcParams['figure.figsize'] = xr,yr 19 | 20 | # function to determine whether coordinate belongs to MB set or not 21 | def mandelbrot_set(xmin, xmax, ymin, ymax, width, height, max_iterations): 22 | x = N.linspace(xmin, xmax, width).reshape((1, width)) 23 | y = N.linspace(ymin, ymax, height).reshape((height, 1)) 24 | c = x + y * 1j 25 | z = N.zeros_like(c) 26 | 27 | for i in range(max_iterations): 28 | z = z**2 + c 29 | 30 | mandelbrot = (abs(z) < 2).astype(int) 31 | 32 | return mandelbrot 33 | 34 | height = int(2160/2) # resolution of image (does not need to match the resolution of the saved frames) 35 | width = int(16/9*height) # aspect ratio = 16/9 36 | 37 | Nmax = 50 # number of frames between the initial and final frame (i.e., sets the zoom speed) 38 | zfac = 20/Nmax 39 | itermax = 600 # this number depends on how deep the zoom is. Deeper means larger itermax 40 | myiters = N.linspace(25,600,Nmax) 41 | 42 | print('\nCalculation starts. Note, calculation time increases with zoom level.') 43 | 44 | for ii in range(0,Nmax): 45 | # present frame zoom (XMIN, XMAX, etc.) 46 | XMIN = -1*16/9*2**(-ii*zfac) - .74877 # .74877 is the x coordinate (real axis) of the point we zoom towards 47 | XMAX = 1*16/9*2**(-ii*zfac) - .74877 48 | YMIN = -1*2**(-ii*zfac) - .065176 # .065176 is the y coordinate (imaginary axis) of the point we zoom towards 49 | YMAX = 1*2**(-ii*zfac) - .065176 50 | ext = [XMIN,XMAX,YMIN,YMAX] 51 | 52 | # function that aims to provide a sufficient number of iterations at a specific zoom level 53 | iters = int(15+zfac*30/abs(XMIN-XMAX)**(.25/zfac)-0*4*N.log(abs(XMIN-XMAX))) 54 | 55 | iters = int(myiters[ii]) 56 | mandelbrot = mandelbrot_set(ext[0],ext[1],ext[2],ext[3],width,height,iters) 57 | plt.imshow(mandelbrot, cmap=mycolor, extent=ext) 58 | plt.axis('off') 59 | plt.savefig('frames/fig'+f"{ii:06d}.png",bbox_inches='tight',pad_inches=0) 60 | plt.close("fig") 61 | 62 | if N.mod(ii,round(Nmax/20)) == 0: 63 | print(round(100*ii/Nmax),'% of the frames saved') 64 | -------------------------------------------------------------------------------- /wavepacket_2D.py: -------------------------------------------------------------------------------- 1 | import numpy as N 2 | import matplotlib.pyplot as plt 3 | import matplotlib as mpl 4 | from scipy import sparse 5 | import scipy.linalg as SLA 6 | from scipy.sparse.linalg import isolve 7 | import os, time 8 | 9 | os.system('mkdir frames') 10 | 11 | # main program PARAMS 12 | Nx = 200 13 | Ny = Nx # only tried for Nx=Ny 14 | tmax = 400 # number of frames 15 | xmax = 1.;ymax = 1. 16 | dx = 1./(Nx-1) 17 | a = 1j/2/dx**2 # time term: Adds to diagonal of FD Hamiltonian 18 | b = 1/4/dx**2. # off-diagonal elements of FD Hamiltonian 19 | TOL = 1e-6 # tolerance in iterative linear solver 20 | 21 | # initial Gaussian and its momentum 22 | sig = .07 # width of Gaussian 23 | cwp = [Nx/3,Ny/4] # start position 24 | pwp = [-4*Nx/10,6*Nx/10] # momentum (2pi/lambda) 25 | 26 | # potential matrix 27 | V = N.zeros((Nx,Ny)) 28 | # define whatever potential you wish here 29 | 30 | # Gaussian wave packet 31 | psi = [N.exp(1j*(pwp[0]*(ii-cwp[0])+pwp[1]*(jj-cwp[1]))*dx-\ 32 | ((ii-cwp[0])**2+(jj-cwp[1])**2)*dx**2/(2*sig**2))\ 33 | for ii in range(Nx) for jj in range(Ny)] 34 | 35 | psi = N.array(psi).reshape(Nx,Ny) 36 | psi = psi/SLA.norm(psi) 37 | 38 | # create FD Hamiltonian for one slice of the domain 39 | lhs = N.zeros((3,Nx)) 40 | lhs[0,:] = b*N.ones(Nx) # upper off-diagonal 41 | lhs[2,:] = lhs[0,::-1] # lower off-diagonal 42 | 43 | # returns FD Ham in sparse csr format to be used in an iterative solver below 44 | # only the diagonal is updated in LHS/LHS2 45 | 46 | def LHS(idx): 47 | mtx = sparse.spdiags([lhs[0],2*a-2*b-V[:,idx]/2,lhs[2]],[1,0,-1],Nx,Nx) 48 | return sparse.csr_matrix(mtx) 49 | 50 | def LHS2(idx): 51 | mtx = sparse.spdiags([lhs[0],2*a-2*b-V[idx,:]/2,lhs[2]],[1,0,-1],Nx,Nx) 52 | return sparse.csr_matrix(mtx) 53 | 54 | def RHS(idx): 55 | return N.array((2*a+2*b+V[:,idx]/2)*psi[:,idx]-\ 56 | b*N.array([psi[ii,min(Nx-1,idx+1)]+psi[ii,max(0,idx-1)] for ii in range(Nx)])) 57 | 58 | def RHS2(idx): 59 | return N.array((2*a+2*b+V[idx,:]/2)*psi[:,idx]-\ 60 | b*N.array([psi[jj,min(Nx-1,idx+1)]+psi[jj,max(0,idx-1)] for jj in range(Nx)])) 61 | 62 | def plotandsave(tt,psi): 63 | plt.imshow(N.abs(psi)**2) 64 | # add possible line plots, etc. here 65 | plt.savefig('frames/fig'+f"{tt:04d}.png") 66 | 67 | t1 = time.process_time() 68 | 69 | for tt in range(tmax): 70 | plotandsave(tt,psi) 71 | psi = N.array([isolve.gmres(LHS(jj),RHS(jj),tol=TOL)[0] for jj in range(Nx)]) 72 | psi = N.array([isolve.gmres(LHS2(ii),RHS2(ii),tol=TOL)[0] for ii in range(Nx)]) 73 | psi = psi/SLA.norm(psi) 74 | 75 | if N.mod(tt,round(tmax/10)) == 0: 76 | print(round(100*tt/tmax),'% of the frames saved') 77 | 78 | tm = time.process_time()-t1 79 | print('\nAll frames saved in ',round(tm,1),'s') 80 | -------------------------------------------------------------------------------- /fouriercontour.py: -------------------------------------------------------------------------------- 1 | import numpy as N 2 | import matplotlib.pyplot as plt 3 | import os,time 4 | 5 | os.system('mkdir ./frames') # creates a folder in which the frames are saved 6 | 7 | timesteps = 121 8 | dt = 1/timesteps # incremental time between subsequent frames 9 | factor = 3; # number of periods 10 | tsRange = timesteps*factor 11 | 12 | nn = 1000 # number of points defining a function when made here 13 | mm = 7 # number of basis functions in the F-expansion 14 | lw = 1 # line width of plots 15 | 16 | # define function in complex plane 17 | function2=[] 18 | for ii in range(nn): 19 | if iiint(nn/4) and iiint(2*nn/4) and iiint(3*nn/4): 29 | function2.append(2-4*(ii-int(3*nn/4))/nn) 30 | 31 | function2 = N.array(function2)+.1*1j 32 | 33 | # returns positions for all rods at each time stamp 34 | def myfourier(function,nn,mm): 35 | cf = [1./nn*N.sum([function[ii]*N.exp(-2*N.pi*1j*ii*jj/nn)\ 36 | for ii in range(0,nn)]) for jj in range(-mm,mm+1)] 37 | fourier = [N.sum([cf[ii]*N.exp(2*N.pi*1j*(ii-mm)*tt*dt)\ 38 | for ii in range(0,2*mm+1)]) for tt in range(0,timesteps+1)] 39 | # sort frequencies to match Fourier coefficients 40 | mmAsUsed = [ii for ii in range(-mm,mm+1)]; 41 | mmSorted = sorted(mmAsUsed,key=abs) 42 | idx = [(N.abs(mmSorted[ii]-N.array(mmAsUsed))).argmin() for ii in range(2*mm+1)] 43 | apprY = [];Ytvec = [];barvec = [] 44 | 45 | for ttmp in range(tsRange): 46 | bars = [] 47 | bars.append(cf[idx[0]]) 48 | 49 | for ii in range(1,2*mm+1): 50 | bars.append(bars[ii-1]+cf[idx[ii]]*N.exp(2*N.pi*1j*(idx[ii]-mm)*ttmp*dt)) 51 | 52 | barvec.append(bars) 53 | apprY.append(N.imag(bars[ii])) 54 | Ytvec.append(N.real(bars[ii])) 55 | 56 | return barvec,apprY,Ytvec 57 | 58 | t1 = time.process_time() 59 | fig2 = myfourier(N.array(function2),nn,mm) 60 | tm = time.process_time()-t1 61 | print('\nFourier expansion done in:',round(tm,2),'s') 62 | 63 | for ttmp in range(tsRange): 64 | plt.plot(N.array(fig2[2][:ttmp+1]), 65 | N.array(fig2[1][:ttmp+1]),color=[0,1,0],linewidth=lw) 66 | 67 | for ii in range(1,2*mm+1): 68 | plt.plot(N.real(fig2[0][ttmp][ii-1:ii+1]), 69 | N.imag(fig2[0][ttmp][ii-1:ii+1]),color='magenta',linewidth=lw) 70 | 71 | plt.xlim([.7,2.3]) 72 | plt.ylim([-.1,1.3]) 73 | plt.savefig('./frames/fig'+f"{ttmp:05d}.png") 74 | plt.close() 75 | 76 | if N.mod(ttmp,round(tsRange/10)) == 0: 77 | print(round(100*ttmp/tsRange),'% of the frames saved') 78 | 79 | print('All',tsRange,' frames saved') 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # animations_ag 2 | 3 | - [Introduction](#introduction) 4 | - [Usage](#usage) 5 | - [Description](#description) 6 | 7 | ## Introduction 8 | Short easy-to-use python scripts that generate frames for animating specific concepts in physics and mathematics. 9 | 10 | ## Usage 11 | To run a script, open Terminal, stand in the directory of the script and type: 12 | 13 | python (or python3)