Animating Brownian particle movement with Python? - python

I am attempting to create a Langevin simulation using python. Currently, I have code which updates the x and y coordinates of a single point based on the Langevin equations, and returns all of these positions in two arrays (x array and y array). I want to plot these changes in position as a moving scatter plot to show how the particle moves over time. How can I do this? Below is what I have so far:
# IMPORT STATEMENTS
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# CONSTANTS
v = 3.12e-5 # swimming speed of B. Subtilis [m/s]
M = 1 # moment of B. Subtilis
k = 1.38e-23 # Boltzmann constant [m^2kg/s^2K]
T = 293 # Room temperature [K]
eta = 0.1 # viscosity of water [Pa s]
a = 2e-6 # spherical cell radius [m]
Dr = k*T/8*np.pi*eta*a**3 # rotational diffusion coefficient of B. Subtilis
# ADJUSTABLE PARAMETERS
B = 1 # strength of the magnetic field [T]
t = 100 # time over which motion is observed [s]
dt = 1 # time step between recorded positions
N = 1000 # number of cells
#INITIAL CONDITIONS
theta_i = 0 # initial swimming orientation [radians]
xi = 0.001 # initial x position [m]
yi = 0.001 # initial y position [m]
x = []
y = []
# MAIN SCRIPT
for i in range (0,t,dt):
theta_j = (theta_i + M*B*np.sin(theta) + np.sqrt(2*Dr)*ksi)*dt
xj = (xi + v*np.cos(theta))*dt
yj = (yi + v*np.sin(theta))*dt
x.append(xj)
y.append(yj)
theta_i = theta_j
xi = xj
yi = yj

take a look at this:
https://github.com/mohammadjafariph/Brownian-Motion-Simulation
animated versions are available

Related

PDE 2D Colormap

Need help finishing the Code specifically steps 3 and 4.
Problem
Compute the temperature (K) profile throughout a cylindrical SiGe wire with thermal conductivity k = 4.2 W/(m*K), length L = 0.05 m, and radius R = 0.005 m.
The boundary conditions are given in the figure below. The solid lines correspond to zero-flux boundary conditions, the long-dashed line to open boundaries with a known temperature, and the short-dashed lines to open boundaries with known flux via convection (the listed T is the “ambient” T). In this figure, assume z (in m) varies in the horizontal direction while r (in m) varies in the vertical direction (the upper left corner is the origin).
Justify your approach. Plot the temperature distribution throughout the wire using a 2D color- map with proper labels. Include contour lines.
https://www.chegg.com/homework-help/questions-and-answers/problem-2-compute-temperature-k-profile-throughout-cylindrical-sige-wire-thermal-conductiv-q96105385
Code
# Problem 2
# Import the required modules
import numpy as np
import matplotlib.pyplot as plt
# Constants
k = 4.2 # Thermal conductivity in W/(m.K)
L = 0.05 # Length in m
R = 0.005 # Radius in m
T = 575 # Ambient temp. in K
T1 = 423 # K
h1 = 45 # kW/(m^2.K)
T2 = 348 # K
h2 = 650 # kW/(m^2.K)
Ta = 298 # K
h = 7.5 # kW/(m^2.K)
# Iteration parameters
maxit = 2000
tol = 0.0001 # Relative tolerance
merr = 1e5
lam = 1.4 # Parameter for convergence rate
# Setup grid
dr = 0.01
nr = int(R/dr) + 1
nz = int(L/dr) + 1
rr = np.linspace(0,R,num=nr,endpoint=True);
zz = np.linspace(0,L,num=nz,endpoint=True);
# Step 1 - Initial Guesses
M = np.ones((nz,nr)) # Create 2D array w/ ones (z = # of rows, r = # of col)
M = M*T # Matrix of T now
# Step 2 - Apply Boundary Conditions
M[0,0:nr] = T; M[-1,0:nr] = T1;
M[0:nz,0] = T2; M[0:nz,-1] = Ta;
# Step 3 and 4 - Apply LDE, walking over nodes
cc = 0; # Counter
a = k*dr*dr/(4*R)
while merr > tol:
Mold = np.copy(M) # Save current values to old
M[-1,-1]=(2*M[-2,-1]+2*M[-1,-2])/(4+a) # Corner
for j in range(1,nz-1):
M[j,-1]=(2*M[j,-2]+M[j-1,-1]+M[j+1,-1])/(4+a)
for i in range(1,nr-1):
M[-1,i]=(M[-1,i-1]+M[-1,i+1]+2*M[-2,i])/(4+a)
for i in range(1,70):
M[0,i]=(M[0,i-1]+M[0,i+1]+2*M[1,i])/(4+a)
for j in range(1,nz-1):
for i in range(1,nr-1):
M[j,i] = (M[j,i-1]+M[j,i+1]+M[j-1,i]+M[j+1,i])/(4+a)
M = lam*M+(1-lam)*Mold # Adjust for convergence rate
ea = np.abs((M-Mold)/Mold)
cc = cc + 1;
merr = np.max(ea)
# Plot color mesh
X, Y = np.meshgrid(rr, zz)
p = plt.pcolormesh(rr, zz, M, cmap="RdBu", shading="flat", vmin=0, vmax=100)
ct = plt.contour(X, Y, M, cmap="gray", levels=10, vmin=0, vmax=100)
c = plt.colorbar(p)
plt.xlabel("R (m)")
plt.ylabel("Z (m)")
c.set_label("Teamperature (K)")
plt.show()
# Print Results
print("Converged in %d iterations" % cc)
print("Max error is %f" % merr)
print("Mean Teamperature along central axis %f K" %(np.mean(M[:,0])))

