Boosted-frame simulation of laser-wakefield accelerationΒΆ

You can download the script below by clicking on this link.

"""
This is a typical input script that runs a simulation of
laser-wakefield acceleration using FBPIC.

Usage
-----
- Modify the parameters below to suit your needs
- Type "python boosted_frame_script.py" in a terminal

Help
----
All the structures implemented in FBPIC are internally documented.
Enter "print(fbpic_object.__doc__)" to have access to this documentation,
where fbpic_object is any of the objects or function of FBPIC.
"""

# -------
# Imports
# -------
import numpy as np
from scipy.constants import c, e, m_e, m_p
# Import the relevant structures in FBPIC
from fbpic.main import Simulation
from fbpic.lpa_utils.laser import add_laser_pulse
from fbpic.lpa_utils.laser.laser_profiles import GaussianLaser
from fbpic.lpa_utils.bunch import add_particle_bunch
from fbpic.lpa_utils.boosted_frame import BoostConverter
from fbpic.openpmd_diag import FieldDiagnostic, ParticleDiagnostic, \
        BackTransformedFieldDiagnostic, BackTransformedParticleDiagnostic
# ----------
# Parameters
# ----------
use_cuda = True

# Order of the stencil for z derivatives in the Maxwell solver.
# Use -1 for infinite order, i.e. for exact dispersion relation in
# all direction (adviced for single-GPU/single-CPU simulation).
# Use a positive number (and multiple of 2) for a finite-order stencil
# (required for multi-GPU/multi-CPU with MPI). A large `n_order` leads
# to more overhead in MPI communications, but also to a more accurate
# dispersion relation for electromagnetic waves. (Typically,
# `n_order = 32` is a good trade-off.)
# See https://arxiv.org/abs/1611.05712 for more information.
n_order = -1

# Boosted frame
gamma_boost = 10.
# Boosted frame converter
boost = BoostConverter(gamma_boost)

# The simulation box
Nz = 600         # Number of gridpoints along z
zmax = 0.e-6     # Length of the box along z (meters)
zmin = -30.e-6
Nr = 75          # Number of gridpoints along r
rmax = 150.e-6   # Length of the box along r (meters)
Nm = 2           # Number of modes used

# The simulation timestep
# (See the section Advanced use > Running boosted-frame simulation
# of the FBPIC documentation for an explanation of the calculation of dt)
dt = min( rmax/(2*boost.gamma0*Nr)/c, (zmax-zmin)/Nz/c )  # Timestep (seconds)

# The laser (conversion to boosted frame is done inside 'add_laser')
a0 = 2.          # Laser amplitude
w0 = 50.e-6      # Laser waist
tau = 16.e-15     # Laser duration
z0 = -10.e-6     # Laser centroid
zfoc = 0.e-6     # Focal position
lambda0 = 0.8e-6 # Laser wavelength

# The density profile
w_matched = 50.e-6
ramp_up = .5e-3
plateau = 3.5e-3
ramp_down = .5e-3

# The particles of the plasma
p_zmin = 0.e-6   # Position of the beginning of the plasma (meters)
p_zmax = ramp_up + plateau + ramp_down
p_rmax = 100.e-6 # Maximal radial position of the plasma (meters)
n_e = 3.e24      # The density in the labframe (electrons.meters^-3)
p_nz = 2         # Number of particles per cell along z
p_nr = 2         # Number of particles per cell along r
p_nt = 6         # Number of particles per cell along theta

# Density profile
# Relative change divided by w_matched^2 that allows guiding
rel_delta_n_over_w2 = 1./( np.pi * 2.81e-15 * w_matched**4 * n_e )
# Define the density function
def dens_func( z, r ):
    """
    User-defined function: density profile of the plasma

    It should return the relative density with respect to n_plasma,
    at the position x, y, z (i.e. return a number between 0 and 1)

    Parameters
    ----------
    z, r: 1darrays of floats
        Arrays with one element per macroparticle
    Returns
    -------
    n : 1d array of floats
        Array of relative density, with one element per macroparticles
    """
    # Allocate relative density
    n = np.ones_like(z)
    # Make ramp up
    inv_ramp_up = 1./ramp_up
    n = np.where( z<ramp_up, z*inv_ramp_up, n )
    # Make ramp down
    inv_ramp_down = 1./ramp_down
    n = np.where( (z >= ramp_up+plateau) & \
                  (z < ramp_up+plateau+ramp_down),
              - (z - (ramp_up+plateau+ramp_down) )*inv_ramp_down, n )
    n = np.where( z >= ramp_up+plateau+ramp_down, 0, n)
    # Add transverse guiding parabolic profile
    n = n * ( 1. + rel_delta_n_over_w2 * r**2 )
    return(n)

