This notebook was created by Sergey Tomin for Workshop: Designing future X-ray FELs. Source and license info is on GitHub. August 2016.

Tutorial N2. Tracking.

As an example, we will use lattice file (converted to Ocelot format) of the European XFEL Injector.

This example will cover the following topics:
  • calculation of the linear optics for the European XFEL Injector.
  • Tracking of the particles in first and second order approximation without collective effects.

Coordiantes

Coordinates in Ocelot are following: $$ \left (x, \quad x' = \frac{p_x}{p_0} \right), \qquad \left (y, \quad y' = \frac{p_y}{p_0} \right), \qquad \left (\Delta s = c\tau, \quad p = \frac{\Delta E}{p_0 c} \right)$$

Requirements

  • injector_lattice.py - input file, the Injector lattice.
  • beam_130MeV.ast - input file, initial beam distribution in ASTRA format.

In [11]:
# the output of plotting commands is displayed inline within frontends, 
# directly below the code cell that produced it
%matplotlib inline

# this python library provides generic shallow (copy) and deep copy (deepcopy) operations 
from copy import deepcopy

# import from Ocelot main modules and functions
from ocelot import *

# import from Ocelot graphical modules
from ocelot.gui.accelerator import *

# import injector lattice
from ocelot.demos.ipython_tutorials.injector_lattice import *

If you want to see injector_lattice.py file you can run following command (lattice file is very large):

$ %load injector_lattice.py

The variable cell contains all the elements of the lattice in right order.

And again Ocelot will work with class MagneticLattice instead of simple sequence of element. So we have to run following command.


In [2]:
lat = MagneticLattice(cell, stop=None)

1. Design optics calculation of the European XFEL Injector

Remark

For convenience reasons, we define optical functions starting at the gun by backtracking of the optical functions derived from ASTRA (or similar space charge code) at 130 MeV at the entrance to the first quadrupole. The optical functions we thus obtain have obviously nothing to do with the actual beam envelope form the gun to the 130 MeV point.

Because we work with linear accelerator we have to define initial energy and initial twiss paramters in order to get correct twiss functions along the Injector.


In [3]:
# initialization of Twiss object
tws0 = Twiss()
# defining initial twiss parameters
tws0.beta_x = 29.171
tws0.beta_y = 29.171
tws0.alpha_x = 10.955
tws0.alpha_y = 10.955
# defining initial electron energy in GeV
tws0.E = 0.005 

# calculate optical functions with initial twiss parameters
tws = twiss(lat, tws0, nPoints=None)

In [4]:
# ploting twiss paramentrs.
plot_opt_func(lat, tws, top_plot=["Dx", "Dy"], fig_name="i1", legend=False)
plt.show()


2. Tracking in first and second order approximation without any collective effects

Remark

Because of the reasons mentioned above, we start the beam tracking from the first quadrupole after RF cavities.

Loading of beam distribution

In order to perform tracking we have to have beam distribution. We will load beam distribution from a ASTRA file ('beam_distrib.ast'). And we convert the Astra beam distribution to Ocelot format - ParticleArray. ParticleArray is designed for tracking. In order to work with converters we have to import specific module from ocelot.adaptors

from ocelot.adaptors.astra2ocelot import *

After importing ocelot.adaptors.astra2ocelot we can use converter astraBeam2particleArray() to load and convert. As you will see beam distribution consists of 200 000 particles (that is why loading can take a few second), charge 250 pC, initial energy is about 6.5 MeV.

ParticleArray is a class which includes several parameters and methods.

  • ParticleArray.rparticles is a 2D numpy array (6 x N) with coordinates of particles in $$ParticleArray.rparticles = [\vec{x_0}, \vec{x_1}, ..., \vec{x_n}], $$ where $$\vec{x_n} = (x_n, x_n', y_n, y_n', \tau_n, p_n)^T$$
  • ParticleArray.s is the longitudinal coordinate of the reference particle in [m].
  • ParticleArray.E is the energy of the reference particle in [GeV].
  • ParticleArray.q_array - is a 1D numpy array of the charges each (macro) particles in [C]

