Cholla 3.0.1-dev
Cholla - Massively parallel hydro on GPUs
Loading...
Searching...
No Matches
stencil.h
1// this file defines stencils that are used within a prescription
2//
3// the goal here is to be somewhat modular to make it easy to support mutliple versions
4// of a perscription
5
6#pragma once
7
8#include <cstdint>
9
10#include "../feedback/feedback.h"
11#include "../global/global.h"
12#include "../utils/basic_structs.h"
13#include "../utils/math_utilities.h" // math_utils::clamp
14
15enum struct StencilEvalKind {
16 enclosed_stencil_vol_frac,
17 enclosed_cell_vol_frac,
18 for_each_overlap_zone
20};
21
22// maybe this should be called feedback_stencil
23namespace fb_stencil
24{
25
26/* helper function used to help implement stencils calculate the nearest location to (pos_x_indU, pos_y_indU,
27 * pos_z_indU) that the stencil's center can be shifted to in order to avoid overlapping with the ghost zone.
28 *
29 * If the specified location already does not overlap with the ghost zone, that is the returned
30 *
31 * \param min_stencil_offset The minimum distance a stencil must be from a cell-edge such that the stencil does not
32 * extend past the edge.
33 *
34 * \note
35 * It's okay for this to be a static function and live in a header since this header should only included in a single
36 * source file (2 if running unit-tests).
37 */
38static inline __device__ hydro_utilities::VectorXYZ<Real> nearest_noGhostOverlap_pos_(
39 Real min_stencil_offset, hydro_utilities::VectorXYZ<Real> pos_indU, int ng_x, int ng_y, int ng_z, int n_ghost)
40{
41 const Real edge_offset = n_ghost + min_stencil_offset;
42 return {math_utils::clamp(pos_indU[0], edge_offset, ng_x - edge_offset),
43 math_utils::clamp(pos_indU[1], edge_offset, ng_y - edge_offset),
44 math_utils::clamp(pos_indU[2], edge_offset, ng_z - edge_offset)};
45}
46
47/* Represents the stencil for cloud-in-cell deposition */
48struct CIC {
49 /* along any axis, gives the max number of neighboring cells that may be enclosed by the stencil,
50 * that are on one side of the cell containing the stencil's center.
51 *
52 * \note
53 * this primarily exists for testing purposes
54 */
55 inline static constexpr int max_enclosed_neighbors = 1;
56
57 /* excute f at each location included in the stencil centered at (pos_x_indU, pos_y_indU, pos_z_indU).
58 *
59 * The function should expect 2 arguments:
60 * 1. ``stencil_enclosed_frac``: the fraction of the stencil enclosed by the cell
61 * 2. ``indx3x``: the index used to index a 3D array (that has ghost zones)
62 */
63 template <typename Function>
64 static __device__ void for_each(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g, Function f)
65 {
66 // Step 1: along each axis, identify the integer-index of the leftmost cell covered by the stencil.
67 // - Consider the cell containing the stencil-center. If the stencil-center is at all to the left
68 // of that cell-center, then the stencil overlaps with the current cell and the one to the left
69 // - otherwise, the stencil covers the current cell and the one to the right
70 int leftmost_indx_x = int(pos_indU[0] - 0.5);
71 int leftmost_indx_y = int(pos_indU[1] - 0.5);
72 int leftmost_indx_z = int(pos_indU[2] - 0.5);
73
74 // Step 2: along each axis, compute the distance between the stencil-center of the leftmost cell
75 // - Recall that an integer index, ``indx``, specifies the position of the left edge of a cell.
76 // In other words the reference point of the cell is on the left edge.
77 // - The center of the cell specified by ``indx`` is actually ``indx+0.5``
78 Real delta_x = pos_indU[0] - (leftmost_indx_x + 0.5);
79 Real delta_y = pos_indU[1] - (leftmost_indx_y + 0.5);
80 Real delta_z = pos_indU[2] - (leftmost_indx_z + 0.5);
81
82 // Step 3: Actually invoke f at each cell-location that overlaps with the stencil location, passing both:
83 // 1. fraction of the total stencil volume enclosed by the given cell
84 // 2. the 1d index specifying cell-location (for a field with ghost zones)
85 //
86 // note: it's not exactly clear to me how we go from delta_x,delta_y,delta_z to volume-frac, (I just
87 // refactored the code I inherited and get consistent and sensible results)
88
89#define to_idx3D(i, j, k) ((leftmost_indx_x + (i)) + nx_g * ((leftmost_indx_y + (j)) + ny_g * (leftmost_indx_z + (k))))
90
91 f((1 - delta_x) * (1 - delta_y) * (1 - delta_z), to_idx3D(0, 0, 0)); // (i=0, j = 0, k = 0)
92 f((1 - delta_x) * (1 - delta_y) * delta_z, to_idx3D(0, 0, 1)); // (i=0, j = 0, k = 1)
93 f((1 - delta_x) * delta_y * (1 - delta_z), to_idx3D(0, 1, 0)); // (i=0, j = 1, k = 0)
94 f((1 - delta_x) * delta_y * delta_z, to_idx3D(0, 1, 1)); // (i=0, j = 1, k = 1)
95 f(delta_x * (1 - delta_y) * (1 - delta_z), to_idx3D(1, 0, 0)); // (i=1, j = 0, k = 0)
96 f(delta_x * (1 - delta_y) * delta_z, to_idx3D(1, 0, 1)); // (i=1, j = 0, k = 1)
97 f(delta_x * delta_y * (1 - delta_z), to_idx3D(1, 1, 0)); // (i=1, j = 1, k = 0)
98 f(delta_x * delta_y * delta_z, to_idx3D(1, 1, 1)); // (i=1, j = 1, k = 1)
99
100#undef to_idx3D
101 }
102
103 /* identical to for_each (provided for compatability with interfaces of other stencils). */
104 template <typename Function>
105 static __device__ void for_each_enclosedCellVol(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g,
106 Function f)
107 {
108 CIC::for_each(pos_indU, nx_g, ny_g, f);
109 }
110
112 // * the stencil.
113 // *
114 // * \note
115 // * This is intended to be conservative (it's okay for this to call the function on a cell with
116 // * non-zero overlap). The reason this exacts (rather than just calling for_each), is that it
117 // * it may be significantly cheaper for some stencils
118 // */
119 template <typename UnaryFunction>
120 static __device__ void for_each_overlap_zone(hydro_utilities::VectorXYZ<Real> pos_indU, int ng_x, int ng_y,
121 UnaryFunction f)
122 {
123 // this is a little crude!
124 CIC::for_each(pos_indU, ng_x, ng_y, [f](double stencil_enclosed_frac, int idx3D) {
125 if (stencil_enclosed_frac > 0) f(idx3D);
126 });
127 }
128
129 /* returns the nearest location to (pos_x_indU, pos_y_indU, pos_z_indU) that the stencil's center
130 * can be shifted to in order to avoid overlapping with the ghost zone.
131 *
132 * If the specified location already does not overlap with the ghost zone, that is the returned
133 * value.
134 */
135 static __device__ hydro_utilities::VectorXYZ<Real> nearest_noGhostOverlap_pos(
136 hydro_utilities::VectorXYZ<Real> pos_indU, int ng_x, int ng_y, int ng_z, int n_ghost)
137 {
138 const Real min_stencil_offset = 0.5;
139 return nearest_noGhostOverlap_pos_(min_stencil_offset, pos_indU, ng_x, ng_y, ng_z, n_ghost);
140 }
141};
142
143/* implements a stencil for depositing scalar quantities into a rectangular-prism region
144 * (each side-length centered on `pos_indU` that has a length of 2 cell-widths along each
145 * direction.
146 */
147template <typename Function, StencilEvalKind flavor>
148static __device__ void for_each_cic27_(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g, Function f)
149{
150 // this visits a 3x3x3 cells region
151
152 int leftmost_indx_x = int(pos_indU[0]) - 1;
153 int leftmost_indx_y = int(pos_indU[1]) - 1;
154 int leftmost_indx_z = int(pos_indU[2]) - 1;
155
156 // compute the distance between the left edge of the cell containing pos_indU and pos_indU
157 Real offset_x = pos_indU[0] - int(pos_indU[0]);
158 Real offset_y = pos_indU[1] - int(pos_indU[1]);
159 Real offset_z = pos_indU[2] - int(pos_indU[2]);
160
161 for (int i = 0; i < 3; i++) {
162 for (int j = 0; j < 3; j++) {
163 for (int k = 0; k < 3; k++) {
164 const int ind3D = (leftmost_indx_x + i) + nx_g * ((leftmost_indx_y + j) + ny_g * (leftmost_indx_z + k));
165
166 // compute the length (in units of cellwidths) of the stencil that overlaps with the current
167 // cell along each axis. Along the x-axis this is given by
168 // 1 - offset_x: when i == 0
169 // 1: when i == 1
170 // offset_x: when i == 2
171 Real x_len = (i < 2) + (i - 1) * offset_x;
172 Real y_len = (j < 2) + (j - 1) * offset_y;
173 Real z_len = (k < 2) + (k - 1) * offset_z;
174
175 // The volume enclosed by the current cell is
176 Real volEnclosed = x_len * y_len * z_len;
177
178 if constexpr (flavor == StencilEvalKind::enclosed_stencil_vol_frac) {
179 // the fraction of the stencil that is enclosed is volEnclosed/volStencil
180 // and volStencil is 4 cell_widths^3
181 f(0.25 * volEnclosed, ind3D);
182 } else if constexpr (flavor == StencilEvalKind::enclosed_cell_vol_frac) {
183 f(volEnclosed, ind3D);
184 } else if constexpr (flavor == StencilEvalKind::for_each_overlap_zone) {
185 if (volEnclosed > 0) f(ind3D);
186 }
187 }
188 }
189 }
190}
191
192// Define the legacy stencil previously used for feedback with momentum deposition
193//
194// I don't totally understand the logic (other than the fact that it uses 27-Cell CIC stencil).
195// It has some quirks. Including the fact that the amount of scalar deposition is directly related
196// to the magnitude of the vector.
197
205static inline __device__ Real Frac(int i, Real dx) { return (-0.5 * i * i - 0.5 * i + 1 + i * dx) * 0.5; }
206
207static inline __device__ Real D_Frac(int i, Real dx)
208{
209 // I believe this is a piecwise function that does the following:
210 // (i == -1, dx <= 0.5): -1.0
211 // (i == -1, dx > 0.5): -2*dx - 2
212 // (i == 0, any dx): 1 - 2*dx
213 // (i == 1, dx <= 0.5): -1.0
214 // (i == 1, dx > 0.5): 2*dx - 2
215 // - elif (i == 1): 2*dx + (1 - 2*dx)*(dx > 0.5)
216 return (dx > 0.5) * i * (1 - 2 * dx) + ((i + 1) * dx + 0.5 * (i - 1)) - 3 * (i - 1) * (i + 1) * (0.5 - dx);
217}
218
220 /* along any axis, gives the max number of neighboring cells that may be enclosed by the stencil,
221 * that are on one side of the cell containing the stencil's center.
222 *
223 * \note
224 * this primarily exists for testing purposes!
225 */
226 inline static constexpr int max_enclosed_neighbors = 1;
227
228 /* excute f at each location included in the stencil centered at (pos_x_indU, pos_y_indU, pos_z_indU).
229 *
230 * The function should expect 2 arguments:
231 * 1. ``stencil_enclosed_frac``: the fraction of the stencil enclosed by the cell
232 * 2. ``indx3x``: the index used to index a 3D array (that has ghost zones)
233 */
234 template <typename Function>
235 static __device__ void for_each(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g, Function &&f)
236 {
237 for_each_cic27_<Function, StencilEvalKind::enclosed_stencil_vol_frac>(pos_indU, nx_g, ny_g,
238 std::forward<Function>(f));
239 }
240
241 /* excute ``f`` at each location included in the stencil centered at (pos_x_indU, pos_y_indU, pos_z_indU).
242 *
243 * This is just like for_each, except that it passes the fraction of the cell-volume that is enclosed
244 * by the stencil to ``f`` (instead of passing fraction of the stencil-volume enclosed by the cell).
245 *
246 * \note
247 * This is primarily intended for testing purposes.
248 */
249 template <typename Function>
250 static __device__ void for_each_enclosedCellVol(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g,
251 Function &&f)
252 {
253 for_each_cic27_<Function, StencilEvalKind::enclosed_cell_vol_frac>(pos_indU, nx_g, ny_g, std::forward<Function>(f));
254 }
255
256 /* excute f at each location included in the stencil centered at (pos_x_indU, pos_y_indU, pos_z_indU).
257 *
258 * The function should expect 3 arguments (it's not totally clear to what the first 2 arguments truly "mean",
259 * but they are used similarly to the corresponding arguments passed by other kernels' for_each_vecflavor):
260 * 1. ``scalar_weight``: multiply this by the scalar to determine how much scalar to inject
261 * 2. ``vec_comp_factor``: multiply each by a momentumvelocity component to get the amount of momentum to inject.
262 * 2. ``indx3x``: the index used to index a 3D array (that has ghost zones)
263 */
264 template <typename Function>
265 static __device__ void for_each_vecflavor(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g, Function f)
266 {
267 const Real pos_x_indU = pos_indU[0];
268 const Real pos_y_indU = pos_indU[1];
269 const Real pos_z_indU = pos_indU[2];
270
271 int indx_x = (int)floor(pos_x_indU);
272 int indx_y = (int)floor(pos_y_indU);
273 int indx_z = (int)floor(pos_z_indU);
274
275 Real delta_x = pos_x_indU - indx_x;
276 Real delta_y = pos_y_indU - indx_y;
277 Real delta_z = pos_z_indU - indx_z;
278
279 // loop over the 27 cells to add up all the allocated feedback
280 // momentum magnitudes. For each cell allocate density and
281 // energy based on the ratio of allocated momentum to this overall sum.
282 Real mag = 0;
283 for (int i = -1; i < 2; i++) {
284 for (int j = -1; j < 2; j++) {
285 for (int k = -1; k < 2; k++) {
286 Real x_frac = D_Frac(i, delta_x) * Frac(j, delta_y) * Frac(k, delta_z);
287 Real y_frac = Frac(i, delta_x) * D_Frac(j, delta_y) * Frac(k, delta_z);
288 Real z_frac = Frac(i, delta_x) * Frac(j, delta_y) * D_Frac(k, delta_z);
289
290 mag += sqrt(x_frac * x_frac + y_frac * y_frac + z_frac * z_frac);
291 }
292 }
293 }
294
295 Real inv_mag = 1.0 / mag;
296
297 for (int i = -1; i < 2; i++) {
298 for (int j = -1; j < 2; j++) {
299 for (int k = -1; k < 2; k++) {
300 // index in array of conserved quantities
301 int indx = (indx_x + i) + (indx_y + j) * nx_g + (indx_z + k) * nx_g * ny_g;
302
303 Real x_frac = D_Frac(i, delta_x) * Frac(j, delta_y) * Frac(k, delta_z);
304 Real y_frac = Frac(i, delta_x) * D_Frac(j, delta_y) * Frac(k, delta_z);
305 Real z_frac = Frac(i, delta_x) * Frac(j, delta_y) * D_Frac(k, delta_z);
306 Real scalar_weight = sqrt(x_frac * x_frac + y_frac * y_frac + z_frac * z_frac) * inv_mag;
307 hydro_utilities::VectorXYZ<Real> momentum_weights{x_frac * inv_mag, y_frac * inv_mag, z_frac * inv_mag};
308
309 f(scalar_weight, momentum_weights, indx);
310
311 } // k loop
312 } // j loop
313 } // i loop
314 }
315
316 /* calls the unary function f at ever location where there probably is non-zero overlap with
317 * the stencil.
318 *
319 * \note
320 * This is intended to be conservative (it's okay for this to call the function on a cell with
321 * non-zero overlap). The reason this exacts (rather than just calling for_each), is that it
322 * it may be significantly cheaper for some stencils
323 */
324 template <typename UnaryFunction>
325 static __device__ void for_each_overlap_zone(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g,
326 UnaryFunction f)
327 {
328 for_each_cic27_<UnaryFunction, StencilEvalKind::for_each_overlap_zone>(pos_indU, nx_g, ny_g,
329 std::forward<UnaryFunction>(f));
330 }
331
332 /* returns the nearest location to (pos_x_indU, pos_y_indU, pos_z_indU) that the stencil's center
333 * can be shifted to in order to avoid overlapping with the ghost zone.
334 *
335 * If the specified location already does not overlap with the ghost zone, that is the returned
336 * value.
337 */
338 static __device__ hydro_utilities::VectorXYZ<Real> nearest_noGhostOverlap_pos(
339 hydro_utilities::VectorXYZ<Real> pos_indU, int ng_x, int ng_y, int ng_z, int n_ghost)
340 {
341 const Real min_stencil_offset = 1.0; // I think this is right, I'm a little fuzzy on the precised
342 return nearest_noGhostOverlap_pos_(min_stencil_offset, pos_indU, ng_x, ng_y, ng_z, n_ghost);
343 }
344};
345
346/* Represents a sphere. This is used to help implement stencils. */
347struct SphereObj {
348 // attributes
349 double center_indU[3];
353 // interface
354
355 /* queries whether the sphere encloses a given point */
356 __forceinline__ __device__ bool encloses_point(double pos_x_indU, double pos_y_indU, double pos_z_indU) const
357 {
358 double delta_x = pos_x_indU - center_indU[0];
359 double delta_y = pos_y_indU - center_indU[1];
360 double delta_z = pos_z_indU - center_indU[2];
361
362 return (delta_x * delta_x + delta_y * delta_y + delta_z * delta_z) < raidus2_indU;
363 }
364
365 /* queries whether the sphere encloses any super-sampled points within a cell that correspond to integer indices of
366 * (cell_idx_x, cell_idx_y, cell_idx_z).
367 *
368 * \tparam Log2DivsionsPerAx parameterizes the amount of super-sampling. There are ``2^Log2DivsionsPerAx``
369 * equidistant points along each axis of the algorithm. In other words, this can return a max value of
370 * ``2^(Log2DivsionsPerAx_PerCell*3)``.
371 */
372 template <int Log2DivsionsPerAx>
373 __device__ bool Encloses_Any_Supersample(int cell_idx_x, int cell_idx_y, int cell_idx_z) const
374 {
375 // compute some basic information for the algorithm
376 // - we employ ternary conditionals to avoid functions-calls/floating-point operations for the most
377 // common choice of Log2DivsionsPerAx
378 // - since Log2DivsionsPerAx is a template-arg, these branches should be compiled away
379 // - we could probably be a little more clever here
380 const int num_subdivisions_per_ax = (Log2DivsionsPerAx == 2) ? 4 : std::pow(2, Log2DivsionsPerAx);
381 const double subgrid_width = (Log2DivsionsPerAx == 2) ? 0.25 : 1.0 / num_subdivisions_per_ax;
382 const double leftmost_subgrid_offset = (Log2DivsionsPerAx == 2) ? 0.125 : 0.5 * subgrid_width;
383
384 // the following is mathematically equivalent to 1-leftmost_subgrid_offset, but we do the following to try to
385 // have consistent rounding with other supersampling calculations
386 const double rightmost_subgrid_offset = leftmost_subgrid_offset + ((num_subdivisions_per_ax - 1) * subgrid_width);
387
388 // IMPLICIT ASSUMPTION is that the radius is 1 cell-width or larger
389
390 double dx_left = center_indU[0] - (cell_idx_x + leftmost_subgrid_offset);
391 double dx_right = center_indU[0] - (cell_idx_x + rightmost_subgrid_offset);
392 double dy_left = center_indU[1] - (cell_idx_y + leftmost_subgrid_offset);
393 double dy_right = center_indU[1] - (cell_idx_y + rightmost_subgrid_offset);
394 double dz_left = center_indU[2] - (cell_idx_z + leftmost_subgrid_offset);
395 double dz_right = center_indU[2] - (cell_idx_z + rightmost_subgrid_offset);
396
397 double min_squared_dist =
398 (fmin(dx_left * dx_left, dx_right * dx_right) + fmin(dy_left * dy_left, dy_right * dy_right) +
399 fmin(dz_left * dz_left, dz_right * dz_right));
400 return min_squared_dist < raidus2_indU;
401 }
402
403 /* returns the count of the number of super-sampled points within a cell that correspond to integer indices of
404 * (cell_idx_x, cell_idx_y, cell_idx_z).
405 *
406 * \tparam Log2DivsionsPerAx parameterizes the amount of super-sampling. The super-sampling algorithm checks
407 * ``2^Log2DivsionsPerAx`` equidistant points along each axis of the algorithm. In other words, this
408 * can return a max value of ``2^(Log2DivsionsPerAx_PerCell*3)``.
409 *
410 * \note
411 * In the context of this function, integer indices specify the left edge of a cell. An integer index + 0.5
412 * specifies the center of a cell.
413 *
414 * \note
415 * None of the super-samples are placed on the edges of the cells.
416 */
417 template <int Log2DivsionsPerAx>
418 __device__ unsigned int Count_Super_Samples(int cell_idx_x, int cell_idx_y, int cell_idx_z) const
419 {
420 static_assert((0 <= Log2DivsionsPerAx) and ((Log2DivsionsPerAx * 3) <= (8 * sizeof(unsigned int))),
421 "Log2DivsionsPerAx must be a non-negative integer AND 2^(Log2DivsionsPerAx*3), the total "
422 "number of super-samples in a given cell, must be representable by an unsigned int");
423
424 // compute some basic information for the algorithm
425 // - we employ ternary conditionals to avoid functions-calls/floating-point operations for the most
426 // common choice of Log2DivsionsPerAx
427 // - since Log2DivsionsPerAx is a template-arg, these branches should be compiled away
428 // - we could probably be a little more clever here
429 const int num_subdivisions_per_ax = (Log2DivsionsPerAx == 2) ? 4 : std::pow(2, Log2DivsionsPerAx);
430 const double subgrid_width = (Log2DivsionsPerAx == 2) ? 0.25 : 1.0 / num_subdivisions_per_ax;
431 const double leftmost_subgrid_offset = (Log2DivsionsPerAx == 2) ? 0.125 : 0.5 * subgrid_width;
432
433 unsigned int count = 0;
434 for (int ix = 0; ix < num_subdivisions_per_ax; ix++) {
435 for (int iy = 0; iy < num_subdivisions_per_ax; iy++) {
436 for (int iz = 0; iz < num_subdivisions_per_ax; iz++) {
437 // since cell_idx_x, cell_idx_y, cell_idx_z are all integers, they specify
438 // the position of the left edge of the cell
439 double x = cell_idx_x + (leftmost_subgrid_offset + ix * subgrid_width);
440 double y = cell_idx_y + (leftmost_subgrid_offset + iy * subgrid_width);
441 double z = cell_idx_z + (leftmost_subgrid_offset + iz * subgrid_width);
442
443 count += encloses_point(x, y, z);
444 }
445 }
446 }
447
448 return count;
449 }
450
451 /* Estimates the volume integral over the overlapping region of a cell of the radial unit-vector measured from
452 * ``ref_pos_IndU``. Specifically, the cell corresponds to integer indices of (cell_idx_x, cell_idx_y, cell_idx_z)
453 * and the integral makes use of super-sampling.
454 *
455 * The result has units of subcell-volume. To convert it to units of cell-volume multiply by
456 * `pow(2,-3*Log2DivsionsPerAx)`
457 *
458 * In more detail, The evaluated integral looks like:
459 * \f[
460 * \hat{x}\int (\hat{x} \cdot\hat{r})\ dV_{\rm cell} + \hat{y}\int (\hat{y} \cdot\hat{r})\ dV_{\rm cell} +
461 * \hat{z}\int (\hat{z} \cdot\hat{r})\ dV_{\rm cell}
462 * \f]
463 * Where the bounds of the integral are understood to only include the portion of the region of the specified sphere
464 * that overlaps with the sphere. Additionally, \f$ \hat{r} \f$ is the radial unit vector measured after transforming
465 * the coordinate-system so that the origin coincides with the ``ref_pos_IndU`` argument.
466 *
467 * The calculation makes 2 assumptions:
468 * 1. We assume that subcells are either entirely enclosed by the sphere or aren't enclosed at all
469 * 2. Throughout a given subcell, \f$ \hat{r} \f$ is constant; it's equal to the value at the center of the subcell.
470 * (Note: it would be possible to avoid assumption. There is an exact analytic solution, it's just very
471 * involved).
472 *
473 * Under these assumptions the evaluated integral becomes:
474 * \f[
475 * V_{\rm subcell}\sum_{ijk}^{\rm subcells} \frac{W_{ijk} (x_i \hat{x} + y_j \hat{y} + z_k\hat{z})}{r_{ijk}}
476 * \f]
477 * where the subscripted variables are computed at the center of each subcell. \f$ W_{ijk} \f$ has a
478 * value of 1 for subcells whose centers lie within the sphere and are zero in other cases
479 *
480 * \tparam Log2DivsionsPerAx parameterizes the amount of super-sampling. The super-sampling algorithm checks
481 * ``2^Log2DivsionsPerAx`` equidistant points along each axis of the algorithm. In other words, this
482 * can return a max value of ``2^(Log2DivsionsPerAx_PerCell*3)``.
483 *
484 * \note
485 * In the context of this function, integer indices specify the left edge of a cell. An integer index + 0.5
486 * specifies the center of a cell.
487 *
488 * \note
489 * None of the super-samples are placed on the edges of the cells.
490 */
491 template <int Log2DivsionsPerAx>
492 __device__ hydro_utilities::VectorXYZ<Real> Super_Sampled_RadialUnitVec_VolIntegral(
493 int cell_idx_x, int cell_idx_y, int cell_idx_z, const hydro_utilities::VectorXYZ<Real> ref_pos_IndU) const
494 {
495 static_assert((0 <= Log2DivsionsPerAx) and ((Log2DivsionsPerAx * 3) <= (8 * sizeof(unsigned int))),
496 "Log2DivsionsPerAx must be a non-negative integer AND 2^(Log2DivsionsPerAx*3), the total "
497 "number of super-samples in a given cell, must be representable by an unsigned int");
498
499 // compute some basic information for the algorithm
500 // - we employ ternary conditionals to avoid functions-calls/floating-point operations for the most
501 // common choice of Log2DivsionsPerAx
502 // - since Log2DivsionsPerAx is a template-arg, these branches should be compiled away
503 // - we could probably be a little more clever here
504 const int num_subdivisions_per_ax = (Log2DivsionsPerAx == 2) ? 4 : std::pow(2, Log2DivsionsPerAx);
505 const double subgrid_width = (Log2DivsionsPerAx == 2) ? 0.25 : 1.0 / num_subdivisions_per_ax;
506 const double leftmost_subgrid_offset = (Log2DivsionsPerAx == 2) ? 0.125 : 0.5 * subgrid_width;
507
508 hydro_utilities::VectorXYZ<Real> out{0.0, 0.0, 0.0};
509
510 for (int ix = 0; ix < num_subdivisions_per_ax; ix++) {
511 for (int iy = 0; iy < num_subdivisions_per_ax; iy++) {
512 for (int iz = 0; iz < num_subdivisions_per_ax; iz++) {
513 // since cell_idx_x, cell_idx_y, cell_idx_z are all integers, they specify
514 // the position of the left edge of the cell
515
516 // compute the center of the subcell
517 const double orig_frame_x = cell_idx_x + (leftmost_subgrid_offset + ix * subgrid_width);
518 const double orig_frame_y = cell_idx_y + (leftmost_subgrid_offset + iy * subgrid_width);
519 const double orig_frame_z = cell_idx_z + (leftmost_subgrid_offset + iz * subgrid_width);
520
521 const bool subcell_enclosed_by_sphere = encloses_point(orig_frame_x, orig_frame_y, orig_frame_z);
522
523 // compute the x, y, and z components in the coordinate system that has been translated so that
524 // ref_pos_IndU coincides with the origin
525 const double x = orig_frame_x - ref_pos_IndU[0];
526 const double y = orig_frame_y - ref_pos_IndU[1];
527 const double z = orig_frame_z - ref_pos_IndU[2];
528
529 const bool coincides_with_origin = ((x == 0.0) && (y == 0.0) && (z == 0.0));
530
531 // for r = sqrt((x*x) + (y*y) + (z*z)), we need to compute x/r, y/r, z/r.
532 // - we add coincides_with_origin here to make sure we don't divide by zero if (x,y,z) = (0,0,0)
533 const double inv_r_mag = 1.0 / (coincides_with_origin + sqrt((x * x) + (y * y) + (z * z)));
534
535 out[0] += subcell_enclosed_by_sphere * x * inv_r_mag;
536 out[1] += subcell_enclosed_by_sphere * y * inv_r_mag;
537 out[2] += subcell_enclosed_by_sphere * z * inv_r_mag;
538 }
539 }
540 }
541
542 return out;
543 }
544};
545
546/* implements a stencil for depositing scalar quantities into a rectangular-prism region
547 * (each side-length centered on `pos_indU` that has a length of 2 cell-widths along each
548 * direction.
549 */
550template <typename Function, StencilEvalKind flavor, int CellsPerDiameter, int Log2DivsionsPerAx_PerCell>
551static __forceinline__ __device__ void for_each_sphere_(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g,
552 Function f)
553{
554 const SphereObj sphere{/* center = */ {pos_indU[0], pos_indU[1], pos_indU[2]},
555 /* squared_radius = */ 1 * 1};
556 // we are intentionally using integer division to speed up the next line
557 // NOLINTNEXTLINE(bugprone-integer-division)
558 const Real l_offset = ((CellsPerDiameter % 2) == 0) ? CellsPerDiameter / 2 : 0.5 * CellsPerDiameter;
559
560 // Step 1: along each axis, identify the integer-index of the leftmost cell covered by the stencil.
561 const int leftmost_indx_x = int(pos_indU[0] - l_offset);
562 const int leftmost_indx_y = int(pos_indU[1] - l_offset);
563 const int leftmost_indx_z = int(pos_indU[2] - l_offset);
564
565 // Step 2: get the number of super-samples within each of the 27 possible cells (This is not
566 // actually necessary for some stencil evaluation-flavors)
567
568 // Step 2a: declare variables used to accumulate the total count and to act as a cache
569 // for tracking the number of super-sample per cell
570 // -> we label these with [[maybe_unused]] attribute to suppress warnings for the flavors where
571 // these variables aren't used.
572 // -> for applicable "flavors", the compiler should optimize out the unusued variables.
573 // -> we want to keep the array-element size small for the cached_counts variable in order to
574 // reduce memory-pressure on the stack (especially since every thread will be allocating this
575 // much stack-space at the same time)
576 [[maybe_unused]] unsigned long total_count = 0;
577 [[maybe_unused]] uint_least16_t cached_counts[3][3][3];
578
579 // Step 2b: actually get the number of supersamples
580 if constexpr (flavor == StencilEvalKind::enclosed_stencil_vol_frac) {
581 for (int i = 0; i < 3; i++) {
582 for (int j = 0; j < 3; j++) {
583 for (int k = 0; k < 3; k++) {
584 unsigned int count = sphere.Count_Super_Samples<Log2DivsionsPerAx_PerCell>(
585 leftmost_indx_x + i, leftmost_indx_y + j, leftmost_indx_z + k);
586 cached_counts[i][j][k] = std::uint_least16_t(count);
587 total_count += count;
588 }
589 }
590 }
591 }
592
593 // kernel_printf("ref: %g, %g, %g\n", pos_indU[0], pos_indU[1], pos_indU[2]);
594
595 // Step 3: actually invoke f at each cell-location that overlaps with the stencil location
596 // (for flavors where we specify some kind of enclosed volume as a function argument,
597 // its okay to specify the function at a location without any overlap)
598 for (int i = 0; i < 3; i++) {
599 for (int j = 0; j < 3; j++) {
600 for (int k = 0; k < 3; k++) {
601 const int indx_x = leftmost_indx_x + i;
602 const int indx_y = leftmost_indx_y + j;
603 const int indx_z = leftmost_indx_z + k;
604 const int ind3D = indx_x + nx_g * (indx_y + ny_g * indx_z);
605
606 // kernel_printf("%d, %d, %d: %g\n", leftmost_indx_x + i, (leftmost_indx_y + j), (leftmost_indx_z + k),
607 // double(counts[i][j][k])/total_count);
608
609 if constexpr (flavor == StencilEvalKind::enclosed_stencil_vol_frac) {
610 // pass both of the following to the function
611 // 1. fraction of the total stencil volume enclosed by the given cell
612 // 2. the 1d index specifying cell-location (for a field with ghost zones)
613 f(double(cached_counts[i][j][k]) / total_count, ind3D);
614
615 } else if constexpr (flavor == StencilEvalKind::enclosed_cell_vol_frac) {
616 // pass both of the following to the function:
617 // 1. pass the fraction of the cell-volume that is enclosed by the stencil
618 // 2. the 1d index specifying cell-location
619
620 // it would nominally make more sense to precompute the following outside of this loop,
621 // but that's probably fine (after all, this branch is mostly for testing purposes)
622 double inverse_max_counts_per_cell = 1.0 / double(std::pow(2, Log2DivsionsPerAx_PerCell * 3));
623
624 // in this case, we have not precomputed the compute
625 unsigned int count = sphere.Count_Super_Samples<Log2DivsionsPerAx_PerCell>(indx_x, indx_y, indx_z);
626 f(count * inverse_max_counts_per_cell, ind3D);
627
628 } else if constexpr (flavor == StencilEvalKind::for_each_overlap_zone) {
629 bool is_enclosed = sphere.Encloses_Any_Supersample<Log2DivsionsPerAx_PerCell>(indx_x, indx_y, indx_z);
630 if (is_enclosed) f(ind3D);
631 }
632 }
633 }
634 }
635}
636
637/* Represents a 27-cell deposition stencil for a sphere with a radius of 1 cell-width. This stencil computes
638 * the fraction of the stencil that is enclosed in each cell. The overlap between the stencil and a given cell
639 * is computed via super-sampling.
640 *
641 * \tparam Log2DivsionsPerAx_PerCell parameterizes the amount of super-sampling. For a given cell, the super-sampling
642 * algorithm the number of subgrid-points enclosed by the stencil; there are ``2^Log2DivsionsPerAx_PerCell``
643 * sub-grid points along each axis. In other words, there are ``2^(Log2DivsionsPerAx_PerCell*3)`` subgrid-points
644 * per cell.
645 */
646template <int Log2DivsionsPerAx_PerCell = 2>
647struct Sphere27 {
648 static_assert((Log2DivsionsPerAx_PerCell >= 0) and (Log2DivsionsPerAx_PerCell <= 5),
649 "Log2DivsionsPerAx_PerCell must be a non-negative integer. It also can't exceed 5 "
650 "so that 2^(Log2DivsionsPerAx_PerCell*3) can be represented by uint16_t");
651
652 /* along any axis, gives the max number of neighboring cells that may be enclosed by the stencil,
653 * that are on one side of the cell containing the stencil's center.
654 *
655 * \note
656 * this primarily exists for testing purposes!
657 */
658 inline static constexpr int max_enclosed_neighbors = 1;
659
660 /* excute f at each location included in the stencil centered at (pos_x_indU, pos_y_indU, pos_z_indU).
661 *
662 * The function should expect 2 arguments:
663 * 1. ``stencil_enclosed_frac``: the fraction of the total stencil volume enclosed by the cell
664 * 2. ``indx3x``: the index used to index a 3D array (that has ghost zones)
665 */
666 template <typename Function>
667 static __device__ void for_each(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g, Function &&f)
668 {
669 for_each_sphere_<Function, StencilEvalKind::enclosed_stencil_vol_frac, 2, Log2DivsionsPerAx_PerCell>(
670 pos_indU, nx_g, ny_g, std::forward<Function>(f));
671 }
672
673 /* excute f at each location included in the stencil centered at (pos_x_indU, pos_y_indU, pos_z_indU).
674 *
675 * The function should expect 3 arguments:
676 * 1. ``stencil_enclosed_frac``: the fraction of the total stencil volume enclosed by the cell. In other
677 * words, its the volume of the cell enclosed by the stencil divided by the total stencil volume.
678 * 2. ``vec_comp_factor``: a `hydro_utilities::VectorXYZ<Real>` where the elements represent math-vector components
679 * (x, y, z). Essentially, this stores the volume integral (of the region enclosed by the stencil) over the
680 * radial-unit vector (originating from the stencil center) divided by a normalization constant.
681 * - The normalization constant is computed by taking the sum of each volume-integrated radial-unit
682 * vector computed at each cell enclosed by the stencil
683 * - The alternative would be to just normalize by the total volume. The problem with this alternative
684 * is if you are trying to inject a constant amount of radial momentum per unit-volume, then
685 * cancelation in the most-central cell may cause you to underinject momementum (primarily in the
686 * case where the stencil is near the center of a cell)
687 * 2. ``indx3x``: the index used to index a 3D array (that has ghost zones)
688 */
689 template <typename Function>
690 static __device__ void for_each_vecflavor(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g, Function f)
691 {
692 // Step 1: along each axis, identify the integer-index of the leftmost cell covered by the stencil.
693 const int leftmost_indx_x = int(pos_indU[0] - 1);
694 const int leftmost_indx_y = int(pos_indU[1] - 1);
695 const int leftmost_indx_z = int(pos_indU[2] - 1);
696
697 // Step 2: get the number of super-samples within each of the 27 possible cells
698 const SphereObj sphere{{pos_indU[0], pos_indU[1], pos_indU[2]}, 1 * 1};
699
700 // we intentionally keep the array-element size to reduce memory pressure on the stack (especially
701 // since every thread will be allocating this much stack-space at the same time)
702 // - If we weren't concerned about memory-pressure (e.g. we used cooperative_groups), we could
703 // save time and consolidate the calculation of integrated vector components and the enclosed
704 // volume into a single operation)
705 uint_least16_t cached_counts[3][3][3];
706
707 unsigned long total_count = 0;
708 Real vector_norm = 0.0;
709 for (int i = 0; i < 3; i++) {
710 for (int j = 0; j < 3; j++) {
711 for (int k = 0; k < 3; k++) {
712 unsigned int cur_count = sphere.Count_Super_Samples<Log2DivsionsPerAx_PerCell>(
713 leftmost_indx_x + i, leftmost_indx_y + j, leftmost_indx_z + k);
714 total_count += cur_count; // update total_count
715 cached_counts[i][j][k] = std::uint_least16_t(cur_count); // cache the value of
716
717 const hydro_utilities::VectorXYZ<Real> integrated_vec =
718 sphere.Super_Sampled_RadialUnitVec_VolIntegral<Log2DivsionsPerAx_PerCell>(
719 leftmost_indx_x + i, leftmost_indx_y + j, leftmost_indx_z + k, pos_indU);
720 vector_norm += norm3d(integrated_vec[0], integrated_vec[1], integrated_vec[2]);
721 // we don't cache the value of integrated_vec... That would put a LOT of strain on registers
722 }
723 }
724 }
725
726 const Real vec_factor = 1.0 / vector_norm;
727
728 // Step 3: actually invoke f at each cell-location that overlaps with the stencil location, passing both:
729 // 1. fraction of the total stencil volume enclosed by the given cell
730 // 2. the volume integral (of the region enclosed by the stencil) over the radial-unit vector (originating
731 // from the stencil center) divided by the total stencil volume
732 // 3. the 1d index specifying cell-location (for a field with ghost zones)
733 for (int i = 0; i < 3; i++) {
734 for (int j = 0; j < 3; j++) {
735 for (int k = 0; k < 3; k++) {
736 const int ind3D = (leftmost_indx_x + i) + nx_g * ((leftmost_indx_y + j) + ny_g * (leftmost_indx_z + k));
737
738 // kernel_printf("%d, %d, %d: %g\n", leftmost_indx_x + i, (leftmost_indx_y + j), (leftmost_indx_z + k),
739 // double(counts[i][j][k])/total_count);
740
741 // this has units of subcell volume. We need to divide it by total_count before passing it along
743 sphere.Super_Sampled_RadialUnitVec_VolIntegral<Log2DivsionsPerAx_PerCell>(
744 leftmost_indx_x + i, leftmost_indx_y + j, leftmost_indx_z + k, pos_indU);
745
746 f(double(cached_counts[i][j][k]) / total_count,
747 hydro_utilities::VectorXYZ<Real>{tmp[0] * vec_factor, tmp[1] * vec_factor, tmp[2] * vec_factor}, ind3D);
748 }
749 }
750 }
751 }
752
753 /* excute ``f`` at each location included in the stencil centered at (pos_x_indU, pos_y_indU, pos_z_indU).
754 *
755 * This is just like for_each, except that it passes the fraction of the cell-volume that is enclosed
756 * by the stencil to ``f`` (instead of passing fraction of the stencil-volume enclosed by the cell).
757 *
758 * \note
759 * This is primarily intended for testing purposes.
760 */
761 template <typename Function>
762 static __device__ void for_each_enclosedCellVol(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g,
763 Function f)
764 {
765 for_each_sphere_<Function, StencilEvalKind::enclosed_cell_vol_frac, 2, Log2DivsionsPerAx_PerCell>(
766 pos_indU, nx_g, ny_g, std::forward<Function>(f));
767 }
768
769 /* calls the unary function f at ever location where there probably is non-zero overlap with
770 * the stencil.
771 *
772 * \note
773 * This is is significantly cheaper than calling for_each.
774 */
775 template <typename UnaryFunction>
776 static __device__ void for_each_overlap_zone(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g,
777 UnaryFunction &&f)
778 {
779 for_each_sphere_<UnaryFunction, StencilEvalKind::for_each_overlap_zone, 2, Log2DivsionsPerAx_PerCell>(
780 pos_indU, nx_g, ny_g, std::forward<UnaryFunction>(f));
781 }
782
783 /* returns the nearest location to (pos_x_indU, pos_y_indU, pos_z_indU) that the stencil's center
784 * can be shifted to in order to avoid overlapping with the ghost zone.
785 *
786 * If the specified location already does not overlap with the ghost zone, that is the returned
787 * value.
788 */
789 static __device__ hydro_utilities::VectorXYZ<Real> nearest_noGhostOverlap_pos(
790 hydro_utilities::VectorXYZ<Real> pos_indU, int ng_x, int ng_y, int ng_z, int n_ghost)
791 {
792 // we actually provide an alternative more clever. technically, we just can't overlap with nearest
793 // super-sampled point inside of ghost zone. Any other amount of overlap is fair game!
794 constexpr Real min_stencil_offset = 1.0;
795 return nearest_noGhostOverlap_pos_(min_stencil_offset, pos_indU, ng_x, ng_y, ng_z, n_ghost);
796 }
797};
798
799/* Represents a spherical stencil with a radius of 3 cells, where the inclusion where inclusion of
800 * cells in the sphere is a binary choice.
801 *
802 * Specifically, a cell is included if the cell-center lies within the sphere.
803 */
804template <int CellsPerRadius = 3>
806 static_assert(CellsPerRadius > 0);
807
808 /* along any axis, gives the max number of neighboring cells that may be enclosed by the stencil,
809 * that are on one side of the cell containing the stencil's center.
810 *
811 * \note
812 * this primarily exists for testing purposes
813 */
814 inline static constexpr int max_enclosed_neighbors = CellsPerRadius;
815
816 template <typename Function>
817 static __device__ void for_each(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g, Function f)
818 {
819 // Step 1: along each axis, identify the integer-index of the leftmost cell covered by the stencil.
820 int leftmost_indx_x = int(pos_indU[0]) - CellsPerRadius;
821 int leftmost_indx_y = int(pos_indU[1]) - CellsPerRadius;
822 int leftmost_indx_z = int(pos_indU[2]) - CellsPerRadius;
823
824 // Step 2: get the number of cells enclosed by the sphere
825 const SphereObj sphere{{pos_indU[0], pos_indU[1], pos_indU[2]}, CellsPerRadius * CellsPerRadius};
826 int total_count = 0;
827
828 const int stop = (2 * CellsPerRadius) + 1;
829 for (int i = 0; i < stop; i++) {
830 for (int j = 0; j < stop; j++) {
831 for (int k = 0; k < stop; k++) {
832 total_count +=
833 sphere.encloses_point(leftmost_indx_x + i + 0.5, leftmost_indx_y + j + 0.5, leftmost_indx_z + k + 0.5);
834 }
835 }
836 }
837
838 double enclosed_stencil_frac = 1.0 / total_count; // each enclosed cell, encloses this fraction of the sphere
839
840 // Step 3: actually invoke f at each cell-location that overlaps with the stencil location, passing both:
841 // 1. fraction of the total stencil volume enclosed by the given cell
842 // 2. the 1d index specifying cell-location (for a field with ghost zones)
843 for (int i = 0; i < stop; i++) {
844 for (int j = 0; j < stop; j++) {
845 for (int k = 0; k < stop; k++) {
846 bool is_enclosed =
847 sphere.encloses_point(leftmost_indx_x + i + 0.5, leftmost_indx_y + j + 0.5, leftmost_indx_z + k + 0.5);
848 // kernel_printf("(%d, %d, %d), enclosed: %d\n", i,j,k, is_enclosed);
849 if (is_enclosed) {
850 const int ind3D = (leftmost_indx_x + i) + nx_g * ((leftmost_indx_y + j) + ny_g * (leftmost_indx_z + k));
851 f(enclosed_stencil_frac, ind3D);
852 }
853 }
854 }
855 }
856 }
857
858 /* excute ``f`` at each location included in the stencil centered at (pos_x_indU, pos_y_indU, pos_z_indU).
859 *
860 * This is just like for_each, except that it passes the fraction of the cell-volume that is enclosed
861 * by the stencil to ``f`` (instead of passing fraction of the stencil-volume enclosed by the cell).
862 *
863 * \note
864 * This is primarily intended for testing purposes.
865 */
866 template <typename Function>
867 static __device__ void for_each_enclosedCellVol(hydro_utilities::VectorXYZ<Real> pos_indU, int nx_g, int ny_g,
868 Function f)
869 {
870 // along each axis, identify the integer-index of the leftmost cell covered by the stencil.
871 int leftmost_indx_x = int(pos_indU[0]) - CellsPerRadius;
872 int leftmost_indx_y = int(pos_indU[1]) - CellsPerRadius;
873 int leftmost_indx_z = int(pos_indU[2]) - CellsPerRadius;
874
875 const SphereObj sphere{{pos_indU[0], pos_indU[1], pos_indU[2]}, CellsPerRadius * CellsPerRadius};
876
877 const int stop = (2 * CellsPerRadius) + 1;
878 for (int i = 0; i < stop; i++) {
879 for (int j = 0; j < stop; j++) {
880 for (int k = 0; k < stop; k++) {
881 bool is_enclosed =
882 sphere.encloses_point(leftmost_indx_x + i + 0.5, leftmost_indx_y + j + 0.5, leftmost_indx_z + k + 0.5);
883 double enclosed_cell_vol = (is_enclosed) ? 1.0 : 0.0; // could just cast is_enclosed
884 const int ind3D = (leftmost_indx_x + i) + nx_g * ((leftmost_indx_y + j) + ny_g * (leftmost_indx_z + k));
885 f(enclosed_cell_vol, ind3D);
886 }
887 }
888 }
889 }
890
891 template <typename UnaryFunction>
892 static __device__ void for_each_overlap_zone(hydro_utilities::VectorXYZ<Real> pos_indU, int ng_x, int ng_y,
893 UnaryFunction f)
894 {
895 // along each axis, identify the integer-index of the leftmost cell covered by the stencil.
896 int leftmost_indx_x = int(pos_indU[0]) - CellsPerRadius;
897 int leftmost_indx_y = int(pos_indU[1]) - CellsPerRadius;
898 int leftmost_indx_z = int(pos_indU[2]) - CellsPerRadius;
899
900 const SphereObj sphere{/* center = */ {pos_indU[0], pos_indU[1], pos_indU[2]},
901 /* squared_radius = */ CellsPerRadius * CellsPerRadius};
902
903 const int stop = (2 * CellsPerRadius) + 1;
904 for (int i = 0; i < stop; i++) {
905 for (int j = 0; j < stop; j++) {
906 for (int k = 0; k < stop; k++) {
907 const int indx_x = leftmost_indx_x + i;
908 const int indx_y = leftmost_indx_y + j;
909 const int indx_z = leftmost_indx_z + k;
910 const int ind3D = indx_x + ng_x * (indx_y + ng_y * indx_z);
911 bool is_enclosed =
912 sphere.encloses_point(leftmost_indx_x + i + 0.5, leftmost_indx_y + j + 0.5, leftmost_indx_z + k + 0.5);
913 if (is_enclosed) f(ind3D);
914 }
915 }
916 }
917 }
918
919 /* returns the nearest location to (pos_x_indU, pos_y_indU, pos_z_indU) that the stencil's center
920 * can be shifted to in order to avoid overlapping with the ghost zone.
921 *
922 * If the specified location already does not overlap with the ghost zone, that is the returned
923 * value.
924 */
925 static __device__ hydro_utilities::VectorXYZ<Real> nearest_noGhostOverlap_pos(
926 hydro_utilities::VectorXYZ<Real> pos_indU, int ng_x, int ng_y, int ng_z, int n_ghost)
927 {
928 // we actually provide an alternative more clever implementation. technically, we just can't overlap with the center
929 // of a cell in the ghost-zone. Any other amount of overlap is fair game!
930 constexpr Real min_stencil_offset = CellsPerRadius;
931 return nearest_noGhostOverlap_pos_(min_stencil_offset, pos_indU, ng_x, ng_y, ng_z, n_ghost);
932 }
933};
934
935} // namespace fb_stencil
__device__ __host__ T clamp(T val, T lo, T hi)
When val lies within the inclusive range [lo, hi], returns val. Otherwise, return the closest value i...
Definition math_utilities.h:144
Definition stencil.h:48
static __device__ void for_each_overlap_zone(hydro_utilities::VectorXYZ< Real > pos_indU, int ng_x, int ng_y, UnaryFunction f)
Definition stencil.h:120
Definition stencil.h:219
Definition stencil.h:647
Definition stencil.h:805
Definition stencil.h:347
int raidus2_indU
Definition stencil.h:351
double center_indU[3]
Definition stencil.h:349
A data only struct that acts as a simple 3 element vector.
Definition basic_structs.h:32