due 09/19/2018 at 11:59pm
ODE_integration_exponential_integrator
and the actual Larmor radius. We call the difference between them the error $e$.When electric fields and magnetic fields are presents, charged particles do not simply gyrate, they move along the electric field direction also. When the electric field is purely perpendicular to B, the charge particle is said to drift, hence the name ExB drift. We are going to use ODE_integration_exponential_integrator
to compute the drift velocity of an electron inside a magnetic field $\vec B=(0,0,1)$ and in an electric field $\vec E=(100,0,0)$.
If you remember we had a little problem with OED_integration_E
. If we integrated our ODE for too long, then an electron inside a 1V electric field would eventually go faster than the speed of light. ODE_integration_exponential_integrator
takes into account relativistic correction and really avoid this problem.
Let's imagine that you want to build a particle accelerator using a steady electric field of $1kV/m$ to accelerate an electron to $0.999c_{light}$
2. How long should the accelerator be?
2. How long should it be if you ignore relativistic corrections, i.e. use OED_integration_E
?
import math
from math import sqrt as sqrt
import numpy as np
from scipy.linalg import expm
from numpy.linalg import norm
import matplotlib.pyplot as plt
import matplotlib.colors as colors
#to display inside the notebook!
%matplotlib inline
Let's look first at the impact of the impact of number of time steps and integration steps on ODE_integration_exponential_integrator
. For that we need to generate a two dimensional grid holding both the number of time steps and the number of iteration steps. We can reuse part of comp_E
from HW01-Problem 3 to do that. First, let's import all the required functions.
def get_E(r,t):
return np.array([0,0,0])
def get_B(r,t):
return np.array([0,0,1])
def Lorentz_factor(v):
v2=norm(v)**2
c=299792458
return 1./sqrt(1-v2/c**2)
def EM_field_tensor(E,B):
c=299792458
F_mu_nu=np.array([0,E[0]/c,E[1]/c,E[2]/c,E[0]/c,0,-B[2],B[1],E[1]/c,B[2],0,-B[0],E[2]/c,-B[1],B[0],0])
F_mu_nu.shape=(4,4)
return F_mu_nu
Now, we load ODE_integration_exponential_integrator
.
def ODE_integration_exponential_integrator(initial_position,initial_velocity,time_frame,n_steps,n_integrate):
c=299792458
loc=np.zeros((3,n_steps)) #3 for x,y,z
loc[:,0]=initial_position
v=initial_velocity
gamma=Lorentz_factor(v)
p_old=np.array([m*c,m*v[0],m*v[1],m*v[2]])*gamma
p_new=np.zeros(4)
t=0
dt=(time_frame[1]-time_frame[0])/n_steps
ds=dt/n_integrate
for i in range(1,n_steps,1): #solution of the momentum equation
B=get_B(loc[:,i-1],t)
E=get_E(loc[:,i-1],t)
F_mu_nu=EM_field_tensor(E,B)
p_new=np.matmul(expm(q/(m*gamma)*F_mu_nu*dt),p_old)
gamma=p_new[0]/(m*c)
P=np.zeros((4,4))
s=0.
dPds=np.eye(4)*ds/2
x=np.zeros(4)
for j in range(0,n_integrate,1): #trapezoidal integration to get the trajectory
s+=ds
if (i>0):
P+=dPds
dPds=expm(q/(m*gamma)*F_mu_nu*s)*ds/2
if (i==0):
P+=dPds
P+=dPds
x=np.matmul(P,p_old)/(m*gamma)
loc[:,i]=x[1:4]+loc[:,i-1] #new position
p_old=p_new
t+=dt
return loc
We will keep the same initial conditions as the ones used in Ch02. The charged particle will start at $(0,0,0)$ so we can get the Larmor radius easily, by taking the maximum value along the x-direction. We use the gyration period t_gyration
as our time unit, though it is not required as long as the run the integration by more than several periods.
m=9e-31
q=-1.6e-19
r_initial=np.array([0,0,0])
v_initial=np.array([1000,0,0])
t_gyration=m/(abs(q)*norm(get_B(r_initial,0)))*(2*math.pi)
r_Larmor=norm(v_initial)*m/(abs(q)*norm(get_B(r_initial,0)))
time_frame=[0,2*t_gyration]
We now write the loop to compute the error for both integration steps.
def comp_error(steps,n):
X=np.zeros(n)
Y=np.zeros(n)
err=np.zeros(n)
k=0
l=0
for i in range(steps[0],steps[1],int((steps[1]-steps[0])/n[0])):
if (k<n[0]): # in case the index goes beyond the array bounds
for j in range(steps[2],steps[3],int((steps[3]-steps[2])/n[1])):
if (l<n[1]): #ditto
r=ODE_integration_exponential_integrator(r_initial,v_initial,time_frame,i,j)
err[k,l]=abs(r_Larmor-r[0,:].max())/r_Larmor*100 #the error is in % of r_Larmor
X[k,l]=i
Y[k,l]=j
l+=1
l=0
k+=1
return X,Y,err
Let's run the code
steps=[5,30,5,30]
ns=[10,10]
x,y,err=comp_error(steps,ns)
We plot the error as a function of the number of steps and the number of integration steps. The error is plotted in $\%$ but on the logarithmic scale to be able to see across many orders of magnitude.
fig, ax = plt.subplots()
im = plt.imshow(err, cmap=plt.cm.jet, norm=colors.LogNorm(vmin=err.min(), vmax=err.max()),extent=steps)
im.set_interpolation('bilinear')
cb = fig.colorbar(im)
plt.xlabel('Number of steps', rotation=0)
plt.ylabel('Number of integration steps', rotation=90)
cb.ax.set_ylabel('$\log_{10}error(\%)$', rotation=-90)
cb.ax.yaxis.set_label_coords(4.5, 0.5)
plt.show()
The error is increasing with the number of integration steps (i.e. the number of steps to compute $\vec x$ from $\vec p$), but the error decreases as we increase the number of time steps to compute $\vec p_{new}$ from $\vec p_{old}$.
We first need to define our new electric field and magnetic field. It was defined for the previous problem with $\vec E=\vec 0$.
def get_E(r,t):
return np.array([100,0,0])
def get_B(r,t):
return np.array([0,0,1])
Then we define the particle properties and the initial conditions to solve our problem. We will place the particle at the origin $(0,0,0)$ so we can get the distance traveled easily. We will use the Larmor period $$\tau_{Larmor}=\frac{2\pi m}{qB}$$ and print it as the problem asks.
m=9e-31
q=-1.6e-19
r_initial=np.array([0,0,0])
v_initial=np.array([1000,0,0])
t_Larmor=m/(abs(q)*norm(get_B(r_initial,0)))*(2*math.pi)
print('t_Larmor=',t_Larmor*1e12,'ps')
t_max=5*t_Larmor
time_frame=[0,t_max]
t_Larmor= 35.34291735288517 ps
According to the lecture notes, we expect the particle to drift in the y-direction. using the function
r=ODE_integration_exponential_integrator(r_initial,v_initial,time_frame,n_steps,n_integration_steps)
So we look only at r[1,:]
. We have to increase the number of steps until the value of distance settles down.
for i in range(100,800,100):
r=ODE_integration_exponential_integrator(r_initial,v_initial,time_frame,i,5)
n_steps=i
print(r[1,n_steps-1])
1.7393292442390594e-08 1.7601841143998278e-08 1.7640535713319194e-08 1.765407272245231e-08 1.766033545504776e-08 1.7663736155163043e-08 1.766578607048881e-08
print('v=',r[1,n_steps-1]/t_max,'m/s')
v= 99.96789961679089 m/s
According to the lecture notes, the drift velocity should be $v_{E\times B}=\frac{100V/m}{1T}=100m/s$, as we just found numerically.
We start this problem all over again for a proton
m=1.6e-27
q=1.6e-19
t_Larmor=m/(abs(q)*norm(get_B(r_initial,0)))*(2*math.pi)
print('t_Larmor=',t_Larmor*1e9,'ns')
t_max=5*t_Larmor
time_frame=[0,t_max]
for i in range(100,600,100):
r=ODE_integration_exponential_integrator(r_initial,v_initial,time_frame,i,5)
n_steps=i
print(r[1,n_steps-1])
print('\n v=',r[1,n_steps-1]/t_max,'m/s')
t_Larmor= 62.83185307179586 ns 3.1899956405269496e-05 3.1538374970232926e-05 3.147051046727202e-05 3.144667023116798e-05 3.143561683239977e-05 v= 100.06267616038424 m/s
We get the right answer here also,i.e. $100m/s$. Indeed, both protons and electrons drift in the same direction. This is an important point, because this drift does not create charge separation. As a result, there is really no force that can counter interact that drift.
A direct consequence: if we try to confine a plasma inside a magnetic "bottle" and and electric fields perpendicular to B is present then the particles will drift out of the bottle.
We now use special relativity to limit the motion of electrons, and clamp their velocity below the speed of light. This is done automatically when we use ODE_integration_exponential_integrator
. As before, we start with redefining our electromagnetic quantities
def get_E(r,t):
return np.array([0,1,0])
def get_B(r,t):
return np.array([0,0,0])
Then we set our initial conditions, as defined in Ch02.
q=-1.6e-19
m=9.1e-31
c=299792458
r_initial=np.array([0,0,0])
v_initial=np.array([0,0,0])
time_frame=[0,3]
n_steps=3000 #this number of steps seems to give good convergence
dt=(time_frame[1]-time_frame[0])/n_steps
r=ODE_integration_exponential_integrator(r_initial,v_initial,time_frame,n_steps,3)
print('v=',abs((r[1,n_steps-1]-r[1,n_steps-2])/dt/c),'c')
v= 0.9998332349707846 c
Indeed we have now an electron which velocity is always below th speed of light. Thank you Prof. Einstein! Now let's answer the second question by using a much higher electric field.
def get_E(r,t):
return np.array([0,1e3,0])
def get_B(r,t):
return np.array([0,0,0])
We could write a code to optimize our answer. Instead we use a faster approach and change the final time to get the right answer.
time_frame=[0,41.9e-6]
n_steps=3000 #this number of steps seems to give good convergence
dt=(time_frame[1]-time_frame[0])/n_steps
r=ODE_integration_exponential_integrator(r_initial,v_initial,time_frame,n_steps,3)
print('v=',abs((r[1,n_steps-1]-r[1,n_steps-2])/dt/c),'c',' Length =',abs(r[1,n_steps-1]),'m')
v= 0.9990067793120605 c Length = 12050.908108883727 m
Note that with an electric field field of 1kV, it take $41.9\mu s$ to bring an electron to the $0.999c$. However the accelerating structure has to be $12km$ long! Note that modern accelerators work with electric fields on the order of $1MV/m$. So it would take only 12 m. But modern accelerators try to reach electron speeds on the order of $0.9998c$. So that's still long!