Skip to content

Commit 55110ec

Browse files
authored
Merge pull request #390 from JamesMcClung/pr/marder-at-edges
Fix Marder correction at conductive and open edges
2 parents df9f2bf + d53d3a1 commit 55110ec

6 files changed

Lines changed: 80 additions & 131 deletions

File tree

src/include/boundary_injector.hxx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,6 @@ public:
127127
get_n_in_cell(1.0, prts_per_unit_density_, true);
128128

129129
for (int prt_count = 0; prt_count < n_prts_to_try_inject; prt_count++) {
130-
// FIXME #948112531345 (also see the other FIXMEs with this id)
131-
// Depositing current in ghost corners leads to false-positive gauss
132-
// errors. This could be avoided here by artificially constraining the
133-
// trajectories of injected particles. However, assuming the particle
134-
// boundary condition is "open", outflowing particles cause the same
135-
// problem, and there's nothing to be done about that.
136130
psc::particle::Inject prt =
137131
particle_generator_.get(cell_corner, grid.domain.dx);
138132

@@ -151,9 +145,6 @@ public:
151145
// Taken from push_particles_1vb.hxx PushParticlesVb::push_mprts()
152146

153147
Real3 initial_normalized_pos = initial_x * dxi;
154-
// pretend it came from the edge to inject proper current
155-
initial_normalized_pos[INJECT_DIM_IDX_] = -1.0;
156-
157148
Real3 final_normalized_pos = prt.x * dxi;
158149
Int3 final_idx = final_normalized_pos.fint();
159150

src/libpsc/psc_checks/checks_impl.hxx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,23 @@ public:
7171
auto item_divj = Item_divj<MfieldsState>{};
7272

7373
auto rho_p = psc::mflds::interior(grid, item_rho(mprts));
74-
auto divj = psc::mflds::interior(grid, item_divj(mflds));
7574
auto d_rho = rho_p - rho_m_;
75+
76+
auto divj = psc::mflds::interior(grid, item_divj(mflds));
77+
78+
// account for insertion/deletion of incoming/outgoing particles
79+
for (int p = 0; p < grid.n_patches(); p++) {
80+
for (int d = 0; d < 3; d++) {
81+
if (grid.atBoundaryLo(p, d) && grid.bc.fld_lo[d] == BND_FLD_OPEN) {
82+
Int3 r = grid.ldims;
83+
r[d] = 1;
84+
85+
divj.view(_s(0, r[0]), _s(0, r[1]), _s(0, r[2]), 0, p) =
86+
-d_rho.view(_s(0, r[0]), _s(0, r[1]), _s(0, r[2]), 0, p) / grid.dt;
87+
}
88+
}
89+
}
90+
7691
auto dt_divj = grid.dt * divj;
7792

7893
double local_err = gt::norm_linf(d_rho + dt_divj);
@@ -144,14 +159,12 @@ public:
144159
auto rho = psc::mflds::interior(grid, item_rho(mprts));
145160
auto dive = psc::mflds::interior(grid, item_dive(mflds));
146161

162+
// account for virtual charges implied by BCs
147163
for (int p = 0; p < grid.n_patches(); p++) {
148164
for (int d = 0; d < 3; d++) {
149165
if (grid.atBoundaryLo(p, d) &&
150166
(grid.bc.fld_lo[d] == BND_FLD_CONDUCTING_WALL ||
151167
grid.bc.fld_lo[d] == BND_FLD_OPEN)) {
152-
153-
// account for implicit surface charges
154-
155168
Int3 r = grid.ldims;
156169
r[d] = 1;
157170

src/libpsc/psc_push_fields/marder_impl.hxx

Lines changed: 58 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,6 @@ namespace psc
1717
namespace marder
1818
{
1919

20-
namespace detail
21-
{
22-
23-
inline void find_limits(const Grid_t& grid, int p, Int3& lx, Int3& rx, Int3& ly,
24-
Int3& ry, Int3& lz, Int3& rz)
25-
{
26-
Int3 l_cc = {0, 0, 0}, r_cc = {0, 0, 0};
27-
Int3 l_nc = {0, 0, 0}, r_nc = {0, 0, 0};
28-
for (int d = 0; d < 3; d++) {
29-
if (grid.bc.fld_lo[d] == BND_FLD_CONDUCTING_WALL &&
30-
grid.atBoundaryLo(p, d)) {
31-
l_cc[d] = -1;
32-
l_nc[d] = -1;
33-
}
34-
if (grid.bc.fld_hi[d] == BND_FLD_CONDUCTING_WALL &&
35-
grid.atBoundaryHi(p, d)) {
36-
r_cc[d] = -1;
37-
r_nc[d] = 0;
38-
}
39-
}
40-
// FIXME, for conducting wall the signs here need checking...
41-
lx = -Int3{l_cc[0], l_nc[1], l_nc[2]} + grid.ibn;
42-
rx = Int3{r_cc[0], r_nc[1], r_nc[2]} + grid.ldims + grid.ibn;
43-
44-
ly = -Int3{l_nc[0], l_cc[1], l_nc[2]} + grid.ibn;
45-
ry = Int3{r_nc[0], r_cc[1], r_nc[2]} + grid.ldims + grid.ibn;
46-
47-
lz = -Int3{l_nc[0], l_nc[1], l_cc[2]} + grid.ibn;
48-
rz = Int3{r_nc[0], r_nc[1], r_cc[2]} + grid.ldims + grid.ibn;
49-
}
50-
51-
} // namespace detail
52-
5320
// ----------------------------------------------------------------------
5421
// correct
5522
//
@@ -70,53 +37,46 @@ inline void correct(const Grid_t& grid, E1& efield, const Int3& efield_ib,
7037
Real3 fac = .5f * real_t(grid.dt) * diffusion * Real3(grid.domain.dx_inv);
7138

7239
for (int p = 0; p < grid.n_patches(); p++) {
73-
Int3 lx, rx, ly, ry, lz, rz;
74-
detail::find_limits(grid, p, lx, rx, ly, ry, lz, rz);
75-
76-
Int3 ls[3] = {lx, ly, lz};
77-
Int3 rs[3] = {rx, ry, rz};
78-
7940
auto res = mf.view(_all, _all, _all, 0, p);
8041
for (int d = 0; d < 3; d++) {
8142
if (grid.isInvar(d)) {
8243
continue;
8344
}
8445

85-
Int3 l = ls[d];
86-
Int3 r = rs[d];
8746
auto e_comp = efield.view(_all, _all, _all, d, p);
8847

89-
gt::gslice s1x = _s(l[0], r[0]);
90-
gt::gslice s1y = _s(l[1], r[1]);
91-
gt::gslice s1z = _s(l[2], r[2]);
92-
48+
Int3 l = grid.ibn;
49+
Int3 r = grid.ldims + grid.ibn;
50+
gt::gslice s1[3] = {_s(l[0], r[0]), _s(l[1], r[1]), _s(l[2], r[2])};
9351
gt::gslice s2[3] = {_s(l[0], r[0]), _s(l[1], r[1]), _s(l[2], r[2])};
94-
s2[d] = _s(l[d] + 1, r[d] + 1);
52+
s2[d].start += 1;
53+
s2[d].stop += 1;
9554

96-
e_comp.view(s1x, s1y, s1z) =
97-
e_comp.view(s1x, s1y, s1z) +
98-
(res.view(s2[0], s2[1], s2[2]) - res.view(s1x, s1y, s1z)) * fac[d];
55+
e_comp.view(s1[0], s1[1], s1[2]) =
56+
e_comp.view(s1[0], s1[1], s1[2]) +
57+
(res.view(s2[0], s2[1], s2[2]) - res.view(s1[0], s1[1], s1[2])) *
58+
fac[d];
9959
}
10060
}
10161
}
10262

10363
#ifdef USE_CUDA
10464

10565
template <typename E1, typename E2>
106-
inline void cuda_marder_correct_yz(E1& efield, E2& res, Float3 fac, Int3 ly,
107-
Int3 ry, Int3 lz, Int3 rz)
66+
inline void cuda_marder_correct_yz(E1& efield, E2& res, Float3 fac, Int3 l,
67+
Int3 r, Int3 l, Int3 r)
10868
{
10969
auto k_efield = efield.to_kernel();
11070
auto k_res = res.to_kernel();
11171
gt::launch<2>(
11272
{k_efield.shape(1), k_efield.shape(2)}, GT_LAMBDA(int iy, int iz) {
113-
if ((iy >= ly[1] && iy < ry[1]) && (iz >= ly[2] && iz < ry[2])) {
73+
if ((iy >= l[1] && iy < r[1]) && (iz >= l[2] && iz < r[2])) {
11474
k_efield(0, iy, iz, 1) =
11575
k_efield(0, iy, iz, 1) +
11676
fac[1] * (k_res(0, iy + 1, iz) - k_res(0, iy, iz));
11777
}
11878

119-
if ((iy >= lz[1] && iy < rz[1]) && (iz >= lz[2] && iz < rz[2])) {
79+
if ((iy >= l[1] && iy < r[1]) && (iz >= l[2] && iz < r[2])) {
12080
k_efield(0, iy, iz, 2) =
12181
k_efield(0, iy, iz, 2) +
12282
fac[2] * (k_res(0, iy, iz + 1) - k_res(0, iy, iz));
@@ -126,30 +86,30 @@ inline void cuda_marder_correct_yz(E1& efield, E2& res, Float3 fac, Int3 ly,
12686
}
12787

12888
template <typename E1, typename E2>
129-
inline void cuda_marder_correct_xyz(E1& efield, E2& res, Float3 fac, Int3 lx,
130-
Int3 rx, Int3 ly, Int3 ry, Int3 lz, Int3 rz)
89+
inline void cuda_marder_correct_xyz(E1& efield, E2& res, Float3 fac, Int3 l,
90+
Int3 r)
13191
{
13292
auto k_efield = efield.to_kernel();
13393
auto k_res = res.to_kernel();
13494
gt::launch<3>(
13595
{k_efield.shape(0), k_efield.shape(1), k_efield.shape(2)},
13696
GT_LAMBDA(int ix, int iy, int iz) {
137-
if ((ix >= lx[0] && ix < rx[0]) && (iy >= lx[1] && iy < rx[1]) &&
138-
(iz >= lx[2] && iz < rx[2])) {
97+
if ((ix >= l[0] && ix < r[0]) && (iy >= l[1] && iy < r[1]) &&
98+
(iz >= l[2] && iz < r[2])) {
13999
k_efield(ix, iy, iz, 0) =
140100
k_efield(ix, iy, iz, 0) +
141101
fac[0] * (k_res(ix, iy + 1, iz) - k_res(ix, iy, iz));
142102
}
143103

144-
if ((ix >= ly[0] && ix < ry[0]) && (iy >= ly[1] && iy < ry[1]) &&
145-
(iz >= ly[2] && iz < ry[2])) {
104+
if ((ix >= l[0] && ix < r[0]) && (iy >= l[1] && iy < r[1]) &&
105+
(iz >= l[2] && iz < r[2])) {
146106
k_efield(ix, iy, iz, 1) =
147107
k_efield(ix, iy, iz, 1) +
148108
fac[1] * (k_res(ix, iy + 1, iz) - k_res(ix, iy, iz));
149109
}
150110

151-
if ((ix >= lz[0] && ix < rz[0]) && (iy >= lz[1] && iy < rz[1]) &&
152-
(iz >= lz[2] && iz < rz[2])) {
111+
if ((ix >= l[0] && ix < r[0]) && (iy >= l[1] && iy < r[1]) &&
112+
(iz >= l[2] && iz < r[2])) {
153113
k_efield(ix, iy, iz, 2) =
154114
k_efield(ix, iy, iz, 2) +
155115
fac[2] * (k_res(ix, iy, iz + 1) - k_res(ix, iy, iz));
@@ -172,15 +132,15 @@ inline void correct(const Grid_t& grid, E1& efield, const Int3& efield_ib,
172132
assert(mf_ib == -grid.ibn);
173133
// OPT, do all patches in one kernel
174134
for (int p = 0; p < grid.n_patches(); p++) {
175-
Int3 lx, rx, ly, ry, lz, rz;
176-
detail::find_limits(grid, p, lx, rx, ly, ry, lz, rz);
135+
Int3 l = grid.ibn;
136+
Int3 r = grid.ibn + grid.ldims;
177137

178138
auto p_efield = efield.view(_all, _all, _all, _all, p);
179139
auto p_res = mf.view(_all, _all, _all, 0, p);
180140
if (grid.isInvar(0)) {
181-
cuda_marder_correct_yz(p_efield, p_res, fac, ly, ry, lz, rz);
141+
cuda_marder_correct_yz(p_efield, p_res, fac, l, r);
182142
} else {
183-
cuda_marder_correct_xyz(p_efield, p_res, fac, lx, rx, ly, ry, lz, rz);
143+
cuda_marder_correct_xyz(p_efield, p_res, fac, l, r);
184144
}
185145
}
186146
}
@@ -201,7 +161,7 @@ public:
201161
using Bnd = BND;
202162
using real_t = typename storage_type::value_type;
203163

204-
// FIXME: checkpointing won't properly restore state
164+
// FIXME: checkpointing won't properl restore state
205165

206166
MarderCommon(const Grid_t& grid, real_t diffusion, int loop, bool dump)
207167
: diffusion_{diffusion}, loop_{loop}, dump_{dump}
@@ -262,6 +222,38 @@ public:
262222
Int3 res_ib = -grid.ibn;
263223
auto res = storage_type{psc::mflds::make_shape(grid, 1, res_ib)};
264224
psc::mflds::interior(grid, res) = dive - rho;
225+
226+
// Gauss' law is ostensibly violated at some boundaries, where virtual
227+
// charges (i.e., charges that aren't associated with actual particles)
228+
// implicitly shape the electric field. To account for virtual charges,
229+
// simply set the error at those boundaries to 0.
230+
for (int p = 0; p < grid.n_patches(); p++) {
231+
for (int d = 0; d < 3; d++) {
232+
if ((grid.bc.fld_lo[d] == BND_FLD_CONDUCTING_WALL ||
233+
grid.bc.fld_lo[d] == BND_FLD_OPEN) &&
234+
grid.atBoundaryLo(p, d)) {
235+
236+
gt::gslice slices[3] = {_s(grid.ibn[0], -grid.ibn[0]),
237+
_s(grid.ibn[1], -grid.ibn[1]),
238+
_s(grid.ibn[2], -grid.ibn[2])};
239+
slices[d].stop = slices[d].start + 1;
240+
res.view(slices[0], slices[1], slices[2], 0, p) = 0.0;
241+
}
242+
243+
if ((grid.bc.fld_hi[d] == BND_FLD_CONDUCTING_WALL ||
244+
grid.bc.fld_hi[d] == BND_FLD_OPEN) &&
245+
grid.atBoundaryHi(p, d)) {
246+
gt::gslice slices[3] = {_s(grid.ibn[0], -grid.ibn[0]),
247+
_s(grid.ibn[1], -grid.ibn[1]),
248+
_s(grid.ibn[2], -grid.ibn[2])};
249+
// Note that upper edges are in the ghost region.
250+
slices[d].start = slices[d].stop;
251+
slices[d].stop += 1;
252+
res.view(slices[0], slices[1], slices[2], 0, p) = 0.0;
253+
}
254+
}
255+
}
256+
265257
bnd_.fill_ghosts(grid, res, res_ib, 0, 1);
266258

267259
print_progress(grid, rho, dive, res);

src/libpsc/psc_push_particles/push_particles_1vb.hxx

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,6 @@
22
// J. Villasenor and O. Buneman, "Rigorous charge conservation for local
33
// electromagnetic field solvers", Computer Physics Communications 69 (1992) 306
44

5-
namespace
6-
{
7-
/**
8-
* @brief "Pushes" an exiting particle to the outer edge of the first layer of
9-
* ghost cells. This should happen before current deposition, so the full
10-
* exiting current is deposited. Note that higher order particles would need to
11-
* be pushed deeper into the ghost region.
12-
*
13-
* Actually, this function works with the grid-normalized particle position, not
14-
* the particle itself (or even its actual position). The grid-normalized
15-
* position is what's actually used to deposit current, and the particle is
16-
* dropped later, so its position doesn't need to be updated.
17-
* @tparam Real `float` or `double`
18-
* @param final_x_normed the particle's grid-normalized position, which is
19-
* possibly mutated
20-
* @param final_i3 the particle's 3d cell index
21-
* @param grid the grid
22-
* @param p the patch index
23-
*/
24-
template <typename Real>
25-
void exit_to_edge(Vec3<Real>& final_x_normed, const Int3& final_i3,
26-
const Grid_t& grid, int p)
27-
{
28-
// FIXME #948112531345 (also see the other FIXMEs with this id)
29-
// Current deposited in ghost corners isn't sent to other patches, and
30-
// thus leads to false-positive gauss errors.
31-
// This is a general problem with non-periodic boundaries that have currents
32-
// in ghost cells (i.e., just open BCs as of now), not just this function.
33-
for (int d = 0; d < 3; d++) {
34-
if (grid.bc.prt_lo[d] == BND_PRT_OPEN && grid.atBoundaryLo(p, d)) {
35-
if (final_i3[d] < 0) {
36-
final_x_normed[d] = -1.0;
37-
}
38-
}
39-
40-
if (grid.bc.prt_hi[d] == BND_PRT_OPEN && grid.atBoundaryHi(p, d)) {
41-
if (final_i3[d] >= grid.ldims[d]) {
42-
final_x_normed[d] = grid.ldims[d] + 1.0;
43-
}
44-
}
45-
}
46-
}
47-
} // namespace
48-
495
// ======================================================================
506
// PushParticlesVb
517

@@ -110,8 +66,6 @@ struct PushParticlesVb
11066
Real3 final_pos_normalized = prt.x() * dxi;
11167
Int3 final_index = final_pos_normalized.fint();
11268

113-
exit_to_edge(final_pos_normalized, final_index, grid, p);
114-
11569
// CURRENT DENSITY BETWEEN (n+.5)*dt and (n+1.5)*dt
11670
Int3 initial_index;
11771
if (!Dim::InvarX::value) {

src/libpsc/tests/test_boundary_injector.cxx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,6 @@ struct ParticleGenerator
8989
uy = 0.0;
9090
}
9191

92-
// FIXME #948112531345 (also see the other FIXMEs with this id)
93-
// If x[2] != 0, the injected particle deposits some
94-
// current in the cells above its path. If one of those cells is in a ghost
95-
// corner, the current deposited in that cell IS NOT sent across patches.
96-
// That current is necessary to compute div E correctly. The run itself
97-
// should be fine; it only throws off the gauss check.
9892
Real3 x = min_pos + pos_range * Real3{0, .999, 0.};
9993
Real3 u{0.0, uy, 0.0};
10094
Real w = 1.0;

src/libpsc/tests/test_push_fields.cxx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ TYPED_TEST(PushFieldsTest, MarderCorrect)
158158

159159
auto efield = mflds.storage().view(_all, _all, _all, _s(EX, EX + 3), _all);
160160
auto efield_ib = mflds.ib();
161+
162+
ASSERT_GT(gt::norm_linf(psc::mflds::interior(grid, mflds.gt()) -
163+
psc::mflds::interior(grid, mflds_ref.gt())),
164+
1e-3);
165+
161166
psc::marder::correct(grid, efield, efield_ib, mphi.storage(), mphi.ib(),
162167
diffusion);
163168

0 commit comments

Comments
 (0)