Fields and particles¶
Relevant headers
framework/containers/fields.h
framework/containers/species.h
framework/containers/particles.h
To store the main data of the simulation, the fields and the particles, Entity provides container classes Fields<D,S>
and Particles<D,C>
, where D
is a template argument for the dimension, S
for the simulation engine, and C
for the coordinate type. Notice, that none of these objects know anything about the geometry of the space-time (i.e., the metric), and thus they should be used in conjunction with the Mesh<M>
object discussed here.
Depending on the simulation engine, and the coordinate system, these object allocate some of the built-in arrays, while ignoring the others. Below is a full breakdown for all the arrays for both of these classes. We assume that \(n_i\) is the number of cells on the given subdomain in the \(i\)-th direction.
Fields¶
Name | Shape | Description | Allocated when... |
---|---|---|---|
em |
\(n_1[\times n_2[\times n_3]]\times 6\) | main container for the electric and magnetic fields \(E^i\), \(B^i\) in SR and \(D^i\), \(B^i\) in GR | always |
bckp |
\(n_1[\times n_2[\times n_3]]\times 6\) | used for intermediate operations (e.g., output) | always |
cur |
\(n_1[\times n_2[\times n_3]]\times 3\) | current densities \(J^i\) | always |
buff |
\(n_1[\times n_2[\times n_3]]\times 3\) | primarily used for storing intermediate values for the currents (e.g., for filtering) | always |
aux |
\(n_1[\times n_2[\times n_3]]\times 6\) | auxiliary GR fields \(E_i\), \(H_i\) | GR |
em0 |
\(n_1[\times n_2[\times n_3]]\times 6\) | second set of electromagnetic fields staggered in time w.r.t. the main ones | GR |
cur0 |
\(n_1[\times n_2[\times n_3]]\times 3\) | current densities at the previous timestep | GR |
All of the field arrays have a type real_t
which compilers to float
when using single precision, and double
when using double precision.
Staggering
Keep in mind that the field components stored in all these arrays are staggered not only in time, but also in space. Entity employs the Yee grid staggering, and this the electric fields are stored at the corresponding cell edges, while the magnetic fields are stored at the cell faces. To see how those fields are staggered in time, refer to the PIC algorithm section.
Field loops
Notice that fields have an additional dimension which stores the component. For convenience, Entity provides aliases to access those components: em::ex1
(which maps to 0
), em::bx3
(which maps to 5
), etc. A typical loop over all the fields on the local subdomain would look like this:
// assume a 2D simulation
auto fields = domain.fields;
auto metric = domain.mesh.metric;
Kokkos::parallel_for("field_loop",
domain.mesh.rangeActiveCells(),
Lambda(index_t i1, index_t i2) {
// get the code-unit coordinate of the cell corner
const auto i1_ = COORD(i1);
const auto i2_ = COORD(i2);
// get the sqrt_det_h at (i + 1/2, j)
const auto sqrtdeth = metric.sqrt_det_h({i1_ + HALF, i2_});
// do something with the Ex1 field (just an example)
fields.em(i1, i2, em::ex1) *= ONE / sqrtdeth;
});
Particles¶
Name | Type | Description | Allocated when... |
---|---|---|---|
i1 |
int |
cell index of the particle in \(x_1\) | always |
i2 |
int |
cell index of the particle in \(x_2\) | 2D or 3D |
i3 |
int |
cell index of the particle in \(x_3\) | 3D |
dx1 |
prtldx_t |
displacement of the particle in the cell in \(x_1\) | always |
dx2 |
prtldx_t |
displacement of the particle in the cell in \(x_2\) | 2D or 3D |
dx3 |
prtldx_t |
displacement of the particle in the cell in \(x_3\) | 3D |
ux1 |
real_t |
velocity of the particle (see comment below) | always |
ux2 |
real_t |
velocity of the particle (see comment below) | always |
ux3 |
real_t |
velocity of the particle (see comment below) | always |
weight |
real_t |
particle weights | always |
tag |
short |
particle tag | always |
phi |
real_t |
\(\phi\) coordinate of the particle | 2D non-Cartesian |
i1_prev |
int |
same as i1 but for the previous step |
always |
i2_prev |
int |
same as i2 but for the previous step |
2D or 3D |
i3_prev |
int |
same as i3 but for the previous step |
3D |
dx1_prev |
prtldx_t |
same as dx1 but for the previous step |
always |
dx2_prev |
prtldx_t |
same as dx2 but for the previous step |
2D or 3D |
dx3_prev |
prtldx_t |
same as dx3 but for the previous step |
3D |
vector of pld |
real_t |
custom particle payloads | as needed (defined in the input) |
prtldx_t
is a type alias for real_t
which is used for the displacement of the particle w.r.t. the corner of the cell (this can be changed to be half-precision). Notice, that we additionally store the phi
coordinate for the particles in non-Cartesian 2D simulations. While in GR this is totally optional, in SR it is required to keep track of the full particle coordinate, to be able to convert to and from the global Cartesian metric.
Particle velocities
Velocities, stored in ux1
, ux2
, and ux3
have different meanings in different configurations. For SR simulations, particle velocities are stored in the global Cartesian basis, while in GR, these velocities correspond to the covariant components \(u_i\) in code units.
All of the particle arrays have shape of maxnpart
, which is set at the beginning of the simulation from the input file. To indicate the number of active particles, one can use the npart()
method of the Particles
object. That being said, remember that particles can always leave the simulation domain, or be sent to another subdomain (while its index would still be less than npart()
). For that, we use the tag
field, which can be set to ParticleTag::dead
to indicate that the particle is no longer active.
Particle loops
A typical loop over all the particles on the local subdomain would look like this:
// for example, taking the first species
auto particles = domain.species[0];
Kokkos::parallel_for("prtl_loop",
particles.rangeActiveParticles(),
Lambda(index_t p) {
if (particles.tag(p) == ParticleTag::dead) {
return;
}
// do something with the particle
});
Below is the diagram showing the structure of the fields and particles objects, and how they are stored in the Domain
object.
classDiagram
class Domain~SimEngine, Metric~{
see domains...*
}
class Particles~Dimension, CoordType~{
-size_t m_npart
-size_t m_ntags
+array_t~int*~ i1
+array_t~int*~ i2
+array_t~int*~ i3
+array_t~prtldx_t*~ dx1
+array_t~prtldx_t*~ dx2
+array_t~prtldx_t*~ dx3
+array_t~real_t*~ ux1
+array_t~real_t*~ ux2
+array_t~real_t*~ ux3
+array_t~real_t*~ weight
+array_t~int*~ i1_prev
+array_t~int*~ i2_prev
+array_t~int*~ i3_prev
+array_t~prtldx_t*~ dx1_prev
+array_t~prtldx_t*~ dx2_prev
+array_t~prtldx_t*~ dx3_prev
+array_t~short*~ tag
+vector~array_t~real_t*~~ pld
+array_t~real_t*~ phi
+array_mirror_t~int*~ i1_h
+array_mirror_t~int*~ i2_h
+array_mirror_t~int*~ i3_h
+array_mirror_t~prtldx_t*~ dx1_h
+array_mirror_t~prtldx_t*~ dx2_h
+array_mirror_t~prtldx_t*~ dx3_h
+array_mirror_t~real_t*~ ux1_h
+array_mirror_t~real_t*~ ux2_h
+array_mirror_t~real_t*~ ux3_h
+array_mirror_t~real_t*~ weight_h
+array_mirror_t~real_t*~ phi_h
+array_mirror_t~short*~ tag_h
+vector<array_mirror_t~real_t*~> pld_h
+rangeActiveParticles() range_t
+rangeAllParticles() range_t
+npart() size_t
+ntags() size_t
+memory_footprint() size_t
+npart_per_tag() vector~size_t~
+set_npart(size_t)
+SortByTags() vector~size_t~
+SyncHostDevice()
}
class Fields~Dimension, SimEngine~{
+ndfield_t~D, 6~ em
+ndfield_t~D, 6~ bckp
+ndfield_t~D, 3~ cur
+ndfield_t~D, 3~ buff
+ndfield_t~D, 6~ aux
+ndfield_t~D, 6~ em0
+ndfield_t~D, 3~ cur0
+memory_footprint() size_t
}
class ParticleSpecies {
-ushort m_index
-string m_label
-float m_mass
-float m_charge
-size_t m_maxnpart
-PrtlPusher m_pusher
-bool m_use_gca
-Cooling m_cooling
-ushort m_npld
+index() ushort
+label() string
+mass() float
+charge() float
+maxnpart() size_t
+pusher() PrtlPusher
+use_gca() bool
+cooling() Cooling
+npld() ushort
}
Domain --* Particles : contains many
Domain --* Fields : contains
ParticleSpecies <|-- Particles : inherits
note "+: public\n-: private\n#: protected\nunderline: static constexpr\nitalic: virtual"