Creating a structured non-rectangular mesh in python

I am trying to model the temperature distribution in a rocket nozzle, I am doing this with the heat equation solved using finite difference method. In order to apply this method I need my mesh to fit the geometry of my modelled rocket engine. I have trouble doing this as the meshgrid function seems to only work for rectangular mesh's.
I tried using the code bellow, and want the mesh to only go up to the red line, but cannot find a way of doing this.
import numpy as np
import matplotlib.pyplot as pl
from mpl_toolkits import mplot3d
# Grid spacing size
Dx = 0.1
L = 10
L_T = 2
D_T = 1
D_C = 2
D_E = 5
maxtime = 10
# forming the x grid spacing
x = np.arange(0,L,Dx)
y = np.zeros(len(x))
# factor to scale loop so have integers
m = 10
# gradient of the engine line
M = (D_C-D_T)/(L_T)
#inital height of engine is at combustion point.
y[0] = D_C
for i in range(1,int(L_T*m),int(Dx*m)):
y[i] = y[i-1] - M*Dx
#Y[i] = np.arange(0,y[i])
for i in range(int(L_T*m),int(L*m),int(Dx*m)):
y[i] = y[i-1] + M*Dx
# set meshgrids
Xg , Yg = np.meshgrid(x,y)
pl.scatter(Xg,Yg)
pl.plot(x,y,c='red')

Problem with earth orbit plot using python

