aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar David Grote <grote1@llnl.gov> 2022-03-08 19:46:28 -0800
committerGravatar GitHub <noreply@github.com> 2022-03-08 19:46:28 -0800
commit4582079c5dcbedcf49e75d30df34a4688c6bcc18 (patch)
treee0ed497628dc182054afa4fdeaa63e3e1e79db09
parent0a64c2b8a42d766e5a893fcab2d4573b33cca74f (diff)
downloadWarpX-4582079c5dcbedcf49e75d30df34a4688c6bcc18.tar.gz
WarpX-4582079c5dcbedcf49e75d30df34a4688c6bcc18.tar.zst
WarpX-4582079c5dcbedcf49e75d30df34a4688c6bcc18.zip
Add background stopping (#2884)
* Added BackgroundStopping * Added BackgroundStopping CI test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * For background stopping, allowed parsed background density and temperature * Updated background stopping CI test * For background stopping, temperature is specified in Kelvin * Added documentation for background stopping * Added ion stopping plus other fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Made dt level dependent * Revert "Made dt level dependent" This reverts commit 1aed9654d24ff1411a7b5fd8558891e0688f0032. The collisions should be done using the time step of the lowest level. * Add a comment about dt * Add const declaration * Added comment regarding stopped particle * Several fixes and clean up of the documentation * Update CI benchmark Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
-rw-r--r--Docs/source/usage/parameters.rst72
-rwxr-xr-xExamples/Tests/ion_stopping/analysis_ion_stopping.py181
-rw-r--r--Examples/Tests/ion_stopping/inputs_3d100
-rw-r--r--Regression/Checksum/benchmarks_json/ion_stopping.json57
-rw-r--r--Regression/WarpX-tests.ini17
-rw-r--r--Source/Particles/Collision/BackgroundStopping/BackgroundStopping.H77
-rw-r--r--Source/Particles/Collision/BackgroundStopping/BackgroundStopping.cpp269
-rw-r--r--Source/Particles/Collision/BackgroundStopping/CMakeLists.txt4
-rw-r--r--Source/Particles/Collision/BackgroundStopping/Make.package3
-rw-r--r--Source/Particles/Collision/CMakeLists.txt1
-rw-r--r--Source/Particles/Collision/CollisionHandler.cpp4
-rw-r--r--Source/Particles/Collision/Make.package1
12 files changed, 771 insertions, 15 deletions
diff --git a/Docs/source/usage/parameters.rst b/Docs/source/usage/parameters.rst
index 32eeec61a..1e70c27b0 100644
--- a/Docs/source/usage/parameters.rst
+++ b/Docs/source/usage/parameters.rst
@@ -1353,14 +1353,7 @@ External fields
Collision initialization
------------------------
-WarpX provides a relativistic elastic Monte Carlo binary Coulomb collision model,
-following the algorithm given by `Perez et al. (Phys. Plasmas 19, 083104, 2012) <https://doi.org/10.1063/1.4742167>`_.
-When the RZ mode is used, `warpx.n_rz_azimuthal_modes` must be set to 1 at the moment,
-since the current implementation of the collision module assumes axisymmetry.
-A non-relativistic Monte Carlo treatment for particles colliding
-with a neutral, uniform background gas is also available. The implementation follows the so-called
-null collision strategy discussed for example in `Birdsall (IEEE Transactions on
-Plasma Science, vol. 19, no. 2, pp. 65-85, 1991) <https://ieeexplore.ieee.org/document/106800>`_.
+WarpX provides several particle collions models, using varying degrees of approximation.
* ``collisions.collision_names`` (`strings`, separated by spaces)
The name of each collision type.
@@ -1368,8 +1361,21 @@ Plasma Science, vol. 19, no. 2, pp. 65-85, 1991) <https://ieeexplore.ieee.org/do
in this documentation we use ``<collision_name>`` as a placeholder.
* ``<collision_name>.type`` (`string`) optional
- The type of collsion. The types implemented are ``pairwisecoulomb`` for pairwise Coulomb collisions and
- ``background_mcc`` for collisions between particles and a neutral background. If not specified, it defaults to ``pairwisecoulomb``.
+ The type of collsion. The types implemented are:
+
+ - ``pairwisecoulomb`` for pairwise Coulomb collisions, the default if unspecified.
+ This provides a pair-wise relativistic elastic Monte Carlo binary Coulomb collision model,
+ following the algorithm given by `Perez et al. (Phys. Plasmas 19, 083104, 2012) <https://doi.org/10.1063/1.4742167>`_.
+ When the RZ mode is used, `warpx.n_rz_azimuthal_modes` must be set to 1 at the moment,
+ since the current implementation of the collision module assumes axisymmetry.
+ - ``background_mcc`` for collisions between particles and a neutral background.
+ This is a non-relativistic Monte Carlo treatment for particles colliding
+ with a neutral, uniform background gas. The implementation follows the so-called
+ null collision strategy discussed for example in `Birdsall (IEEE Transactions on
+ Plasma Science, vol. 19, no. 2, pp. 65-85, 1991) <https://ieeexplore.ieee.org/document/106800>`_.
+ - ``background_stopping`` for slowing of ions due to collisions with electrons or ions.
+ This implements the approximate formulae as derived in Introduction to Plasma Physics,
+ from Goldston and Rutherford, section 14.2.
* ``<collision_name>.species`` (`strings`)
If using ``pairwisecoulomb`` type this should be the names of two species,
@@ -1395,21 +1401,57 @@ Plasma Science, vol. 19, no. 2, pp. 65-85, 1991) <https://ieeexplore.ieee.org/do
Execute collision every # time steps. The default value is 1.
* ``<collision_name>.background_density`` (`float`)
- Only for ``background_mcc``. The density of the neutral background gas in :math:`m^{-3}`.
+ Only for ``background_mcc`` and ``background_stopping``. The density of the background in :math:`m^{-3}`.
Can also provide ``<collision_name>.background_density(x,y,z,t)`` using the parser
- initialization style for spatially and temporally varying density. If a function
+ initialization style for spatially and temporally varying density. With ``background_mcc``, if a function
is used for the background density, the input parameter ``<collision_name>.max_background_density``
must also be provided to calculate the maximum collision probability.
* ``<collision_name>.background_temperature`` (`float`)
- Only for ``background_mcc``. The temperature of the neutral background gas in Kelvin.
+ Only for ``background_mcc`` and ``background_stopping``. The temperature of the background in Kelvin.
Can also provide ``<collision_name>.background_temperature(x,y,z,t)`` using the parser
initialization style for spatially and temporally varying temperature.
* ``<collision_name>.background_mass`` (`float`) optional
- Only for ``background_mcc``. The mass of the background gas in kg. If not
- given the mass of the colliding species will be used unless ionization is
+ Only for ``background_mcc`` and ``background_stopping``. The mass of the background gas in kg.
+ With ``background_mcc``, if not given the mass of the colliding species will be used unless ionization is
included in which case the mass of the product species will be used.
+ With ``background_stopping``, and ``background_type`` set to ``electrons``, if not given defaults to the electron mass. With
+ ``background_type`` set to ``ions``, the mass must be given.
+
+* ``<collision_name>.background_charge_state`` (`float`)
+ Only for ``background_stopping``, where it is required when ``background_type`` is set to ``ions``.
+ This specifies the charge state of the background ions.
+
+* ``<collision_name>.background_type`` (`string`)
+ Only for ``background_stopping``, where it is required, the type of the background.
+ The possible values are ``electrons`` and ``ions``. When ``electrons``, equation 14.12 from Goldston and Rutherford is used.
+ This formula is based on Coulomb collisions with the approximations that :math:`M_b >> m_e` and :math:`V << v_{thermal\_e}`,
+ and the assumption that the electrons have a Maxwellian distribution with temperature :math:`T_e`.
+
+ .. math::
+ \frac{dV}{dt} = - \frac{2^{1/2}n_eZ_b^2e^4m_e^{1/2}\log\Lambda}{12\pi^{3/2}\epsilon_0M_bT_e^{3/2}}V
+
+ where :math:`V` is each velocity component, :math:`n_e` is the background density, :math:`Z_b` is the ion charge state,
+ :math:`e` is the electron charge, :math:`m_e` is the background mass, :math:`\log\Lambda=\log((12\pi/Z_b)(n_e\lambda_{de}^3))`,
+ :math:`\lambda_{de}` is the DeBye length, and :math:`M_b` is the ion mass.
+ The equation is integrated over a time step, giving :math:`V(t+dt) = V(t)*\exp(-\alpha*{dt})`
+ where :math:`\alpha` is the factor multiplying :math:`V`.
+
+ When ``ions``, equation 14.20 is used.
+ This formula is based on Coulomb collisions with the approximations that :math:`M_b >> M` and :math:`V >> v_{thermal\_i}`.
+ The background ion temperature only appears in the :math:`\log\Lambda` term.
+
+ .. math::
+ \frac{dW_b}{dt} = - \frac{2^{1/2}n_iZ^2Z_b^2e^4M_b^{1/2}\log\Lambda}{8\pi\epsilon_0MW_b^{1/2}}
+
+ where :math:`W_b` is the ion energy, :math:`n_i` is the background density,
+ :math:`Z` is the charge state of the background ions, :math:`Z_b` is the ion charge state,
+ :math:`e` is the electron charge, :math:`M_b` is the ion mass, :math:`\log\Lambda=\log((12\pi/Z_b)(n_i\lambda_{di}^3))`,
+ :math:`\lambda_{di}` is the DeBye length, and :math:`M` is the background ion mass.
+ The equation is integrated over a time step, giving :math:`W_b(t+dt) = ((W_b(t)^{3/2}) - 3/2\beta{dt})^{2/3}`
+ where :math:`\beta` is the term on the r.h.s except :math:`W_b`.
+
* ``<collision_name>.scattering_processes`` (`strings` separated by spaces)
Only for ``background_mcc``. The MCC scattering processes that should be
diff --git a/Examples/Tests/ion_stopping/analysis_ion_stopping.py b/Examples/Tests/ion_stopping/analysis_ion_stopping.py
new file mode 100755
index 000000000..1ddec4a62
--- /dev/null
+++ b/Examples/Tests/ion_stopping/analysis_ion_stopping.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+
+# Copyright 2022 David Grote
+#
+# This file is part of WarpX.
+#
+# License: BSD-3-Clause-LBNL
+
+# This script tests the ion stopping model.
+# It uses the same stopping power formula that
+# is used in the C++ to check the resulting
+# particle energies.
+
+import os
+import re
+import sys
+
+import numpy as np
+from scipy.constants import c, e, epsilon_0, k, m_e, m_p
+import yt
+
+sys.path.insert(1, '../../../../warpx/Regression/Checksum/')
+import checksumAPI
+
+# Define constants using the WarpX names for the evals below
+q_e = e
+kb = k
+
+# Tolerance on the error in the final energy (in eV)
+tolerance = 1.e-7
+
+last_filename = sys.argv[1]
+
+# Remove trailing '/' from file name, if necessary
+last_filename.rstrip('/')
+# Find last iteration in file name, such as 'test_name_plt000001' (last_it = '000001')
+last_it = re.search('\d+$', last_filename).group()
+# Find output prefix in file name, such as 'test_name_plt000001' (prefix = 'test_name_plt')
+prefix = last_filename[:-len(last_it)]
+
+def stopping_from_electrons(ne, Te, Zb, ion_mass):
+ """Calculate the coefficient in equation 14.13 from
+ "Introduction to Plasma Physics", Goldston and Rutherford.
+ ne: electron density
+ Te: electron temperature (eV)
+ Zb: ion charge state
+ ion_mass: (kg)
+ """
+ vthe = np.sqrt(3.*Te*e/m_e)
+ wpe = np.sqrt(ne*e**2/(epsilon_0*m_e))
+ lambdadb = vthe/wpe
+ loglambda = np.log((12.*np.pi/Zb)*(ne*lambdadb**3))
+ # Goldston's equation 14.13
+ dEdt = - np.sqrt(2.)*ne*Zb**2*e**4*np.sqrt(m_e)*loglambda/(6.*np.pi**1.5*epsilon_0**2*ion_mass*(Te*e)**1.5)
+ return dEdt
+
+def stopping_from_ions(dt, ni, Ti, mi, Zi, Zb, ion_mass, ion_energy):
+ """
+ ni: background ion density
+ Ti: background ion temperature (eV)
+ mi: background ion mass
+ Zi: background ion charge state
+ Zb: ion charge state
+ ion_mass: (kg)
+ ion_energy: (eV)
+ """
+ vthi = np.sqrt(3.*Ti*e/mi)
+ wpi = np.sqrt(ni*e**2/(epsilon_0*mi))
+ lambdadb = vthi/wpi
+ loglambda = np.log((12.*np.pi/Zb)*(ni*lambdadb**3))
+ alpha = np.sqrt(2.)*ni*Zi**2*Zb**2*e**4*np.sqrt(ion_mass)*loglambda/(8.*np.pi*epsilon_0**2*mi)
+ f1 = np.clip((ion_energy*e)**(3./2.) - 3./2.*alpha*dt, 0., None)
+ ion_energy = (f1)**(2./3.)/e
+ return ion_energy
+
+# Fetch background parameters and inital particle data
+ds0 = yt.load(f'{prefix}{len(last_it)*"0"}')
+ad0 = ds0.all_data()
+
+Zb = 1. # Ion charge state
+
+ne = float(ds0.parameters['stopping_on_electrons_constant.background_density'])
+Te = eval(ds0.parameters['stopping_on_electrons_constant.background_temperature'])*kb/e
+ion_mass12 = m_p
+
+mi = eval(ds0.parameters['stopping_on_ions_constant.background_mass'])
+Zi = float(ds0.parameters['stopping_on_ions_constant.background_charge_state'])
+ni = float(ds0.parameters['stopping_on_ions_constant.background_density'])
+Ti = eval(ds0.parameters['stopping_on_ions_constant.background_temperature'])*kb/e
+ion_mass34 = 4.*m_p
+
+# For ions1, the background parameters are constants
+vx = ad0[('ions1', 'particle_momentum_x')].to_ndarray()/ion_mass12
+vy = ad0[('ions1', 'particle_momentum_y')].to_ndarray()/ion_mass12
+vz = ad0[('ions1', 'particle_momentum_z')].to_ndarray()/ion_mass12
+EE1 = 0.5*ion_mass12*(vx**2 + vy**2 + vz**2)/e
+
+# For ions2, the background parameters are parsed
+xx = ad0[('ions2', 'particle_position_x')].to_ndarray()/ion_mass12
+yy = ad0[('ions2', 'particle_position_y')].to_ndarray()/ion_mass12
+ne2 = np.where(xx > 0., 1.e20, 1.e21)
+Te2 = np.where(yy > 0., 1., 2.)
+
+vx = ad0[('ions2', 'particle_momentum_x')].to_ndarray()/ion_mass12
+vy = ad0[('ions2', 'particle_momentum_y')].to_ndarray()/ion_mass12
+vz = ad0[('ions2', 'particle_momentum_z')].to_ndarray()/ion_mass12
+EE2 = 0.5*ion_mass12*(vx**2 + vy**2 + vz**2)/e
+
+# For ions3, the background parameters are constants
+vx = ad0[('ions3', 'particle_momentum_x')].to_ndarray()/ion_mass34
+vy = ad0[('ions3', 'particle_momentum_y')].to_ndarray()/ion_mass34
+vz = ad0[('ions3', 'particle_momentum_z')].to_ndarray()/ion_mass34
+EE3 = 0.5*ion_mass34*(vx**2 + vy**2 + vz**2)/e
+
+# For ions4, the background parameters are parsed
+xx = ad0[('ions4', 'particle_position_x')].to_ndarray()/ion_mass34
+yy = ad0[('ions4', 'particle_position_y')].to_ndarray()/ion_mass34
+ni4 = np.where(xx > 0., 1.e20, 1.e21)
+Ti4 = np.where(yy > 0., 0.05, 0.10)
+
+vx = ad0[('ions4', 'particle_momentum_x')].to_ndarray()/ion_mass34
+vy = ad0[('ions4', 'particle_momentum_y')].to_ndarray()/ion_mass34
+vz = ad0[('ions4', 'particle_momentum_z')].to_ndarray()/ion_mass34
+EE4 = 0.5*ion_mass34*(vx**2 + vy**2 + vz**2)/e
+
+
+ds = yt.load(last_filename)
+ad = ds.all_data()
+dt = ds.current_time.to_value()/int(last_it)
+
+# Step through the same number of time steps
+a_EE1 = EE1
+a_EE2 = EE2
+a_EE3 = EE3
+a_EE4 = EE4
+for it in range(int(last_it)):
+ dEdt1 = stopping_from_electrons(ne, Te, Zb, ion_mass12)
+ a_EE1 *= np.exp(dEdt1*dt)
+ dEdt2 = stopping_from_electrons(ne2, Te2, Zb, ion_mass12)
+ a_EE2 *= np.exp(dEdt2*dt)
+ a_EE3 = stopping_from_ions(dt, ni, Ti, mi, Zi, Zb, ion_mass34, a_EE3)
+ a_EE4 = stopping_from_ions(dt, ni4, Ti4, mi, Zi, Zb, ion_mass34, a_EE4)
+
+# Fetch the final particle data
+vx = ad[('ions1', 'particle_momentum_x')].to_ndarray()/ion_mass12
+vy = ad[('ions1', 'particle_momentum_y')].to_ndarray()/ion_mass12
+vz = ad[('ions1', 'particle_momentum_z')].to_ndarray()/ion_mass12
+EE1 = 0.5*ion_mass12*(vx**2 + vy**2 + vz**2)/e
+
+vx = ad[('ions2', 'particle_momentum_x')].to_ndarray()/ion_mass12
+vy = ad[('ions2', 'particle_momentum_y')].to_ndarray()/ion_mass12
+vz = ad[('ions2', 'particle_momentum_z')].to_ndarray()/ion_mass12
+EE2 = 0.5*ion_mass12*(vx**2 + vy**2 + vz**2)/e
+
+vx = ad[('ions3', 'particle_momentum_x')].to_ndarray()/ion_mass34
+vy = ad[('ions3', 'particle_momentum_y')].to_ndarray()/ion_mass34
+vz = ad[('ions3', 'particle_momentum_z')].to_ndarray()/ion_mass34
+EE3 = 0.5*ion_mass34*(vx**2 + vy**2 + vz**2)/e
+
+vx = ad[('ions4', 'particle_momentum_x')].to_ndarray()/ion_mass34
+vy = ad[('ions4', 'particle_momentum_y')].to_ndarray()/ion_mass34
+vz = ad[('ions4', 'particle_momentum_z')].to_ndarray()/ion_mass34
+EE4 = 0.5*ion_mass34*(vx**2 + vy**2 + vz**2)/e
+
+error1 = np.abs(EE1 - a_EE1)
+error2 = np.abs(EE2 - a_EE2)
+error3 = np.abs(EE3 - a_EE3)
+error4 = np.abs(EE4 - a_EE4)
+print('stopping on electrons error with constant = ', error1)
+print('stopping on electrons error with parsed = ', error2)
+print('stopping on ions error with constant = ', error3)
+print('stopping on ions error with parsed = ', error4)
+print('tolerance = ', tolerance)
+
+assert np.all(error1 < tolerance)
+assert np.all(error2 < tolerance)
+assert np.all(error3 < tolerance)
+assert np.all(error4 < tolerance)
+
+test_name = os.path.split(os.getcwd())[1]
+checksumAPI.evaluate_checksum(test_name, last_filename)
diff --git a/Examples/Tests/ion_stopping/inputs_3d b/Examples/Tests/ion_stopping/inputs_3d
new file mode 100644
index 000000000..9352946b8
--- /dev/null
+++ b/Examples/Tests/ion_stopping/inputs_3d
@@ -0,0 +1,100 @@
+my_constants.max_ion_energy = 100. # eV
+my_constants.max_ion_velocity12 = sqrt(2.*max_ion_energy*q_e/m_p)/clight
+my_constants.max_ion_velocity34 = sqrt(2.*max_ion_energy*q_e/(4.*m_p))/clight
+
+max_step = 10
+
+amr.n_cell = 8 8 8
+amr.max_level = 0
+geometry.dims = 3
+geometry.prob_lo = -1. -1. -1.
+geometry.prob_hi = +1. +1. +1.
+boundary.field_lo = periodic periodic periodic
+boundary.field_hi = periodic periodic periodic
+boundary.particle_lo = periodic periodic periodic
+boundary.particle_hi = periodic periodic periodic
+algo.particle_shape = 1
+
+particles.species_names = ions1 ions2 ions3 ions4
+
+ions1.species_type = proton
+ions1.injection_style = "MultipleParticles"
+ions1.multiple_particles_pos_x = 0. 0. 0. 0.
+ions1.multiple_particles_pos_y = 0. 0. 0. 0.
+ions1.multiple_particles_pos_z = 0. 0. 0. 0.
+ions1.multiple_particles_vel_x = max_ion_velocity12 0. 0. max_ion_velocity12/2.
+ions1.multiple_particles_vel_y = 0. max_ion_velocity12 0. max_ion_velocity12/2.
+ions1.multiple_particles_vel_z = 0. 0. -max_ion_velocity12 -max_ion_velocity12/2.
+ions1.multiple_particles_weight = 1. 1. 1. 1.
+ions1.do_not_deposit = 1
+
+ions2.species_type = proton
+ions2.injection_style = "MultipleParticles"
+ions2.multiple_particles_pos_x = -0.1 +0.1 -0.1 +0.1
+ions2.multiple_particles_pos_y = -0.1 -0.1 +0.1 +0.1
+ions2.multiple_particles_pos_z = 0. 0. 0. 0.
+ions2.multiple_particles_vel_x = max_ion_velocity12 max_ion_velocity12 max_ion_velocity12 max_ion_velocity12
+ions2.multiple_particles_vel_y = 0. 0. 0. 0.
+ions2.multiple_particles_vel_z = 0. 0. 0. 0.
+ions2.multiple_particles_weight = 1. 1. 1. 1.
+ions2.do_not_deposit = 1
+
+ions3.charge = q_e
+ions3.mass = 4*m_p
+ions3.injection_style = "MultipleParticles"
+ions3.multiple_particles_pos_x = 0. 0. 0. 0.
+ions3.multiple_particles_pos_y = 0. 0. 0. 0.
+ions3.multiple_particles_pos_z = 0. 0. 0. 0.
+ions3.multiple_particles_vel_x = max_ion_velocity34 0. 0. max_ion_velocity34/2.
+ions3.multiple_particles_vel_y = 0. max_ion_velocity34 0. max_ion_velocity34/2.
+ions3.multiple_particles_vel_z = 0. 0. -max_ion_velocity34 -max_ion_velocity34/2.
+ions3.multiple_particles_weight = 1. 1. 1. 1.
+ions3.do_not_deposit = 1
+
+ions4.charge = q_e
+ions4.mass = 4*m_p
+ions4.injection_style = "MultipleParticles"
+ions4.multiple_particles_pos_x = -0.1 +0.1 -0.1 +0.1
+ions4.multiple_particles_pos_y = -0.1 -0.1 +0.1 +0.1
+ions4.multiple_particles_pos_z = 0. 0. 0. 0.
+ions4.multiple_particles_vel_x = max_ion_velocity34 max_ion_velocity34 max_ion_velocity34 max_ion_velocity34
+ions4.multiple_particles_vel_y = 0. 0. 0. 0.
+ions4.multiple_particles_vel_z = 0. 0. 0. 0.
+ions4.multiple_particles_weight = 1. 1. 1. 1.
+ions4.do_not_deposit = 1
+
+collisions.collision_names = stopping_on_electrons_constant stopping_on_electrons_parsed stopping_on_ions_constant stopping_on_ions_parsed
+
+stopping_on_electrons_constant.type = background_stopping
+stopping_on_electrons_constant.species = ions1
+stopping_on_electrons_constant.background_type = electrons
+stopping_on_electrons_constant.background_mass = m_e
+stopping_on_electrons_constant.background_density = 1.e20
+stopping_on_electrons_constant.background_temperature = 1.*q_e/kb # Kelvin
+
+stopping_on_electrons_parsed.type = background_stopping
+stopping_on_electrons_parsed.species = ions2
+stopping_on_electrons_parsed.background_type = electrons
+stopping_on_electrons_parsed.background_density(x,y,z,t) = if(x>0,1.e20,1.e21)
+stopping_on_electrons_parsed.background_temperature(x,y,z,t) = if(y>0,1.,2.)*q_e/kb
+
+stopping_on_ions_constant.type = background_stopping
+stopping_on_ions_constant.species = ions3
+stopping_on_ions_constant.background_type = ions
+stopping_on_ions_constant.background_mass = m_p
+stopping_on_ions_constant.background_charge_state = 1.
+stopping_on_ions_constant.background_density = 1.e20
+stopping_on_ions_constant.background_temperature = 0.05*q_e/kb # Kelvin
+
+stopping_on_ions_parsed.type = background_stopping
+stopping_on_ions_parsed.species = ions4
+stopping_on_ions_parsed.background_type = ions
+stopping_on_ions_parsed.background_mass = m_p
+stopping_on_ions_parsed.background_charge_state = 1.
+stopping_on_ions_parsed.background_density(x,y,z,t) = if(x>0,1.e20,1.e21)
+stopping_on_ions_parsed.background_temperature(x,y,z,t) = if(y>0,0.05,0.10)*q_e/kb
+
+diagnostics.diags_names = diag1
+diag1.diag_type = Full
+diag1.format = plotfile
+diag1.intervals = 1
diff --git a/Regression/Checksum/benchmarks_json/ion_stopping.json b/Regression/Checksum/benchmarks_json/ion_stopping.json
new file mode 100644
index 000000000..feb378914
--- /dev/null
+++ b/Regression/Checksum/benchmarks_json/ion_stopping.json
@@ -0,0 +1,57 @@
+{
+ "ions1": {
+ "particle_cpu": 0.0,
+ "particle_id": 10.0,
+ "particle_momentum_x": 3.4560250353184555e-22,
+ "particle_momentum_y": 3.4560250353184555e-22,
+ "particle_momentum_z": 3.4560250353184555e-22,
+ "particle_position_x": 0.0006978687369019061,
+ "particle_position_y": 0.0006978687369019061,
+ "particle_position_z": 0.0006978687369019061,
+ "particle_weight": 4.0
+ },
+ "ions2": {
+ "particle_cpu": 0.0,
+ "particle_id": 26.0,
+ "particle_momentum_x": 9.111625188610108e-22,
+ "particle_momentum_y": 0.0,
+ "particle_momentum_z": 0.0,
+ "particle_position_x": 0.4000131356596105,
+ "particle_position_y": 0.4,
+ "particle_position_z": 0.0,
+ "particle_weight": 4.0
+ },
+ "ions3": {
+ "particle_cpu": 0.0,
+ "particle_id": 42.0,
+ "particle_momentum_x": 6.943047764036734e-22,
+ "particle_momentum_y": 6.943047764036734e-22,
+ "particle_momentum_z": 6.943047764036734e-22,
+ "particle_position_x": 0.000349793860769889,
+ "particle_position_y": 0.000349793860769889,
+ "particle_position_z": 0.000349793860769889,
+ "particle_weight": 4.0
+ },
+ "ions4": {
+ "particle_cpu": 0.0,
+ "particle_id": 58.0,
+ "particle_momentum_x": 1.8496124473858214e-21,
+ "particle_momentum_y": 0.0,
+ "particle_momentum_z": 0.0,
+ "particle_position_x": 0.4000005258500433,
+ "particle_position_y": 0.4,
+ "particle_position_z": 0.0,
+ "particle_weight": 4.0
+ },
+ "lev=0": {
+ "Bx": 0.0,
+ "By": 0.0,
+ "Bz": 0.0,
+ "Ex": 0.0,
+ "Ey": 0.0,
+ "Ez": 0.0,
+ "jx": 0.0,
+ "jy": 0.0,
+ "jz": 0.0
+ }
+}
diff --git a/Regression/WarpX-tests.ini b/Regression/WarpX-tests.ini
index 61d038b8f..22452b898 100644
--- a/Regression/WarpX-tests.ini
+++ b/Regression/WarpX-tests.ini
@@ -3266,3 +3266,20 @@ compileTest = 0
doVis = 0
compareParticles = 0
analysisRoutine = Examples/Tests/scraping/analysis_rz.py
+
+[ion_stopping]
+buildDir = .
+inputFile = Examples/Tests/ion_stopping/inputs_3d
+runtime_params =
+dim = 3
+addToCompileString =
+cmakeSetupOpts =
+restartTest = 0
+useMPI = 1
+numprocs = 1
+useOMP = 1
+numthreads = 1
+compileTest = 0
+doVis = 0
+compareParticles = 1
+analysisRoutine = Examples/Tests/ion_stopping/analysis_ion_stopping.py
diff --git a/Source/Particles/Collision/BackgroundStopping/BackgroundStopping.H b/Source/Particles/Collision/BackgroundStopping/BackgroundStopping.H
new file mode 100644
index 000000000..698943918
--- /dev/null
+++ b/Source/Particles/Collision/BackgroundStopping/BackgroundStopping.H
@@ -0,0 +1,77 @@
+/* Copyright 2022 David Grote
+ *
+ * This file is part of WarpX.
+ *
+ * License: BSD-3-Clause-LBNL
+ */
+#ifndef WARPX_PARTICLES_COLLISION_BACKGROUNSTOPPING_H_
+#define WARPX_PARTICLES_COLLISION_BACKGROUNSTOPPING_H_
+
+#include "Particles/MultiParticleContainer.H"
+#include "Particles/Collision/CollisionBase.H"
+
+#include <AMReX_REAL.H>
+
+#include <string>
+
+enum class BackgroundStoppingType {
+ INVALID,
+ ELECTRONS,
+ IONS,
+};
+
+class BackgroundStopping final
+ : public CollisionBase
+{
+public:
+ BackgroundStopping (std::string collision_name);
+
+ virtual ~BackgroundStopping () = default;
+
+ /** Perform the stopping calculation
+ *
+ * @param cur_time Current time
+ * @param mypc Container of species involved
+ *
+ */
+ void doCollisions (amrex::Real cur_time, MultiParticleContainer* mypc) override;
+
+ /** Perform the stopping calculation within a tile for stopping on electrons
+ *
+ * @param pti particle iterator
+ * @param dt time step size
+ * @param t current simulation time
+ * @param species_mass mass of the active species
+ * @param species_charge charge of the active species
+ *
+ */
+ void doBackgroundStoppingOnElectronsWithinTile (WarpXParIter& pti, amrex::Real dt, amrex::Real t,
+ amrex::Real species_mass, amrex::Real species_charge);
+
+ /** Perform the stopping calculation within a tile for stopping on ions
+ *
+ * @param pti particle iterator
+ * @param dt time step size
+ * @param t current simulation time
+ * @param species_mass mass of the active species
+ * @param species_charge charge of the active species
+ *
+ */
+ void doBackgroundStoppingOnIonsWithinTile (WarpXParIter& pti, amrex::Real dt, amrex::Real t,
+ amrex::Real species_mass, amrex::Real species_charge);
+
+private:
+
+ amrex::Real m_background_mass;
+ amrex::Real m_background_charge_state;
+ BackgroundStoppingType m_background_type;
+
+ amrex::Parser m_background_density_parser;
+ amrex::Parser m_background_temperature_parser;
+
+ amrex::ParserExecutor<4> m_background_density_func;
+ amrex::ParserExecutor<4> m_background_temperature_func;
+
+};
+
+#endif // WARPX_PARTICLES_COLLISION_BACKGROUNSTOPPING_H_
diff --git a/Source/Particles/Collision/BackgroundStopping/BackgroundStopping.cpp b/Source/Particles/Collision/BackgroundStopping/BackgroundStopping.cpp
new file mode 100644
index 000000000..dd51a3d0d
--- /dev/null
+++ b/Source/Particles/Collision/BackgroundStopping/BackgroundStopping.cpp
@@ -0,0 +1,269 @@
+/* Copyright 2022 David Grote
+ *
+ * This file is part of WarpX.
+ *
+ * License: BSD-3-Clause-LBNL
+ */
+#include "BackgroundStopping.H"
+#include "Utils/ParticleUtils.H"
+#include "Utils/WarpXUtil.H"
+#include "Utils/WarpXProfilerWrapper.H"
+#include "WarpX.H"
+
+#include <AMReX_ParmParse.H>
+#include <AMReX_REAL.H>
+
+#include <string>
+
+BackgroundStopping::BackgroundStopping (std::string const collision_name)
+ : CollisionBase(collision_name)
+{
+ AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_species_names.size() == 1,
+ "Background stopping must have exactly one species.");
+
+ amrex::ParmParse pp_collision_name(collision_name);
+
+ std::string background_type_str;
+ pp_collision_name.get("background_type", background_type_str);
+ if (background_type_str == "electrons") {
+ m_background_type = BackgroundStoppingType::ELECTRONS;
+ } else if (background_type_str == "ions") {
+ m_background_type = BackgroundStoppingType::IONS;
+ } else {
+ AMREX_ALWAYS_ASSERT_WITH_MESSAGE(false, "background_type must be either electrons or ions");
+ }
+
+ amrex::Real background_density;
+ std::string background_density_str;
+ if (queryWithParser(pp_collision_name, "background_density", background_density)) {
+ AMREX_ALWAYS_ASSERT_WITH_MESSAGE(background_density > 0,
+ "For background stopping, the background density must be greater than 0");
+ m_background_density_parser = makeParser(std::to_string(background_density), {"x", "y", "z", "t"});
+ } else if (pp_collision_name.query("background_density(x,y,z,t)", background_density_str)) {
+ m_background_density_parser = makeParser(background_density_str, {"x", "y", "z", "t"});
+ } else {
+ AMREX_ALWAYS_ASSERT_WITH_MESSAGE(false,
+ "For background stopping, the background density must be specified.");
+ }
+
+ amrex::Real background_temperature;
+ std::string background_temperature_str;
+ if (queryWithParser(pp_collision_name, "background_temperature", background_temperature)) {
+ AMREX_ALWAYS_ASSERT_WITH_MESSAGE(background_temperature > 0,
+ "For background stopping, the background temperature must be greater than 0");
+ m_background_temperature_parser = makeParser(std::to_string(background_temperature), {"x", "y", "z", "t"});
+ } else if (pp_collision_name.query("background_temperature(x,y,z,t)", background_temperature_str)) {
+ m_background_temperature_parser = makeParser(background_temperature_str, {"x", "y", "z", "t"});
+ } else {
+ AMREX_ALWAYS_ASSERT_WITH_MESSAGE(false,
+ "For background stopping, the background temperature must be specified.");
+ }
+
+ m_background_density_func = m_background_density_parser.compile<4>();
+ m_background_temperature_func = m_background_temperature_parser.compile<4>();
+
+ if (m_background_type == BackgroundStoppingType::ELECTRONS) {
+ m_background_mass = PhysConst::m_e;
+ queryWithParser(pp_collision_name, "background_mass", m_background_mass);
+ } else if (m_background_type == BackgroundStoppingType::IONS) {
+ getWithParser(pp_collision_name, "background_mass", m_background_mass);
+ getWithParser(pp_collision_name, "background_charge_state", m_background_charge_state);
+ }
+ AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_background_mass > 0,
+ "For background stopping, the background mass must be greater than 0");
+
+}
+
+void
+BackgroundStopping::doCollisions (amrex::Real cur_time, MultiParticleContainer* mypc)
+{
+ WARPX_PROFILE("BackgroundStopping::doCollisions()");
+ using namespace amrex::literals;
+
+ // Note that the lowest level time step is used for all levels since the
+ // collision operation will be done only once per step at the lowest level.
+ const amrex::Real dt = WarpX::GetInstance().getdt(0) * m_ndt;
+ if (int(std::floor(cur_time/dt)) % m_ndt != 0) return;
+
+ auto& species = mypc->GetParticleContainerFromName(m_species_names[0]);
+ amrex::Real species_mass = species.getMass();
+ amrex::Real species_charge = species.getCharge();
+
+ BackgroundStoppingType background_type = m_background_type;
+
+ // Loop over refinement levels
+ auto const flvl = species.finestLevel();
+ for (int lev = 0; lev <= flvl; ++lev) {
+
+ auto cost = WarpX::getCosts(lev);
+
+ // loop over particles box by box
+#ifdef _OPENMP
+#pragma omp parallel if (amrex::Gpu::notInLaunchRegion())
+#endif
+ for (WarpXParIter pti(species, lev); pti.isValid(); ++pti) {
+ if (cost && WarpX::load_balance_costs_update_algo == LoadBalanceCostsUpdateAlgo::Timers)
+ {
+ amrex::Gpu::synchronize();
+ }
+ amrex::Real wt = amrex::second();
+
+ if (background_type == BackgroundStoppingType::ELECTRONS) {
+ doBackgroundStoppingOnElectronsWithinTile(pti, dt, cur_time, species_mass, species_charge);
+ } else if (background_type == BackgroundStoppingType::IONS) {
+ doBackgroundStoppingOnIonsWithinTile(pti, dt, cur_time, species_mass, species_charge);
+ }
+
+ if (cost && WarpX::load_balance_costs_update_algo == LoadBalanceCostsUpdateAlgo::Timers)
+ {
+ amrex::Gpu::synchronize();
+ wt = amrex::second() - wt;
+ amrex::HostDevice::Atomic::Add(&(*cost)[pti.index()], wt);
+ }
+ }
+
+ }
+}
+
+void BackgroundStopping::doBackgroundStoppingOnElectronsWithinTile (WarpXParIter& pti, amrex::Real dt, amrex::Real t,
+ amrex::Real species_mass, amrex::Real species_charge)
+{
+ using namespace amrex::literals;
+
+ // So that CUDA code gets its intrinsic, not the host-only C++ library version
+ using std::sqrt, std::abs, std::log, std::exp;
+
+ // get particle count
+ const long np = pti.numParticles();
+
+ // get background particle mass
+ amrex::Real mass_e = m_background_mass;
+
+ // setup parsers for the background density and temperature
+ auto n_e_func = m_background_density_func;
+ auto T_e_func = m_background_temperature_func;
+
+ // get Struct-Of-Array particle data, also called attribs
+ auto& attribs = pti.GetAttribs();
+ amrex::ParticleReal* const AMREX_RESTRICT ux = attribs[PIdx::ux].dataPtr();
+ amrex::ParticleReal* const AMREX_RESTRICT uy = attribs[PIdx::uy].dataPtr();
+ amrex::ParticleReal* const AMREX_RESTRICT uz = attribs[PIdx::uz].dataPtr();
+
+ // May be needed to evaluate the density and/or temperature functions
+ auto GetPosition = GetParticlePosition(pti);
+
+ amrex::ParallelFor(np,
+ [=] AMREX_GPU_HOST_DEVICE (long ip)
+ {
+
+ amrex::ParticleReal x, y, z;
+ GetPosition.AsStored(ip, x, y, z);
+ amrex::Real const n_e = n_e_func(x, y, z, t);
+ amrex::Real const T_e = T_e_func(x, y, z, t)*PhysConst::kb;
+
+ // This implements the equation 14.12 from Introduction to Plasma Physics,
+ // Goldston and Rutherford, the slowing down of beam ions due to collisions with electrons.
+ // The equation is written as dV/dt = -alpha*V, and integrated to
+ // give V(t+dt) = V(t)*exp(-alpha*dt)
+
+ amrex::Real constexpr pi = MathConst::pi;
+ amrex::Real constexpr ep0 = PhysConst::ep0;
+ amrex::Real constexpr q_e = PhysConst::q_e;
+ amrex::Real constexpr q_e2 = q_e*q_e;
+ amrex::Real constexpr ep02 = ep0*ep0;
+
+ amrex::Real const Zb = abs(species_charge/q_e);
+
+ amrex::Real const vth = sqrt(3._rt*T_e/mass_e);
+ amrex::Real const wp = sqrt(n_e*q_e2/(ep0*mass_e));
+ amrex::Real const lambdadb = vth/wp;
+ amrex::Real const lambdadb3 = lambdadb*lambdadb*lambdadb;
+ amrex::Real const loglambda = log((12._rt*pi/Zb)*(n_e*lambdadb3));
+
+ amrex::Real const pi32 = pi*sqrt(pi);
+ amrex::Real const q2 = species_charge*species_charge;
+ amrex::Real const T32 = T_e*sqrt(T_e);
+
+ amrex::Real const alpha = sqrt(2._rt)*n_e*q2*q_e2*sqrt(mass_e)*loglambda/(12._rt*pi32*ep02*species_mass*T32);
+
+ ux[ip] *= exp(-alpha*dt);
+ uy[ip] *= exp(-alpha*dt);
+ uz[ip] *= exp(-alpha*dt);
+
+ }
+ );
+}
+
+void BackgroundStopping::doBackgroundStoppingOnIonsWithinTile (WarpXParIter& pti, amrex::Real dt, amrex::Real t,
+ amrex::Real species_mass, amrex::Real species_charge)
+{
+ using namespace amrex::literals;
+
+ // So that CUDA code gets its intrinsic, not the host-only C++ library version
+ using std::sqrt, std::abs, std::log, std::exp, std::pow;
+
+ // get particle count
+ const long np = pti.numParticles();
+
+ // get background particle mass
+ amrex::Real mass_i = m_background_mass;
+ amrex::Real charge_state_i = m_background_charge_state;
+
+ // setup parsers for the background density and temperature
+ auto n_i_func = m_background_density_func;
+ auto T_i_func = m_background_temperature_func;
+
+ // get Struct-Of-Array particle data, also called attribs
+ auto& attribs = pti.GetAttribs();
+ amrex::ParticleReal* const AMREX_RESTRICT ux = attribs[PIdx::ux].dataPtr();
+ amrex::ParticleReal* const AMREX_RESTRICT uy = attribs[PIdx::uy].dataPtr();
+ amrex::ParticleReal* const AMREX_RESTRICT uz = attribs[PIdx::uz].dataPtr();
+
+ // May be needed to evaluate the density function
+ auto GetPosition = GetParticlePosition(pti);
+
+ amrex::ParallelFor(np,
+ [=] AMREX_GPU_HOST_DEVICE (long ip)
+ {
+
+ amrex::ParticleReal x, y, z;
+ GetPosition.AsStored(ip, x, y, z);
+ amrex::Real const n_i = n_i_func(x, y, z, t);
+ amrex::Real const T_i = T_i_func(x, y, z, t)*PhysConst::kb;
+
+ // This implements the equation 14.20 from Introduction to Plasma Physics,
+ // Goldston and Rutherford, the slowing down of beam ions due to collisions with electrons.
+ // The equation is written with energy, W, as dW/dt = -alpha/W**0.5, and integrated to
+ // give W(t+dt) = (W(t)**1.5 - 3./2.*alpha*dt)**(2/3)
+
+ amrex::Real constexpr pi = MathConst::pi;
+ amrex::Real constexpr q_e = PhysConst::q_e;
+ amrex::Real constexpr q_e2 = q_e*q_e;
+ amrex::Real constexpr ep0 = PhysConst::ep0;
+ amrex::Real constexpr ep02 = ep0*ep0;
+
+ amrex::Real const qi2 = charge_state_i*charge_state_i*q_e2;
+ amrex::Real const qb2 = species_charge*species_charge;
+ amrex::Real const Zb = abs(species_charge/q_e);
+
+ amrex::Real const vth = sqrt(3._rt*T_i/mass_i);
+ amrex::Real const wp = sqrt(n_i*q_e2/(ep0*mass_i));
+ amrex::Real const lambdadb = vth/wp;
+ amrex::Real const lambdadb3 = lambdadb*lambdadb*lambdadb;
+ amrex::Real const loglambda = log((12._rt*pi/Zb)*(n_i*lambdadb3));
+
+ amrex::Real const alpha = sqrt(2._rt)*n_i*qi2*qb2*sqrt(species_mass)*loglambda/(8._rt*pi*ep02*mass_i);
+
+ amrex::Real const W0 = 0.5_rt*species_mass*(ux[ip]*ux[ip] + uy[ip]*uy[ip] + uz[ip]*uz[ip]);
+ amrex::Real const f1 = pow(W0, 1.5_rt) - 1.5_rt*alpha*dt;
+ // If f1 goes negative, the particle has fully stopped, so set W1 to 0.
+ amrex::Real const W1 = pow((f1 > 0._rt ? f1 : 0._rt), 2._rt/3._rt);
+ amrex::Real const vscale = (W0 > 0._rt ? std::sqrt(W1/W0) : 0._rt);
+
+ ux[ip] *= vscale;
+ uy[ip] *= vscale;
+ uz[ip] *= vscale;
+
+ }
+ );
+}
diff --git a/Source/Particles/Collision/BackgroundStopping/CMakeLists.txt b/Source/Particles/Collision/BackgroundStopping/CMakeLists.txt
new file mode 100644
index 000000000..fa7f65a9c
--- /dev/null
+++ b/Source/Particles/Collision/BackgroundStopping/CMakeLists.txt
@@ -0,0 +1,4 @@
+target_sources(WarpX
+ PRIVATE
+ BackgroundStopping.cpp
+)
diff --git a/Source/Particles/Collision/BackgroundStopping/Make.package b/Source/Particles/Collision/BackgroundStopping/Make.package
new file mode 100644
index 000000000..638e6f4b7
--- /dev/null
+++ b/Source/Particles/Collision/BackgroundStopping/Make.package
@@ -0,0 +1,3 @@
+CEXE_sources += BackgroundStopping.cpp
+
+VPATH_LOCATIONS += $(WARPX_HOME)/Source/Particles/Collision/BackgroundStopping
diff --git a/Source/Particles/Collision/CMakeLists.txt b/Source/Particles/Collision/CMakeLists.txt
index fe7446ca1..d0348fdb4 100644
--- a/Source/Particles/Collision/CMakeLists.txt
+++ b/Source/Particles/Collision/CMakeLists.txt
@@ -7,3 +7,4 @@ target_sources(WarpX
)
add_subdirectory(BinaryCollision)
+add_subdirectory(BackgroundStopping)
diff --git a/Source/Particles/Collision/CollisionHandler.cpp b/Source/Particles/Collision/CollisionHandler.cpp
index 45d599a9e..1c1fff447 100644
--- a/Source/Particles/Collision/CollisionHandler.cpp
+++ b/Source/Particles/Collision/CollisionHandler.cpp
@@ -11,6 +11,7 @@
#include "Particles/Collision/BinaryCollision/BinaryCollision.H"
#include "Particles/Collision/BinaryCollision/NuclearFusion/NuclearFusionFunc.H"
#include "Particles/Collision/BinaryCollision/ParticleCreationFunc.H"
+#include "Particles/Collision/BackgroundStopping/BackgroundStopping.H"
#include "Utils/TextMsg.H"
#include <AMReX_ParmParse.H>
@@ -48,6 +49,9 @@ CollisionHandler::CollisionHandler(MultiParticleContainer const * const mypc)
else if (type == "background_mcc") {
allcollisions[i] = std::make_unique<BackgroundMCCCollision>(collision_names[i]);
}
+ else if (type == "background_stopping") {
+ allcollisions[i] = std::make_unique<BackgroundStopping>(collision_names[i]);
+ }
else if (type == "nuclearfusion") {
allcollisions[i] =
std::make_unique<BinaryCollision<NuclearFusionFunc, ParticleCreationFunc>>(
diff --git a/Source/Particles/Collision/Make.package b/Source/Particles/Collision/Make.package
index f7e073ecf..330e99e08 100644
--- a/Source/Particles/Collision/Make.package
+++ b/Source/Particles/Collision/Make.package
@@ -4,5 +4,6 @@ CEXE_sources += BackgroundMCCCollision.cpp
CEXE_sources += MCCProcess.cpp
include $(WARPX_HOME)/Source/Particles/Collision/BinaryCollision/Make.package
+include $(WARPX_HOME)/Source/Particles/Collision/BackgroundStopping/Make.package
VPATH_LOCATIONS += $(WARPX_HOME)/Source/Particles/Collision