This notebook was created by Sergey Tomin (sergey.tomin@desy.de). Source and license info is on GitHub. June 2019.
Synchrotron radiation module is also included to the OCELOT multiphysics simulation toolkit .
The OCELOT SR module is capable of calculating spectrum and spatial distribution of spontaneous radiation from a single electron in a magnetic field defined by file data (field on an insertion device axis or 3D magnetic field map) or using standard elements as Undulator with arbitrary defined period, length and $K$.
Some details about SR module can be found in S. Tomin, G. Geloni, Synchrotron Radiation Module in OCELOT Toolkit
In [1]:
# To activate interactive matplolib in notebook
#%matplotlib notebook
In [6]:
# import main functions from Synchrotron Radation (SR) module
from ocelot.rad import *
# import OCELOT main functions
from ocelot import *
# import OCELOT plotting functions
from ocelot.gui import *
import time
As usual we start from creating elements and lattice.
At the moment SR module recognize only Undulator element. Even if you want to calculate radiation from dipole magnet.
In [7]:
und = Undulator(Kx=0.43, nperiods=500, lperiod=0.007, eid="und")
lat = MagneticLattice((und))
To calculate radiation one needs two additional objects
Beam()
to provide the electron beam energy and beam current.
Screen()
class to store radiation field and to provide information about screen parameters where radiation will be observed.
In [8]:
beam = Beam()
beam.E = 2.5 # beam energy in [GeV]
beam.I = 0.1 # beam current in [A]
screen = Screen()
screen.z = 100.0 # distance from the begining of lattice to the screen
screen.size_x = 0.002 # half of screen size in [m] in horizontal plane
screen.size_y = 0. # half of screen size in [m] in vertical plane
screen.nx = 101 # number of points in horizontal plane
screen.ny = 1 # number of points in vertical plane
screen.start_energy = 7761.2 # [eV], starting photon energy
screen.end_energy = 7900 # [eV], ending photon energy
screen.num_energy = 1 # number of energy points[eV]
to calculate SR from one electron there is a function:
screen = calculate_radiation(lat, screen, beam)
lat
: MagneticLattice should include element Undulatorscreen
: Screen classbeam
: Beam class, the radiation is calculated from one electronOptional parameters:
energy_loss
: False, if True includes energy loss after each periodquantum_diff
: False, if True introduces random energy kickaccuracy
: 1, scale for trajectory points number end_poles
: False, if True includes end poles with 1/4, -3/4, 1, ...
In [9]:
start = time.time()
screen = calculate_radiation(lat, screen, beam)
print("time exec: ", time.time() - start, " sec")
Electric field is stored in 1D arrays
screen.arReEx
: array, Real part of horizontal component of the electric fieldscreen.arImEx
: array, Imaginary part of horizontal component of the electric fieldscreen.arReEy
: array, Real part of the vertical component of the electric fieldscreen.arImEy
: array, Imaginary part of the vertical component of the electric fieldscreen.arPhase
: array, phase between Re and Im componentsAlso, Screen has coordinates where radiation was calculated
screen.Xph
, 1D array with coordinates in horizontal plane screen.Yph
, 1D array with coordinates in vertical planescreen.Eph
, 1D array with coordinates in energetic planePhoton flux is calculated from the electric field and stored in 1D arrays:
screen.Sigma
: horizontal polarization component in $\left[\frac{ph}{sec \cdot mm^2 10^{-3}BW}\right]$screen.Pi
: vertical polarization component in $\left[\frac{ph}{sec \cdot mm^2 10^{-3}BW}\right]$screen.Total = screen.Sigma + screen.Pi
: total flux density in $\left[\frac{ph}{sec \cdot mm^2 10^{-3}BW}\right]$
In [10]:
plt.figure(10)
plt.plot(screen.Xph, screen.Total)
plt.ylabel(r"F, $\frac{ph}{sec \cdot mm^2 10^{-3}BW}$")
plt.xlabel(r"X [mm]")
plt.show()
In [11]:
show_flux(screen, unit="mm")
In [12]:
show_flux(screen, unit="mrad", nfig=2)
Relation between the time $\tau$ at the observer and the time $t$ of emission
\begin{equation} \tau(t) = t + \frac{1}{c}\big|\vec{x_{scr}} - \vec{r(t)}\big| = \tau_0 + \int_0^t{\left[1 - \vec{n(t')} \vec{\beta(t')} \right]dt'} \end{equation}where $\vec{x_{scr}} = [x_{scr}, y_{scr}, z_{scr}]$
\begin{equation} \phi(z, \vec{x_{scr}}) = \frac{2\pi c}{\lambda}\tau(t(z)) = \frac{2\pi }{\lambda}\left(c t(z) + \big|\vec{x_{scr}} - \vec{r(z)}\big|\right) \end{equation}\begin{equation} \phi(z, \vec{x_{scr}}) = \phi(z_0, \vec{x_{scr}}) + \frac{2\pi }{\lambda}\int_{z_0}^z{\frac{dz'}{\sqrt{\beta^2 - \beta^2_x(z') - \beta_y^2(z')}}} + \frac{2\pi }{\lambda}\big|\vec{x_{scr}} - \vec{r(z)}\big| - \frac{2\pi }{\lambda}\big|\vec{x_{scr}} - \vec{r(z_0)}\big| \end{equation}where \begin{equation} \phi(z_0, \vec{x_{scr}}) = \frac{2\pi c }{\lambda} t(z_0)+ \frac{2\pi }{\lambda}\big|\vec{x_{scr}} - \vec{r(z_0)}\big| \end{equation} $\vec{r(z)}= [x(z), y(z), z]$ is the electron trajectory,
Using assumptions: \begin{equation} \begin{split} &\gamma >> 1, \qquad |\beta_x|<< 1, \qquad |\beta_y|<<1 \\ (z_{scr} - z_0) >> (z - z_0), &\qquad (z_{scr} - z_0) >> (x_{scr} - x(z)), \qquad (z_{scr} - z_0) >> (y_{scr} - y(z)) \end{split} \end{equation}
we finally get \begin{equation} \begin{split} \phi(z, \vec{x_{scr}}) = \phi(z_0, \vec{x_{scr}}) + \frac{\pi}{\lambda \gamma^2}\left[(z - z_0) + \gamma \int_{z_0}^z\left\{\beta_x^2(z') + \beta_y^2(z')\right\}dz' + \gamma^2 \left[\frac{(x_{scr} - x(z))^2}{z_{scr} - z} - \frac{(x_{scr} - x(z_0))^2}{z_{scr} - z_0}\right] + \gamma^2 \left[\frac{(y_{scr} - y(z))^2}{z_{scr} - z} - \frac{(y_{scr} - y(z_0))^2}{z_{scr} - z_0}\right]\right] \end{split} \end{equation}
During calculation of the radiation we assume $\phi(z_0, \vec{x_{scr}}) = 0$.
At the last step in the function calculate_radiation
method screen.add_fast_oscilating_term(x0=0, y0=0, z0=0)
is called which adds the subtracted term.
In [13]:
plt.figure(1000)
plt.plot(screen.Xph, screen.arPhase)
plt.xlabel("x [mm]")
plt.ylabel("Phase")
plt.show()
In [14]:
beam = Beam()
beam.E = 2.5 # beam energy in [GeV]
beam.I = 0.1 # beam current in [A]
screen = Screen()
screen.z = 100.0 # distance from the begining of lattice to the screen
screen.start_energy = 7600 # [eV], starting photon energy
screen.end_energy = 7900 # [eV], ending photon energy
screen.num_energy = 1000 # number of energy points[eV]
# Calculate radiation
start = time.time()
screen = calculate_radiation(lat, screen, beam)
print("time exec: ", time.time() - start, " sec")
# show result
show_flux(screen, unit="mrad", nfig=12)
In [15]:
beam = Beam()
beam.E = 2.5 # beam energy in [GeV]
beam.I = 0.1 # beam current in [A]
screen = Screen()
screen.z = 100.0 # distance from the begining of lattice to the screen
screen.size_x = 0.002 # half of screen size in [m] in horizontal plane
screen.size_y = 0.002 # half of screen size in [m] in vertical plane
screen.nx = 51 # number of points in horizontal plane
screen.ny = 51 # number of points in vertical plane
screen.start_energy = 7761.2 # [eV], starting photon energy
screen.end_energy = 7900 # [eV], ending photon energy
screen.num_energy = 1 # number of energy points[eV]
start = time.time()
# Calculate radiation
screen = calculate_radiation(lat, screen, beam)
print("time exec: ", time.time() - start, " sec")
# show result
show_flux(screen, unit="mrad", nfig=13)
see tutorial pfs_4_synchrotron_radiation_visualization
At the moment, spatial coordinates must be in [mm]. Field map can have 3 formats:
First off we will generate magnetic field.
In [16]:
lperiod = 0.04 # [m] undulator period
nperiods = 30 # number of periods
B0 = 1 # [T] amplitude of the magnetic field
# longitudinal coordinates from 0 to lperiod*nperiods in [mm]
z = np.linspace(0, lperiod*nperiods, num=500)*1000 # [mm]
lperiod_mm = lperiod * 1000 # in [mm]
By = B0*np.cos(2*np.pi/lperiod_mm*z)
plt.figure(100)
plt.plot(z, By)
plt.xlabel("z [mm]")
plt.ylabel("By [T]")
plt.show()
In [17]:
filed_map = np.vstack((z, By)).T
np.savetxt("filed_map.txt", filed_map)
Create undulator element with field map and initialize MagneticLattice
In [18]:
und_m = Undulator(field_file="filed_map.txt", eid="und")
lat_m = MagneticLattice((und_m))
In [19]:
beam = Beam()
beam.E = 17.5 # beam energy in [GeV]
beam.I = 0.1 # beam current in [A]
screen = Screen()
screen.z = 1000.0 # distance from the begining of lattice to the screen
screen.start_energy = 7000 # [eV], starting photon energy
screen.end_energy = 12000 # [eV], ending photon energy
screen.num_energy = 1000 # number of energy points[eV]
# Calculate radiation
screen = calculate_radiation(lat_m, screen, beam)
# show result
show_flux(screen, unit="mrad", nfig=103)
We can estimate radiation properties using function:
print_rad_props(beam, K, lu, L, distance)
beam
is Beam classK
is undulator parameterlu
is undulator period in [m]L
is undulator length in [m]distance
is distance to the screen in [m]Also we have simple functions which can translate one undulator parameter to another, like:
field2K(field, lu=0.04)
In [20]:
K = field2K(field=B0, lu=lperiod)
print_rad_props(beam, K, lu=lperiod, L=lperiod*nperiods, distance=screen.z)
In [21]:
K = field2K(field=B0, lu=lperiod)
beam = Beam()
beam.E = 0.13
beam.I = 0.1
print_rad_props(beam, K=20, lu=0.2, L=lperiod*20, distance=100)
In [22]:
lperiod = 0.04 # [m] undulator period
nperiods = 30 # number of periods
B0 = 1 # [T] amplitude of the magnetic field
# longitudinal coordinates from 0 to lperiod*nperiods in [mm]
z = np.linspace(0, lperiod*nperiods, num=1000)*1000 # [mm]
By = B0*np.cos(2*np.pi/lperiod*z)
def py_mag_field(x, y, z, lperiod, B0):
"""
x, y, z = coordinates
"""
Bx = 0
By = B0*np.cos(2*np.pi/lperiod*z)
Bz = 0
return (Bx, By, Bz)
plt.figure(110)
plt.plot(z, py_mag_field(x=0, y=0, z=z, lperiod=lperiod, B0=B0)[1])
plt.xlabel("z [mm]")
plt.ylabel("By [T]")
plt.show()
mag_field
Undulator
element has the attribute mag_field
, which takes on the function as follows:
(Bx, By, Bz) = f(x, y, z)
.
For example, we define only the vertical magnetic field and other components are zero:
field = lambda x, y, z: (0, cos(kz * z), 0)
In case, the attribute mag_field is a function, you still need to define lperiod
and nperiods
in the Undulator
. It will allow to calculate the length of the undulator.
In [23]:
und_m = Undulator(lperiod=lperiod, nperiods=nperiods, Kx=0.0,eid="und")
und_m.mag_field = lambda x, y, z: py_mag_field(x, y, z, lperiod=lperiod, B0=B0)
# next, all the same.
lat_m = MagneticLattice((und_m))
beam = Beam()
beam.E = 17.5 # beam energy in [GeV]
beam.I = 0.1 # beam current in [A]
screen = Screen()
screen.z = 1000.0 # distance from the begining of lattice to the screen
screen.start_energy = 7000 # [eV], starting photon energy
screen.end_energy = 12000 # [eV], ending photon energy
screen.num_energy = 1000 # number of energy points[eV]
# Calculate radiation
screen = calculate_radiation(lat_m, screen, beam, accuracy=2)
# show result
show_flux(screen, unit="mrad", nfig=104)
As it was pointed out above, the function calculate_radiation
has argument accuracy=1
which scales number of trajectory points.
In the current version of ocelot (19.06) the number of points is calculated by simple expression:
n = int((undul_length*1500 + 100)*accuracy)
The object Screen
after the radiation calculation contains the electron trajectory what was used in a special object BeamTraject
which is attached to:
screen.beam_traj = BeamTraject()
To retrieve trajectory you need to specify number of electron what you are interested, for example:
x = screen.beam_traj.x(n=0)
for more details have a look to Tutorial #10 "Simple accelerator based THz source"
In case of calculate_radiation
the BeamTraject
contains only one trajectory, so n = 0
In [24]:
n = 0
x = screen.beam_traj.x(n)
y = screen.beam_traj.y(n)
z = screen.beam_traj.z(n)
plt.title("trajectory of " + str(n)+"th particle")
plt.plot(z, x, label="X")
plt.plot(z, y, label="Y")
plt.xlabel("Z [m]")
plt.ylabel("X/Y [m]")
plt.legend()
plt.show()
print(f"Number of trajectory points n={len(z)}")
In [ ]:
In [ ]: