Skip to content

Commit 1f88620

Browse files
authored
Implement div(B) Cleaning With FDTD (#1829)
* Implement div(B) Cleaning With FDTD * Add CI Test * Clean Up
1 parent 7113d7e commit 1f88620

File tree

19 files changed

+463
-14
lines changed

19 files changed

+463
-14
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#! /usr/bin/env python
2+
3+
# Copyright 2019
4+
#
5+
# This file is part of WarpX.
6+
#
7+
# License: BSD-3-Clause-LBNL
8+
9+
import sys
10+
sys.path.insert(1, '../../../../warpx/Regression/Checksum/')
11+
import numpy as np
12+
import yt
13+
yt.funcs.mylog.setLevel(50)
14+
import re
15+
import checksumAPI
16+
17+
# Name of the last plotfile
18+
fn = sys.argv[1]
19+
20+
# Load yt data
21+
ds_old = yt.load('divb_cleaning_3d_plt00398')
22+
ds_mid = yt.load('divb_cleaning_3d_plt00399')
23+
ds_new = yt.load(fn) # this is the last plotfile
24+
25+
ad_old = ds_old.covering_grid(level = 0, left_edge = ds_old.domain_left_edge, dims = ds_old.domain_dimensions)
26+
ad_mid = ds_mid.covering_grid(level = 0, left_edge = ds_mid.domain_left_edge, dims = ds_mid.domain_dimensions)
27+
ad_new = ds_new.covering_grid(level = 0, left_edge = ds_new.domain_left_edge, dims = ds_new.domain_dimensions)
28+
29+
G_old = ad_old['boxlib', 'G'].v.squeeze()
30+
G_new = ad_new['boxlib', 'G'].v.squeeze()
31+
divB = ad_mid['boxlib', 'divB'].v.squeeze()
32+
33+
# Check max norm of error on div(B) = dG/dt
34+
# (the time interval between old and new is 2*dt)
35+
dt = 1.504557189e-15
36+
x = G_new - G_old
37+
y = divB * 2 * dt
38+
39+
rel_error = np.amax(abs(x - y)) / np.amax(abs(y))
40+
tolerance = 1e-1
41+
42+
assert(rel_error < tolerance)
43+
44+
test_name = fn[:-9] # Could also be os.path.split(os.getcwd())[1]
45+
46+
if re.search('single_precision', fn):
47+
checksumAPI.evaluate_checksum(test_name, fn, rtol=1.e-3)
48+
else:
49+
checksumAPI.evaluate_checksum(test_name, fn)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Iterations
2+
max_step = 400
3+
4+
# Domain
5+
amr.n_cell = 32 32 32
6+
amr.max_grid_size = 16
7+
amr.max_level = 0
8+
9+
# Geometry
10+
geometry.coord_sys = 0
11+
geometry.is_periodic = 0 0 0
12+
geometry.prob_lo = -50.e-6 -50.e-6 -50.e-6
13+
geometry.prob_hi = 50.e-6 50.e-6 50.e-6
14+
15+
# Shape factors
16+
interpolation.nox = 3
17+
interpolation.noy = 3
18+
interpolation.noz = 3
19+
20+
# Numerics
21+
warpx.cfl = 0.25
22+
warpx.do_divb_cleaning = 1
23+
warpx.use_filter = 1
24+
25+
# External magnetic field
26+
my_constants.qm = 1e-1
27+
warpx.B_ext_grid_init_style = parse_B_ext_grid_function
28+
warpx.Bx_external_grid_function(x,y,z) = qm * x / (x*x + y*y + z*z)
29+
warpx.By_external_grid_function(x,y,z) = qm * y / (x*x + y*y + z*z)
30+
warpx.Bz_external_grid_function(x,y,z) = qm * z / (x*x + y*y + z*z)
31+
32+
# Particle beam
33+
particles.species_names = beam
34+
beam.charge = -q_e
35+
beam.mass = 1.e30
36+
beam.injection_style = "gaussian_beam"
37+
beam.x_rms = 2.e-6
38+
beam.y_rms = 2.e-6
39+
beam.z_rms = 2.e-6
40+
beam.x_m = 0.
41+
beam.y_m = 0.
42+
beam.z_m = 0.e-6
43+
beam.npart = 20000
44+
beam.q_tot = -1.e-20
45+
beam.momentum_distribution_type = "gaussian"
46+
beam.ux_m = 0.0
47+
beam.uy_m = 0.0
48+
beam.uz_m = 0.5
49+
50+
# Diagnostics
51+
diagnostics.diags_names = diag1
52+
diag1.intervals = 0, 398:400:1
53+
diag1.diag_type = Full
54+
diag1.fields_to_plot = Ex Ey Ez Bx By Bz divE divB G
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"lev=0": {
3+
"Bx": 30110529.73244452,
4+
"By": 30110529.732444517,
5+
"Bz": 30110529.73244452,
6+
"Ex": 137103615680453.66,
7+
"Ey": 137103615680454.56,
8+
"Ez": 137103615680494.5,
9+
"G": 0.08248210392635753,
10+
"divB": 6944252335584.074,
11+
"divE": 60881293.88523142
12+
}
13+
}