# The bunch
bunch_zmin = z0 - 15.e-6
bunch_zmax = bunch_zmin + 3.e-6
bunch_rmax = 10.e-6
bunch_gamma = 400.
bunch_n = 5.e23

# The moving window (moves with the group velocity in a plasma)
v_window = c*( 1 - 0.5*n_e/1.75e27 )

# Velocity of the Galilean frame (for suppression of the NCI)
v_comoving = - c * np.sqrt( 1. - 1./boost.gamma0**2 )

# The interaction length of the simulation, in the lab frame (meters)
L_interact = (p_zmax-p_zmin) # the plasma length
# Interaction time, in the boosted frame (seconds)
T_interact = boost.interaction_time( L_interact, (zmax-zmin), v_window )
# (i.e. the time it takes for the moving window to slide across the plasma)

## The diagnostics

# Number of discrete diagnostic snapshots, for the diagnostics in the
# boosted frame (i.e. simulation frame) and in the lab frame
# (i.e. back-transformed from the simulation frame to the lab frame)
N_boosted_diag = 15+1
N_lab_diag = 10+1
# Time interval between diagnostic snapshots *in the lab frame*
# (first at t=0, last at t=T_interact)
dt_lab_diag_period = (L_interact + (zmax-zmin)) / v_window / (N_lab_diag - 1)
# Time interval between diagnostic snapshots *in the boosted frame*
dt_boosted_diag_period = T_interact / (N_boosted_diag - 1)
# Period of writing the cached, backtransformed lab frame diagnostics to disk
write_period = 50

# Whether to tag and track the particles of the bunch
track_bunch = False

# ---------------------------
# Carrying out the simulation
# ---------------------------
# NB: The code below is only executed when running the script,
# (`python boosted_frame_sim.py`), but not when importing it.
if __name__ == '__main__':

    # Initialize the simulation object
    sim = Simulation( Nz, zmax, Nr, rmax, Nm, dt, zmin=zmin,
        v_comoving=v_comoving, gamma_boost=boost.gamma0,
        n_order=n_order, use_cuda=use_cuda,
        boundaries={'z':'open', 'r':'reflective'})
        # 'r': 'open' can also be used, but is more computationally expensive

    # Add the plasma electron and plasma ions
    plasma_elec = sim.add_new_species( q=-e, m=m_e, n=n_e,
                    dens_func=dens_func, boost_positions_in_dens_func=True,
                    p_zmin=p_zmin, p_zmax=p_zmax, p_rmax=p_rmax,
                    p_nz=p_nz, p_nr=p_nr, p_nt=p_nt )
    plasma_ions = sim.add_new_species( q=e, m=m_p, n=n_e,
                    dens_func=dens_func, boost_positions_in_dens_func=True,
                    p_zmin=p_zmin, p_zmax=p_zmax, p_rmax=p_rmax,
                    p_nz=p_nz, p_nr=p_nr, p_nt=p_nt )

    # Add a relativistic electron bunch
    bunch = add_particle_bunch( sim, -e, m_e, bunch_gamma,
        bunch_n, bunch_zmin, bunch_zmax, 0, bunch_rmax, boost=boost )
    if track_bunch:
        bunch.track( sim.comm )

    # Create a Gaussian laser profile
    laser_profile = GaussianLaser(a0, w0, tau, z0, lambda0=lambda0, zf=zfoc)
    # Add a laser to the fields of the simulation
    add_laser_pulse( sim, laser_profile, gamma_boost=boost.gamma0,
                     method='antenna', z0_antenna=0)

    # Convert parameter to boosted frame
    v_window_boosted, = boost.velocity( [ v_window ] )
    # Configure the moving window
    sim.set_moving_window( v=v_window_boosted )

    # Add a field diagnostic
    sim.diags = [
                  # Diagnostics in the boosted frame
                  FieldDiagnostic( dt_period=dt_boosted_diag_period,
                                   fldobject=sim.fld, comm=sim.comm ),
                  ParticleDiagnostic( dt_period=dt_boosted_diag_period,
                        species={"electrons":plasma_elec, "bunch":bunch},
                        comm=sim.comm),
                  # Diagnostics in the lab frame (back-transformed)
                  BackTransformedFieldDiagnostic( zmin, zmax, v_window,
                    dt_lab_diag_period, N_lab_diag, boost.gamma0,
                    fieldtypes=['rho','E','B'], period=write_period,
                    fldobject=sim.fld, comm=sim.comm ),
                  BackTransformedParticleDiagnostic( zmin, zmax, v_window,
                    dt_lab_diag_period, N_lab_diag, boost.gamma0,
                    write_period, sim.fld, select={'uz':[0.,None]},
                    species={'bunch':bunch}, comm=sim.comm )
                ]

    # Number of iterations to perform
    N_step = int(T_interact/sim.dt)

    ### Run the simulation
    sim.step( N_step )
    print('')