Particle Products#
Overview#
We consider a scenario where N existing particles are to each create M new particles. These new particles will ultimately be added to the ParticleGroup which contains the original N particles and hence each of the NM new particles require all of the properties to be set to sensible values. The particle properties can always be modified after the new particles are added to the ParticleGroup by using a ParticleLoop therefore here we discuss methods to define particle properties before the particles are added.
Particles are created via the DescendantProducts data structure which provides space for each of the new particle properties. These particle properties are accessed from each of the N parent particles from a ParticleLoop kernel. Once a DescendantProducts instance is populated with values those values can be used to create new particles in the parent ParticleGroup by calling add_particles_local with the new properties.
Properties of New Particles#
When a new DescendantProducts instance is created the number of output products M per existing particle is specified along with the REAL and INT particle properties which will be explicitly set in the ParticleLoop kernel. This kernel must also set the parent particle of the new product particle by calling set_parent for the product particle to be included when add_particles_local is called.
The set_parent mechanism provides two functions, firstly it defines the product as a product which should be included when add_particles_local is called. This set_parent call can be intentionally omitted to mask off any number of the M products which ultimately were not required. Secondly for particle properties which are not defined in the DescendantProducts constructor the set_parent call defines the parent particle from which these properties should be copied.
The destination ParticleGroup for the new particle products has a set of particle properties for each of the particles in the ParticleGroup. When the DescendantProducts are are added via add_particles_local there are two options. If the property is explicitly defined in the DescendantProducts instance then the component values, for all particles and all components of that property, are copied from the DescendantProducts into the corresponding ParticleDats in the ParticleGroup. If a property is defined in the ParticleGroup and not in the DescendantProducts then for all new particle entries in the DescendantProducts the component values for that property are copied from the parents specified in the DescendantProducts.
Note that the decision to copy property values from a parent particle or from a DescendantProducts entry is taken property wise for all particles for all components of the property. If finer control is required, e.g. to inherit particle properties on a per particle or per component level, then the user should specify the property in the DescendantProducts instance and populate the new properties for all particles and components in the ParticleLoop kernel which is responsible for populating the entries in the DescendantProducts instance.
Property Ordering#
In the ParticleLoop kernel which sets the properties of the new products the property, and component, are specified by an integer index instead of the Sym objects used in host code. The ordering of the properties is defined as the order in which particle properties are specified for the DescendantProducts specification. This ordering is contiguous within all properties of the same data type, e.g. if two INT properties are specified then the integer properties are indexed with 0 and 1 and if three REAL properties are specified then the real valued properties are indexed 0,1 and 2. Interlacing of INT and REAL properties in the specification is ignored, the properties are indexed within the set of properties that have the same type in the order they are specified.
Example#
inline void descendant_products_example(
ParticleGroupSharedPtr particle_group
) {
/* For this example we assume that the particle group has the following REAL
and INT properties with the specified number of components:
REAL
----
P 2 (positions)
V 3
Q 1
INT
---
CELL_ID 1 (cell id)
ID 1
*/
/*
Example print output with 5 particles in particle_group:
particle_group->print(
Sym<REAL>("P"),
Sym<REAL>("V"),
Sym<REAL>("Q"),
Sym<INT>("CELL_ID"),
Sym<INT>("ID")
);
================================================================================
------- 194 -------
| P | V | Q | CELL_ID | ID |
| 0.503483 3.132274 | -1.029170 -0.238606 0.833977 | 1.000000 | 194 | 3 |
------- 205 -------
| P | V | Q | CELL_ID | ID |
| 3.443890 3.179283 | -1.879651 -0.262682 -0.862215 | 1.000000 | 205 | 2 |
------- 217 -------
| P | V | Q | CELL_ID | ID |
| 2.443217 3.438988 | 1.305861 -1.304251 -0.096116 | 1.000000 | 217 | 1 |
------- 285 -------
| P | V | Q | CELL_ID | ID |
| 3.271273 4.276710 | -0.101299 -0.826377 0.081399 | 1.000000 | 285 | 0 |
------- 419 -------
| P | V | Q | CELL_ID | ID |
| 0.993615 6.648731 | -0.338175 0.151852 -1.346172 | 1.000000 | 419 | 4 |
================================================================================
*/
// We create a DescendantProducts with the following specification.
// The number of components for a property must match the number of
// components in the ParticleGroup the products are added to.
auto product_spec = product_matrix_spec(
ParticleSpec(
ParticleProp(Sym<REAL>("V"), 3),
ParticleProp(Sym<REAL>("Q"), 1),
ParticleProp(Sym<INT>("ID"), 1)
)
);
/* Re-visiting the properties in the particle group and the product
specification:
REAL
----
P: Is a ParticleGroup property not in the product spec therefore the values
will be copied from the parent particles.
V: Is defined in the product spec and the values set in the
DescendantProducts will be used for the new particles.
Q: Is defined in the product spec and the values set in the
DescendantProducts will be used for the new particles.
INT
---
CELL_ID: Is a ParticleGroup property not in the product spec therefore the
values will be copied from the parent particles.
ID: Is defined in the product spec and the values set in the
DescendantProducts will be used for the new particles.
*/
// Create a DescendantProducts with the above product spec for at most 2
// products per parent particle.
const int num_products_per_particle = 2;
auto dp = std::make_shared<DescendantProducts>(
particle_group->sycl_target,
product_spec,
num_products_per_particle
);
// Define a ParticleLoop which creates the products from the parent
// particles.
auto loop = particle_loop(
particle_group,
[=](
auto DP, auto parent_index, auto V, auto Q, auto ID
){
for(int childx=0 ; childx<num_products_per_particle ; childx++){
// Enable this product by calling set_parent
DP.set_parent(parent_index, childx);
// The V property was the first REAL product we specified and therefore
// has property index 0 for at_real.
const int V_index = 0;
for(int dimx=0 ; dimx<3 ; dimx++){
// Copy V from parent but negate the sign.
DP.at_real(parent_index, childx, V_index, dimx) = -1.0 * V.at(dimx);
}
// The Q property was the second REAL product specified and hence has
// property index 1 for set_real.
const int Q_index = 1;
// Simply copy the parent Q value in this kernel.
DP.at_real(parent_index, childx, Q_index, 0) = Q.at(0);
// The ID property is the first INT property we specified and hence has
// index 0 for at_int.
const int ID_index = 0;
// Copy parent ID but modify it.
DP.at_int(parent_index, childx, ID_index, 0) = -1 * ID.at(0)
- 100 * childx;
}
},
Access::write(dp),
Access::read(ParticleLoopIndex{}),
Access::read(Sym<REAL>("V")),
Access::read(Sym<REAL>("Q")),
Access::read(Sym<INT>("ID"))
);
// Before a loop is executed that accesses a DescendantProducts data
// structure the reset method must be called with the number of particles in
// the iteration set of the loop.
dp->reset(particle_group->get_npart_local());
// Execute the loop to create the products.
loop->execute();
// Finally add the new products to the ParticleGroup.
particle_group->add_particles_local(dp);
/* Example print output with 5 particles in particle_group:
particle_group->print(
Sym<REAL>("P"),
Sym<REAL>("V"),
Sym<REAL>("Q"),
Sym<INT>("CELL_ID"),
Sym<INT>("ID")
);
================================================================================
------- 194 -------
| P | V | Q | CELL_ID | ID |
| 0.503483 3.132274 | -1.029170 -0.238606 0.833977 | 1.000000 | 194 | 3 |
| 0.503483 3.132274 | 1.029170 0.238606 -0.833977 | 1.000000 | 194 | -3 |
| 0.503483 3.132274 | 1.029170 0.238606 -0.833977 | 1.000000 | 194 | -103 |
------- 205 -------
| P | V | Q | CELL_ID | ID |
| 3.443890 3.179283 | -1.879651 -0.262682 -0.862215 | 1.000000 | 205 | 2 |
| 3.443890 3.179283 | 1.879651 0.262682 0.862215 | 1.000000 | 205 | -102 |
| 3.443890 3.179283 | 1.879651 0.262682 0.862215 | 1.000000 | 205 | -2 |
------- 217 -------
| P | V | Q | CELL_ID | ID |
| 2.443217 3.438988 | 1.305861 -1.304251 -0.096116 | 1.000000 | 217 | 1 |
| 2.443217 3.438988 | -1.305861 1.304251 0.096116 | 1.000000 | 217 | -1 |
| 2.443217 3.438988 | -1.305861 1.304251 0.096116 | 1.000000 | 217 | -101 |
------- 285 -------
| P | V | Q | CELL_ID | ID |
| 3.271273 4.276710 | -0.101299 -0.826377 0.081399 | 1.000000 | 285 | 0 |
| 3.271273 4.276710 | 0.101299 0.826377 -0.081399 | 1.000000 | 285 | -100 |
| 3.271273 4.276710 | 0.101299 0.826377 -0.081399 | 1.000000 | 285 | 0 |
------- 419 -------
| P | V | Q | CELL_ID | ID |
| 0.993615 6.648731 | -0.338175 0.151852 -1.346172 | 1.000000 | 419 | 4 |
| 0.993615 6.648731 | 0.338175 -0.151852 1.346172 | 1.000000 | 419 | -4 |
| 0.993615 6.648731 | 0.338175 -0.151852 1.346172 | 1.000000 | 419 | -104 |
================================================================================
*/
return;
}