'Generating isotropic steps with lengths distributed as exp(-x/lambda)
I'm writing a code about radiation transportation and have generated isotropic vectors as shown by my code. I'm now unsure about how to generate isotropic steps with lengths distributed as exp(-x/lambda). Any suggestions?
def isotropic_unit_vectors():
nparticles = 1000
lambda_a = 45
lambda_s = 0.3
sigma_a = 1/lambda_a
sigma_s = 1/lambda_s
sigma_T = sigma_a + sigma_s
lambda_T = 1/sigma_T
u = np.random.uniform(low = 0, high =1, size = 1000)
step = -lambda_T*np.log(u)
theta = 2*np.pi*np.random.rand(nparticles)
phi = np.arccos(2*np.random.rand(nparticles)-1)
dx = step*np.cos(theta)*np.cos(phi)
dy = step*np.cos(theta)*np.sin(phi)
dz = step*np.sin(theta)
rand = np.column_stack((dx,dy,dz))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(dx, dy, dz, c='r', marker='.')
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
plt.show()
return rand
edit: I want multiple particles to start at 0,0,0 then move randomly.
Solution 1:[1]
Ok, you have right bits and pieces, but they are not quite right together. Here what it might look like, Python 3.7, Anaconda WIndows 10 x64
import numpy as np
def isotropic_unit_vectors():
"""Generates troika of directional cosines isotropically on the sphere"""
cs_theta = 2.0 * np.random.random() - 1.0
sn_theta = np.sqrt((1.0 - cs_theta)*(1.0 + cs_theta))
phi = 2.0 * np.pi * np.random.random()
wx = sn_theta * np.cos(phi)
wy = sn_theta * np.sin(phi)
wz = cs_theta
return (wx, wy, wz)
def random_step(?):
u = 1.0 - np.random.random() # NB! avoids log(0)
return -? * np.log(u)
nparticles = 100
?_a = 45 # absorbtion
?_s = 0.3 # scattering
# cross-sections
?_a = 1/?_a
?_s = 1/?_s
?_t = ?_a + ?_s
?_t = 1/?_t
trajectories = {} # dictionary for all trajectories
for k in range(0, nparticles):
trajectory = []
# initialize particle position
x = 0.0
y = 0.0
z = 0.0
print(f"Partile number {k+1}")
print(f" Position {x}, {y}, {z}")
trajectory.append((x, y, z))
while True:
(wx, wy, wz) = isotropic_unit_vectors() # sample direction
step = random_step(?_t)
# make step and find new position
x += wx * step
y += wy * step
z += wz * step
print(f" Position {x}, {y}, {z}")
trajectory.append((x, y, z))
# simulate absorbtion
if np.random.random() < ?_a/?_t:
trajectories[k] = trajectory
break # absorbtion, end of trajectory, next particle would be tracked
else:
pass # scattering, continue with this trajectory
Note, code is NOT vectorized, could be made better and faster with NumPy vectors.
UPDATE
Added code to keep all trajectories in the dictionary indexed by #. Trajectory itself is list of tuples, each tuple contains 3 coordinates of the walk
UPDATE II
If you add code after you got trajectories dictionary, it will plot one trajectory with vertices. Vertix color is defined by distance from (0,0,0).
from mpl_toolkits import mplot3d
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(projection='3d')
# Data for a three-dimensional line
k = 7 # print particle #8
trj = trajectories[k]
xline = [x for x, y, z in trj]
yline = [y for x, y, z in trj]
zline = [z for x, y, z in trj]
# plot trajectory
ax.plot3D(xline, yline, zline, 'gray')
# plot vertices
xpts = [x for x, y, z in trj]
ypts = [y for x, y, z in trj]
zpts = [z for x, y, z in trj]
rpts = [np.sqrt(x*x+y*y+z*z) for x, y, z in trj]
ax.scatter3D(xpts, ypts, zpts, c=rpts, cmap='hsv')
plt.show()
Solution 2:[2]
I assume you want to generate random number with CDF; F(x) = exp(-x/lambda), for valid support of x such that 0 <= F(x) <= 1.
Fact: CDF of any random number is uniformly distributed as uniform(0,1) (the CDF Inverse method). That is, generate a uniform(0,1) random number and set it equal to the CDF, written in terms of x. Then solve for x (See step 2).
Method You can follow a process as follows:
- Generate a random number uniformly between (0, 1), let's call is u1.
- Set u1 = exp(-x/lambda) => x = -lambda*log(u1)
- Save x in a list or something.
- Go back to step 1 until desired number of random numbers are generated.
Note:
If exp(-x/lambda) is the PDF and not the CDF, you can find the CDF by integrating the PDF and do the same steps above, with a change in step 2, depending on what the CDF is. For step 1, you can generate a unif(0,1) in Py using random.random()
.
Hope this helps.
Solution 3:[3]
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def isotropic_step_generator(l, size):
# l is the attenuation length (it's the l in the distribution exp(-x/l))
theta = theta = np.arccos(1-2*np.random.uniform(0, 1, size))
phi = np.random.uniform(0, 2*np.pi, size)
radius = -l * np.log(np.random.uniform(size=size))
x = radius * np.sin(theta) * np.cos(phi)
y = radius * np.sin(theta) * np.sin(phi)
z = radius * np.cos(theta)
return x, y, z
empty_array = np.empty((1000,3))
i=0
while i<1000:
empty_array[i] = isotropic_step_generator(45, 1) # attenuation length for water is 45 cm
i+=1
x_array = empty_array[:, 0]
y_array = empty_array[:, 1]
z_array = empty_array[:, 2]
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(x_array, y_array, z_array, s=7)
plt.show()
The above code generates isotropic steps with lengths distributed as exp(-x/lambda) and plots them for water. Note that you must use arccos for theta to prevent bunching of points towards the poles.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Ashwin |
Solution 3 | funny_little_frog |