New Particle Variable
Sometimes you may wish to output a new variable associated with a particle. Such extensions may be specific to a particular task, and may be too niche to be added to the core EPOCH code. Alternatively, users may wish to quickly add new particle variables, without submitting a request to the developers and waiting for a new release. This section will detail how to define a new particle variable, and how to dump it to SDF files.
There are multiple parts of the code which need to be modified to create and print a new particle variable, these are:
Makefile
: New pre-compiler option to deactivate new variable when not needed (for speed)shared_data.F90
: Give the particle datatype a new variablehousekeeping/welcome.F90
: Alert the user when the code is compiled with your new optionhousekeeping/partlist.F90
: As particles move between MPI ranks, move your new variable with themconstants.F90
: Create tags for the new output and compiler flagdeck/deck_io_block.F90
: Lets the output block see the new output variableio/diagnostics.F90
: Writes the new variable to the SDF fileio/iterators.F90
: Gives diagnostics.F90 variables to write to file- Extra: The user should set the value of the new variable to something
On this page, we provide an example of how to add and output a new particle variable, which has actually been requested by an EPOCH user. When electrons emit photons in the QED package, the photon macro-particle contains no information about the electron which generated it. In this example, let us output the Lorentz $\gamma$ factor of the radiating electron for each emitted photon, at the time of photon emission. We will use EPOCH2D, although this methodology would work for any dimension of the code. In these examples, we will include some of the neighbouring lines of code, so you can get a sense of where to insert the new code for your variable.
Makefile
We want a new pre-compiler flag for the new addition. As macro-particles move around the simulation, they are passed between MPI ranks. The more particle variables to transfer, the slower the code runs. Hence, if we don’t need to use the new particle variables, the code should be compiled without them. This is achieved with new pre-compiler variables.
In the Makefile, there is a section with multiple commented-out pre-compiler flags. Let us add a new one called “extended_io”. To do this, insert a line such that the Makefile reads like:
# Use second order particle weighting (default is third order).
#DEFINES += $(D)PARTICLE_SHAPE_TOPHAT
# Use fifth order particle weighting (default is third order).
#DEFINES += $(D)PARTICLE_SHAPE_BSPLINE3
# Include a unique global particle ID. The first flag defines the ID using
# an 8-byte integer, the second uses 4-bytes.
#DEFINES += $(D)PARTICLE_ID
#DEFINES += $(D)PARTICLE_ID4
# Include QED routines
DEFINES += $(D)PHOTONS
# Extended I/O for QED routines
DEFINES += $(D)EXTENDED_IO
# Use the Trident process for pair production
#DEFINES += $(D)TRIDENT_PHOTONS
# Include bremsstrahlung routines
#DEFINES += $(D)BREMSSTRAHLUNG
Note that we have un-commented the new extended_io flag, and the photons flag, as we will have to run the code with both for the new output in this example.
Constants.F90
Let us make new global integer flags for both the pre-compiler flag, and the new output.
For the EXTENDED_IO
pre-compiler flag, find the c_def...
list and append a
new c_def_extended_io
variable to the end:
INTEGER(i8), PARAMETER :: c_def_use_mpi3 = 2**25
INTEGER(i8), PARAMETER :: c_def_bremsstrahlung = 2**26
INTEGER(i8), PARAMETER :: c_def_probe_time = 2**27
INTEGER(i8), PARAMETER :: c_def_extended_io = 2**28 ! New line
Next, let us make a new output flag: c_dump_qed_el_gamma
. Find the list of
c_dump...
parameters, and add our new one to the end. Remember to update the
num_vars_to_dump
variable with the new number of dump flags.
INTEGER, PARAMETER :: c_dump_probe_time = 72
INTEGER, PARAMETER :: c_dump_cou_log = 73
INTEGER, PARAMETER :: c_dump_qed_el_gamma = 74 ! New line
INTEGER, PARAMETER :: num_vars_to_dump = 74 ! Modified line
Note, you may wish to introduce multiple outputs with one pre-compiler flag.
For example, the WORK_DONE_INTEGRATED
pre-compiler flag introduces six new
outputs.
Shared_data.F90
Here we declare a new variable for the particle data-type. Be sure to wrap this
inside your new pre-compiler flag using #ifdef
commands (note these cannot be
indented - the first character of these lines must be #
). In our example, we
have called the new variable electron_gamma
.
! Object representing a particle
! If you add or remove from this section then you *must* update the
! particle pack and unpack routines
TYPE particle
REAL(num), DIMENSION(3) :: part_p
REAL(num), DIMENSION(c_ndims) :: part_pos
#ifdef EXTENDED_IO
REAL(num) :: electron_gamma ! New line, and the surrounding ifdef, endif
#endif
Welcome.F90
For consistency with the other EPOCH pre-compiler options, we should print a
message when the code has been compiled with our new EXTENDED_IO
.
The first part of the module scans through all possible pre-compiler variables, and checks if any are present. Add new lines for the new pre-compiler flag.
#ifdef PHOTONS
found = .TRUE.
#ifdef TRIDENT_PHOTONS
found = .TRUE.
#endif
#endif
#ifdef EXTENDED_IO
found = .TRUE. ! New line
#endif
Next, a message is printed for each present module. Here we have written a generic message for our extended_io pre-compiler flag.
#ifdef PHOTONS
defines = IOR(defines, c_def_photons)
WRITE(*,*) 'QED Effects -DPHOTONS'
#ifdef TRIDENT_PHOTONS
defines = IOR(defines, c_def_trident_photons)
WRITE(*,*) 'Pair production by Trident process -DTRIDENT_PHOTONS'
#endif
#endif
#ifdef EXTENDED_IO
defines = IOR(defines, c_def_extended_io) ! New line
WRITE(*,*) 'Additional particle variables for output -DEXTENDED_IO' ! New line
#endif
Partlist.F90
Each MPI rank only tracks particles present within its section of the domain. As macro-particles move around the simulation, they can pass between cells controlled by different ranks. When this happens, a new particle is created on the new rank, its properties are set to match the properties on the old rank, and the particle on the old rank is destroyed. New particle variables must also be transferred when particles move between ranks, and this is done here.
There are 4 steps here:
- Tell the code how many variables are associated with the particle
- Copy the variables on the old rank to an array for transfer
- Set variables on the new rank from the transferred array
- For new particles, initialise the particle variable
To tell the code how many variables are present, find the set_partlist_size
subroutine. The count of particle variables is stored in the nvar
integer.
In this example, we only introduce one more particle variable when the code
is compiled with EXTENDED_IO
, so add 1 to nvar
if we have compiled with
EXTENEDED_IO
:
#ifdef BREMSSTRAHLUNG
nvar = nvar+1
#endif
#ifdef EXTENDED_IO
nvar = nvar+1 ! New line
#endif
#ifdef PROBE_TIME
nvar = nvar+1
#endif
Next, navigate to pack_particle
- the subroutine responsible for collecting
particle variables to transfer to neighbouring ranks. Now we can add our new
variable to the transfer array. Make a note of the position here - variables
must be read in the same order they are stored.
#ifdef BREMSSTRAHLUNG
array(cpos) = a_particle%optical_depth_bremsstrahlung
cpos = cpos+1
#endif
#ifdef EXTENDED_IO
array(cpos) = a_particle%electron_gamma ! New line
cpos = cpos+1 ! Only 1 new variable, so add 1
#endif
#ifdef PROBE_TIME
array(cpos) = a_particle%probe_time
cpos = cpos+1
#endif
Once the particle variables are packed into an array, this is sent to the
neighbouring ranks. These call unpack_particle
to set the properties of new
particles. The syntax for this with our new variable would be:
#ifdef BREMSSTRAHLUNG
a_particle%optical_depth_bremsstrahlung = array(cpos)
cpos = cpos+1
#endif
#ifdef EXTENDED_IO
a_particle%electron_gamma = array(cpos) ! New line
cpos = cpos+1 ! Note this is also between optical_depth_bremsstrahlung and probe_time
#endif
#ifdef PROBE_TIME
a_particle%probe_time = array(cpos)
cpos = cpos+1
#endif
Finally, we must set the initial values for these variables. If we don’t do
this, the code can act unpredictably. In the init_particle
subroutine, add
the lines:
#ifdef EXTENDED_IO
new_particle%electron_gamma = 0.0_num ! New line
#endif
#ifdef PROBE_TIME
new_particle%probe_time = 0.0_num
#endif
Deck_io_block.F90
Unless you want to make a new physics package, a new particle variable is only
useful if you can output it. This is done using the standard EPOCH output block.
Recall we’ve already defined a c_dump_qed_el_gamma
flag - now we just need
to tell EPOCH how it will appear in the input deck.
Navigate to io_block_handle_element
, find the str_cmp
lines which interpret
the the deck lines, and add:
#ifdef BREMSSTRAHLUNG
ELSE IF (str_cmp(element, 'bremsstrahlung_optical_depth')) THEN
elementselected = c_dump_part_opdepth_brem
#endif
#ifdef EXTENDED_IO
ELSE IF (str_cmp(element, 'electron_gamma')) THEN ! New line
elementselected = c_dump_qed_el_gamma ! New line
#endif
Whatever you write as the second argument to str_cmp
is how you will
have to write the variable in the EPOCH output block. As we are keeping things
simple in this example, we will not bother with restart dumps. The value of
electron_gamma
will not be re-loaded when you restart the code from a
restart dump.
Diagnostics.F90
At this point we have declared a new particle variable, wrapped it in pre-compiler flags, made it visible to MPI routines, and told EPOCH it can be dumped. Now we actually have to write the variable to file.
There is a section in the output_routines
subroutine which contains lines
with write_particle_variable
calls. Add a new one for our variable:
#ifdef BREMSSTRAHLUNG
CALL write_particle_variable(c_dump_part_opdepth_brem, code, &
'Bremsstrahlung Depth', '', it_output_real)
#endif
#ifdef EXTENDED_IO
CALL write_particle_variable(c_dump_qed_el_gamma, code, &
'Emitting Electron Gamma', '', it_output_real)
#endif
The 'Emitting Electron Gamma'
string controls how the variable will be
labelled in the SDF file.
Iterators.F90
One of the arguments of the write_particle_variable
subroutine is a call to
the it_output_real
function. We need to modify this function to give our new
variable, when requested. To do this, add the following case to the
it_output_real
function:
#ifdef EXTENDED_IO
CASE (c_dump_qed_el_gamma)
DO WHILE (ASSOCIATED(cur) .AND. (part_count < npoint_it))
part_count = part_count + 1
array(part_count) = cur%electron_gamma
cur => cur%next
END DO
#endif
Setting your variable
All the previous steps are required whatever your particle variable is. All we need to do now is figure out how to set it. This may be straightforward or complicated, depending on what the variable is. We can’t cover every possible particle variable here, but you may find it useful if we finish our example, to see how one would go about it.
Here, we want to save the electron gamma factor when a photon has been emitted.
Photon emission occurs in the generate_photon
subroutine of photons.F90
.
When in this subroutine, the generating electron information can be accessed
through the generating_electron
particle pointer. Fortunately for us, the
electron $\gamma$ factor has already been calculated in this subroutine, and is
given by generating_gamma
. Let us set our new particle variable to this
value, as soon as the photon macro-particle has been created.
CALL create_particle(new_photon)
new_photon%part_pos = generating_electron%part_pos
#ifdef EXTENDED_IO
new_photon%electron_gamma = generating_gamma ! New line
#endif
Testing
If all has gone well, we have a new particle variable ready for printing.
Compile EPOCH (you may need to run make clean
first if you have changed the
pre-compiler flags).
It’s best to run with a small input deck for code testing. We have shrank the qed_rese.deck found in the source code, such that it can run in a few seconds. This tiny deck has been included at the end of this section.
When running, the code correctly generates the compiler-flag messages:
Welcome to EPOCH2D version 4.19.1 (commit v4.19.1-7-g6a1bfa99-dirty)
The code was compiled with the following compile time options
*************************************************************
QED Effects -DPHOTONS
Additional particle variables for output -DEXTENDED_IO
*************************************************************
When we inspect 0001.sdf
, we find that 21798 macro-photons have been created,
and all of them have an emitting electron gamma value. These range from 20 to
1896 in this example. The input deck used for this example is given below. A
new particle variable has been successfully created!
begin:control
nx = 50 # in x
ny = 50
nparticles = nx * ny * 2
t_end = 100e-15
x_min = 0
x_max = 50 * 10.0e-9
y_min = 0
y_max = 50 * 10.0e-9
dt_multiplier = 0.8
end:control
begin:qed
use_qed = T
qed_start_time = 0
produce_photons = T
photon_energy_min = 50 * kev
produce_pairs = F
photon_dynamics = F
end:qed
begin:boundaries
bc_x_min = simple_laser
bc_x_max = reflect
bc_y_max = reflect
bc_y_min = reflect
end:boundaries
begin:species
name = Electron
fraction = 0.5
dump = T
temperature = 0
number_density = if (x gt 0.1*x_max, 1.0e20, 0)
identify:electron
end:species
begin:species
name = Ion
fraction = 0.5
dump = T
number_density = number_density(Electron)
temperature = 0
identify:proton
end:species
begin:species
name = Photon
frac = 0
dump = T
identify:photon
end:species
begin:output
dt_snapshot = t_end
electron_gamma = always
end:output
begin:laser
boundary = x_min
intensity = 1.0e21 * 1.0e4
lambda = 1.0e-6
polarisation = 0.0
phase = 0.0
t_profile = 1
profile = 1
end:laser