Skip to content

Commit f13c05a

Browse files
Add automated test for particle absorption on new stair-case approximation (#5562)
This introduces automated tests in 2D, 3D and RZ, as a follow up to #5534, to check that a particle does not leave a spurious charge behind, when absorbed by the embedded boundary, and when using an EM solver. In this test, the embedded boundary is a cylinder aligned with the z axis. (In 2D, this reduces to two parallel plates.) The tests fail on the `development` branch, before #5534 is merged. They pass after #5534 is merged. - `development` branch, before #5534 is merged: ![movie](https://github.com/user-attachments/assets/7d00f181-0744-47bd-ab30-4cc0b9e62956) - after #5534 is merged: ![movie](https://github.com/user-attachments/assets/dc4af640-502b-4c42-a51d-f30007228061) Note that these tests fails for higher-order shapes. This will be fixed in #5209 --------- Co-authored-by: Roelof Groenewald <40245517+roelof-groenewald@users.noreply.github.com>
1 parent 70dbbb1 commit f13c05a

11 files changed

+234
-0
lines changed

Examples/Tests/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_subdirectory(electrostatic_sphere)
1515
add_subdirectory(electrostatic_sphere_eb)
1616
add_subdirectory(embedded_boundary_cube)
1717
add_subdirectory(embedded_boundary_diffraction)
18+
add_subdirectory(embedded_boundary_em_particle_absorption)
1819
add_subdirectory(embedded_boundary_python_api)
1920
add_subdirectory(embedded_boundary_rotated_cube)
2021
add_subdirectory(embedded_circle)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Add tests (alphabetical order) ##############################################
2+
#
3+
4+
if(WarpX_EB)
5+
add_warpx_test(
6+
test_3d_embedded_boundary_em_particle_absorption_sh_factor_1 # name
7+
3 # dims
8+
1 # nprocs
9+
inputs_test_3d_embedded_boundary_em_particle_absorption_sh_factor_1 # inputs
10+
"analysis.py" # analysis
11+
"analysis_default_regression.py --path diags/diag1" # checksum
12+
OFF # dependency
13+
)
14+
endif()
15+
16+
17+
if(WarpX_EB)
18+
add_warpx_test(
19+
test_2d_embedded_boundary_em_particle_absorption_sh_factor_1 # name
20+
2 # dims
21+
1 # nprocs
22+
inputs_test_2d_embedded_boundary_em_particle_absorption_sh_factor_1 # inputs
23+
"analysis.py" # analysis
24+
"analysis_default_regression.py --path diags/diag1" # checksum
25+
OFF # dependency
26+
)
27+
endif()
28+
29+
if(WarpX_EB)
30+
add_warpx_test(
31+
test_rz_embedded_boundary_em_particle_absorption_sh_factor_1 # name
32+
RZ # dims
33+
1 # nprocs
34+
inputs_test_rz_embedded_boundary_em_particle_absorption_sh_factor_1 # inputs
35+
"analysis.py" # analysis
36+
"analysis_default_regression.py --path diags/diag1" # checksum
37+
OFF # dependency
38+
)
39+
endif()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
This analysis script checks that there is no spurious charge build-up when a particle is absorbed by an embedded boundary.
5+
6+
More specifically, this test simulates two particles of oppposite charge that are initialized at
7+
the same position and then move in opposite directions. The particles are surrounded by a cylindrical
8+
embedded boundary, and are absorbed when their trajectory intersects this boundary. With an
9+
electromagnetic solver, this can lead to spurious charge build-up (i.e., div(E)!= rho/epsion_0)
10+
that remains at the position where particle was absorbed.
11+
12+
Note that, in this test, there will also be a (non-spurious) component of div(E) that propagates
13+
along the embedded boundary, due to electromagnetic waves reflecting on this boundary.
14+
When checking for static, spurious charge build-up, we average div(E) in time to remove this component.
15+
16+
The test is performed in 2D, 3D and RZ.
17+
(In 2D, the cylindrical embedded boundary becomes two parallel plates)
18+
"""
19+
20+
from openpmd_viewer import OpenPMDTimeSeries
21+
22+
ts = OpenPMDTimeSeries("./diags/diag1/")
23+
24+
divE_stacked = ts.iterate(
25+
lambda iteration: ts.get_field("divE", iteration=iteration)[0]
26+
)
27+
start_avg_iter = 25
28+
end_avg_iter = 100
29+
divE_avg = divE_stacked[start_avg_iter:end_avg_iter].mean(axis=0)
30+
31+
# Adjust the tolerance so that the remaining error due to the propagating
32+
# div(E) (after averaging) is below this tolerance, but so that any typical
33+
# spurious charge build-up is above this tolerance. This is dimension-dependent.
34+
dim = ts.fields_metadata["divE"]["geometry"]
35+
if dim == "3dcartesian":
36+
tolerance = 7e-11
37+
elif dim == "2dcartesian":
38+
tolerance = 3.5e-10
39+
elif dim == "thetaMode":
40+
# In RZ: there are issues with divE on axis
41+
# Set the few cells around the axis to 0 for this test
42+
divE_avg[13:19] = 0
43+
tolerance = 4e-12
44+
45+
46+
def check_tolerance(array, tolerance):
47+
assert abs(array).max() <= tolerance, (
48+
f"Test did not pass: the max error {abs(array).max()} exceeded the tolerance of {tolerance}."
49+
)
50+
print("All elements of are within the tolerance.")
51+
52+
53+
check_tolerance(divE_avg, tolerance)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../analysis_default_regression.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
max_step = 100
2+
amr.max_level = 0
3+
amr.blocking_factor = 8
4+
amr.max_grid_size = 256
5+
6+
algo.charge_deposition = standard
7+
algo.field_gathering = energy-conserving
8+
warpx.const_dt = 1.17957283598e-09
9+
warpx.use_filter = 0
10+
11+
my_constants.R = 6.35
12+
warpx.eb_implicit_function = "(x**2 + y**2 - R**2)"
13+
14+
particles.species_names = electron positron
15+
16+
electron.charge = -q_e
17+
electron.mass = m_e
18+
electron.injection_style = "SingleParticle"
19+
electron.single_particle_pos = 0.0 0.0 0.0
20+
electron.single_particle_u = 1.e20 0.0 0.4843221e20 # gamma*beta
21+
electron.single_particle_weight = 1.0
22+
23+
positron.charge = q_e
24+
positron.mass = m_e
25+
positron.injection_style = "SingleParticle"
26+
positron.single_particle_pos = 0.0 0.0 0.0
27+
positron.single_particle_u = -1.e20 0.0 -0.4843221e20 # gamma*beta
28+
positron.single_particle_weight = 1.0
29+
30+
# Diagnostics
31+
diagnostics.diags_names = diag1
32+
diag1.intervals = 1
33+
diag1.diag_type = Full
34+
diag1.fields_to_plot = divE rho
35+
diag1.format = openpmd
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# base input parameters
2+
FILE = inputs_base
3+
4+
geometry.dims = 2
5+
amr.n_cell = 32 32
6+
geometry.prob_lo = -10 -10
7+
geometry.prob_hi = 10 10
8+
boundary.field_lo = pec absorbing_silver_mueller
9+
boundary.field_hi = pec absorbing_silver_mueller
10+
11+
algo.particle_shape = 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# base input parameters
2+
FILE = inputs_base
3+
4+
geometry.dims = 3
5+
amr.n_cell = 32 32 32
6+
geometry.prob_lo = -10 -10 -10
7+
geometry.prob_hi = 10 10 10
8+
boundary.field_lo = pec pec absorbing_silver_mueller
9+
boundary.field_hi = pec pec absorbing_silver_mueller
10+
11+
algo.particle_shape = 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# base input parameters
2+
FILE = inputs_base
3+
4+
geometry.dims = RZ
5+
amr.n_cell = 16 32
6+
geometry.prob_lo = 0 -10
7+
geometry.prob_hi = 10 10
8+
boundary.field_lo = none absorbing_silver_mueller
9+
boundary.field_hi = pec absorbing_silver_mueller
10+
11+
algo.particle_shape = 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"lev=0": {
3+
"divE": 3.059581906777539e-08,
4+
"rho": 0.0
5+
},
6+
"electron": {
7+
"particle_position_x": 0.0,
8+
"particle_position_y": 0.0,
9+
"particle_position_z": 0.0,
10+
"particle_momentum_x": 0.0,
11+
"particle_momentum_y": 0.0,
12+
"particle_momentum_z": 0.0,
13+
"particle_weight": 0.0
14+
},
15+
"positron": {
16+
"particle_position_x": 0.0,
17+
"particle_position_y": 0.0,
18+
"particle_position_z": 0.0,
19+
"particle_momentum_x": 0.0,
20+
"particle_momentum_y": 0.0,
21+
"particle_momentum_z": 0.0,
22+
"particle_weight": 0.0
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"lev=0": {
3+
"divE": 4.928354322096152e-07,
4+
"rho": 0.0
5+
},
6+
"electron": {
7+
"particle_position_x": 0.0,
8+
"particle_position_y": 0.0,
9+
"particle_position_z": 0.0,
10+
"particle_momentum_x": 0.0,
11+
"particle_momentum_y": 0.0,
12+
"particle_momentum_z": 0.0,
13+
"particle_weight": 0.0
14+
},
15+
"positron": {
16+
"particle_position_x": 0.0,
17+
"particle_position_y": 0.0,
18+
"particle_position_z": 0.0,
19+
"particle_momentum_x": 0.0,
20+
"particle_momentum_y": 0.0,
21+
"particle_momentum_z": 0.0,
22+
"particle_weight": 0.0
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"lev=0": {
3+
"divE": 1.4599714697029335e-08,
4+
"rho": 0.0
5+
},
6+
"electron": {
7+
"particle_position_x": 0.0,
8+
"particle_position_y": 0.0,
9+
"particle_position_z": 0.0,
10+
"particle_momentum_x": 0.0,
11+
"particle_momentum_y": 0.0,
12+
"particle_momentum_z": 0.0,
13+
"particle_weight": 0.0
14+
},
15+
"positron": {
16+
"particle_position_x": 0.0,
17+
"particle_position_y": 0.0,
18+
"particle_position_z": 0.0,
19+
"particle_momentum_x": 0.0,
20+
"particle_momentum_y": 0.0,
21+
"particle_momentum_z": 0.0,
22+
"particle_weight": 0.0
23+
}
24+
}

0 commit comments

Comments
 (0)