ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERFPC.H
Go to the documentation of this file.
1 #ifndef ERF_PC_H_
2 #define ERF_PC_H_
3 
4 #ifdef ERF_USE_PARTICLES
5 
6 #include <string>
7 #include <AMReX_Particles.H>
8 #include <ERF_Constants.H>
9 
10 struct ERFParticlesIntIdxAoS
11 {
12  enum {
13  k = 0,
14  ncomps
15  };
16 };
17 
18 struct ERFParticlesRealIdxAoS
19 {
20  enum {
21  ncomps = 0
22  };
23 };
24 
25 struct ERFParticlesIntIdxSoA
26 {
27  enum {
28  ncomps = 0
29  };
30 };
31 
32 struct ERFParticlesRealIdxSoA
33 {
34  enum {
35  vx = 0,
36  vy,
37  vz,
38  mass,
39  temperature,
40  ncomps
41  };
42 };
43 
44 namespace ERFParticleInitializations
45 {
46  /* list of particle initializations */
47  const std::string init_box_uniform = "box";
48 }
49 
50 namespace ERFParticleNames
51 {
52  const std::string tracers = "tracer_particles";
53 }
54 
55 struct ERFParticlesAssignor
56 {
57  template <typename P>
58  AMREX_GPU_HOST_DEVICE
59  amrex::IntVect operator() ( P const& p,
60  amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& plo,
61  amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& dxi,
62  const amrex::Box& domain ) const noexcept
63  {
64  amrex::IntVect iv(
65  AMREX_D_DECL( int(amrex::Math::floor((p.pos(0)-plo[0])*dxi[0])),
66  int(amrex::Math::floor((p.pos(1)-plo[1])*dxi[1])),
67  p.idata(ERFParticlesIntIdxAoS::k) ) );
68  iv[0] += domain.smallEnd()[0];
69  iv[1] += domain.smallEnd()[1];
70  iv[2] = amrex::max(domain.smallEnd()[2],
71  amrex::min(iv[2], domain.bigEnd()[2]));
72  return iv;
73  }
74 };
75 
76 /** Particle binner using ERFParticlesAssignor (k-index for z) for DenseBins. */
77 struct GetParticleBinERF
78 {
79  amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> plo;
80  amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> dxi;
81  amrex::Box domain;
82  amrex::IntVect bin_size;
83  amrex::Box box;
84 
85  template <typename P>
86  AMREX_GPU_HOST_DEVICE
87  unsigned int operator() (const P& p) const noexcept
88  {
89  amrex::Box tbx;
90  auto iv = ERFParticlesAssignor()(p, plo, dxi, domain);
91  auto tid = amrex::getTileIndex(iv, box, true, bin_size, tbx);
92  return static_cast<unsigned int>(tid);
93  }
94 };
95 
96 template <typename P>
97 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
98 void update_location_idata ( P& a_p,
99  amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& a_plo,
100  amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& a_dxi,
101  const amrex::Array4<amrex::Real const>& a_height_arr )
102 {
103  if (a_height_arr) {
104  // Compute horizontal indices from particle position
105  int ix = int(amrex::Math::floor((a_p.pos(0)-a_plo[0])*a_dxi[0]));
106  int iy = int(amrex::Math::floor((a_p.pos(1)-a_plo[1])*a_dxi[1]));
107 
108  // Check if horizontal indices are within the valid array bounds.
109  // We need ix, ix+1, iy, iy+1 to be valid for bilinear interpolation.
110  // If particle has moved outside the local tile region (after advection
111  // but before redistribution), skip the k-index update. The particle
112  // will be redistributed to the correct tile and can update then.
113  if (ix < a_height_arr.begin[0] || ix+1 >= a_height_arr.end[0] ||
114  iy < a_height_arr.begin[1] || iy+1 >= a_height_arr.end[1]) {
115  return;
116  }
117 
118  amrex::Real lx = (a_p.pos(0)-a_plo[0])*a_dxi[0] - static_cast<amrex::Real>(ix);
119  amrex::Real ly = (a_p.pos(1)-a_plo[1])*a_dxi[1] - static_cast<amrex::Real>(iy);
120 
121  // Clamp k to the valid range of the height array so the iteration
122  // can proceed even with partial-z AMR tiles.
123  int klo = a_height_arr.begin[2];
124  int khi = a_height_arr.end[2] - 2; // need iz+1 < end[2]
125  if (khi < klo) { return; }
126  int kcur = a_p.idata(ERFParticlesIntIdxAoS::k);
127  if (kcur < klo || kcur > khi) {
128  a_p.idata(ERFParticlesIntIdxAoS::k) = amrex::max(klo, amrex::min(kcur, khi));
129  }
130 
131  while (1) {
132  int iz = a_p.idata(ERFParticlesIntIdxAoS::k);
133 
134  if (iz < klo || iz > khi) {
135  break;
136  }
137 
138  auto zlo = a_height_arr(ix ,iy ,iz ) * (one-lx) * (one-ly) +
139  a_height_arr(ix+1,iy ,iz ) * lx * (one-ly) +
140  a_height_arr(ix ,iy+1,iz ) * (one-lx) * ly +
141  a_height_arr(ix+1,iy+1,iz ) * lx * ly;
142  auto zhi = a_height_arr(ix ,iy ,iz+1) * (one-lx) * (one-ly) +
143  a_height_arr(ix+1,iy ,iz+1) * lx * (one-ly) +
144  a_height_arr(ix ,iy+1,iz+1) * (one-lx) * ly +
145  a_height_arr(ix+1,iy+1,iz+1) * lx * ly;
146 
147  if (a_p.pos(2) > zhi) {
148  a_p.idata(ERFParticlesIntIdxAoS::k) += 1;
149  } else if (a_p.pos(2) <= zlo) {
150  a_p.idata(ERFParticlesIntIdxAoS::k) -= 1;
151  } else {
152  break;
153  }
154  }
155  }
156 }
157 
158 /*! \brief Compute vertical cell index k from particle z-position.
159  *
160  * Handles both uniform and non-uniform (stretched) vertical grids.
161  * For non-uniform grids, uses the level-0 z-levels array with the
162  * cumulative refinement ratio to compute fine-level cell boundaries.
163  * Starts from an initial guess and iterates (like update_location_idata).
164  *
165  * \param[in] z Particle z-position (physical coordinates)
166  * \param[in] plo_z Domain low z from Geometry
167  * \param[in] dxi_z Inverse cell size in z from Geometry
168  * \param[in] k_max Maximum valid k index (domain.bigEnd(2))
169  * \param[in] zlevels Level-0 z-level node positions (nullptr if uniform)
170  * \param[in] nz_lev0 Number of level-0 cells (nz_levels - 1)
171  * \param[in] ref_ratio Cumulative refinement ratio from level 0 to target level
172  * \return Cell index k in [0, k_max]
173  */
174 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
175 int compute_k_from_z (amrex::Real z,
176  amrex::Real plo_z,
177  amrex::Real dxi_z,
178  int k_max,
179  const amrex::Real* zlevels,
180  int nz_lev0,
181  int ref_ratio)
182 {
183  if (zlevels == nullptr || nz_lev0 <= 0) {
184  // Uniform grid: direct computation
185  int k = static_cast<int>(amrex::Math::floor((z - plo_z) * dxi_z));
186  return amrex::max(0, amrex::min(k, k_max));
187  }
188 
189  // Non-uniform z-levels: initial guess from uniform formula, then iterate.
190  // Fine-level cell boundary positions are linearly interpolated from
191  // the level-0 z-levels: for fine cell k_f, the coarse cell is k_f/R
192  // and the sub-cell position is (k_f%R)/R within that coarse cell.
193  int k = static_cast<int>(amrex::Math::floor((z - plo_z) * dxi_z));
194  k = amrex::max(0, amrex::min(k, k_max));
195 
196  // Helper: compute z-position of fine cell boundary k_f
197  // fine_z(k_f) = zlevels[k_f/R] + (k_f%R) * (zlevels[k_f/R+1] - zlevels[k_f/R]) / R
198  auto fine_z = [=] AMREX_GPU_HOST_DEVICE (int k_f) -> amrex::Real {
199  int kc = k_f / ref_ratio;
200  int sub = k_f % ref_ratio;
201  // kc is clamped to valid range for safety
202  kc = amrex::max(0, amrex::min(kc, nz_lev0));
203  if (kc >= nz_lev0) { return zlevels[nz_lev0]; }
204  return zlevels[kc]
205  + static_cast<amrex::Real>(sub)
206  * (zlevels[kc+1] - zlevels[kc])
207  / static_cast<amrex::Real>(ref_ratio);
208  };
209 
210  for (int iter = 0; iter <= k_max; iter++) {
211  amrex::Real z_lo = fine_z(k);
212  amrex::Real z_hi = fine_z(k + 1);
213  if (z >= z_hi && k < k_max) {
214  k++;
215  } else if (z < z_lo && k > 0) {
216  k--;
217  } else {
218  break;
219  }
220  }
221  return k;
222 }
223 
224 class ERFPC : public amrex::ParticleContainer< ERFParticlesRealIdxAoS::ncomps, // AoS real attributes
225  ERFParticlesIntIdxAoS::ncomps, // AoS integer attributes
226  ERFParticlesRealIdxSoA::ncomps, // SoA real attributes
227  ERFParticlesIntIdxSoA::ncomps, // SoA integer attributes
228  amrex::DefaultAllocator,
229  ERFParticlesAssignor >
230 {
231  public:
232 
233  /*! Constructor */
234  ERFPC ( amrex::ParGDBBase* a_gdb,
235  const std::string& a_name = "particles" )
236  : amrex::ParticleContainer< ERFParticlesRealIdxAoS::ncomps, // AoS real attributes
237  ERFParticlesIntIdxAoS::ncomps, // AoS integer attributes
238  ERFParticlesRealIdxSoA::ncomps, // SoA real attributes
239  ERFParticlesIntIdxSoA::ncomps, // SoA integer attributes
240  amrex::DefaultAllocator,
241  ERFParticlesAssignor> (a_gdb)
242  {
243  BL_PROFILE("ERFPCPC::ERFPC()");
244  m_name = a_name;
245  readInputs();
246  }
247 
248  /*! Constructor */
249  ERFPC ( const amrex::Geometry& a_geom,
250  const amrex::DistributionMapping& a_dmap,
251  const amrex::BoxArray& a_ba,
252  const std::string& a_name = "particles" )
253  : amrex::ParticleContainer< ERFParticlesRealIdxAoS::ncomps, // AoS real attributes
254  ERFParticlesIntIdxAoS::ncomps, // AoS real attributes
255  ERFParticlesRealIdxSoA::ncomps, // SoA real attributes
256  ERFParticlesIntIdxSoA::ncomps, // SoA integer attributes
257  amrex::DefaultAllocator,
258  ERFParticlesAssignor> ( a_geom, a_dmap, a_ba )
259  {
260  BL_PROFILE("ERFPCPC::ERFPC()");
261  m_name = a_name;
262  readInputs();
263  }
264 
265  /*! Initialize particles in domain */
266  virtual void InitializeParticles (const amrex::Real time, const std::unique_ptr<amrex::MultiFab>& a_ptr = nullptr);
267 
268  /*! Evolve particles for one time step */
269  virtual void EvolveParticles ( int,
270  amrex::Real,
271  amrex::Vector<amrex::Vector<amrex::MultiFab>>&,
272  const amrex::Vector<std::unique_ptr<amrex::MultiFab>>& );
273 
274  /*! Get real-type particle attribute names */
275  virtual amrex::Vector<std::string> varNames () const
276  {
277  BL_PROFILE("ERFPCPC::varNames()");
278  return {AMREX_D_DECL("xvel","yvel","zvel"),"mass","temperature"};
279  }
280 
281  /*! Get real-type particle attribute names */
282  virtual amrex::Vector<std::string> meshPlotVarNames () const
283  {
284  BL_PROFILE("ERFPCPC::varNames()");
285  return {"mass_density"};
286  }
287 
288  /*! Uses midpoint method to advance particles using flow velocity. */
289  virtual void AdvectWithFlow ( amrex::MultiFab*,
290  int,
291  amrex::Real,
292  const std::unique_ptr<amrex::MultiFab>& );
293 
294  /*! Uses midpoint method to advance particles falling under gravity. */
295  virtual void AdvectWithGravity ( int,
296  amrex::Real,
297  const std::unique_ptr<amrex::MultiFab>& );
298 
299  /*! Interpolates flow temperature to particles */
300  virtual void ComputeTemperature ( const amrex::MultiFab&,
301  int,
302  amrex::Real,
303  const std::unique_ptr<amrex::MultiFab>& );
304 
305  /*! Compute mass density */
306  virtual void massDensity ( amrex::MultiFab&, const int&, const int& a_comp = 0) const;
307 
308  /*! Compute mesh variable from particles */
309  virtual void computeMeshVar( const std::string& a_var_name,
310  amrex::MultiFab& a_mf,
311  const int a_lev) const
312  {
313  if (a_var_name == "mass_density") {
314  massDensity( a_mf, a_lev );
315  } else {
316  a_mf.setVal(0.0);
317  }
318  }
319 
320  /*! Specify if particles should advect with flow */
321  inline void setAdvectWithFlow (bool a_flag)
322  {
323  BL_PROFILE("ERFPCPC::setAdvectWithFlow()");
324  m_advect_w_flow = a_flag;
325  }
326  /*! Specify if particles fall under gravity */
327  inline void setAdvectWithGravity (bool a_flag)
328  {
329  BL_PROFILE("ERFPCPC::setAdvectWithGravity()");
330  m_advect_w_gravity = a_flag;
331  }
332 
333  // the following functions should ideally be private or protected, but need to be
334  // public due to CUDA extended lambda capture rules
335 
336  /*! Default particle initialization */
337  void initializeParticlesUniformDistributionInBox (const std::unique_ptr<amrex::MultiFab>& a_ptr,
338  const amrex::RealBox& particle_box);
339 
340  /*! Fix k-indices for all particles after AMR regrid/removal */
341  void FixKIndexAMR (const amrex::Vector<std::unique_ptr<amrex::MultiFab>>& a_z_phys_nd);
342 
343  /*! Extract and route out-of-range particles to proper levels */
344  void ExtractAndRouteOORParticles ( int a_lev,
345  const amrex::Vector<std::unique_ptr<amrex::MultiFab>>& a_z_phys_nd );
346 
347  /*! Diagnostic: count particles per level and halo (for AMR) */
348  void CountParticlesPerLevelAndHalo ( int a_finest_level );
349 
350  protected:
351 
352  bool m_advect_w_flow; /*!< advect with flow velocity */
353  bool m_advect_w_gravity; /*!< advect under gravitational force */
354 
355  amrex::RealBox m_particle_box; /*!< box within which to place particles */
356 
357  std::string m_name; /*!< name of this particle species */
358 
359  std::string m_initialization_type; /*!< initial particle distribution type */
360  int m_ppc_init; /*!< initial number of particles per cell */
361 
362  amrex::Real m_inject_start_time; /*!< particles will only be initialized/injected if time >= m_inject_start_time */
363 
364  bool m_initialized = false; /*!< have the particles been initialized */
365 
366  bool m_stable_redistribute; /*!< use stable redistribute for deterministic simulations */
367 
368  amrex::Gpu::DeviceVector<amrex::Real> m_zlevels_d; /*!< Level-0 z-coordinate node positions for non-uniform vertical grids */
369 
370  /*! read inputs from file */
371  virtual void readInputs ();
372 
373  private:
374 
375  bool place_randomly_in_cells; /*!< place particles at random positions? */
376 };
377 
378 #endif
379 #endif
constexpr amrex::Real one
Definition: ERF_Constants.H:7
const int khi
Definition: ERF_InitCustomPert_Bubble.H:21
Real * p
Definition: ERF_InitCustomPert_SquallLine.H:61
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ P
Definition: ERF_IndexDefines.H:148
Definition: ERF_ConsoleIO.cpp:12