I am trying to write a code for the orbit of the earth in SI using a symplectic integrator, my attempt is as follows:
import numpy as np
import matplotlib.pyplot as plt
#Set parameters
G = 6.67348e-11
mEar = 5.972e24
mSun = 1.989e30
def earth_orbit(x0, y0, vx0, vy0, N):
dt = 1/N #timestep
pos_arr = np.zeros((N,2)) #empty array to store position
vel_arr = np.zeros((N,2)) #empty array to store velocities
#Initial conditions
# x0 = x
# y0 = y
# vx0 = vx
# vy0 = vy
pos_arr[0] = (x0,y0) #set the intial positions in the array
vel_arr[0] = (vx0,vy0) #set the initial velocities in the array
#Implement Verlet Algorithm
for k in range (N-1):
pos_arr[k+1] = pos_arr[k] + vel_arr[k]*dt #update positions
force = -G * mSun * mEar * pos_arr[k+1] / (np.linalg.norm(pos_arr[k+1])**3) #force calculation
vel_arr[k+1] = vel_arr[k] + (force/mEar) * dt #update velocities
#Plot:
plt.plot(pos_arr, 'go', markersize = 1, label = 'Earth trajectory')
# plt.plot(0,0,'yo', label = 'Sun positon') # yellow marker
# plt.plot(pos_arr[0],'bo', label = 'Earth initial positon') # dark blue marker
plt.axis('equal')
plt.xlabel ('x')
plt.ylabel ('y')
return pos_arr, vel_arr
earth_orbit(149.59787e9, 0, 0, 29800, 1000)
The output is 2 dots and I can't figure out if this is a unit issue or a calculation issue?
Display the trajectory
pos_arr contains the x and y coordinates in its columns. To display the whole trajectory, plt.plot(pos_arr[:,0], pos_arr[:,1]) can thus be used. I would prefer to use plt.plot(*pos_arr.T) as a shorter alternative. The line that displays the trajectory must be replaced by:
plt.plot(*pos_arr.T, 'g', label = 'Earth trajectory')
Change the timestep
Here the timestep (in second) is chosen as 1/N, where N is the number of iterations. So, the total duration of the simulation is equal to timestep * N = 1 second ! For N=1000, you can instead try with timestep = 3600*12 (half-day), so that the total duration is a little less than 1.5 years. I suggest passing the duration as a parameter of the function earth_orbit and then setting timestep as duration / N.
def earth_orbit(x0, y0, vx0, vy0, N=1000, duration=3.15e7):
dt = duration / N
...
As said in the comments, this is not the Verlet algorithm, but the symplectic Euler algorithm. The difference is in the initialization, but in comparing against a more exact reference solution and with several step sizes, the difference in the orders, 2 vs. 1, will be quite visible.
A short change to the time loop ensuring that the velocities are at the half-time steps as required for Leapfrog Verlet could look like this:
def force(pos): return -G * mSun * mEar * pos_arr[k+1] / (np.linalg.norm(pos_arr[k+1])**3) #force calculation
pos_arr[0] = (x0,y0) #set the intial positions in the array
vel_arr[0] = (vx0,vy0) #set the initial velocities in the array
vel_arr[0] += (force(pos_arr[0])/mEar) * (0.5*dt) #correct for velocity at half-time
#Implement Verlet Algorithm
for k in range (N-1):
pos_arr[k+1] = pos_arr[k] + vel_arr[k] * dt #update positions
vel_arr[k+1] = vel_arr[k] + (force(pos_arr[k+1])/mEar) * dt #update velocities

Want to create an animation based on a function that generates plots

