-
Notifications
You must be signed in to change notification settings - Fork 256
Fix charge conservation in charge exchange and add automated test #6796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
3ea212f
d9600be
58fd6aa
6592e8d
23c92ad
d80af6d
aba1fd7
59b3358
1c80c0d
acd9cb2
d7eecf3
2e3ad1d
39fcf1d
31eaa1c
5197f6a
0f9b6f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| # 2D charge exchange collision test (DSMC), with electrostatic solver. | ||
| # Electrons (neutralizing background) + He+ ions + He neutrals. | ||
| # He+ and He undergo DSMC charge exchange using the He resonant cross-section. | ||
|
RemiLehe marked this conversation as resolved.
Outdated
|
||
| # The electrostatic solver is active; total energy (field + particle) conservation | ||
| # is verified using analysis_test_2d_collisions_split_momentum_push.py. | ||
|
|
||
| # algo | ||
| algo.field_gathering = energy-conserving | ||
| algo.particle_shape = 2 | ||
|
|
||
| # amr | ||
| amr.max_level = 0 | ||
| amr.n_cell = 16 16 | ||
|
|
||
| # boundary | ||
| boundary.field_hi = periodic periodic | ||
| boundary.field_lo = periodic periodic | ||
| boundary.particle_hi = periodic periodic | ||
| boundary.particle_lo = periodic periodic | ||
|
|
||
| # collisions | ||
| collisions.collision_names = cx_col | ||
| cx_col.type = dsmc | ||
| cx_col.scattering_processes = charge_exchange | ||
| cx_col.species = Heplus He | ||
| cx_col.product_species = He Heplus | ||
| cx_col.charge_exchange_cross_section = ../../../../warpx-data/MCC_cross_sections/He/charge_exchange.dat | ||
|
|
||
| # diagnostics | ||
| diagnostics.diags_names = diag1 | ||
| diag1.diag_type = Full | ||
| diag1.format = openpmd | ||
| diag1.intervals = 2000 | ||
| diag1.fields_to_plot = Ex Ey Ez Bx By Bz jx jy jz rho | ||
| diag1.write_species = 1 | ||
| diag1.species = electrons Heplus He | ||
| diag1.electrons.variables = w x z ux uy uz | ||
| diag1.Heplus.variables = w x z ux uy uz | ||
| diag1.He.variables = w x z ux uy uz | ||
|
|
||
| # electrons (neutralizing background for He+) | ||
| electrons.charge = -q_e | ||
| electrons.density = n0 | ||
| electrons.injection_style = "NUniformPerCell" | ||
| electrons.mass = m_e | ||
| electrons.momentum_distribution_type = gaussian | ||
| electrons.num_particles_per_cell_each_dim = 8 8 | ||
| electrons.profile = constant | ||
| electrons.species_type = electron | ||
| electrons.ux_th = sqrt(Te*q_e/m_e)/clight | ||
| electrons.uy_th = sqrt(Te*q_e/m_e)/clight | ||
| electrons.uz_th = sqrt(Te*q_e/m_e)/clight | ||
| electrons.xmax = 10*de0 | ||
| electrons.xmin = 0 | ||
| electrons.zmax = 10*de0 | ||
| electrons.zmin = 0 | ||
|
|
||
| # Heplus (He+ ions undergoing charge exchange) | ||
| Heplus.charge = q_e | ||
| Heplus.density = n0 | ||
| Heplus.injection_style = "NUniformPerCell" | ||
| Heplus.mass = m_He | ||
| Heplus.momentum_distribution_type = gaussian | ||
| Heplus.num_particles_per_cell_each_dim = 8 8 | ||
| Heplus.profile = constant | ||
| Heplus.ux_th = sqrt(Ti*q_e/m_He)/clight | ||
| Heplus.uy_th = sqrt(Ti*q_e/m_He)/clight | ||
| Heplus.uz_th = sqrt(Ti*q_e/m_He)/clight | ||
| Heplus.xmax = 10*de0 | ||
| Heplus.xmin = 0 | ||
| Heplus.zmax = 10*de0 | ||
| Heplus.zmin = 0 | ||
|
|
||
| # He (neutral He atoms undergoing charge exchange) | ||
| He.charge = 0 | ||
| He.density = n0 | ||
| He.injection_style = "NUniformPerCell" | ||
| He.mass = m_He | ||
| He.momentum_distribution_type = gaussian | ||
| He.num_particles_per_cell_each_dim = 8 8 | ||
| He.profile = constant | ||
| He.ux_th = sqrt(Ti*q_e/m_He)/clight | ||
| He.uy_th = sqrt(Ti*q_e/m_He)/clight | ||
| He.uz_th = sqrt(Ti*q_e/m_He)/clight | ||
| He.xmax = 10*de0 | ||
| He.xmin = 0 | ||
| He.zmax = 10*de0 | ||
| He.zmin = 0 | ||
|
|
||
| # reduced diagnostics | ||
| field_energy.intervals = 1 | ||
| field_energy.type = FieldEnergy | ||
| particle_energy.intervals = 1 | ||
| particle_energy.type = ParticleEnergy | ||
|
|
||
| # geometry | ||
| geometry.dims = 2 | ||
| geometry.prob_hi = 10*de0 10*de0 | ||
| geometry.prob_lo = 0. 0. | ||
|
|
||
| # interpolation | ||
| interpolation.galerkin_scheme = 1 | ||
|
|
||
| # max_step | ||
| max_step = 2000 | ||
|
|
||
| # my_constants | ||
| my_constants.m_He = 4*m_p # He mass, kg | ||
| my_constants.n0 = 1e30 # number density, m^-3 | ||
| my_constants.Te = 100. # electron temperature, eV | ||
| my_constants.Ti = 100. # He ion/neutral temperature, eV | ||
| my_constants.wpe = q_e*sqrt(n0/(m_e*epsilon0)) # electron plasma frequency, rad/s | ||
| my_constants.de0 = clight/wpe # electron skin depth, m | ||
| my_constants.dt = 0.1/wpe # time step (electron timescale), s | ||
|
|
||
| # particles | ||
| particles.species_names = electrons Heplus He | ||
|
|
||
| # warpx | ||
| warpx.const_dt = dt | ||
| warpx.do_electrostatic = labframe | ||
| warpx.reduced_diags_names = field_energy particle_energy | ||
| warpx.serialize_initial_conditions = 1 | ||
| warpx.use_filter = 0 | ||
| warpx.verbose = 1 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| { | ||
| "lev=0": { | ||
| "Bx": 0.0, | ||
| "By": 0.0, | ||
| "Bz": 0.0, | ||
| "Ex": 14448333080954.348, | ||
| "Ey": 0.0, | ||
| "Ez": 17169859243066.512, | ||
| "jx": 7.803855689663588e+18, | ||
| "jy": 7.215854549193545e+18, | ||
| "jz": 6.576572210896873e+18, | ||
| "rho": 53358037967.6566 | ||
| }, | ||
| "He": { | ||
| "particle_position_x": 0.000435321087901117, | ||
| "particle_position_y": 0.0, | ||
| "particle_position_z": 0.00043532495740775497, | ||
| "particle_momentum_x": 4.2993604934866694e-18, | ||
| "particle_momentum_y": 4.284989545488133e-18, | ||
| "particle_momentum_z": 4.295691097769843e-18, | ||
| "particle_weight": 2823958725036869.5 | ||
| }, | ||
| "Heplus": { | ||
| "particle_position_x": 0.00043531587816417113, | ||
| "particle_position_y": 0.0, | ||
| "particle_position_z": 0.0004353330259468823, | ||
| "particle_momentum_x": 4.306240128575128e-18, | ||
| "particle_momentum_y": 4.269568445141001e-18, | ||
| "particle_momentum_z": 4.2453148876079736e-18, | ||
| "particle_weight": 2823958725036869.5 | ||
| }, | ||
| "electrons": { | ||
| "particle_position_x": 0.00043485561435631804, | ||
| "particle_position_y": 0.0, | ||
| "particle_position_z": 0.0004366002075857368, | ||
| "particle_momentum_x": 4.9302740059601715e-20, | ||
| "particle_momentum_y": 5.076441659627337e-20, | ||
| "particle_momentum_z": 4.984100225110185e-20, | ||
| "particle_weight": 2823958725036869.5 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -142,7 +142,8 @@ public: | |||
| [=] AMREX_GPU_DEVICE (index_type i) -> index_type { | ||||
| return ((mask[i] > 0) & | ||||
| (mask[i] != int(ScatteringProcessType::IONIZATION)) & | ||||
| (mask[i] != int(ScatteringProcessType::TWOPRODUCT_REACTION))) ? 1 : 0; | ||||
| (mask[i] != int(ScatteringProcessType::TWOPRODUCT_REACTION)) & | ||||
| (mask[i] != int(ScatteringProcessType::CHARGE_EXCHANGE))) ? 1 : 0; | ||||
| }, | ||||
| [=] AMREX_GPU_DEVICE (index_type i, index_type s) { no_product_offsets_data[i] = s; }, | ||||
| amrex::Scan::Type::exclusive, amrex::Scan::retSum | ||||
|
|
@@ -167,7 +168,8 @@ public: | |||
| auto const with_product_total = amrex::Scan::PrefixSum<index_type>(n_total_pairs, | ||||
| [=] AMREX_GPU_DEVICE (index_type i) -> index_type { | ||||
| return ((mask[i] == int(ScatteringProcessType::IONIZATION)) | | ||||
| (mask[i] == int(ScatteringProcessType::TWOPRODUCT_REACTION))) ? 1 : 0; | ||||
| (mask[i] == int(ScatteringProcessType::TWOPRODUCT_REACTION)) | | ||||
| (mask[i] == int(ScatteringProcessType::CHARGE_EXCHANGE))) ? 1 : 0; | ||||
| }, | ||||
| [=] AMREX_GPU_DEVICE (index_type i, index_type s) { with_product_offsets_data[i] = s; }, | ||||
| amrex::Scan::Type::exclusive, amrex::Scan::retSum | ||||
|
|
@@ -236,7 +238,7 @@ public: | |||
| amrex::ParallelForRNG(n_total_pairs, | ||||
| [=] AMREX_GPU_DEVICE (int i, amrex::RandomEngine const& engine) noexcept | ||||
| { | ||||
| if ((mask[i] > 0) & (mask[i] != int(ScatteringProcessType::IONIZATION)) & (mask[i] != int(ScatteringProcessType::TWOPRODUCT_REACTION))) | ||||
| if ((mask[i] > 0) & (mask[i] != int(ScatteringProcessType::IONIZATION)) & (mask[i] != int(ScatteringProcessType::TWOPRODUCT_REACTION)) & (mask[i] != int(ScatteringProcessType::CHARGE_EXCHANGE))) | ||||
| { | ||||
| const auto slot0_idx = products_np_data[0] + no_product_offsets_data[i]; | ||||
| // Make a copy of the particle from the first reactant species | ||||
|
|
@@ -336,7 +338,8 @@ public: | |||
| } | ||||
|
|
||||
| // Next perform all product-producing collisions | ||||
| else if (mask[i] == int(ScatteringProcessType::TWOPRODUCT_REACTION)) | ||||
| else if ((mask[i] == int(ScatteringProcessType::TWOPRODUCT_REACTION)) || | ||||
| (mask[i] == int(ScatteringProcessType::CHARGE_EXCHANGE))) | ||||
|
roelof-groenewald marked this conversation as resolved.
|
||||
| { | ||||
| // create a copy of the first product species at the location of the first reactant species | ||||
| const auto src0_idx = static_cast<int>(p_pair_indices_0[i]); | ||||
|
|
@@ -354,6 +357,16 @@ public: | |||
| // Set the weight of the new particle to p_pair_reaction_weight[i] | ||||
| soa_products_data[3].m_rdata[PIdx::w][slot3_idx] = p_pair_reaction_weight[i]; | ||||
|
|
||||
| // For charge exchange, swap positions: each product appears at the position | ||||
| // of the *other* reactant, reflecting the physical transfer of charge across space. | ||||
|
RemiLehe marked this conversation as resolved.
Outdated
|
||||
| // Position components occupy indices 0 through PIdx::w-1 in m_rdata. | ||||
| if (mask[i] == int(ScatteringProcessType::CHARGE_EXCHANGE)) { | ||||
| for (int idim = 0; idim < PIdx::w; ++idim) { | ||||
| soa_products_data[2].m_rdata[idim][slot2_idx] = soa_1.m_rdata[idim][src1_idx]; | ||||
| soa_products_data[3].m_rdata[idim][slot3_idx] = soa_0.m_rdata[idim][src0_idx]; | ||||
| } | ||||
| } | ||||
|
|
||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you guys think about making this more future proof by using the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In retrospect, since the particle iterators are not themselves easily accessible from this location, I think reverting to this original approach is better (more efficient and more readable). Sorry for the flip-flopping!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh man, @oshapoval, I'm so sorry but I just realized there might be a problem with this approach due to the fact that
I think the balance of things then is to just have some code duplication here that will keep things easy to read. So making this something like: Thoughts?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I don't know what is happening here, but could this be done using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @roelof-groenewald Good point, thanks for catching this.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dpgrote |
||||
| // Update the momenta of the products | ||||
| TwoProductComputeProductMomenta( | ||||
| soa_0.m_rdata[PIdx::ux][src0_idx], | ||||
|
|
||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new test requires the tolerance to be slightly relaxed.