ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERFPCParticleToMesh.H
Go to the documentation of this file.
1 /*! \file ERFPCParticleToMesh.H
2  * \brief Template implementation of ERFPC::ERFPCParticleToMesh
3  *
4  * Separated from ERFPC.H to keep the header lightweight.
5  * Include this in .cpp files that call ERFPCParticleToMesh().
6  */
7 
8 #ifndef ERFPC_PARTICLE_TO_MESH_H_
9 #define ERFPC_PARTICLE_TO_MESH_H_
10 
11 #include <ERFPC.H>
12 #include <ERF_TerrainConversion.H>
13 #include <AMReX_MultiFabUtil.H>
14 
15 template<typename ValueFunc>
16 void ERFPC::ERFPCParticleToMesh (amrex::MultiFab& a_mf,
17  const amrex::MultiFab& a_z_phys_nd,
18  int a_lev, int a_comp,
19  ValueFunc&& value_func,
20  bool composite) const
21 {
22  using namespace amrex;
23 
24  AMREX_ASSERT(OK());
25  AMREX_ASSERT(numParticlesOutOfRange(*this, 0) == 0);
26 
27  // CIC deposition writes one cell outside the particle's own cell
28  AMREX_ALWAYS_ASSERT(a_mf.nGrowVect().allGE(amrex::IntVect(1)));
29 
30  const auto& geom = Geom(a_lev);
31  const auto plo = geom.ProbLoArray();
32  const auto dxi = geom.InvCellSizeArray();
33  const Real dx = geom.CellSize(0);
34  const Real dy = geom.CellSize(1);
35  const Box domain = geom.Domain();
36  const int k_max = domain.bigEnd(AMREX_SPACEDIM-1);
37  const int i_lo = domain.smallEnd(0), i_hi = domain.bigEnd(0);
38  const int j_lo = domain.smallEnd(1), j_hi = domain.bigEnd(1);
39  const int k_lo = domain.smallEnd(AMREX_SPACEDIM-1);
40  const bool per_x = geom.isPeriodic(0);
41  const bool per_y = geom.isPeriodic(1);
42 
43  iMultiFab owned;
44  if (composite) {
45  owned.define(a_mf.boxArray(), a_mf.DistributionMap(), 1, IntVect(1));
46  owned.setVal(0);
47  if (a_lev < finestLevel()) {
48  iMultiFab exposed = makeFineMask(a_mf, ParticleBoxArray(a_lev+1),
49  GetParGDB()->refRatio(a_lev), 1, 0);
50  iMultiFab::Copy(owned, exposed, 0, 0, 1, 0);
51  } else {
52  owned.setVal(1, 0, 1, 0);
53  }
54  owned.FillBoundary(geom.periodicity());
55  }
56 
57  a_mf.setVal(0.0);
58 
59 #ifdef AMREX_USE_OMP
60 #pragma omp parallel if (Gpu::notInLaunchRegion())
61 #endif
62  for (ParConstIterType pti(*this, a_lev); pti.isValid(); ++pti)
63  {
64  const int grid = pti.index();
65  const auto& ptile = ParticlesAt(a_lev, pti);
66  const auto& aos = ptile.GetArrayOfStructs();
67  const int np = aos.numParticles();
68  if (np == 0) { continue; }
69 
70  auto rho = a_mf[grid].array();
71  auto zheight = a_z_phys_nd[grid].array();
72  const Array4<const int> valid = composite ? owned.const_array(grid) : Array4<const int>{};
73 
74  const auto ptd = ptile.getConstParticleTileData();
75 
76  ParallelFor(np, [=] AMREX_GPU_DEVICE (int i)
77  {
78  const auto& p = ptd.m_aos[i];
79  if (p.id() <= 0) { return; }
80 
81  const Real pval = static_cast<Real>(value_func(ptd, i));
82 
83  const Real lx = static_cast<Real>((p.pos(0) - plo[0]) * dxi[0]) + Real(0.5);
84  const int ix = static_cast<int>(Math::floor(lx)) - 1;
85  const Real wx1 = lx - static_cast<Real>(ix + 1);
86  const Real wx0 = Real(1.0) - wx1;
87 
88  const Real ly = static_cast<Real>((p.pos(1) - plo[1]) * dxi[1]) + Real(0.5);
89  const int iy = static_cast<int>(Math::floor(ly)) - 1;
90  const Real wy1 = ly - static_cast<Real>(iy + 1);
91  const Real wy0 = Real(1.0) - wy1;
92 
93  const int kz = static_cast<int>(amrex::Math::floor((p.pos(AMREX_SPACEDIM-1) - plo[AMREX_SPACEDIM-1])
94  * dxi[AMREX_SPACEDIM-1]));
95  const int ix_p = static_cast<int>(Math::floor((p.pos(0) - plo[0]) * dxi[0]));
96  const int iy_p = static_cast<int>(Math::floor((p.pos(1) - plo[1]) * dxi[1]));
97  const Real fx = static_cast<Real>((p.pos(0) - plo[0]) * dxi[0]) - static_cast<Real>(ix_p);
98  const Real fy = static_cast<Real>((p.pos(1) - plo[1]) * dxi[1]) - static_cast<Real>(iy_p);
99 
100  auto zface = [=] AMREX_GPU_DEVICE (int k_face) -> Real {
101  return zheight(ix_p , iy_p , k_face) * (Real(1.0)-fx) * (Real(1.0)-fy)
102  + zheight(ix_p+1, iy_p , k_face) * fx * (Real(1.0)-fy)
103  + zheight(ix_p , iy_p+1, k_face) * (Real(1.0)-fx) * fy
104  + zheight(ix_p+1, iy_p+1, k_face) * fx * fy;
105  };
106 
107  // Effective k bounds for safe zface access: must respect both the
108  // geometry domain (k_max) and the local fab's z extent. With AMR
109  // partial-z fabs (especially with many small boxes from large rank
110  // counts), a particle at the top/bottom of its fab can have kz
111  // such that kz+2 / kz-1 read outside the fab. Use the fallback
112  // (one-sided) span formula in those cases.
113  const int kz_hi_safe = amrex::min(k_max, zheight.end[2] - 2);
114  const int kz_lo_safe = amrex::max(0, zheight.begin[2] + 1);
115 
116  const Real z_lo_k = zface(kz);
117  const Real z_hi_k = zface(kz + 1);
118  const Real z_ctr_k = Real(0.5) * (z_lo_k + z_hi_k);
119 
120  // Particle pos(2) is computational zeta; convert to physical z so
121  // the cell-center comparison and weight span are in the same units
122  // as zface() (which is built from the height array).
123  const Real p_z = static_cast<Real>(ERF::ParticlePos::z_from_zeta(
124  p.pos(0), p.pos(1), p.pos(AMREX_SPACEDIM-1),
125  plo, dxi, zheight));
126 
127  int kz_lo, kz_hi;
128  Real wz_lo, wz_hi;
129  if (p_z >= z_ctr_k) {
130  kz_lo = kz;
131  kz_hi = kz + 1;
132  Real span;
133  if (kz < kz_hi_safe) {
134  const Real z_hi2 = zface(kz + 2);
135  const Real z_ctr_hi = Real(0.5) * (z_hi_k + z_hi2);
136  span = z_ctr_hi - z_ctr_k;
137  } else {
138  span = z_hi_k - z_lo_k;
139  }
140  wz_hi = (p_z - z_ctr_k) / span;
141  wz_lo = Real(1.0) - wz_hi;
142  } else {
143  kz_hi = kz;
144  kz_lo = kz - 1;
145  Real span;
146  if (kz > kz_lo_safe) {
147  const Real z_lo2 = zface(kz - 1);
148  const Real z_ctr_lo = Real(0.5) * (z_lo2 + z_lo_k);
149  span = z_ctr_k - z_ctr_lo;
150  } else {
151  span = z_hi_k - z_lo_k;
152  }
153  wz_lo = (z_ctr_k - p_z) / span;
154  wz_hi = Real(1.0) - wz_lo;
155  }
156 
157  // Per-corner node-averaged cell thickness for volume normalization.
158  auto dz_cell = [=] AMREX_GPU_DEVICE (int ic, int jc, int kc) -> Real {
159  const Real dz = Real(0.25) * (
160  zheight(ic , jc , kc+1) - zheight(ic , jc , kc)
161  + zheight(ic+1, jc , kc+1) - zheight(ic+1, jc , kc)
162  + zheight(ic , jc+1, kc+1) - zheight(ic , jc+1, kc)
163  + zheight(ic+1, jc+1, kc+1) - zheight(ic+1, jc+1, kc) );
164  return dz;
165  };
166 
167  const Real inv_dxdy = Real(1.0) / (dx * dy);
168 
169  // Restrict appended-species CIC to the owning level's composite
170  // cells. Renormalization preserves each particle's mass at walls
171  // and coarse/fine edges without creating another grid reservoir.
172  Real weight = Real(1);
173  if (composite) {
174  weight = Real(0);
175  for (int iz = 0; iz < 2; ++iz) {
176  const int kc = iz ? kz_hi : kz_lo;
177  const Real wz = iz ? wz_hi : wz_lo;
178  for (int jy = 0; jy < 2; ++jy) {
179  for (int jx = 0; jx < 2; ++jx) {
180  if (valid(ix+jx, iy+jy, kc)) {
181  weight += (jx ? wx1 : wx0) * (jy ? wy1 : wy0) * wz;
182  }
183  }
184  }
185  }
186  AMREX_ALWAYS_ASSERT(weight > Real(0));
187  }
188 
189  auto deposit = [=] AMREX_GPU_DEVICE (int ic, int jc, int kc, Real w_horiz, Real w_z)
190  {
191  // A corner with zero CIC weight deposits nothing. Skip it before
192  // touching dz_cell: when a particle sits exactly on a cell-center
193  // plane the outermost corner lands in the ghost region, where the
194  // node-averaged thickness can come out exactly zero (z_phys_nd
195  // ghost nodes outside the domain are not filled), and 0/0 raises
196  // an invalid-operation FPE.
197  if (w_horiz == Real(0.0) || w_z == Real(0.0)) { return; }
198  if (composite && !valid(ic,jc,kc)) { return; }
199 
200  // Clamp only for the dz lookup; still write to the ghost rho cell. In a
201  // periodic direction the exterior nodes hold the wrapped image, so the
202  // unclamped stencil already gives the thickness of the cell SumBoundary
203  // folds into; only the non-periodic exterior nodes need clamping.
204  const int ic_dz = per_x ? ic : amrex::max(i_lo, amrex::min(i_hi, ic));
205  const int jc_dz = per_y ? jc : amrex::max(j_lo, amrex::min(j_hi, jc));
206  const int kc_dz = amrex::max(k_lo, amrex::min(k_max, kc));
207  const Real dz = dz_cell(ic_dz, jc_dz, kc_dz);
208  AMREX_ALWAYS_ASSERT(dz > Real(0.0));
209  const Real contrib = pval * w_horiz * w_z * inv_dxdy / (dz * weight);
210  Gpu::Atomic::AddNoRet(&rho(ic, jc, kc, a_comp), contrib);
211  };
212 
213  deposit(ix , iy , kz_lo, wx0*wy0, wz_lo);
214  deposit(ix+1, iy , kz_lo, wx1*wy0, wz_lo);
215  deposit(ix , iy+1, kz_lo, wx0*wy1, wz_lo);
216  deposit(ix+1, iy+1, kz_lo, wx1*wy1, wz_lo);
217  deposit(ix , iy , kz_hi, wx0*wy0, wz_hi);
218  deposit(ix+1, iy , kz_hi, wx1*wy0, wz_hi);
219  deposit(ix , iy+1, kz_hi, wx0*wy1, wz_hi);
220  deposit(ix+1, iy+1, kz_hi, wx1*wy1, wz_hi);
221  });
222  }
223 
224  a_mf.SumBoundary(geom.periodicity());
225 }
226 
227 #endif
@ zface
Definition: ERF_EBStruct.H:29
const Real lx
Definition: ERF_InitCustomPertVels_CloudChamber.H:10
const Real ly
Definition: ERF_InitCustomPertVels_CloudChamber.H:11
const Real dy
Definition: ERF_InitCustomPert_ABL.H:45
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
ParallelFor(fab_box, [=] AMREX_GPU_DEVICE(int i, int j, int k) { qrcuten_arr(i, j, k)=Real(0);qscuten_arr(i, j, k)=Real(0);qicuten_arr(i, j, k)=Real(0);})
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real z_from_zeta(amrex::Real x, amrex::Real y, amrex::Real zeta, amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > const &plo, amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > const &dxi, amrex::Array4< amrex::Real const > const &height_arr) noexcept
Convert computational vertical coordinate zeta to physical z at column (x, y).
Definition: ERF_TerrainConversion.H:77
@ rho
Definition: ERF_Kessler.H:25
@ dz
Definition: ERF_AdvanceWDM6.cpp:273
@ p
Definition: ERF_WSM6.H:301
Definition: ERF_ConsoleIO.cpp:17