I want to create an animation based on running a bunch of plots together. I just can't figure out how to get it to work for my purposes, this is the code I'm trying to work with. It generates a bunch of plots, but I want it to create an animation.
pi = 3.14159
velocity = 220 #kilometers per second
def dtheta(r): #creating a function that gives angular velocity based on distance from galactic center
dtheta = velocity/r #This function comes from the equation for angular velocity, ω=v/r, and ω =
dtheta/dt, which is what our function represents
return dtheta
#Creating frames at specific times for a set of distances
velocity = 220 #in km/s or pc/My
frames = 11
tstart = 0 #in units of Million Years
tfinal = 1
Stars= 25 #The number of stars being observed, equally spaced from 2 to 20 parsecs from the galactic center
t = np.linspace(tstart,tfinal,frames)
r = np.linspace(2,20,Stars)
TimeMatrix = []
for k in t:
snapshot = list([k*dtheta(r) for r in r]) # creating a list of the positions of a set of stars for a given time = k
print()
print('t =', k, 'Million Years')
plt.axes(projection = 'polar')
plt.ylim(0,22)
plt.plot(snapshot, r, 'ok')
plt.show()
TimeMatrix.append(list(snapshot))
def plotfunction(n):
plt.axes(projection = 'polar')
plt.ylim(0,22)
return plt.plot(TimeMatrix[n],r,'ok')
plotfunction(1) #needs integer input, pulls out the nth frame of the above series of plots
Anything would help, thanks!
Here's one way to do it:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
pi = 3.14159
velocity = 220 #kilometers per second
def dtheta(r): #creating a function that gives angular velocity based on distance from galactic center
dtheta = velocity/r #This function comes from the equation for angular velocity, ω=v/r, and ω =
#dtheta/dt, which is what our function represents
return dtheta
#Creating frames at specific times for a set of distances
velocity = 220 #in km/s or pc/My
frames = 11
tstart = 0 #in units of Million Years
tfinal = 1
Stars= 25 #The number of stars being observed, equally spaced from 2 to 20 parsecs from the galactic center
t = np.linspace(tstart,tfinal,frames)
r = np.linspace(2,20,Stars)
plt.figure(figsize=(12,4))
plt.axes(projection = 'polar')
plt.ylim(0,22)
snapi = plt.plot([t[0]*dtheta(i) for i in r] , r, 'ok', lw=1.5)
plt.ion() # set interactive mode
plt.show()
for i,snap in enumerate(t):
# for l in snapi:
# l.remove()
# del l
snapp=[snap*dtheta(k) for k in r]
snapi = plt.plot(snapp, r, 'ok', lw=1.5)
plt.legend()
plt.gcf().canvas.draw()
plt.pause(2)

Method to uniformly randomly populate a disk with points in python

