Skip to content
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

Added CI to test secondary ion emission in RZ. #5576

Open
wants to merge 1 commit into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Examples/Tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ add_subdirectory(resampling)
add_subdirectory(restart)
add_subdirectory(restart_eb)
add_subdirectory(rigid_injection)
add_subdirectory(secondary_ion_emission)
add_subdirectory(scraping)
add_subdirectory(effective_potential_electrostatic)
add_subdirectory(silver_mueller)
Expand Down
14 changes: 14 additions & 0 deletions Examples/Tests/secondary_ion_emission/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Add tests (alphabetical order) ##############################################
#

if(WarpX_EB)
add_warpx_test(
test_rz_secondary_ion_emission_picmi # name
RZ # dims
1 # nprocs
inputs_test_rz_secondary_ion_emission_picmi.py # inputs
"analysis.py diags/diag1/" # analysis
"analysis_default_regression.py --path diags/diag1/" # checksum
OFF # dependency
)
endif()
62 changes: 62 additions & 0 deletions Examples/Tests/secondary_ion_emission/analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python
"""
This script tests the last coordinates of the emitted secondary electrons.
The EB sphere is centered on O and has a radius of 0.2.
The proton is initially at: (0,0,-0.25) and moves with a velocity:
(0.1e6,0,1.5e6) with a time step of dt = 0.000000015.
The simulation uses a fixed random seed (np.random.seed(10025015))
to ensure the emission of secodnary electrons.
An input file inputs_test_rz_secondary_ion_emission_picmi.py is used.
"""

import sys

import numpy as np
import yt
from openpmd_viewer import OpenPMDTimeSeries

yt.funcs.mylog.setLevel(0)

# Open plotfile specified in command line
filename = sys.argv[1]
ts = OpenPMDTimeSeries(filename)

it = ts.iterations
x, y, z = ts.get_particle(["x", "y", "z"], species="electrons", iteration=it[-1])
print("x", x)
print("y", y)
print("z", z)
# Analytical results calculated
x_analytic = [0.004028, 0.003193]
y_analytic = [-0.0001518, -0.0011041]
z_analytic = [-0.19967, -0.19926]

N_sec_e = np.size(z) # number of the secondary electrons

assert N_sec_e == 2, (
"Test did not pass: for this set up we expect 2 secondary electrons emitted"
)

tolerance = 1e-3

for i in range(0, N_sec_e):
print("\n")
print(f"Electron # {i}:")
print("NUMERICAL coordinates of the emitted electrons:")
print("x=%5.5f, y=%5.5f, z=%5.5f" % (x[i], y[i], z[i]))
print("\n")
print("ANALYTICAL coordinates of the point of contact:")
print("x=%5.5f, y=%5.5f, z=%5.5f" % (x_analytic[i], y_analytic[i], z_analytic[i]))

diff_x = np.abs((x[i] - x_analytic[i]) / x_analytic[i])
diff_y = np.abs((y[i] - y_analytic[i]) / y_analytic[i])
diff_z = np.abs((z[i] - z_analytic[i]) / z_analytic[i])

print("\n")
print("percentage error for x = %5.4f %%" % (diff_x * 100))
print("percentage error for y = %5.4f %%" % (diff_y * 100))
print("percentage error for z = %5.4f %%" % (diff_z * 100))

