-
Notifications
You must be signed in to change notification settings - Fork 191
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
Ionization module #260
Ionization module #260
Conversation
…on and definition
Thanks for starting this review @rlehe. I implemented your comments in a bunch of small commits. I enabled ionization when sub cycling is on, and ran the automated
This required changes in the input file:
In both cases, the patch occupies half of the plasma and the test is successful. I can add these to the automated tests if you think it is useful, though they take a little bit of time. |
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.
Thanks again for this PR.
Here are a few more comments.
Also: did you test the code on GPU (using the Python automatic tests)?
ions.physical_element = N | ||
ions.do_continuous_injection=1 | ||
|
||
electrons.mass = m_p |
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.
Should this be m_e
?
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.
I implemented the changes you suggested in the input files.
ions.ionization_product_species = electrons | ||
ions.physical_element = N | ||
|
||
electrons.mass = m_p |
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.
m_e
?
particles.nspecies = 2 | ||
particles.species_names = electrons ions | ||
|
||
ions.mass = m_p |
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.
Technically, this should be ions.mass = 14*m_p
(approximate value), this the ions are Nitrogen.
particles.nspecies = 2 | ||
particles.species_names = electrons ions | ||
|
||
ions.mass = m_p |
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.
14*m_p
?
@@ -0,0 +1,79 @@ | |||
max_step = 500 |
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.
Could this file be removed?
It does not seem to be used in the automated tests.
@@ -26,6 +26,7 @@ void doDepositionShapeN(const amrex::Real * const xp, | |||
const amrex::Real * const uxp, | |||
const amrex::Real * const uyp, | |||
const amrex::Real * const uzp, | |||
const int * const ion_lev, |
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.
Could you add the documentation from ion_lev
in the above Doxygen description?
In particular, could you mention that:
- This is needed in order to have the charge of each macroparticle (since
q
is a scalar here). - For non-ionizable species,
ion_lev
is a null pointer.
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.
Comments added.
@@ -36,6 +37,7 @@ void doDepositionShapeN(const amrex::Real * const xp, | |||
const amrex::Real stagger_shift, | |||
const amrex::Real q) | |||
{ | |||
const bool do_ionization = ion_lev; |
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.
Could you add a comment saying that this line checks whether ion_lev
is a null pointer?
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.
Comments added.
amrex::Real wq = q*wp[ip]; | ||
if (do_ionization){ | ||
wq *= ion_lev[ip]; | ||
} |
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.
This is maybe a bit dangerous, because here, the charge q
is directly the charge that the user enters in the input script. Now for ionizable species, users might be a bit confused as to what charge should be entered (e.g. they might think that <species>.charge
should be 0
, if the ionizable species is initially neutral).
For this reason, it might be better if wq
is independent of the user-entered charge, in the case of an ionizable species:
if (do_ionization){
wq = wp[ip] * ion_lev[ip] * PhysConst::q_e;
} else {
wq = wp[ip] * q;
}
An alternative would be to explicitly check that the user-entered charge is q_e
for an ionizable species.
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.
I implemented the second option: if the user specifies a charge that is not q_e
, the charge is over-written and the code raises a Warning.
{ | ||
// For particle i in mfi, if is_ionized[i]=1, copy particle | ||
// particle i from container pc_source into pc_product | ||
static void createIonizedParticles ( |
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.
Why does this need to be static void
and in a namespace
?
Could this go into a separate file, for ionization?
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.
I removed the static
keyword
Also: could you add the modified |
amrex::Real wq = q*wp[ip]; | ||
if (do_ionization){ | ||
wq *= ion_lev[ip]; | ||
} |
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.
See remark below, in the Esirkepov
kernel.
@@ -179,6 +184,7 @@ void doEsirkepovDepositionShapeN (const amrex::Real * const xp, | |||
const amrex::Real * const uxp, | |||
const amrex::Real * const uyp, | |||
const amrex::Real * const uzp, | |||
const int * ion_lev, |
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.
Same remark as before: could you add the doc for ion_lev
?
const int tile_id = mfi.LocalTileIndex(); | ||
pc_product->GetParticles(lev)[std::make_pair(grid_id,tile_id)]; | ||
pc_product->DefineAndReturnParticleTile(lev, grid_id, tile_id); | ||
} |
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.
@MaxThevenet Could you add a comment in the above code, explaining why you need to touch each tile, and briefly explaining what DefineAndReturnParticleTile
does?
[=] AMREX_GPU_DEVICE (long i) { | ||
Real qp = q; | ||
if (ion_lev){ qp *= ion_lev[i]; } |
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.
Same remark as for the current deposition:
maybe it is safer to do
if (ion_lev) {
qp = ion_lev[i] * PhysConst::q_e;
} else {
qp = q;
}
[=] AMREX_GPU_DEVICE (long i) { | ||
Real qp = q; | ||
if (ion_lev){ qp *= ion_lev[i]; } |
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.
Same remark
pp.query("ionization_initial_level", ionization_initial_level); | ||
pp.get("ionization_product_species", ionization_product_name); | ||
pp.get("physical_element", physical_element); | ||
// Add Real component for ionization level |
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.
Should this comment be Add integer component for ionization level
?
pp.get("physical_element", physical_element); | ||
// Add Real component for ionization level | ||
AddIntComp("ionization_level"); | ||
plot_flags.resize(PIdx::nattribs + 1, 1); |
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.
Is the above line really needed? It seems to me that there is already a similar line in the ParticleIO.cpp
file...
@@ -0,0 +1,67 @@ | |||
import re, os |
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.
Could you add a string at the beginning of this file, explaining its purpose?
(e.g. this could be copy/pasted from this PR's description)
// The advantage of inclusive_scan is that the sum of is_ionized | ||
// is in the last element, so no other reduction is required to get | ||
// number of particles. | ||
amrex::Gpu::inclusive_scan(is_ionized.begin(), is_ionized.end(), i_product.begin()); |
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.
Could you add a comment here, saying that Gpu::inclusive_scan
runs on the current GPU stream, and synchronizes with the CPU, so that the next line (executed by the CPU) has the updated values of i_product
?
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.
Comment added.
@RemiLehe Thanks for your remarks, I implemented the changes. A few comments:
|
This PR proposes an implementation for field ionization (using the ADK model) in WarpX. The main points are:
Ionization energies and initialization
The tables for ionization energies were taken from the NIST Atomic Spectra Database. This is handled with:
Utils/atomic_data.txt
: the text file generated by NIST website.Utils/write_atomic_data_cpp.py
: a python script that readsatomic_data.txt
and extracts ionization levels into a C++ file (see line below).Utils/IonizationEnergiesTable.H
: C++ file with ionization tables data and metadata.PhysicalParticleContainer::InitIonizationModule
handles the init of the ionization module. The mapping between ionizable species and their product species is done at theMultiParticleContainer
level inmapSpeciesProduct
.Creation of new electrons
As ionization is a two-species process, it is handled at the
MultiParticleContainer
inMultiParticleContainer::doFieldIonization
, in a 2-step approach. Within each tile:PhysicalParticleContainer::buildIonizationMask
.amrex::Gpu::inclusive_scan
for portability). Particle quantities are copied from source to product increateIonizedParticles
.Peripheral changes
Ionization required a number of peripheral changes, listed below:
ion_lev
, that is a pointer to the array of ionization levels when ionization is on (nullptr
otherwise).WarpXParticleContainer
(GetiAttribs
is the equivalent ofGetAttribs
forint
attributes, and the mapping between the names and indices ofint
attributes is done inparticle_icomps
, similar toparticle_comps
forReal
attributes).Caveats
Tests
This implementation was tested for plasma mirror in 2D and 3D (the image below shows the reflection of a laser pulse on a silicon target with 1 electron ionized per ion initially, in 2d). The same simulation was run on GPU, and gave similar image (and ionization does not trigger CPU-GPU exchanges at each iteration). Other tests were done for laser-plasma acceleration, in the lab frame and in the boosted frame.