WarpX
Loading...
Searching...
No Matches
SplitAndScatterFunc.H
Go to the documentation of this file.
1/* Copyright 2023-2024 The WarpX Community
2 *
3 * This file is part of WarpX.
4 *
5 * Authors: Roelof Groenewald (TAE Technologies)
6 *
7 * License: BSD-3-Clause-LBNL
8 */
9#ifndef WARPX_SPLIT_AND_SCATTER_FUNC_H_
10#define WARPX_SPLIT_AND_SCATTER_FUNC_H_
11
17#include "Utils/ParticleUtils.H"
19
20#include <array>
21
27{
28 // Define shortcuts for frequently-used type names
31 using ParticleTileDataType = typename ParticleTileType::ParticleTileDataType;
34 using SoaData_type = WarpXParticleContainer::ParticleTileType::ParticleTileDataType;
35
36public:
40 SplitAndScatterFunc () = default;
41
48 SplitAndScatterFunc (const std::string& collision_name, MultiParticleContainer const * mypc);
49
101 const index_type& n_total_pairs,
102 ParticleTileType& ptile0, ParticleTileType& ptile1,
104 ParticleTileType** AMREX_RESTRICT tile_products,
105 const amrex::ParticleReal m0, const amrex::ParticleReal m1,
106 const amrex::Vector<amrex::ParticleReal>& /*products_mass*/,
108 amrex::Vector<index_type>& products_np,
109 const SmartCopy* AMREX_RESTRICT copy_species0,
110 const SmartCopy* AMREX_RESTRICT copy_species1,
111 const index_type* AMREX_RESTRICT p_pair_indices_0,
112 const index_type* AMREX_RESTRICT p_pair_indices_1,
113 const amrex::ParticleReal* AMREX_RESTRICT p_pair_reaction_weight,
114 const amrex::ParticleReal* /*p_product_data*/ ) const
115 {
116 using namespace amrex::literals;
117
118 // Product species slot layout (m_num_product_species slots total):
119 // Slot 0: copy of the first reactant species, i.e. the species from ptile0 (always present)
120 // Slot 1: copy of the other reactant species, i.e. the species from ptile1 (always present)
121 // Slot 2: first true product species
122 // Slot 3: second true product species
123 //
124 // For non-product producing collisions (elastic, back, forward scattering), only slots 0
125 // and 1 receive new macroparticles: one weight-split copy per reactant per colliding pair.
126 // These are counted by `no_product_total` and NOT reflected in `m_num_products_host`.
127 //
128 // For product-producing collisions (ionization, two-product reaction, charge exchange),
129 // slots 2 and 3 receive one new particle per event. For ionization, slot 0 also receives one
130 // particle (the surviving reactant species copy). These counts are stored in
131 // `m_num_products_host[i]` and multiplied by `with_product_total` to get the totals.
132
133 // If there are no colliding pairs, skip the rest of this kernel and return a vector of
134 // zeros, indicating that for all the "product" species, there were no new macroparticles added.
135 if (n_total_pairs == 0) { return amrex::Vector<int>(m_num_product_species, 0); }
136
137 // The list of scattering processes, used to look up the type (and, further below, the
138 // energy penalty) of the process selected for each pair via its index.
139 const ScatteringProcess::Executor* AMREX_RESTRICT scattering_processes =
141
142 // The following is used to calculate the appropriate offsets for
143 // non-product producing collisions (i.e., not ionization, not two-product reaction).
144 // Note that a standard cummulative sum is not appropriate since we count 1 for each
145 // colliding pair, regardless of the index of the selected scattering process.
146 amrex::Gpu::DeviceVector<index_type> no_product_offsets(n_total_pairs);
147 index_type* AMREX_RESTRICT no_product_offsets_data = no_product_offsets.data();
148 auto const no_product_total = amrex::Scan::PrefixSum<index_type>(n_total_pairs,
150 if (mask[i] == 0) { return 0; }
151 const auto selected_process_index = mask[i] - 1;
152 auto const& scattering_process = scattering_processes[selected_process_index];
153 return !scattering_process.m_produces_products;
154 },
155 [=] AMREX_GPU_DEVICE (index_type i, index_type s) { no_product_offsets_data[i] = s; },
157 );
158
160 for (int i = 0; i < 2; i++)
161 {
162 // Record the number of non-product producing collisions that lead to new particles
163 // for the first and second reactant species (slots 0 and 1). Only 1
164 // weight-split particle is created per reactant per event.
165 num_added_vec[i] = static_cast<int>(no_product_total);
166 }
167
168 // The following is used to calculate the appropriate offsets for
169 // product producing processes (i.e., ionization and two-product reaction).
170 // Note that a standard cummulative sum is not appropriate since we count 1 for each
171 // colliding pair, regardless of the index of the selected scattering process.
172 amrex::Gpu::DeviceVector<index_type> with_product_offsets(n_total_pairs);
173 index_type* AMREX_RESTRICT with_product_offsets_data = with_product_offsets.data();
174 auto const with_product_total = amrex::Scan::PrefixSum<index_type>(n_total_pairs,
176 if (mask[i] == 0) { return 0; }
177 const auto selected_process_index = mask[i] - 1;
178 auto const& scattering_process = scattering_processes[selected_process_index];
179 return scattering_process.m_produces_products;
180 },
181 [=] AMREX_GPU_DEVICE (index_type i, index_type s) { with_product_offsets_data[i] = s; },
183 );
184
185 for (int i = 0; i < m_num_product_species; i++)
186 {
187 // Add the number of product producing events to the species involved in those processes.
188 const int num_products = m_num_products_host[i];
189 const index_type num_added = with_product_total * num_products;
190 num_added_vec[i] += static_cast<int>(num_added);
191 }
192
193 // resize the particle tiles to accomodate the new particles
194 // The code works correctly, even if the same species appears
195 // several times in the product species list.
196 // (e.g., for e- + H -> 2 e- + H+, the product species list is [e-, H, e-, H+])
197 // In that case, the species that appears multiple times is resized several times ;
198 // `products_np` keeps track of array positions at which it has been resized.
199 for (int i = 0; i < m_num_product_species; i++)
200 {
201 products_np[i] = tile_products[i]->numParticles();
202 tile_products[i]->resize(products_np[i] + num_added_vec[i]);
203 }
204
205 const auto soa_0 = ptile0.getParticleTileData();
206 const auto soa_1 = ptile1.getParticleTileData();
207
208 // Create necessary GPU vectors, that will be used in the kernel below
209 amrex::Vector<SoaData_type> soa_products;
210 for (int i = 0; i < m_num_product_species; i++)
211 {
212 soa_products.push_back(tile_products[i]->getParticleTileData());
213 }
214#ifdef AMREX_USE_GPU
217
219 soa_products.end(),
220 device_soa_products.begin());
222 products_np.end(),
223 device_products_np.begin());
224
226 SoaData_type* AMREX_RESTRICT soa_products_data = device_soa_products.data();
227 const index_type* AMREX_RESTRICT products_np_data = device_products_np.data();
228#else
229 SoaData_type* AMREX_RESTRICT soa_products_data = soa_products.data();
230 const index_type* AMREX_RESTRICT products_np_data = products_np.data();
231#endif
232
233 const int num_product_species = m_num_product_species;
234
235 // Grab the masses of the true product species (slots 2 and 3)
236 amrex::ParticleReal mass_slot2 = 0;
237 amrex::ParticleReal mass_slot3 = 0;
238 if (num_product_species > 2) {
239 mass_slot2 = pc_products[2]->getMass();
240 mass_slot3 = pc_products[3]->getMass();
241 }
242
243 // First perform all non-product producing collisions
244 amrex::ParallelForRNG(n_total_pairs,
245 [=] AMREX_GPU_DEVICE (int i, amrex::RandomEngine const& engine) noexcept
246 {
247 // Skip pairs for which no collision occurred.
248 if (mask[i] == 0) { return; }
249
250 // The scattering process selected for this pair, and its type.
251 const auto selected_process_index = mask[i] - 1;
252 auto const& scattering_process = scattering_processes[selected_process_index];
253 const auto process_type = scattering_process.m_type;
254
255 if (!scattering_process.m_produces_products)
256 {
257 const auto slot0_idx = products_np_data[0] + no_product_offsets_data[i];
258 // Make a copy of the particle from the first reactant species
259 copy_species0[0](soa_products_data[0], soa_0, static_cast<int>(p_pair_indices_0[i]),
260 static_cast<int>(slot0_idx), engine);
261 // Set the weight of the new particles to p_pair_reaction_weight[i]
262 soa_products_data[0].m_rdata[PIdx::w][slot0_idx] = p_pair_reaction_weight[i];
263
264 const auto slot1_idx = products_np_data[1] + no_product_offsets_data[i];
265 // Make a copy of the particle from the other reactant species
266 copy_species1[1](soa_products_data[1], soa_1, static_cast<int>(p_pair_indices_1[i]),
267 static_cast<int>(slot1_idx), engine);
268 // Set the weight of the new particles to p_pair_reaction_weight[i]
269 soa_products_data[1].m_rdata[PIdx::w][slot1_idx] = p_pair_reaction_weight[i];
270
271 // Set the child particle properties appropriately
272 auto& ux0 = soa_products_data[0].m_rdata[PIdx::ux][slot0_idx];
273 auto& uy0 = soa_products_data[0].m_rdata[PIdx::uy][slot0_idx];
274 auto& uz0 = soa_products_data[0].m_rdata[PIdx::uz][slot0_idx];
275 auto& ux1 = soa_products_data[1].m_rdata[PIdx::ux][slot1_idx];
276 auto& uy1 = soa_products_data[1].m_rdata[PIdx::uy][slot1_idx];
277 auto& uz1 = soa_products_data[1].m_rdata[PIdx::uz][slot1_idx];
278
279 // for simplicity (for now) we assume non-relativistic particles
280 // and simply calculate the center-of-momentum velocity from the
281 // rest masses
282 // TODO: this could be made relativistic by using TwoProductComputeProductMomenta
283 auto const uCOM_x = (m0 * ux0 + m1 * ux1) / (m0 + m1);
284 auto const uCOM_y = (m0 * uy0 + m1 * uy1) / (m0 + m1);
285 auto const uCOM_z = (m0 * uz0 + m1 * uz1) / (m0 + m1);
286
287 // transform to COM frame
288 ux0 -= uCOM_x;
289 uy0 -= uCOM_y;
290 uz0 -= uCOM_z;
291 ux1 -= uCOM_x;
292 uy1 -= uCOM_y;
293 uz1 -= uCOM_z;
294
295 if (process_type == ScatteringProcessType::ELASTIC ||
296 process_type == ScatteringProcessType::EXCITATION) {
297 // Subtract the excitation energy penalty from the total COM
298 // kinetic energy, then randomly rotate the velocity vector of
299 // the first particle. For elastic collisions the energy penalty
300 // is zero, so this reduces to a random rotation that conserves
301 // the COM kinetic energy.
302 // Total KE in COM frame = 0.5 * m0 * u0^2 * (m0+m1)/m1
303 const auto u0_sq = ux0*ux0 + uy0*uy0 + uz0*uz0;
304 const auto E_com = 0.5_prt * m0 * u0_sq * (m0 + m1) / m1;
305 const auto E_out = E_com - scattering_process.m_energy_penalty * PhysConst::q_e;
306 const auto u0_new = std::sqrt(u0_sq * E_out / E_com);
307 ParticleUtils::RandomizeVelocity(ux0, uy0, uz0, u0_new, engine);
308 // set the second particles velocity so that the total momentum
309 // is zero
310 ux1 = -ux0 * m0 / m1;
311 uy1 = -uy0 * m0 / m1;
312 uz1 = -uz0 * m0 / m1;
313 } else if (process_type == ScatteringProcessType::BACK) {
314 // reverse the velocity vectors of both particles
315 ux0 *= -1.0_prt;
316 uy0 *= -1.0_prt;
317 uz0 *= -1.0_prt;
318 ux1 *= -1.0_prt;
319 uy1 *= -1.0_prt;
320 uz1 *= -1.0_prt;
321 } else if (process_type == ScatteringProcessType::FORWARD) {
322 amrex::Abort("Forward scattering with DSMC not implemented yet.");
323 }
324 else {
325 amrex::Abort("Unknown scattering process.");
326 }
327 // transform back to labframe
328 ux0 += uCOM_x;
329 uy0 += uCOM_y;
330 uz0 += uCOM_z;
331 ux1 += uCOM_x;
332 uy1 += uCOM_y;
333 uz1 += uCOM_z;
334
335 }
336
337 // Next perform all product-producing collisions
338 else if ((process_type == ScatteringProcessType::TWOPRODUCT_REACTION) ||
340 {
341 // create a copy of the first product species at the location of the first reactant species
342 const auto src0_idx = static_cast<int>(p_pair_indices_0[i]);
343 const auto slot2_idx = products_np_data[2] + with_product_offsets_data[i];
344 copy_species0[2](soa_products_data[2], soa_0, src0_idx,
345 static_cast<int>(slot2_idx), engine);
346 // Set the weight of the new particle to p_pair_reaction_weight[i]
347 soa_products_data[2].m_rdata[PIdx::w][slot2_idx] = p_pair_reaction_weight[i];
348
349 // create a copy of the other product species at the location of the other reactant species
350 const auto src1_idx = static_cast<int>(p_pair_indices_1[i]);
351 const auto slot3_idx = products_np_data[3] + with_product_offsets_data[i];
352 copy_species1[3](soa_products_data[3], soa_1, src1_idx,
353 static_cast<int>(slot3_idx), engine);
354 // Set the weight of the new particle to p_pair_reaction_weight[i]
355 soa_products_data[3].m_rdata[PIdx::w][slot3_idx] = p_pair_reaction_weight[i];
356
357 // For charge exchange, swap positions at which the products are created
358 // to ensure local charge conservation. For instance in the reaction
359 // `A+ + B -> A + B+`, B+ will be created at the position of A+, instead of that of B.
360 if (process_type == ScatteringProcessType::CHARGE_EXCHANGE) {
361 // The SoA components that define the positions depend on the geometry.
362#if defined(WARPX_DIM_1D_Z)
363 constexpr std::array<int, 1> position_indices = {PIdx::z};
364#elif defined(WARPX_DIM_XZ)
365 constexpr std::array<int, 2> position_indices = {PIdx::x, PIdx::z};
366#elif defined(WARPX_DIM_RZ)
367 constexpr std::array<int, 3> position_indices = {PIdx::r, PIdx::z, PIdx::theta};
368#elif defined(WARPX_DIM_RCYLINDER)
369 constexpr std::array<int, 2> position_indices = {PIdx::r, PIdx::theta};
370#elif defined(WARPX_DIM_RSPHERE)
371 constexpr std::array<int, 3> position_indices = {PIdx::r, PIdx::theta, PIdx::phi};
372#elif defined(WARPX_DIM_3D)
373 constexpr std::array<int, 3> position_indices = {PIdx::x, PIdx::y, PIdx::z};
374#endif
375 // Loop over the components that define the positions and swap the positions of the products.
376 for (int const idim : position_indices) {
377 soa_products_data[2].m_rdata[idim][slot2_idx] = soa_1.m_rdata[idim][src1_idx];
378 soa_products_data[3].m_rdata[idim][slot3_idx] = soa_0.m_rdata[idim][src0_idx];
379 }
380 }
381
382 // Update the momenta of the products
383 TwoProductComputeProductMomenta(
384 soa_0.m_rdata[PIdx::ux][src0_idx],
385 soa_0.m_rdata[PIdx::uy][src0_idx],
386 soa_0.m_rdata[PIdx::uz][src0_idx], m0,
387 soa_1.m_rdata[PIdx::ux][src1_idx],
388 soa_1.m_rdata[PIdx::uy][src1_idx],
389 soa_1.m_rdata[PIdx::uz][src1_idx], m1,
390 soa_products_data[2].m_rdata[PIdx::ux][slot2_idx],
391 soa_products_data[2].m_rdata[PIdx::uy][slot2_idx],
392 soa_products_data[2].m_rdata[PIdx::uz][slot2_idx], mass_slot2,
393 soa_products_data[3].m_rdata[PIdx::ux][slot3_idx],
394 soa_products_data[3].m_rdata[PIdx::uy][slot3_idx],
395 soa_products_data[3].m_rdata[PIdx::uz][slot3_idx], mass_slot3,
396 -scattering_process.m_energy_penalty*PhysConst::q_e,
397 // TwoProductComputeProductMomenta expects the *released* energy here, hence the negative sign
398 // We also convert from eV to Joules
400 // no angular scattering: the products' momenta have the same direction
401 // as that of the reactant particle, in the center-of-mass frame
402 engine);
403 }
404 else if (process_type == ScatteringProcessType::IONIZATION)
405 {
406 const auto slot0_idx = products_np_data[0] + no_product_total + with_product_offsets_data[i];
407 // Make a copy of the particle from the first reactant species (slot 0)
408 copy_species0[0](soa_products_data[0], soa_0, static_cast<int>(p_pair_indices_0[i]),
409 static_cast<int>(slot0_idx), engine);
410 // Set the weight of the new particles to p_pair_reaction_weight[i]
411 soa_products_data[0].m_rdata[PIdx::w][slot0_idx] = p_pair_reaction_weight[i];
412
413 // create a copy of the first product species at the location of the other reactant species
414 // (the other reactant species in slot 1 is the "target species", i.e. the species that is ionized)
415 const auto slot2_idx = products_np_data[2] + with_product_offsets_data[i];
416 copy_species1[2](soa_products_data[2], soa_1, static_cast<int>(p_pair_indices_1[i]),
417 static_cast<int>(slot2_idx), engine);
418 // Set the weight of the new particle to p_pair_reaction_weight[i]
419 soa_products_data[2].m_rdata[PIdx::w][slot2_idx] = p_pair_reaction_weight[i];
420
421 // create a copy of the other product species at the location of the other reactant species
422 // (the other reactant species in slot 1 is the "target species", i.e. the species that is ionized)
423 const auto slot3_idx = products_np_data[3] + with_product_offsets_data[i];
424 copy_species1[3](soa_products_data[3], soa_1, static_cast<int>(p_pair_indices_1[i]),
425 static_cast<int>(slot3_idx), engine);
426 // Set the weight of the new particle to p_pair_reaction_weight[i]
427 soa_products_data[3].m_rdata[PIdx::w][slot3_idx] = p_pair_reaction_weight[i];
428
429 // Grab the colliding particle velocities to calculate the COM
430 // Note that the two product particles currently have the same
431 // velocity as the "target" particle
432 auto& ux0 = soa_products_data[0].m_rdata[PIdx::ux][slot0_idx];
433 auto& uy0 = soa_products_data[0].m_rdata[PIdx::uy][slot0_idx];
434 auto& uz0 = soa_products_data[0].m_rdata[PIdx::uz][slot0_idx];
435 auto& ux2 = soa_products_data[2].m_rdata[PIdx::ux][slot2_idx];
436 auto& uy2 = soa_products_data[2].m_rdata[PIdx::uy][slot2_idx];
437 auto& uz2 = soa_products_data[2].m_rdata[PIdx::uz][slot2_idx];
438 auto& ux3 = soa_products_data[3].m_rdata[PIdx::ux][slot3_idx];
439 auto& uy3 = soa_products_data[3].m_rdata[PIdx::uy][slot3_idx];
440 auto& uz3 = soa_products_data[3].m_rdata[PIdx::uz][slot3_idx];
441
442#if defined(WARPX_DIM_RZ) || defined(WARPX_DIM_RCYLINDER)
443 /* In RZ and RCYLINDER geometry, macroparticles can collide with other macroparticles
444 * in the same *cylindrical* cell. For this reason, collisions between macroparticles
445 * are actually not local in space. In this case, the underlying assumption is that
446 * particles within the same cylindrical cell represent a cylindrically-symmetry
447 * momentum distribution function. Therefore, here, we temporarily rotate the
448 * momentum of one of the macroparticles in agreement with this cylindrical symmetry.
449 * (This is technically only valid if we use only the m=0 azimuthal mode in the simulation;
450 * there is a corresponding assert statement at initialization.)
451 */
452 amrex::ParticleReal const theta = (
453 soa_products_data[2].m_rdata[PIdx::theta][slot2_idx]
454 - soa_products_data[0].m_rdata[PIdx::theta][slot0_idx]
455 );
456 amrex::ParticleReal const ux0buf = ux0;
457 ux0 = ux0buf*std::cos(theta) - uy0*std::sin(theta);
458 uy0 = ux0buf*std::sin(theta) + uy0*std::cos(theta);
459#endif
460
461 // for simplicity (for now) we assume non-relativistic particles
462 // and simply calculate the center-of-momentum velocity from the
463 // rest masses
464 auto const uCOM_x = (m0 * ux0 + m1 * ux3) / (m0 + m1);
465 auto const uCOM_y = (m0 * uy0 + m1 * uy3) / (m0 + m1);
466 auto const uCOM_z = (m0 * uz0 + m1 * uz3) / (m0 + m1);
467
468 // transform to COM frame
469 ux0 -= uCOM_x;
470 uy0 -= uCOM_y;
471 uz0 -= uCOM_z;
472 ux2 -= uCOM_x;
473 uy2 -= uCOM_y;
474 uz2 -= uCOM_z;
475 ux3 -= uCOM_x;
476 uy3 -= uCOM_y;
477 uz3 -= uCOM_z;
478
479 // calculate kinetic energy of the collision
480 const amrex::ParticleReal p_non_target_in = m0 * std::sqrt(ux0*ux0 + uy0*uy0 + uz0*uz0);
481 const amrex::ParticleReal E_coll = 0.5_prt * p_non_target_in * p_non_target_in * (1.0_prt/m0 + 1.0_prt/m1);
482 // subtract the energy cost for ionization (converted from eV to J)
483 const amrex::ParticleReal E_out =
484 E_coll - scattering_process.m_energy_penalty * PhysConst::q_e;
485
486 // The kinematics of the ionization event (i.e. how the energy and momentum are
487 // distributed among the products) depend on the nature of the incident particle
488 // (first reactant species, slot 0, with mass m0).
489 if (m0 > 2.0_prt * PhysConst::m_e)
490 {
491 // Ion impact ionization (the incident particle is heavier than an electron).
492 // We use the collinear model: in the center-of-mass frame, the two products
493 // (the ion and the electron) are assumed to have equal velocity, and their
494 // velocity vector is aligned with that of the incident (non-target) particle.
495
496 // Momentum and energy division after the ionization event is done as follows:
497 // we assume that, in the center of mass frame, the two products (the ion and the electron)
498 // have equal velocity and that their velocity vector is aligned with that of the target particle
499
500 // With these assumptions, conservation of momentum in the center of mass frame allows to express the momenta
501 // of the products, as a function of the outcoming momentum of the non-target particle:
502 // p2 = - p_non_target_out * mass_slot2 / m1
503 // p3 = - p_non_target_out * mass_slot3 / m1
504 // (with m1 = mass_slot2 + mass_slot3, by conservation of mass)
505
506 // Amplitude of the momentum of the non-target particle, obtained from conservation of energy
507 const amrex::ParticleReal p_non_target_out = std::sqrt( 2.0_prt * E_out / (1.0_prt / m0 + 1.0_prt / (mass_slot2 + mass_slot3) ) );
508
509 // Reduce the velocity of the non-target particle, to reflect the loss of energy due to ionization
510 const amrex::ParticleReal reduce_factor = p_non_target_out / p_non_target_in;
511 ux0 *= reduce_factor;
512 uy0 *= reduce_factor;
513 uz0 *= reduce_factor;
514 // Obtain the other velocity vectors by conservation of momentum
515 const amrex::ParticleReal mass_ratio = m0 / (mass_slot2 + mass_slot3);
516 ux2 = -mass_ratio * ux0;
517 uy2 = -mass_ratio * uy0;
518 uz2 = -mass_ratio * uz0;
519 ux3 = -mass_ratio * ux0;
520 uy3 = -mass_ratio * uy0;
521 uz3 = -mass_ratio * uz0;
522 }
523 else
524 {
525 // Electron impact ionization (the incident particle is an electron).
526 // We use the following model: in the center-of-mass frame, the two
527 // electrons (the incident electron and the newly freed electron)
528 // are scattered isotropically and are assumed to carry an equal share
529 // of the energy. All the other unknowns (magnitude of the isotropically
530 // scattered electron momentum, momentum of the ion) are determined by
531 // conservation of energy and momentum.
532
533 // The freed electron has the same mass as the incident electron (m0);
534 // the ion is the heavier of the two products in slots 2 and 3.
535 const amrex::ParticleReal m_ion = amrex::max(mass_slot2, mass_slot3);
536
537 // Sample two isotropic unit vectors: one for the incident electron
538 // (slot 0) and one for the newly freed electron.
539 // These units vectors have the right direction, but not the right magnitude ;
540 // they will be multiplied by the right magnitude p_e further down.
541 amrex::ParticleReal scaled_ux0, scaled_uy0, scaled_uz0;
542 amrex::ParticleReal scaled_ux_new, scaled_uy_new, scaled_uz_new;
543 ParticleUtils::getRandomVector(scaled_ux0, scaled_uy0, scaled_uz0, engine);
544 ParticleUtils::getRandomVector(scaled_ux_new, scaled_uy_new, scaled_uz_new, engine);
545
546 // Sum of the two electron directions; the ion momentum is
547 // proportional to its opposite (conservation of momentum in COM).
548 const amrex::ParticleReal scaled_ux_ion = -(scaled_ux0 + scaled_ux_new);
549 const amrex::ParticleReal scaled_uy_ion = -(scaled_uy0 + scaled_uy_new);
550 const amrex::ParticleReal scaled_uz_ion = -(scaled_uz0 + scaled_uz_new);
551 const amrex::ParticleReal scaled_u2_ion = scaled_ux_ion*scaled_ux_ion + scaled_uy_ion*scaled_uy_ion + scaled_uz_ion*scaled_uz_ion;
552
553 // Magnitude of the momentum of the two electrons, from conservation
554 // of energy in the center-of-mass frame (both electrons are assumed
555 // to carry an equal share of the energy):
556 // 2 * p_e^2 / (2 m_e) + p_ion^2 / (2 m_ion) = E_out
557 const amrex::ParticleReal p_e = std::sqrt(
558 E_out / (1.0_prt/m0 + 0.5_prt*scaled_u2_ion/m_ion) );
559
560 // Assign the normalized momenta (still in the COM frame; the surrounding code
561 // transforms them back to the lab frame). The incident electron is in
562 // slot 0; the two products of the ionized target (slots 2 and 3) are
563 // one electron and one ion, the electron being the lighter of the two.
564 ux0 = p_e/m0 * scaled_ux0;
565 uy0 = p_e/m0 * scaled_uy0;
566 uz0 = p_e/m0 * scaled_uz0;
567 if (mass_slot2 < mass_slot3) {
568 // slot 2 is the freed electron, slot 3 is the ion
569 ux2 = p_e/m0 * scaled_ux_new;
570 uy2 = p_e/m0 * scaled_uy_new;
571 uz2 = p_e/m0 * scaled_uz_new;
572 ux3 = p_e/m_ion * scaled_ux_ion;
573 uy3 = p_e/m_ion * scaled_uy_ion;
574 uz3 = p_e/m_ion * scaled_uz_ion;
575 } else {
576 // slot 3 is the freed electron, slot 2 is the ion
577 ux3 = p_e/m0 * scaled_ux_new;
578 uy3 = p_e/m0 * scaled_uy_new;
579 uz3 = p_e/m0 * scaled_uz_new;
580 ux2 = p_e/m_ion * scaled_ux_ion;
581 uy2 = p_e/m_ion * scaled_uy_ion;
582 uz2 = p_e/m_ion * scaled_uz_ion;
583 }
584 }
585
586 // transform back to labframe
587 ux0 += uCOM_x;
588 uy0 += uCOM_y;
589 uz0 += uCOM_z;
590 ux2 += uCOM_x;
591 uy2 += uCOM_y;
592 uz2 += uCOM_z;
593 ux3 += uCOM_x;
594 uy3 += uCOM_y;
595 uz3 += uCOM_z;
596
597#if defined(WARPX_DIM_RZ) || defined(WARPX_DIM_RCYLINDER)
598 /* Undo the earlier velocity rotation. */
599 amrex::ParticleReal const ux0buf_new = ux0;
600 ux0 = ux0buf_new*std::cos(-theta) - uy0*std::sin(-theta);
601 uy0 = ux0buf_new*std::sin(-theta) + uy0*std::cos(-theta);
602#endif
603 }
604 });
605
606 // Initialize the user runtime components
607 for (int i = 0; i < m_num_product_species; i++)
608 {
609 const int start_index = int(products_np[i]);
610 const int stop_index = int(products_np[i] + num_added_vec[i]);
612 *pc_products[i], start_index, stop_index);
613 }
614
616 return num_added_vec;
617 }
618
619private:
620 // How many different type of species the collision produces
622 // The scattering processes, in the same order as the input list (and as the indices encoded
623 // in the per-pair mask). Used to look up, for each pair, the process type and energy penalty.
626 // Vector of size m_num_product_species: for each product slot, the number of new particles
627 // created per *reaction* event (ionization, two-product reaction, charge exchange).
628 // Weight-split particles added to slots 0 and 1 during non-reaction events are NOT counted
629 // here; they are tracked separately via `no_product_total` inside operator().
632};
633#endif // WARPX_SPLIT_AND_SCATTER_FUNC_H_
#define AMREX_RESTRICT
#define AMREX_INLINE
#define AMREX_GPU_DEVICE
Array4< int const > mask
CollisionType
Definition BinaryCollisionUtils.H:19
@ BACK
Definition ScatteringProcess.H:23
@ CHARGE_EXCHANGE
Definition ScatteringProcess.H:25
@ ELASTIC
Definition ScatteringProcess.H:22
@ EXCITATION
Definition ScatteringProcess.H:26
@ TWOPRODUCT_REACTION
Definition ScatteringProcess.H:24
@ IONIZATION
Definition ScatteringProcess.H:27
@ FORWARD
Definition ScatteringProcess.H:28
@ Forward
Definition WarpXAlgorithmSelection.H:198
Definition MultiParticleContainer.H:69
typename ParticleBins::index_type index_type
Definition SplitAndScatterFunc.H:33
typename ParticleTileType::ParticleTileDataType ParticleTileDataType
Definition SplitAndScatterFunc.H:31
int m_num_product_species
Definition SplitAndScatterFunc.H:621
SplitAndScatterFunc()=default
Default constructor of the SplitAndScatterFunc class.
amrex::Gpu::DeviceVector< ScatteringProcess::Executor > m_scattering_processes_exe
Definition SplitAndScatterFunc.H:625
typename WarpXParticleContainer::ParticleType ParticleType
Definition SplitAndScatterFunc.H:29
CollisionType m_collision_type
Definition SplitAndScatterFunc.H:631
amrex::DenseBins< ParticleTileDataType > ParticleBins
Definition SplitAndScatterFunc.H:32
typename WarpXParticleContainer::ParticleTileType ParticleTileType
Definition SplitAndScatterFunc.H:30
amrex::Vector< ScatteringProcess > m_scattering_processes
Definition SplitAndScatterFunc.H:624
AMREX_INLINE amrex::Vector< int > operator()(const index_type &n_total_pairs, ParticleTileType &ptile0, ParticleTileType &ptile1, const amrex::Vector< WarpXParticleContainer * > &pc_products, ParticleTileType **AMREX_RESTRICT tile_products, const amrex::ParticleReal m0, const amrex::ParticleReal m1, const amrex::Vector< amrex::ParticleReal > &, const index_type *AMREX_RESTRICT mask, amrex::Vector< index_type > &products_np, const SmartCopy *AMREX_RESTRICT copy_species0, const SmartCopy *AMREX_RESTRICT copy_species1, const index_type *AMREX_RESTRICT p_pair_indices_0, const index_type *AMREX_RESTRICT p_pair_indices_1, const amrex::ParticleReal *AMREX_RESTRICT p_pair_reaction_weight, const amrex::ParticleReal *) const
Function that performs binary collision between macroparticle pairs (referred to as "reactant species...
Definition SplitAndScatterFunc.H:100
amrex::Gpu::HostVector< int > m_num_products_host
Definition SplitAndScatterFunc.H:630
WarpXParticleContainer::ParticleTileType::ParticleTileDataType SoaData_type
Definition SplitAndScatterFunc.H:34
iterator begin() noexcept
T * data() noexcept
std::conditional_t< is_rtsoa_pc, ParticleTileRT< typename ParticleType::RealType, typename ParticleType::IntType >, ParticleTile< ParticleType, NArrayReal, NArrayInt, Allocator > > ParticleTileType
amrex_particle_real ParticleReal
PinnedVector< T > HostVector
PODVector< T, ArenaAllocator< T > > DeviceVector
__host__ __device__ constexpr const T & max(const T &a, const T &b) noexcept
void DefaultInitializeRuntimeAttributes(PTile &ptile, const DstPC &pc, int start, int stop, const int n_external_attr_real=0, const int n_external_attr_int=0, const bool do_qed_comps=false)
Default initialize runtime attributes in a tile. This routine does not initialize the first n_externa...
Definition DefaultInitialization.H:114
AMREX_GPU_HOST_DEVICE AMREX_INLINE void RandomizeVelocity(amrex::ParticleReal &ux, amrex::ParticleReal &uy, amrex::ParticleReal &uz, const amrex::ParticleReal vp, amrex::RandomEngine const &engine)
Function to perform scattering of a particle that results in a random velocity vector with given magn...
Definition ParticleUtils.H:218
AMREX_GPU_HOST_DEVICE AMREX_INLINE void getRandomVector(amrex::ParticleReal &x, amrex::ParticleReal &y, amrex::ParticleReal &z, amrex::RandomEngine const &engine)
Generate a random unit vector with isotropic distribution, following the method described in equation...
Definition ParticleUtils.H:195
constexpr auto m_e
electron mass [kg]
Definition constant.H:172
constexpr auto q_e
elementary charge [C]
Definition constant.H:169
void synchronize() noexcept
void copyAsync(HostToDevice, InIter begin, InIter end, OutIter result) noexcept
static constexpr HostToDevice hostToDevice
void streamSynchronize() noexcept
static constexpr struct amrex::Scan::Type::Exclusive exclusive
static constexpr RetSum retSum
T PrefixSum(N n, FIN const &fin, FOUT const &fout, TYPE, RetSum a_ret_sum=retSum)
AMREX_ATTRIBUTE_FLATTEN_FOR void ParallelForRNG(T n, L const &f) noexcept
void Abort(const std::string &msg)
const int[]
@ x
Definition WarpXParticleContainer.H:70
@ uz
Definition WarpXParticleContainer.H:70
@ w
Definition WarpXParticleContainer.H:70
@ uy
Definition WarpXParticleContainer.H:70
@ z
Definition WarpXParticleContainer.H:70
@ ux
Definition WarpXParticleContainer.H:70
Definition ScatteringProcess.H:76
This is a functor for performing a "smart copy" that works in both host and device code.
Definition SmartCopy.H:34