assert (diff_x < tolerance) and (diff_y < tolerance) and (diff_z < tolerance), (
"Test particle_boundary_interaction did not pass"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
#!/usr/bin/env python3
# --- Input file for secondary-ion emission testing in RZ via callback function.
import numpy as np
from scipy.constants import e, elementary_charge, m_e, proton_mass

from pywarpx import callbacks, particle_containers, picmi

##########################
# numerics parameters
##########################

dt = 0.000000015

# --- Nb time steps
Te = 0.0259 # in eV
dist_th = np.sqrt(Te * elementary_charge / m_e)

max_steps = 3
diagnostic_interval = 1

# --- grid
nr = 64
nz = 64

rmin = 0.0
rmax = 2
zmin = -2
zmax = 2
delta_H = 0.4
E_HMax = 250

np.random.seed(10025015)
##########################
# numerics components
##########################

grid = picmi.CylindricalGrid(
number_of_cells=[nr, nz],
n_azimuthal_modes=1,
lower_bound=[rmin, zmin],
upper_bound=[rmax, zmax],
lower_boundary_conditions=["none", "dirichlet"],
upper_boundary_conditions=["dirichlet", "dirichlet"],
lower_boundary_conditions_particles=["none", "reflecting"],
upper_boundary_conditions_particles=["absorbing", "reflecting"],
)

solver = picmi.ElectrostaticSolver(
grid=grid, method="Multigrid", warpx_absolute_tolerance=1e-7
)

embedded_boundary = picmi.EmbeddedBoundary(
implicit_function="-(x**2+y**2+z**2-radius**2)", radius=0.2
)

##########################
# physics components
##########################

i_dist = picmi.ParticleListDistribution(
x=0.0, y=0.0, z=-0.25, ux=0.1e6, uy=0.0, uz=1.50e6, weight=1
)
electrons = picmi.Species(
particle_type="electron", # Specify the particle type
name="electrons", # Name of the species
)

ions = picmi.Species(
name="ions",
mass=proton_mass,
charge=e,
initial_distribution=i_dist,
warpx_save_particles_at_eb=1,
)

##########################
# diagnostics
##########################

field_diag = picmi.FieldDiagnostic(
name="diag1",
grid=grid,
period=diagnostic_interval,
data_list=["Er", "Ez", "phi", "rho"], # , "rho_electrons"],
warpx_format="openpmd",
)

part_diag = picmi.ParticleDiagnostic(
name="diag1",
period=diagnostic_interval,
species=[ions, electrons],
warpx_format="openpmd",
)

##########################
# simulation setup
##########################
# num_procs = [1, 1]
# warpx_numprocs=num_procs,
sim = picmi.Simulation(
solver=solver,
time_step_size=dt,
max_steps=max_steps,
warpx_embedded_boundary=embedded_boundary,
warpx_amrex_the_arena_is_managed=1,
)

sim.add_species(
electrons,
layout=picmi.GriddedLayout(n_macroparticle_per_cell=[0, 0, 0], grid=grid),
)

sim.add_species(
ions,
layout=picmi.GriddedLayout(n_macroparticle_per_cell=[10, 1, 1], grid=grid),
)

sim.add_diagnostic(part_diag)
sim.add_diagnostic(field_diag)

sim.initialize_inputs()
sim.initialize_warpx()

##########################
# python particle data access
##########################


def concat(list_of_arrays):
if len(list_of_arrays) == 0:
# Return a 1d array of size 0
return np.empty(0)
else:
return np.concatenate(list_of_arrays)


def sigma_nascap(energy_kEv, delta_H, E_HMax):
"""
Compute sigma_nascap for each element in the energy array using a loop.

Parameters:
- energy: ndarray or list, energy values in KeV
- delta_H: float, parameter for the formula
- E_HMax: float, parameter for the formula in KeV

Returns:
- numpy array, computed probability sigma_nascap
"""
sigma_nascap = np.array([])
# Loop through each energy value
for energy in energy_kEv:
if energy > 0.0:
sigma = (
delta_H
* (E_HMax + 1.0)
/ (E_HMax * 1.0 + energy)
* np.sqrt(energy / 1.0)
)
else:
sigma = 0.0
sigma_nascap = np.append(sigma_nascap, sigma)
return sigma_nascap


def secondary_emission():
buffer = particle_containers.ParticleBoundaryBufferWrapper() # boundary buffer
# STEP 1: extract the different parameters of the boundary buffer (normal, time, position)
lev = 0 # level 0 (no mesh refinement here)
n = buffer.get_particle_boundary_buffer_size("ions", "eb")
elect_pc = particle_containers.ParticleContainerWrapper("electrons")

if n != 0:
r = concat(buffer.get_particle_boundary_buffer("ions", "eb", "x", lev))
theta = concat(buffer.get_particle_boundary_buffer("ions", "eb", "theta", lev))
z = concat(buffer.get_particle_boundary_buffer("ions", "eb", "z", lev))
x = r * np.cos(theta) # from RZ coordinates to 3D coordinates
y = r * np.sin(theta)
ux = concat(buffer.get_particle_boundary_buffer("ions", "eb", "ux", lev))
uy = concat(buffer.get_particle_boundary_buffer("ions", "eb", "uy", lev))
uz = concat(buffer.get_particle_boundary_buffer("ions", "eb", "uz", lev))
w = concat(buffer.get_particle_boundary_buffer("ions", "eb", "w", lev))
nx = concat(buffer.get_particle_boundary_buffer("ions", "eb", "nx", lev))
ny = concat(buffer.get_particle_boundary_buffer("ions", "eb", "ny", lev))
nz = concat(buffer.get_particle_boundary_buffer("ions", "eb", "nz", lev))
delta_t = concat(
buffer.get_particle_boundary_buffer("ions", "eb", "deltaTimeScraped", lev)
)
energy_ions = 0.5 * proton_mass * w * (ux**2 + uy**2 + uz**2)
energy_ions_in_kEv = energy_ions / (1.602176634e-19 * 1000)
sigma_nascap_ions = sigma_nascap(energy_ions_in_kEv, delta_H, E_HMax)
# Loop over all ions in the EB buffer
for i in range(0, n):
sigma = sigma_nascap_ions[i]
sigma_int = int(sigma)
rn = np.random.uniform(sigma_int, sigma_int + 1 + np.finfo(float).eps)
if rn < sigma:
Ne_sec = (
sigma_int + 1
) # number of the secondary electrons to be emitted
for j in [0, Ne_sec - 1]:

Check failure

Code scanning / CodeQL

Suspicious unused loop iteration variable Error

For loop variable 'j' is not used in the loop body.
xe = np.array([])
ye = np.array([])
ze = np.array([])
we = np.array([])
delta_te = np.array([])
uxe = np.array([])
uye = np.array([])
uze = np.array([])

# Random thermal momenta distribution
ux_th = np.random.normal(0, dist_th)
uy_th = np.random.normal(0, dist_th)
uz_th = np.random.normal(0, dist_th)

un_th = nx[i] * ux_th + ny[i] * uy_th + nz[i] * uz_th

if un_th < 0:
ux_th_reflect = (
-2 * un_th * nx[i] + ux_th
) # for a "mirror reflection" u(sym)=-2(u.n)n+u
uy_th_reflect = -2 * un_th * ny[i] + uy_th
uz_th_reflect = -2 * un_th * nz[i] + uz_th

uxe = np.append(uxe, ux_th_reflect)
uye = np.append(uye, uy_th_reflect)
uze = np.append(uze, uz_th_reflect)
else:
uxe = np.append(uxe, ux_th)
uye = np.append(uye, uy_th)
uze = np.append(uze, uz_th)

xe = np.append(xe, x[i])
ye = np.append(ye, y[i])
ze = np.append(ze, z[i])
we = np.append(we, w[i])
delta_te = np.append(delta_te, delta_t[i])

elect_pc.add_particles(
x=xe + (dt - delta_te) * uxe,
y=ye + (dt - delta_te) * uye,
z=ze + (dt - delta_te) * uze,
ux=uxe,
uy=uye,
uz=uze,
w=we,
)
buffer.clear_buffer() # reinitialise the boundary buffer


# using the new particle container modified at the last step
callbacks.installafterstep(secondary_emission)
##########################
# simulation run
##########################
sim.step(max_steps) # the whole process is done "max_steps" times
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"electrons": {
"particle_momentum_x": 6.863524723051401e-26,
"particle_momentum_y": 1.0324224192517369e-25,
"particle_momentum_z": 5.621885683115495e-26,
"particle_position_x": 0.0072217326490580675,
"particle_position_y": 0.001255871169011761,
"particle_position_z": 0.39892796754417836,
"particle_weight": 2.0
},
"ions": {
"particle_momentum_x": 0.0,
"particle_momentum_y": 0.0,
"particle_momentum_z": 0.0,
"particle_position_x": 0.0,
"particle_position_y": 0.0,
"particle_position_z": 0.0,
"particle_weight": 0.0
},
"lev=0": {
"Er": 3.996898574068735e-08,
"Ez": 3.77948124311196e-08,
"phi": 2.359076749421693e-08,
"rho": 4.6171309216540875e-15
}
}
Loading