Skip to content

Commit 995c63a

Browse files
RemiLeheaeriforme
andauthored
Add CI test to read particles from openPMD (#5937)
It seems that reading particles from an openPMD file is not being automatically tested in WarpX. This PR adds a CI test that generates particles in Python and saves them with openPMD-api. The beam is then read in WarpX. --------- Co-authored-by: Arianna Formenti <[email protected]>
1 parent bbbdb3d commit 995c63a

File tree

5 files changed

+228
-0
lines changed

5 files changed

+228
-0
lines changed

Docs/source/usage/parameters.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,8 @@ Particle initialization
10631063
If the external file also contains ``openPMD::Records`` for ``mass`` and ``charge`` (constant `double` scalars) then the species will use these, unless overwritten in the input file (see ``<species_name>.mass``, ``<species_name>.charge`` or ``<species_name>.species_type``).
10641064
The ``external_file`` option is currently implemented for 2D, 3D and RZ geometries, with record components in the cartesian coordinates ``(x,y,z)`` for 3D and RZ, and ``(x,z)`` for 2D.
10651065
For more information on the `openPMD format <https://github.com/openPMD>`__ and how to build WarpX with it, please visit :ref:`the install section <install-developers>`.
1066+
See `this file <https://github.com/BLAST-WarpX/WarpX/Examples/Tests/gaussian_beam/inputs_test_3d_focusing_gaussian_beam_from_openpmd_prepare.py>`__
1067+
for an example of how to prepare the openPMD data file.
10661068

10671069
* ``NFluxPerCell``: Continuously inject a flux of macroparticles from a surface. The emitting surface can be chosen to be either a plane
10681070
defined by the user (using some of the parameters listed below), or the embedded boundary (see :ref:`Embedded Boundary Conditions <running-cpp-parameters-eb>`).

Examples/Tests/gaussian_beam/CMakeLists.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@ add_warpx_test(
1111
OFF # dependency
1212
)
1313

14+
add_warpx_test(
15+
test_3d_focusing_gaussian_beam_from_openpmd # name
16+
3 # dims
17+
2 # nprocs
18+
inputs_test_3d_focusing_gaussian_beam_from_openpmd # inputs
19+
"analysis.py" # analysis
20+
"analysis_default_regression.py --path diags/diag1000000" # checksum
21+
test_3d_focusing_gaussian_beam_from_openpmd_prepare # dependency
22+
)
23+
24+
add_warpx_test(
25+
test_3d_focusing_gaussian_beam_from_openpmd_prepare # name
26+
3 # dims
27+
1 # nprocs
28+
inputs_test_3d_focusing_gaussian_beam_from_openpmd_prepare.py # inputs
29+
OFF # analysis
30+
OFF # checksum
31+
OFF # dependency
32+
)
33+
1434
add_warpx_test(
1535
test_3d_gaussian_beam_picmi # name
1636
3 # dims
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#################################
2+
########## MY CONSTANTS #########
3+
#################################
4+
my_constants.nano = 1.0e-9
5+
my_constants.micro = 1.e-6
6+
7+
my_constants.sigmax = 516.0*nano
8+
my_constants.sigmay = 7.7*nano
9+
my_constants.sigmaz = 300.*micro
10+
11+
# BOX
12+
my_constants.Lx = 20*sigmax
13+
my_constants.Ly = 20*sigmay
14+
my_constants.Lz = 20*sigmaz
15+
my_constants.nx = 256
16+
my_constants.ny = 256
17+
my_constants.nz = 256
18+
19+
#################################
20+
####### GENERAL PARAMETERS ######
21+
#################################
22+
max_step = 0
23+
amr.n_cell = nx ny nz
24+
amr.max_grid_size = 256
25+
amr.blocking_factor = 2
26+
amr.max_level = 0
27+
geometry.dims = 3
28+
geometry.prob_lo = -0.5*Lx -0.5*Ly -0.5*Lz
29+
geometry.prob_hi = 0.5*Lx 0.5*Ly 0.5*Lz
30+
31+
#################################
32+
######## BOUNDARY CONDITION #####
33+
#################################
34+
boundary.field_lo = PEC PEC PEC
35+
boundary.field_hi = PEC PEC PEC
36+
boundary.particle_lo = Absorbing Absorbing Absorbing
37+
boundary.particle_hi = Absorbing Absorbing Absorbing
38+
39+
#################################
40+
############ NUMERICS ###########
41+
#################################
42+
algo.particle_shape = 3
43+
44+
#################################
45+
########### PARTICLES ###########
46+
#################################
47+
particles.species_names = beam1
48+
49+
beam1.injection_style = external_file
50+
beam1.injection_file = ../test_3d_focusing_gaussian_beam_from_openpmd_prepare/openpmd_generated_particles.h5
51+
52+
#################################
53+
######### DIAGNOSTICS ###########
54+
#################################
55+
# FULL
56+
diagnostics.diags_names = diag1 openpmd
57+
58+
diag1.intervals = 1
59+
diag1.diag_type = Full
60+
diag1.write_species = 1
61+
diag1.species = beam1
62+
diag1.fields_to_plot = rho_beam1
63+
diag1.format = plotfile
64+
diag1.dump_last_timestep = 1
65+
66+
67+
openpmd.intervals = 1
68+
openpmd.diag_type = Full
69+
openpmd.write_species = 1
70+
openpmd.species = beam1
71+
openpmd.beam1.variables = w x y z
72+
openpmd.fields_to_plot = none
73+
openpmd.format = openpmd
74+
openpmd.dump_last_timestep = 1
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""
2+
This script prepares the inputs for the test_3d_focusing_gaussian_beam_from_openpmd script.
3+
4+
It generates a set of particles and saves in an openPMD file, to be read as input by the WarpX simulation.
5+
"""
6+
7+
import numpy as np
8+
from scipy.constants import c, e, m_e
9+
10+
np.random.seed(0)
11+
12+
# Beam properties
13+
14+
mc2 = m_e * c * c
15+
nano = 1.0e-9
16+
micro = 1.0e-6
17+
GeV = e * 1.0e9
18+
energy = 125.0 * GeV
19+
gamma = energy / mc2
20+
npart = 2.0e10
21+
nmacropart = 2000000
22+
charge = e * npart
23+
sigmax = 516.0 * nano
24+
sigmay = 7.7 * nano
25+
sigmaz = 300.0 * micro
26+
ux = 0.0
27+
uy = 0.0
28+
uz = gamma
29+
emitx = 50 * micro
30+
emity = 20 * nano
31+
emitz = 0.0
32+
dux = emitx / sigmax
33+
duy = emity / sigmay
34+
duz = emitz / sigmaz
35+
focal_distance = 4 * sigmaz
36+
37+
# Generate arrays of particle quantities using numpy
38+
39+
# Generate Gaussian distribution (corresponds to the beam at focus)
40+
x = np.random.normal(0, sigmax, nmacropart)
41+
y = np.random.normal(0, sigmay, nmacropart)
42+
z = np.random.normal(0, sigmaz, nmacropart)
43+
ux = np.random.normal(ux, dux, nmacropart)
44+
uy = np.random.normal(uy, duy, nmacropart)
45+
uz = np.random.normal(uz, duz, nmacropart)
46+
47+
# Change transverse beam positions, in way consistent with
48+
# the beam being focused to focal_distance downstream
49+
x = x - (focal_distance - z) * ux / uz
50+
y = y - (focal_distance - z) * uy / uz
51+
52+
# Write particles to file using openPMD API
53+
54+
from openpmd_api import (
55+
Access_Type,
56+
Dataset,
57+
Mesh_Record_Component,
58+
Series,
59+
)
60+
61+
SCALAR = Mesh_Record_Component.SCALAR
62+
63+
# open file for writing
64+
f = Series("openpmd_generated_particles.h5", Access_Type.create)
65+
f.set_meshes_path("fields")
66+
f.set_particles_path("particles")
67+
cur_it = f.iterations[0]
68+
electrons = cur_it.particles["electrons"]
69+
70+
# All particle quantities will be written as float
71+
d = Dataset(np.dtype("float64"), extent=(nmacropart,))
72+
73+
# The weight is the number of physical particles per macro particle.
74+
# In this particular case, it is the same for all macro particles.
75+
electrons["weighting"].reset_dataset(d)
76+
electrons["weighting"][SCALAR].make_constant(npart / nmacropart)
77+
78+
# Write mass and charge
79+
electrons["mass"].reset_dataset(d)
80+
electrons["mass"][SCALAR].make_constant(m_e)
81+
electrons["charge"].reset_dataset(d)
82+
electrons["charge"][SCALAR].make_constant(-e)
83+
84+
# Write particle positions
85+
electrons["position"]["x"].reset_dataset(d)
86+
electrons["position"]["x"].store_chunk(x)
87+
electrons["position"]["y"].reset_dataset(d)
88+
electrons["position"]["y"].store_chunk(y)
89+
electrons["position"]["z"].reset_dataset(d)
90+
electrons["position"]["z"].store_chunk(z)
91+
92+
# Write particle momenta
93+
electrons["momentum"]["x"].reset_dataset(d)
94+
electrons["momentum"]["x"].store_chunk(ux)
95+
electrons["momentum"]["x"].unit_SI = m_e * c # Convert from unitless (gamma*beta) to SI
96+
electrons["momentum"]["y"].reset_dataset(d)
97+
electrons["momentum"]["y"].store_chunk(uy)
98+
electrons["momentum"]["y"].unit_SI = m_e * c # Convert from unitless (gamma*beta) to SI
99+
electrons["momentum"]["z"].reset_dataset(d)
100+
electrons["momentum"]["z"].store_chunk(uz)
101+
electrons["momentum"]["z"].unit_SI = m_e * c # Convert from unitless (gamma*beta) to SI
102+
103+
# Write the particle offset
104+
# (required by the openPMD standard but set to 0 here)
105+
electrons["positionOffset"]["x"].reset_dataset(d)
106+
electrons["positionOffset"]["x"].make_constant(0.0)
107+
electrons["positionOffset"]["y"].reset_dataset(d)
108+
electrons["positionOffset"]["y"].make_constant(0.0)
109+
electrons["positionOffset"]["z"].reset_dataset(d)
110+
electrons["positionOffset"]["z"].make_constant(0.0)
111+
112+
# at any point in time you may decide to dump already created output to
113+
# disk note that this will make some operations impossible (e.g. renaming
114+
# files)
115+
f.flush()
116+
117+
# now the file is closed
118+
del f
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"beam1": {
3+
"particle_momentum_x": 4.222627241785716e-14,
4+
"particle_momentum_y": 1.1315831971311022e-15,
5+
"particle_momentum_z": 1.3360487849541028e-10,
6+
"particle_position_x": 1.12883910539703,
7+
"particle_position_y": 0.023913938068817645,
8+
"particle_position_z": 478.7122905262462,
9+
"particle_weight": 19999660000.0
10+
},
11+
"lev=0": {
12+
"rho_beam1": 5637684521411.177
13+
}
14+
}

0 commit comments

Comments
 (0)