Regression/WarpX-tests.ini

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,23 @@ runtime_params = warpx.do_dynamic_scheduling=0
980980
analysisRoutine = Examples/Tests/initial_plasma_profile/analysis.py
981981
tolerance = 1.e-14
982982

983+
[divb_cleaning_3d]
984+
buildDir = .
985+
inputFile = Examples/Tests/divb_cleaning/inputs_3d
986+
runtime_params =
987+
dim = 3
988+
addToCompileString =
989+
restartTest = 0
990+
useMPI = 1
991+
numprocs = 2
992+
useOMP = 1
993+
numthreads = 1
994+
compileTest = 0
995+
doVis = 0
996+
compareParticles = 0
997+
analysisRoutine = Examples/Tests/divb_cleaning/analysis.py
998+
tolerance = 1.e-14
999+
9831000
[dive_cleaning_2d]
9841001
buildDir = .
9851002
inputFile = Examples/Modules/dive_cleaning/inputs_3d

Source/Diagnostics/Diagnostics.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ Diagnostics::BaseReadParameters ()
6262
"plot F only works if warpx.do_dive_cleaning = 1");
6363
}
6464

65+
// G can be written to file only if WarpX::do_divb_cleaning = 1
66+
if (WarpXUtilStr::is_in(m_varnames, "G"))
67+
{
68+
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(
69+
warpx.do_divb_cleaning, "G can be written to file only if warpx.do_divb_cleaning = 1");
70+
}
71+
6572
// If user requests to plot proc_number for a serial run,
6673
// delete proc_number from fields_to_plot
6774
if (amrex::ParallelDescriptor::NProcs() == 1){

Source/Diagnostics/FullDiagnostics.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ FullDiagnostics::InitializeFieldFunctors (int lev)
425425
i++;
426426
} else if ( m_varnames[comp] == "F" ){
427427
m_all_field_functors[lev][comp] = std::make_unique<CellCenterFunctor>(warpx.get_pointer_F_fp(lev), lev, m_crse_ratio);
428+
} else if ( m_varnames[comp] == "G" ){
429+
m_all_field_functors[lev][comp] = std::make_unique<CellCenterFunctor>(warpx.get_pointer_G_fp(lev), lev, m_crse_ratio);
428430
} else if ( m_varnames[comp] == "phi" ){
429431
m_all_field_functors[lev][comp] = std::make_unique<CellCenterFunctor>(warpx.get_pointer_phi_fp(lev), lev, m_crse_ratio);
430432
} else if ( m_varnames[comp] == "part_per_cell" ){

Source/Evolve/WarpXEvolve.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,9 @@ WarpX::OneStep_nosub (Real cur_time)
359359
}
360360
} else {
361361
EvolveF(0.5_rt * dt[0], DtType::FirstHalf);
362+
EvolveG(0.5_rt * dt[0], DtType::FirstHalf);
362363
FillBoundaryF(guard_cells.ng_FieldSolverF);
364+
FillBoundaryG(guard_cells.ng_FieldSolverG);
363365
EvolveB(0.5_rt * dt[0]); // We now have B^{n+1/2}
364366

365367
if (do_silver_mueller) ApplySilverMuellerBoundary( dt[0] );
@@ -377,6 +379,7 @@ WarpX::OneStep_nosub (Real cur_time)
377379

378380
FillBoundaryE(guard_cells.ng_FieldSolver);
379381
EvolveF(0.5_rt * dt[0], DtType::SecondHalf);
382+
EvolveG(0.5_rt * dt[0], DtType::SecondHalf);
380383
EvolveB(0.5_rt * dt[0]); // We now have B^{n+1}
381384

382385
// Synchronize E and B fields on nodal points

Source/FieldSolver/FiniteDifferenceSolver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ target_sources(WarpX
77
EvolveEPML.cpp
88
EvolveF.cpp
99
EvolveFPML.cpp
10+
EvolveG.cpp
1011
FiniteDifferenceSolver.cpp
1112
MacroscopicEvolveE.cpp
1213
ApplySilverMuellerBoundary.cpp

Source/FieldSolver/FiniteDifferenceSolver/EvolveB.cpp

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ using namespace amrex;
2525
void FiniteDifferenceSolver::EvolveB (
2626
std::array< std::unique_ptr<amrex::MultiFab>, 3 >& Bfield,
2727
std::array< std::unique_ptr<amrex::MultiFab>, 3 > const& Efield,
28+
std::unique_ptr<amrex::MultiFab> const& Gfield,
2829
std::array< std::unique_ptr<amrex::MultiFab>, 3 > const& face_areas,
2930
int lev, amrex::Real const dt ) {
3031

@@ -38,21 +39,20 @@ void FiniteDifferenceSolver::EvolveB (
3839
#else
3940
if (m_do_nodal) {
4041

41-
EvolveBCartesian <CartesianNodalAlgorithm> ( Bfield, Efield, face_areas, lev, dt );
42+
EvolveBCartesian <CartesianNodalAlgorithm> ( Bfield, Efield, Gfield, face_areas, lev, dt );
4243

4344
} else if (m_fdtd_algo == MaxwellSolverAlgo::Yee) {
4445

45-
EvolveBCartesian <CartesianYeeAlgorithm> ( Bfield, Efield, face_areas, lev, dt );
46+
EvolveBCartesian <CartesianYeeAlgorithm> ( Bfield, Efield, Gfield, face_areas, lev, dt );
4647

4748
} else if (m_fdtd_algo == MaxwellSolverAlgo::CKC) {
4849

49-
EvolveBCartesian <CartesianCKCAlgorithm> ( Bfield, Efield, face_areas, lev, dt );
50+
EvolveBCartesian <CartesianCKCAlgorithm> ( Bfield, Efield, Gfield, face_areas, lev, dt );
5051

5152
#endif
5253
} else {
5354
amrex::Abort("EvolveB: Unknown algorithm");
5455
}
55-
5656
}
5757

5858

@@ -62,6 +62,7 @@ template<typename T_Algo>
6262
void FiniteDifferenceSolver::EvolveBCartesian (
6363
std::array< std::unique_ptr<amrex::MultiFab>, 3 >& Bfield,
6464
std::array< std::unique_ptr<amrex::MultiFab>, 3 > const& Efield,
65+
std::unique_ptr<amrex::MultiFab> const& Gfield,
6566
std::array< std::unique_ptr<amrex::MultiFab>, 3 > const& face_areas,
6667
int lev, amrex::Real const dt ) {
6768

@@ -71,6 +72,8 @@ void FiniteDifferenceSolver::EvolveBCartesian (
7172

7273
amrex::LayoutData<amrex::Real>* cost = WarpX::getCosts(lev);
7374

75+
Real constexpr c2 = PhysConst::c * PhysConst::c;
76+
7477
// Loop through the grids, and over the tiles within each grid
7578
#ifdef AMREX_USE_OMP
7679
#pragma omp parallel if (amrex::Gpu::notInLaunchRegion())
@@ -138,9 +141,32 @@ void FiniteDifferenceSolver::EvolveBCartesian (
138141
Bz(i, j, k) += dt * T_Algo::UpwardDy(Ex, coefs_y, n_coefs_y, i, j, k)
139142
- dt * T_Algo::UpwardDx(Ey, coefs_x, n_coefs_x, i, j, k);
140143
}
141-
142144
);
143145

146+
// div(B) cleaning correction for errors in magnetic Gauss law (div(B) = 0)
147+
if (Gfield)
148+
{
149+
// Extract field data for this grid/tile
150+
Array4<Real> G = Gfield->array(mfi);
151+
152+
// Loop over cells and update G
153+
amrex::ParallelFor(tbx, tby, tbz,
154+
155+
[=] AMREX_GPU_DEVICE (int i, int j, int k)
156+
{
157+
Bx(i,j,k) += c2 * dt * T_Algo::DownwardDx(G, coefs_x, n_coefs_x, i, j, k);
158+
},
159+
[=] AMREX_GPU_DEVICE (int i, int j, int k)
160+
{
161+
By(i,j,k) += c2 * dt * T_Algo::DownwardDy(G, coefs_y, n_coefs_y, i, j, k);
162+
},
163+
[=] AMREX_GPU_DEVICE (int i, int j, int k)
164+
{
165+
Bz(i,j,k) += c2 * dt * T_Algo::DownwardDz(G, coefs_z, n_coefs_z, i, j, k);
166+
}
167+
);
168+
}
169+
144170
if (cost && WarpX::load_balance_costs_update_algo == LoadBalanceCostsUpdateAlgo::Timers)
145171
{
146172
amrex::Gpu::synchronize();
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/* Copyright 2020
2+
*
3+
* This file is part of WarpX.
4+
*
5+
* License: BSD-3-Clause-LBNL
6+
*/
7+
8+
#include "Utils/WarpXAlgorithmSelection.H"
9+
#include "FiniteDifferenceSolver.H"
10+
#ifdef WARPX_DIM_RZ
11+
# include "FiniteDifferenceAlgorithms/CylindricalYeeAlgorithm.H"
12+
#else
13+
# include "FiniteDifferenceAlgorithms/CartesianYeeAlgorithm.H"
14+
# include "FiniteDifferenceAlgorithms/CartesianCKCAlgorithm.H"
15+
# include "FiniteDifferenceAlgorithms/CartesianNodalAlgorithm.H"
16+
#endif
17+
#include "Utils/WarpXConst.H"
18+
#include "WarpX.H"
19+
#include <AMReX_Gpu.H>
20+
21+
using namespace amrex;
22+
23+
void FiniteDifferenceSolver::EvolveG (
24+
std::unique_ptr<amrex::MultiFab>& Gfield,
25+
std::array<std::unique_ptr<amrex::MultiFab>,3> const& Bfield,
26+
amrex::Real const dt)
27+
{
28+
#ifdef WARPX_DIM_RZ
29+
// TODO Implement G update equation in RZ geometry
30+
amrex::ignore_unused(Gfield, Bfield, dt);
31+
#else
32+
// Select algorithm
33+
if (m_do_nodal)
34+
{
35+
EvolveGCartesian<CartesianNodalAlgorithm>(Gfield, Bfield, dt);
36+
}
37+
else if (m_fdtd_algo == MaxwellSolverAlgo::Yee)
38+
{
39+
EvolveGCartesian<CartesianYeeAlgorithm>(Gfield, Bfield, dt);
40+
}
41+
else if (m_fdtd_algo == MaxwellSolverAlgo::CKC)
42+
{
43+
EvolveGCartesian<CartesianCKCAlgorithm>(Gfield, Bfield, dt);
44+
}
45+
else
46+
{
47+
amrex::Abort("EvolveG: unknown FDTD algorithm");
48+
}
49+
#endif
50+
}
51+
52+
#ifndef WARPX_DIM_RZ
53+
54+
template<typename T_Algo>
55+
void FiniteDifferenceSolver::EvolveGCartesian (
56+
std::unique_ptr<amrex::MultiFab>& Gfield,
57+
std::array<std::unique_ptr<amrex::MultiFab>,3> const& Bfield,
58+
amrex::Real const dt)
59+
{
60+
#ifdef AMREX_USE_OMP
61+
#pragma omp parallel if (amrex::Gpu::notInLaunchRegion())
62+
#endif
63+
64+
// Loop over grids and over tiles within each grid
65+
for (amrex::MFIter mfi(*Gfield, TilingIfNotGPU()); mfi.isValid(); ++mfi)
66+
{
67+
// Extract field data for this grid/tile
68+
amrex::Array4<amrex::Real> const& G = Gfield->array(mfi);
69+
amrex::Array4<amrex::Real> const& Bx = Bfield[0]->array(mfi);
70+
amrex::Array4<amrex::Real> const& By = Bfield[1]->array(mfi);
71+
amrex::Array4<amrex::Real> const& Bz = Bfield[2]->array(mfi);
72+
73+
// Extract stencil coefficients
74+
amrex::Real const* const AMREX_RESTRICT coefs_x = m_stencil_coefs_x.dataPtr();
75+
amrex::Real const* const AMREX_RESTRICT coefs_y = m_stencil_coefs_y.dataPtr();
76+
amrex::Real const* const AMREX_RESTRICT coefs_z = m_stencil_coefs_z.dataPtr();
77+
78+
const int n_coefs_x = m_stencil_coefs_x.size();
79+
const int n_coefs_y = m_stencil_coefs_y.size();
80+
const int n_coefs_z = m_stencil_coefs_z.size();
81+
82+
// Extract tilebox to loop over
83+
amrex::Box const& tf = mfi.tilebox(Gfield->ixType().toIntVect());
84+
85+
// Loop over cells and update G
86+
amrex::ParallelFor(tf, [=] AMREX_GPU_DEVICE (int i, int j, int k)
87+
{
88+
G(i,j,k) += dt * (T_Algo::UpwardDx(Bx, coefs_x, n_coefs_x, i, j, k)
89+
+ T_Algo::UpwardDy(By, coefs_y, n_coefs_y, i, j, k)
90+
+ T_Algo::UpwardDz(Bz, coefs_z, n_coefs_z, i, j, k));
91+
});
92+
}
93+
}
94+
95+
#endif

0 commit comments

Comments
 (0)