I have an application that requires a disk populated with 'n' points in a quasi-random fashion. I want the points to be somewhat random, but still have a more or less regular density over the disk.
My current method is to place a point, check if it's inside the disk, and then check if it is also far enough away from all other points already kept. My code is below:
import os
import random
import math
# ------------------------------------------------ #
# geometric constants
center_x = -1188.2
center_y = -576.9
center_z = -3638.3
disk_distance = 2.0*5465.6
disk_diam = 5465.6
# ------------------------------------------------ #
pts_per_disk = 256
closeness_criteria = 200.0
min_closeness_criteria = disk_diam/closeness_criteria
disk_center = [(center_x-disk_distance),center_y,center_z]
pts_in_disk = []
while len(pts_in_disk) < (pts_per_disk):
potential_pt_x = disk_center[0]
potential_pt_dy = random.uniform(-disk_diam/2.0, disk_diam/2.0)
potential_pt_y = disk_center[1]+potential_pt_dy
potential_pt_dz = random.uniform(-disk_diam/2.0, disk_diam/2.0)
potential_pt_z = disk_center[2]+potential_pt_dz
potential_pt_rad = math.sqrt((potential_pt_dy)**2+(potential_pt_dz)**2)
if potential_pt_rad < (disk_diam/2.0):
far_enough_away = True
for pt in pts_in_disk:
if math.sqrt((potential_pt_x - pt[0])**2+(potential_pt_y - pt[1])**2+(potential_pt_z - pt[2])**2) > min_closeness_criteria:
pass
else:
far_enough_away = False
break
if far_enough_away:
pts_in_disk.append([potential_pt_x,potential_pt_y,potential_pt_z])
outfile_name = "pt_locs_x_lo_"+str(pts_per_disk)+"_pts.txt"
outfile = open(outfile_name,'w')
for pt in pts_in_disk:
outfile.write(" ".join([("%.5f" % (pt[0]/1000.0)),("%.5f" % (pt[1]/1000.0)),("%.5f" % (pt[2]/1000.0))])+'\n')
outfile.close()
In order to get the most even point density, what I do is basically iteratively run this script using another script, with the 'closeness' criteria reduced for each successive iteration. At some point, the script can not finish, and I just use the points of the last successful iteration.
So my question is rather broad: is there a better way to do this? My method is ok for now, but my gut says that there is a better way to generate such a field of points.
An illustration of the output is graphed below, one with a high closeness criteria, and another with a 'lowest found' closeness criteria (what I want).
A simple solution based on Disk Point Picking from MathWorld:
import numpy as np
import matplotlib.pyplot as plt
n = 1000
r = np.random.uniform(low=0, high=1, size=n) # radius
theta = np.random.uniform(low=0, high=2*np.pi, size=n) # angle
x = np.sqrt(r) * np.cos(theta)
y = np.sqrt(r) * np.sin(theta)
# for plotting circle line:
a = np.linspace(0, 2*np.pi, 500)
cx,cy = np.cos(a), np.sin(a)
fg, ax = plt.subplots(1, 1)
ax.plot(cx, cy,'-', alpha=.5) # draw unit circle line
ax.plot(x, y, '.') # plot random points
ax.axis('equal')
ax.grid(True)
fg.canvas.draw()
plt.show()
It gives.
Alternatively, you also could create a regular grid and distort it randomly:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.tri as tri
n = 20
tt = np.linspace(-1, 1, n)
xx, yy = np.meshgrid(tt, tt) # create unit square grid
s_x, s_y = xx.ravel(), yy.ravel()
ii = np.argwhere(s_x**2 + s_y**2 <= 1).ravel() # mask off unwanted points
x, y = s_x[ii], s_y[ii]
triang = tri.Triangulation(x, y) # create triangluar grid
# distort the grid
g = .5 # distortion factor
rx = x + np.random.uniform(low=-g/n, high=g/n, size=x.shape)
ry = y + np.random.uniform(low=-g/n, high=g/n, size=y.shape)
rtri = tri.Triangulation(rx, ry, triang.triangles) # distorted grid
# for circle:
a = np.linspace(0, 2*np.pi, 500)
cx,cy = np.cos(a), np.sin(a)
fg, ax = plt.subplots(1, 1)
ax.plot(cx, cy,'k-', alpha=.2) # circle line
ax.triplot(triang, "g-", alpha=.4)
ax.triplot(rtri, 'b-', alpha=.5)
ax.axis('equal')
ax.grid(True)
fg.canvas.draw()
plt.show()
It gives
The triangles are just there for visualization. The obvious disadvantage is that depending on your choice of grid, either in the middle or on the borders (as shown here), there will be more or less large "holes" due to the grid discretization.
If you have a defined area like a disc (circle) that you wish to generate random points within you are better off using an equation for a circle and limiting on the radius:
x^2 + y^2 = r^2 (0 < r < R)
or parametrized to two variables
cos(a) = x/r
sin(a) = y/r
sin^2(a) + cos^2(a) = 1
To generate something like the pseudo-random distribution with low density you should take the following approach:
For randomly distributed ranges of r and a choose n points.
This allows you to generate your distribution to roughly meet your density criteria.
To understand why this works imagine your circle first divided into small rings of length dr, now imagine your circle divided into pie slices of angle da. Your randomness now has equal probability over the whole boxed area arou d the circle. If you divide the areas of allowed randomness throughout your circle you will get a more even distribution around the overall circle and small random variation for the individual areas giving you the psudo-random look and feel you are after.
Now your job is just to generate n points for each given area. You will want to have n be dependant on r as the area of each division changes as you move out of the circle. You can proportion this to the exact change in area each space brings:
for the n-th to n+1-th ring:
d(Area,n,n-1) = Area(n) - Area(n-1)
The area of any given ring is:
Area = pi*(dr*n)^2 - pi*(dr*(n-1))
So the difference becomes:
d(Area,n,n-1) = [pi*(dr*n)^2 - pi*(dr*(n-1))^2] - [pi*(dr*(n-1))^2 - pi*(dr*(n-2))^2]
d(Area,n,n-1) = pi*[(dr*n)^2 - 2*(dr*(n-1))^2 + (dr*(n-2))^2]
You could expound this to gain some insight on how much n should increase but it may be faster to just guess at some percentage increase (30%) or something.
The example I have provided is a small subset and decreasing da and dr will dramatically improve your results.
Here is some rough code for generating such points:
import random
import math
R = 10.
n_rings = 10.
n_angles = 10.
dr = 10./n_rings
da = 2*math.pi/n_angles
base_points_per_division = 3
increase_per_level = 1.1
points = []
ring = 0
while ring < n_rings:
angle = 0
while angle < n_angles:
for i in xrange(int(base_points_per_division)):
ra = angle*da + da*math.random()
rr = r*dr + dr*random.random()
x = rr*math.cos(ra)
y = rr*math.sin(ra)
points.append((x,y))
angle += 1
base_points_per_division = base_points_per_division*increase_per_level
ring += 1
I tested it with the parameters:
n_rings = 20
n_angles = 20
base_points = .9
increase_per_level = 1.1
And got the following results:
It looks more dense than your provided image, but I imagine further tweaking of those variables could be beneficial.
You can add an additional part to scale the density properly by calculating the number of points per ring.
points_per_ring = densitymath.pi(dr**2)*(2*n+1)
points_per_division = points_per_ring/n_angles
This will provide a an even better scaled distribution.
density = .03
points = []
ring = 0
while ring < n_rings:
angle = 0
base_points_per_division = density*math.pi*(dr**2)*(2*ring+1)/n_angles
while angle < n_angles:
for i in xrange(int(base_points_per_division)):
ra = angle*da + min(da,da*random.random())
rr = ring*dr + dr*random.random()
x = rr*math.cos(ra)
y = rr*math.sin(ra)
points.append((x,y))
angle += 1
ring += 1
Giving better results using the following parameters
R = 1.
n_rings = 10.
n_angles = 10.
density = 10/(dr*da) # ~ ten points per unit area
With a graph...
and for fun you can graph the divisions to see how well it is matching your distriubtion and adjust.
Depending on how random the points need to be, it may be simple enough to just make a grid of points within the disk, and then displace each point by some small but random amount.
It may be that you want more randomness, but if you just want to fill your disc with an even-looking distribution of points that aren't on an obvious grid, you could try a spiral with a random phase.
import math
import random
import pylab
n = 300
alpha = math.pi * (3 - math.sqrt(5)) # the "golden angle"
phase = random.random() * 2 * math.pi
points = []
for k in xrange(n):
theta = k * alpha + phase
r = math.sqrt(float(k)/n)
points.append((r * math.cos(theta), r * math.sin(theta)))
pylab.scatter(*zip(*points))
pylab.show()
Probability theory ensures that the rejection method is an appropriate method
to generate uniformly distributed points within the disk, D(0,r), centered at origin and of radius r. Namely, one generates points within the square [-r,r] x [-r,r], until a point falls within the disk:
do{
generate P in [-r,r]x[-r,r];
}while(P[0]**2+P[1]**2>r);
return P;
unif_rnd_disk is a generator function implementing this rejection method:
import matplotlib.pyplot as plt
import numpy as np
import itertools
def unif_rnd_disk(r=1.0):
pt=np.zeros(2)
while True:
yield pt
while True:
pt=-r+2*r*np.random.random(2)
if (pt[0]**2+pt[1]**2<=r):
break
G=unif_rnd_disk()# generator of points in disk D(0,r=1)
X,Y=zip(*[pt for pt in itertools.islice(G, 1, 1000)])
plt.scatter(X, Y, color='r', s=3)
plt.axis('equal')
If we want to generate points in a disk centered at C(a,b), we have to apply a translation to the points in the disk D(0,r):
C=[2.0, -3.5]
plt.scatter(C[0]+np.array(X), C[1]+np.array(Y), color='r', s=3)
plt.axis('equal')

Categories

Resources