In [5]:
#from ocelot.adaptors.astra2ocelot import *
#p_array_init = astraBeam2particleArray(filename='beam_130MeV.ast')
#p_array_init = astraBeam2particleArray(filename='beam_130MeV_off_crest.ast')

# save ParticleArray to compresssed numpy array 
#save_particle_array("tracking_beam.npz", p_array_init)
p_array_init = load_particle_array("sc_beam.npz")

Selection of the tracking order and lattice for the tracking.

MagneticLattice(sequence, start=None, stop=None, method=MethodTM()) have wollowing arguments:

  • sequence - list of the elements,
  • start - first element of the lattice. If None, then lattice starts from the first element of the sequence,
  • stop - last element of the lattice. If None, then lattice stops by the last element of the sequence,
  • method=MethodTM() - method of the tracking. MethodTM() class assigns transfer map to every element. By default all elements are assigned first order transfer map - TransferMap. One can create one's own map, but there are following predefined maps:
    • TransferMap - first order matrices.
    • SecondTM - 2nd order matrices.
    • KickTM - kick applyed.
    • RungeKuttaTM - Runge-Kutta integrator is applyed, but required 3D magnetic field function element.mag_field = lambda x, y, z: (Bx, By, Bz) (see example ocelot/demos/ebeam/tune_shift.py)

In [6]:
# initialization of tracking method
method = MethodTM()

# for second order tracking we have to choose SecondTM 
method.global_method = SecondTM

# for first order tracking uncomment next line
# method.global_method = TransferMap

# we will start simulation from the first quadrupole (QI.46.I1) after RF section.
# you can change stop element (and the start element, as well) 
# START_73_I1 - marker before Dog leg
# START_96_I1 - marker before Bunch Compresion
lat_t = MagneticLattice(cell, start=start_sim, stop=None, method=method)

Tracking

for tracking we have to define following objects:

  • Navigator defines step (dz) of tracking and which, if it exists, physical process will be applied at each step. In order to add collective effects (Space charge, CSR or wake) method add_physics_proc() must be run.

    • Method:
      • Navigator.add_physics_proc(physics_proc, elem1, elem2)
        • physics_proc - physics process, can be CSR, SpaceCharge or Wake,
        • elem1 and elem2 - first and last elements between which the physics process will be applied.
  • track(MagneticLatice, ParticleArray, Navigator) - the function performs tracking through the lattice [lat] of the particles [p_array]. This function also calculate twiss parameters of the beam distribution on each tracking step.


In [7]:
navi = Navigator(lat_t)
p_array = deepcopy(p_array_init)
tws_track, p_array = track(lat_t, p_array, navi)


z = 93.4041010008 / 93.40410100084006 : applied: 

In [8]:
# you can change top_plot argument, for example top_plot=["alpha_x", "alpha_y"]
plot_opt_func(lat_t, tws_track, top_plot=["E"], fig_name=0, legend=False)
plt.show()


Current profile


In [9]:
bins_start, hist_start = get_current(p_array, charge=p_array.q_array[0], num_bins=200)

plt.figure(4)
plt.title("current: end")
plt.plot(bins_start*1000, hist_start)
plt.xlabel("s, mm")
plt.ylabel("I, A")
plt.grid(True)
plt.show()


Imax =  13.2739500619

Beam distribution


In [10]:
tau = np.array([p.tau for p in p_array])
dp = np.array([p.p for p in p_array])
x = np.array([p.x for p in p_array])
y = np.array([p.y for p in p_array])

ax1 = plt.subplot(311)
ax1.plot(-tau*1000, x*1000, 'r.')
plt.setp(ax1.get_xticklabels(), visible=False)
plt.ylabel("x, mm")
plt.grid(True)

ax2 = plt.subplot(312, sharex=ax1)
ax2.plot(-tau*1000, y*1000, 'r.')
plt.setp(ax2.get_xticklabels(), visible=False)
plt.ylabel("y, mm")
plt.grid(True)

ax3 = plt.subplot(313, sharex=ax1)
ax3.plot(-tau*1000, dp, 'r.')
plt.ylabel("dE/E")
plt.xlabel("s, mm")
plt.grid(True)
plt.show()



In [ ]: