ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_CloudChamber.H
Go to the documentation of this file.
1 #ifndef ERF_CLOUD_CHAMBER_H_
2 #define ERF_CLOUD_CHAMBER_H_
3 
4 #include <AMReX_Array.H>
5 #include <AMReX_ParmParse.H>
6 #include <AMReX_REAL.H>
7 
8 #include "ERF_Constants.H"
9 #include "ERF_DataStruct.H"
10 #include "ERF_EOS.H"
11 #include "ERF_MicrophysicsUtils.H"
13 
14 #include <cmath>
15 #include <limits>
16 #include <string>
17 #include <vector>
18 
19 namespace erf_cloud_chamber {
20 
21 /**
22  * Cloud Chamber wall metadata. This is deliberately a small, trivially
23  * copyable description: it can be captured by an AMReX device kernel without
24  * virtual dispatch. The per-channel contract supports resolved molecular,
25  * fixed bulk-aerodynamic, and neutral roughness-log transfer, with MOST
26  * coefficient generation on the supported horizontal faces.
27  */
28 enum class WallFace : unsigned char { XLo, XHi, YLo, YHi, ZLo, ZHi };
29 
30 /**
31  * @brief Metadata describing a chamber wall.
32  */
33 struct WallSpec {
36 };
37 
38 /**
39  * @brief Configuration parameters for cloud chamber initialization.
40  */
41 struct Config {
42  bool active = false;
43  bool has_moisture = false;
49  amrex::Real theta_bottom = amrex::Real(0.0); // legacy_theta_qv only
50  amrex::Real theta_top = amrex::Real(0.0); // legacy_theta_qv only
52  amrex::Real qv_bottom = amrex::Real(0.0); // legacy_theta_qv only
53  amrex::Real qv_top = amrex::Real(0.0); // legacy_theta_qv only
54  amrex::GpuArray<amrex::Real, AMREX_SPACEDIM> prob_lo = {};
55  amrex::GpuArray<amrex::Real, AMREX_SPACEDIM> prob_hi = {};
56  amrex::GpuArray<WallSpec, 2 * AMREX_SPACEDIM> walls = {};
57 
58  /**
59  * @brief Collect thermodynamic boundary conditions for all chamber walls.
60  * @return Boundary array containing thermodynamics for each face.
61  */
63  {
65  for (int n = 0; n < 2 * AMREX_SPACEDIM; ++n) {
66  boundary[n] = walls[n].wall;
67  }
68  return boundary;
69  }
70 
71  bool has_bulk_scalar_wall () const noexcept
72  {
73  for (const auto& wall : walls) {
74  if (wall.wall.heat.model == erf_wall_thermodynamics::ScalarModel::BulkAero ||
75  (wall.wall.vapor.model == erf_wall_thermodynamics::ScalarModel::BulkAero &&
76  wall.wall.moisture ==
78  return true;
79  }
80  }
81  return false;
82  }
83 
84  bool has_wall_rate_channel () const noexcept
85  {
86  for (const auto& wall : walls) {
87  if (wall.wall.momentum.model ==
89  wall.wall.momentum.model ==
91  wall.wall.heat.model ==
93  wall.wall.heat.model ==
95  (wall.wall.vapor.model ==
97  wall.wall.moisture ==
99  (wall.wall.vapor.model ==
101  wall.wall.moisture ==
103  return true;
104  }
105  }
106  return false;
107  }
108 };
109 
110 enum class InitializationMode : unsigned char {
113 };
114 
115 /**
116  * @brief Contract used to validate initialization parameter consistency.
117  */
120  // True for any active moisture model, including noncondensing transport.
121  bool has_moisture = false;
125 };
126 
127 /**
128  * @brief Validate the initialization contract and return an error message if violated.
129  * @param[in] contract The contract to validate.
130  * @return Error message string, empty if valid.
131  */
132 inline std::string
134 {
135  switch (contract.mode) {
137  if (contract.has_moisture) {
138  if (!contract.has_initial_relative_humidity) {
139  return "Cloud Chamber: physical moist initialization requires prob.initial_relative_humidity";
140  }
141  } else if (contract.has_initial_relative_humidity) {
142  return "Cloud Chamber: prob.initial_relative_humidity requires an active moisture model";
143  }
144  if (contract.has_legacy_profile_keys) {
145  return "Cloud Chamber: physical_temperature_rh cannot be combined with legacy theta/qv profile keys";
146  }
147  break;
149  if (contract.has_physical_profile_keys) {
150  return "Cloud Chamber: legacy_theta_qv cannot be combined with physical temperature/RH profile keys";
151  }
152  break;
153  }
154 
155  return {};
156 }
157 
158 /**
159  * @brief Return whether a moisture type is supported by the Cloud Chamber.
160  *
161  * This is intentionally an allowlist. In particular, particle-based
162  * SuperDroplets and spectral SBM remain outside the chamber's supported
163  * moisture configurations even though they are valid ERF moisture types.
164  */
165 inline bool
166 supports_moisture_model (const MoistureType moisture_type) noexcept
167 {
168  switch (moisture_type) {
169  case MoistureType::None:
170  case MoistureType::SatAdj:
171  case MoistureType::MoistNoCondensation:
172  case MoistureType::Kessler_NoRain:
173  case MoistureType::Kessler:
174  case MoistureType::SAM_NoPrecip_NoIce:
175  case MoistureType::SAM_NoIce:
176  case MoistureType::SAM:
177  case MoistureType::WSM6:
178  case MoistureType::WDM6:
179  case MoistureType::Morrison_NoIce:
180  case MoistureType::Morrison:
181  return true;
182  case MoistureType::SBM:
183  case MoistureType::SuperDroplets:
184  return false;
185  }
186  return false;
187 }
188 
189 /**
190  * @brief Compute a linear profile between two values.
191  * @param[in] bottom Value at the lower boundary.
192  * @param[in] top Value at the upper boundary.
193  * @param[in] coordinate Current coordinate.
194  * @param[in] lower Lower boundary coordinate.
195  * @param[in] length Length of the domain.
196  * @return Interpolated value.
197  */
198 AMREX_FORCE_INLINE
199 AMREX_GPU_HOST_DEVICE
202  amrex::Real coordinate, amrex::Real lower,
203  amrex::Real length) noexcept
204 {
205  return bottom + (top - bottom) * (coordinate - lower) / length;
206 }
207 
208 /**
209  * @brief Compute a deterministic 3D sine perturbation.
210  * @param[in] x x-coordinate.
211  * @param[in] y y-coordinate.
212  * @param[in] z z-coordinate.
213  * @param[in] lo Lower boundary coordinates.
214  * @param[in] length Domain lengths.
215  * @param[in] amplitude Perturbation amplitude.
216  * @return Perturbation value at the given coordinates.
217  */
218 AMREX_FORCE_INLINE
219 AMREX_GPU_HOST_DEVICE
222  const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>& lo,
223  const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>& length,
224  amrex::Real amplitude) noexcept
225 {
226  constexpr amrex::Real pi = amrex::Real(3.1415926535897932384626433832795);
227  const amrex::Real sx = std::sin(amrex::Real(2.0) * pi * (x - lo[0]) / length[0]);
228  const amrex::Real sy = std::sin(amrex::Real(2.0) * pi * (y - lo[1]) / length[1]);
229  const amrex::Real sz = std::sin(pi * (z - lo[2]) / length[2]);
230  return amplitude * sx * sy * sz;
231 }
232 
233 /**
234  * @brief Discrete derivative ratio for the staggered initial velocity field.
235  *
236  * The ratio makes the finite-volume x/y divergence cancel on rectangular
237  * grids. A one-cell horizontal direction has an identically zero sine
238  * pattern, so the perturbation is zero rather than dividing by a zero
239  * discrete derivative factor.
240  */
241 AMREX_FORCE_INLINE
242 AMREX_GPU_HOST_DEVICE
246  int nx, int ny) noexcept
247 {
248  if (nx <= 1 || ny <= 1) { return amrex::Real(0.0); }
249  if (lx == ly && dx == dy) { return amrex::Real(1.0); }
250  constexpr amrex::Real pi = amrex::Real(3.1415926535897932384626433832795);
251  const amrex::Real kx_d = std::sin(pi * dx / lx) / dx;
252  const amrex::Real ky_d = std::sin(pi * dy / ly) / dy;
253  return ky_d == amrex::Real(0.0) ? amrex::Real(0.0) : kx_d / ky_d;
254 }
255 
256 /**
257  * @brief Evaluate the x-face component of the Cloud Chamber initial velocity.
258  */
259 AMREX_FORCE_INLINE
260 AMREX_GPU_HOST_DEVICE
264  const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>& lo,
265  const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>& length,
266  amrex::Real U_0, int nx, int ny) noexcept
267 {
268  if (nx <= 1 || ny <= 1) { return amrex::Real(0.0); }
269  constexpr amrex::Real pi = amrex::Real(3.1415926535897932384626433832795);
270  const amrex::Real sx = std::sin(pi * (x - lo[0]) / length[0]);
271  const amrex::Real sz = std::sin(pi * (z - lo[2]) / length[2]);
272  return U_0 * sx * sx *
273  std::sin(amrex::Real(2.0) * pi * (y - lo[1]) / length[1]) * sz * sz;
274 }
275 
276 /**
277  * @brief Evaluate the y-face component of the Cloud Chamber initial velocity.
278  */
279 AMREX_FORCE_INLINE
280 AMREX_GPU_HOST_DEVICE
284  const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>& lo,
285  const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>& length,
286  amrex::Real U_0, amrex::Real v_scale, int nx, int ny) noexcept
287 {
288  if (nx <= 1 || ny <= 1) { return amrex::Real(0.0); }
289  constexpr amrex::Real pi = amrex::Real(3.1415926535897932384626433832795);
290  const amrex::Real sy = std::sin(pi * (y - lo[1]) / length[1]);
291  const amrex::Real sz = std::sin(pi * (z - lo[2]) / length[2]);
292  return -U_0 * v_scale *
293  std::sin(amrex::Real(2.0) * pi * (x - lo[0]) / length[0]) *
294  sy * sy * sz * sz;
295 }
296 
297 /**
298  * @brief The Cloud Chamber initial vertical velocity perturbation.
299  */
300 AMREX_FORCE_INLINE
301 AMREX_GPU_HOST_DEVICE
304 {
305  return amrex::Real(0.0);
306 }
307 
308 /**
309  * @brief Compute the potential temperature at a given location.
310  * @param[in] config Configuration parameters.
311  * @param[in] x x-coordinate.
312  * @param[in] y y-coordinate.
313  * @param[in] z z-coordinate.
314  * @return Potential temperature at the specified location.
315  */
316 AMREX_FORCE_INLINE
317 AMREX_GPU_HOST_DEVICE
320  amrex::Real z) noexcept
321 {
322  const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM> length = {
323  config.prob_hi[0] - config.prob_lo[0],
324  config.prob_hi[1] - config.prob_lo[1],
325  config.prob_hi[2] - config.prob_lo[2]};
326  const amrex::Real bottom = config.physical_initialization ?
327  config.initial_temperature_bottom : config.theta_bottom;
328  const amrex::Real top = config.physical_initialization ?
329  config.initial_temperature_top : config.theta_top;
330  const amrex::Real amplitude = config.physical_initialization ?
331  config.temperature_perturbation_amplitude : config.theta_perturbation_amplitude;
332  return linear_profile(bottom, top, z,
333  config.prob_lo[2], length[2]) +
335  amplitude);
336 }
337 
338 /**
339  * @brief Compute the water vapor mixing ratio at a given height.
340  * @param[in] config Configuration parameters.
341  * @param[in] z Vertical coordinate.
342  * @return Vapor mixing ratio at the specified height.
343  */
344 AMREX_FORCE_INLINE
345 AMREX_GPU_HOST_DEVICE
347 qv_at (const Config& config, amrex::Real z) noexcept
348 {
349  return linear_profile(config.qv_bottom, config.qv_top, z,
350  config.prob_lo[2],
351  config.prob_hi[2] - config.prob_lo[2]);
352 }
353 
354 /**
355  * @brief Convert relative humidity to water vapor mixing ratio.
356  * @param[in] temperature_K Temperature in Kelvin.
357  * @param[in] pressure_Pa Pressure in Pascals.
358  * @param[in] relative_humidity Relative humidity [0,1].
359  * @return Water vapor mixing ratio.
360  */
361 AMREX_FORCE_INLINE
362 AMREX_GPU_HOST_DEVICE
365  amrex::Real pressure_Pa,
366  amrex::Real relative_humidity) noexcept
367 {
368  // erf_esatw returns hPa; convert saturation and vapor pressure to Pa before
369  // applying the dry-air vapor mixing-ratio definition.
370  const amrex::Real es_Pa = erf_esatw(temperature_K) * amrex::Real(100.0);
371  const amrex::Real ev_Pa = relative_humidity * es_Pa;
372  return RdoRv * ev_Pa / (pressure_Pa - ev_Pa);
373 }
374 
375 inline bool finite (amrex::Real value) noexcept
376 {
377  return std::isfinite(static_cast<double>(value));
378 }
379 
380 inline void require_finite (const std::string& key, amrex::Real value)
381 {
382  if (!finite(value)) {
383  amrex::Error("Cloud Chamber: " + key + " must be finite");
384  }
385 }
386 
387 inline std::string face_name (WallFace face)
388 {
389  switch (face) {
390  case WallFace::XLo: return "xlo";
391  case WallFace::XHi: return "xhi";
392  case WallFace::YLo: return "ylo";
393  case WallFace::YHi: return "yhi";
394  case WallFace::ZLo: return "zlo";
395  case WallFace::ZHi: return "zhi";
396  }
397  return "unknown";
398 }
399 
403  bool heat_model_specified = false;
404  bool vapor_model_specified = false;
409  bool z0_m_specified = false;
410  bool z0_h_specified = false;
411  bool z0_q_specified = false;
412  std::string momentum_model;
413  std::string heat_model;
414  std::string vapor_model;
415  std::string coefficient_source;
422 };
423 
424 inline std::string
426  const std::string& face)
427 {
428  const bool momentum_neutral = contract.momentum_model_specified &&
429  (contract.momentum_model == "neutral_roughness_log" ||
430  contract.momentum_model == "law_of_wall_momentum");
431  const bool momentum_bulk = contract.momentum_model_specified &&
432  contract.momentum_model == "bulk_aero";
433  const bool heat_neutral = contract.heat_model_specified &&
434  contract.heat_model == "neutral_roughness_log";
435  const bool vapor_neutral = contract.vapor_model_specified &&
436  contract.vapor_model == "neutral_roughness_log";
437  const bool any_neutral =
438  momentum_neutral || heat_neutral || vapor_neutral;
439 
440  const bool any_channel_key =
441  contract.momentum_model_specified ||
442  contract.heat_model_specified ||
443  contract.vapor_model_specified ||
444  contract.coefficient_source_specified ||
445  contract.heat_coefficient_specified ||
446  contract.vapor_coefficient_specified ||
448  contract.z0_m_specified ||
449  contract.z0_h_specified ||
450  contract.z0_q_specified;
451  if (contract.legacy_aggregate_specified && any_channel_key) {
452  return "Cloud Chamber: " + face +
453  " cannot combine wall_transfer_model with per-channel wall model keys";
454  }
455 
456  if (contract.momentum_model_specified &&
457  contract.momentum_model != "resolved_noslip" &&
458  !momentum_bulk && !momentum_neutral) {
459  return "Cloud Chamber: " + face +
460  ".momentum_transfer_model must be resolved_noslip, bulk_aero, "
461  "neutral_roughness_log, or law_of_wall_momentum";
462  }
463 
464  const bool heat_bulk = contract.heat_model_specified &&
465  contract.heat_model == "bulk_aero";
466  const bool vapor_bulk = contract.vapor_model_specified &&
467  contract.vapor_model == "bulk_aero";
468  if (contract.heat_model_specified &&
469  contract.heat_model != "resolved_molecular" && !heat_bulk && !heat_neutral) {
470  return "Cloud Chamber: " + face +
471  ".heat_transfer_model must be resolved_molecular, bulk_aero, or neutral_roughness_log";
472  }
473  if (contract.vapor_model_specified &&
474  contract.vapor_model != "resolved_molecular" && !vapor_bulk && !vapor_neutral) {
475  return "Cloud Chamber: " + face +
476  ".vapor_transfer_model must be resolved_molecular, bulk_aero, or neutral_roughness_log";
477  }
478 
479  const bool bulk_active = heat_bulk || vapor_bulk || momentum_bulk;
480  const bool most_provider = contract.coefficient_source_specified &&
481  contract.coefficient_source == "most";
482  if (most_provider && !(face == "zlo" || face == "zhi")) {
483  return "Cloud Chamber: " + face +
484  " supports coefficient_source = most only on zlo or zhi";
485  }
486  if ((any_neutral || (bulk_active && most_provider)) &&
487  !contract.z0_m_specified) {
488  return "Cloud Chamber: " + face +
489  " requires z0_m for the active neutral/MOST wall-transfer channels";
490  }
491  if ((heat_neutral || (heat_bulk && most_provider)) &&
492  !contract.z0_h_specified) {
493  return "Cloud Chamber: " + face +
494  " requires z0_h for neutral roughness-log heat or MOST heat";
495  }
496  if ((vapor_neutral || (vapor_bulk && most_provider)) &&
497  !contract.z0_q_specified) {
498  return "Cloud Chamber: " + face +
499  " requires z0_q for neutral roughness-log vapor or MOST vapor";
500  }
501  if (contract.z0_m_specified &&
502  !(any_neutral || (bulk_active && most_provider))) {
503  return "Cloud Chamber: " + face +
504  ".z0_m requires a neutral_roughness_log or MOST channel";
505  }
506  if (contract.z0_h_specified &&
507  !(heat_neutral || (heat_bulk && most_provider))) {
508  return "Cloud Chamber: " + face +
509  ".z0_h requires neutral_roughness_log heat or MOST heat";
510  }
511  if (contract.z0_q_specified &&
512  !(vapor_neutral || (vapor_bulk && most_provider))) {
513  return "Cloud Chamber: " + face +
514  ".z0_q requires neutral_roughness_log vapor or MOST vapor";
515  }
516  if ((contract.z0_m_specified &&
517  (!finite(contract.z0_m) || contract.z0_m <= amrex::Real(0.0))) ||
518  (contract.z0_h_specified &&
519  (!finite(contract.z0_h) || contract.z0_h <= amrex::Real(0.0))) ||
520  (contract.z0_q_specified &&
521  (!finite(contract.z0_q) || contract.z0_q <= amrex::Real(0.0)))) {
522  return "Cloud Chamber: " + face +
523  " active roughness lengths z0_m, z0_h, and z0_q must be finite and positive";
524  }
525 
526  if (bulk_active) {
527  if (!contract.coefficient_source_specified ||
528  (contract.coefficient_source != "fixed" &&
529  contract.coefficient_source != "most")) {
530  return "Cloud Chamber: " + face +
531  " active bulk_aero channels require coefficient_source = fixed or most";
532  }
533  } else if (contract.coefficient_source_specified) {
534  return "Cloud Chamber: " + face +
535  ".coefficient_source is only valid when bulk_aero is active";
536  }
537 
538  if (momentum_bulk && !most_provider &&
539  !contract.momentum_coefficient_specified) {
540  return "Cloud Chamber: " + face +
541  " requires C_D when momentum_transfer_model = bulk_aero with coefficient_source = fixed";
542  }
543  if (momentum_bulk && most_provider && contract.momentum_coefficient_specified) {
544  return "Cloud Chamber: " + face +
545  ".C_D is not valid with coefficient_source = most";
546  }
547  if (heat_bulk && !most_provider && !contract.heat_coefficient_specified) {
548  return "Cloud Chamber: " + face +
549  " requires C_H when heat_transfer_model = bulk_aero";
550  }
551  if (heat_bulk && most_provider && contract.heat_coefficient_specified) {
552  return "Cloud Chamber: " + face +
553  ".C_H is not valid with coefficient_source = most";
554  }
555  if (vapor_bulk && !most_provider && !contract.vapor_coefficient_specified) {
556  return "Cloud Chamber: " + face +
557  " requires C_E when vapor_transfer_model = bulk_aero";
558  }
559  if (vapor_bulk && most_provider && contract.vapor_coefficient_specified) {
560  return "Cloud Chamber: " + face +
561  ".C_E is not valid with coefficient_source = most";
562  }
563  if (contract.momentum_coefficient_specified && !momentum_bulk) {
564  return "Cloud Chamber: " + face +
565  ".C_D requires momentum_transfer_model = bulk_aero";
566  }
567  if (contract.heat_coefficient_specified && !heat_bulk) {
568  return "Cloud Chamber: " + face +
569  ".C_H requires heat_transfer_model = bulk_aero";
570  }
571  if (contract.vapor_coefficient_specified && !vapor_bulk) {
572  return "Cloud Chamber: " + face +
573  ".C_E requires vapor_transfer_model = bulk_aero";
574  }
575  if ((contract.momentum_coefficient_specified &&
576  (!finite(contract.momentum_coefficient) ||
577  contract.momentum_coefficient < amrex::Real(0.0))) ||
578  (contract.heat_coefficient_specified &&
579  (!finite(contract.heat_coefficient) ||
580  contract.heat_coefficient < amrex::Real(0.0))) ||
581  (contract.vapor_coefficient_specified &&
582  (!finite(contract.vapor_coefficient) ||
583  contract.vapor_coefficient < amrex::Real(0.0)))) {
584  return "Cloud Chamber: " + face +
585  " bulk coefficients C_D, C_H, and C_E must be finite and nonnegative";
586  }
587  return {};
588 }
589 
590 inline std::string
592  const std::string& face,
593  bool wet_wall)
594 {
595  const bool heat_most = contract.heat_model_specified &&
596  contract.heat_model == "bulk_aero" &&
597  contract.coefficient_source_specified &&
598  contract.coefficient_source == "most";
599  const bool vapor_most = contract.vapor_model_specified &&
600  contract.vapor_model == "bulk_aero" &&
601  contract.coefficient_source_specified &&
602  contract.coefficient_source == "most";
603  if (wet_wall && heat_most && vapor_most &&
604  contract.z0_h != contract.z0_q) {
605  return "Cloud Chamber: " + face +
606  " coupled heat/vapor MOST requires z0_h == z0_q because the "
607  "prescribed-temperature bulk-Richardson closure uses one scalar "
608  "similarity resistance; z0_h=" +
609  std::to_string(static_cast<double>(contract.z0_h)) +
610  ", z0_q=" +
611  std::to_string(static_cast<double>(contract.z0_q));
612  }
613  return {};
614 }
615 
617 {
618  amrex::ParmParse pp_geom("geometry");
619  std::vector<int> periodic;
620  pp_geom.getarr("is_periodic", periodic);
621  if (periodic.size() != AMREX_SPACEDIM) {
622  amrex::Error("Cloud Chamber: geometry.is_periodic must provide three values");
623  }
624  for (int d = 0; d < AMREX_SPACEDIM; ++d) {
625  if (periodic[d] != 0) {
626  amrex::Error("Cloud Chamber: geometry.is_periodic must be 0 0 0");
627  }
628  }
629 }
630 
632 {
633  amrex::ParmParse pp_erf("erf");
634  std::string value;
635  if (pp_erf.query("terrain_type", value) &&
636  amrex::toLower(value) != "none") {
637  amrex::Error("Cloud Chamber: erf.terrain_type must be None; terrain and embedded boundaries are out of scope");
638  }
639  value.clear();
640  if (pp_erf.query("buildings_type", value) &&
641  amrex::toLower(value) != "none") {
642  amrex::Error("Cloud Chamber: erf.buildings_type must be None; immersed buildings are out of scope");
643  }
644  value.clear();
645  if (pp_erf.query("mesh_type", value) &&
646  amrex::toLower(value) != "constantdz") {
647  amrex::Error("Cloud Chamber: erf.mesh_type must be ConstantDz");
648  }
649  // A stretched or listed vertical grid makes the mesh StretchedDz without erf.mesh_type
650  if (SolverChoice::mesh_type != MeshType::ConstantDz) {
651  amrex::Error("Cloud Chamber: the vertical grid must be uniform (no erf.grid_stretching_ratio, "
652  "erf.initial_dz or erf.terrain_z_levels)");
653  }
654 }
655 
656 inline void reject_unsupported_face_keys (amrex::ParmParse& pp,
657  const std::string& face)
658 {
659  if (pp.contains("physical_temperature") || pp.contains("relative_humidity") ||
660  pp.contains("rh") || pp.contains("roughness") ||
661  pp.contains("roughness_length") || pp.contains("z0") ||
662  pp.contains("most") || pp.contains("MOST")) {
663  amrex::Error("Cloud Chamber: " + face +
664  " must use temperature and moisture for physical walls; "
665  "roughness aliases and legacy aliases are unsupported");
666  }
667 }
668 
669 inline std::string
672  const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>& dx)
673 {
674  for (int n = 0; n < 2 * AMREX_SPACEDIM; ++n) {
675  const auto& wall = walls[n];
676  const int dir = n / 2;
677  const amrex::Real z_ref = amrex::Real(0.5) * dx[dir];
678  const std::string face = face_name(static_cast<WallFace>(n));
679  const auto check = [&](bool active, const char* key,
680  amrex::Real z0) -> std::string {
681  if (!active) { return {}; }
682  if (!finite(z_ref) || !finite(z0) ||
683  !(z0 > amrex::Real(0.0)) || !(z_ref > z0)) {
684  return "Cloud Chamber: " + face + " " + key +
685  " requires 0 < " + key + " < z_ref; " + key + "=" +
686  std::to_string(static_cast<double>(z0)) +
687  ", z_ref=" +
688  std::to_string(static_cast<double>(z_ref));
689  }
690  return {};
691  };
692  const bool momentum_neutral =
693  wall.momentum.model ==
695  const bool heat_neutral =
696  wall.heat.model ==
698  const bool vapor_neutral =
699  wall.vapor.model ==
701  const bool any_neutral =
702  momentum_neutral || heat_neutral || vapor_neutral;
703  const bool most_momentum =
704  wall.momentum.model == erf_wall_thermodynamics::MomentumModel::BulkAero &&
705  wall.momentum.provider == erf_wall_thermodynamics::CoefficientProvider::MOST;
706  const bool most_heat =
709  const bool most_vapor =
712  const auto momentum_error = check(
713  any_neutral || most_momentum || most_heat || most_vapor,
714  "z0_m", wall.momentum.z0_m);
715  if (!momentum_error.empty()) { return momentum_error; }
716  const auto heat_error = check(
717  heat_neutral || most_heat,
718  "z0_h", wall.heat.z0);
719  if (!heat_error.empty()) { return heat_error; }
720  const auto vapor_error = check(
721  vapor_neutral || most_vapor,
722  "z0_q", wall.vapor.z0);
723  if (!vapor_error.empty()) { return vapor_error; }
724  }
725  return {};
726 }
727 
728 inline void
731  const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>& dx)
732 {
733  const auto error = wall_roughness_geometry_error(walls, dx);
734  if (!error.empty()) { amrex::Error(error); }
735 }
736 
738  const amrex::Real* probhi,
739  const MoistureType selected_moisture_model)
740 {
741  Config config;
742  config.active = true;
743  for (int d = 0; d < AMREX_SPACEDIM; ++d) {
744  config.prob_lo[d] = problo[d];
745  config.prob_hi[d] = probhi[d];
746  require_finite("geometry extent", config.prob_lo[d]);
747  require_finite("geometry extent", config.prob_hi[d]);
748  if (!(config.prob_hi[d] > config.prob_lo[d])) {
749  amrex::Error("Cloud Chamber: geometry.prob_hi must exceed geometry.prob_lo in every direction");
750  }
751  }
752 
753  amrex::ParmParse pp_erf("erf");
754  int anelastic = 0;
755  if (!pp_erf.query("anelastic", anelastic) || anelastic != 1) {
756  amrex::Error("Cloud Chamber: erf.anelastic = 1 is required");
757  }
758  bool use_gravity = false;
759  if (!pp_erf.query("use_gravity", use_gravity) || !use_gravity) {
760  amrex::Error("Cloud Chamber: erf.use_gravity = true is required");
761  }
762 
763  amrex::ParmParse pp_amr("amr");
764  int max_level = 0;
765  pp_amr.query("max_level", max_level);
766  if (max_level != 0) {
767  amrex::Error("Cloud Chamber: amr.max_level must be 0");
768  }
771 
772  amrex::ParmParse pp_prob("prob");
774  if (pp_prob.query("p_inf", p_inf)) {
775  require_finite("prob.p_inf", p_inf);
776  const amrex::Real pressure_tolerance = amrex::Real(1.0e-6) * p_0;
777  if (std::abs(p_inf - p_0) > pressure_tolerance) {
778  amrex::Error("Cloud Chamber: prob.p_inf must match ERF's p_0 = 100000 Pa within 1e-6 relative tolerance");
779  }
780  }
781 
782  if (!supports_moisture_model(selected_moisture_model)) {
783  amrex::Error("Cloud Chamber: selected moisture model is unsupported; use an Eulerian model from the supported moisture allowlist");
784  }
785  config.has_moisture = selected_moisture_model != MoistureType::None;
786  if (!config.has_moisture && pp_erf.contains("moisture_model")) {
787  amrex::Error("Cloud Chamber: omit erf.moisture_model for dry mode instead of explicitly selecting None");
788  }
789  if (!config.has_moisture &&
790  (pp_prob.contains("qv_bottom") || pp_prob.contains("qv_top"))) {
791  amrex::Error("Cloud Chamber: qv_bottom/qv_top require an active moisture model");
792  }
793 
794  std::string init_mode;
795  pp_prob.get("thermodynamic_initialization", init_mode);
796  init_mode = amrex::toLower(init_mode);
797  if (init_mode == "physical_temperature_rh") {
798  config.physical_initialization = true;
799  std::string init_type;
800  pp_erf.get("init_type", init_type);
801  if (amrex::toLower(init_type) != "constantdensity") {
802  amrex::Error("Cloud Chamber: physical initialization requires the existing HSE-backed erf.init_type = ConstantDensity path");
803  }
804  std::string molec_diff_type;
805  pp_erf.get("molec_diff_type", molec_diff_type);
806  if (amrex::toLower(molec_diff_type) != "constantalpha") {
807  amrex::Error("Cloud Chamber: physical wall transfer requires erf.molec_diff_type = ConstantAlpha");
808  }
809  bool vert_implicit = false;
810  pp_erf.query("vert_implicit", vert_implicit);
811  if (vert_implicit) {
812  amrex::Error("Cloud Chamber: physical wall transfer requires erf.vert_implicit = false");
813  }
814  pp_prob.get("initial_temperature_bottom", config.initial_temperature_bottom);
815  pp_prob.get("initial_temperature_top", config.initial_temperature_top);
816  const bool has_initial_relative_humidity = pp_prob.contains("initial_relative_humidity");
817  auto physical_contract_error = [&](bool has_legacy_profile_keys) {
818  const InitializationContract contract {
820  config.has_moisture,
821  has_initial_relative_humidity,
822  has_legacy_profile_keys,
823  false};
824  return initialization_contract_error(contract);
825  };
826  std::string contract_error = physical_contract_error(false);
827  if (!contract_error.empty()) {
828  amrex::Error(contract_error);
829  }
830  if (config.has_moisture) {
831  pp_prob.get("initial_relative_humidity", config.initial_relative_humidity);
832  }
833  pp_prob.query("temperature_perturbation_amplitude",
834  config.temperature_perturbation_amplitude);
835  const bool has_legacy_profile_keys = pp_prob.contains("theta_bottom") ||
836  pp_prob.contains("theta_top") ||
837  pp_prob.contains("theta_perturbation_amplitude") ||
838  pp_prob.contains("qv_bottom") ||
839  pp_prob.contains("qv_top");
840  contract_error = physical_contract_error(has_legacy_profile_keys);
841  if (!contract_error.empty()) {
842  amrex::Error(contract_error);
843  }
844  require_finite("prob.initial_temperature_bottom", config.initial_temperature_bottom);
845  require_finite("prob.initial_temperature_top", config.initial_temperature_top);
846  require_finite("prob.temperature_perturbation_amplitude",
847  config.temperature_perturbation_amplitude);
848  if (config.initial_temperature_bottom <= amrex::Real(0.0) ||
849  config.initial_temperature_top <= amrex::Real(0.0)) {
850  amrex::Error("Cloud Chamber: physical initial temperatures must be positive");
851  }
852  if (config.has_moisture) {
853  require_finite("prob.initial_relative_humidity", config.initial_relative_humidity);
854  if (config.initial_relative_humidity < amrex::Real(0.0) ||
855  config.initial_relative_humidity > amrex::Real(1.0)) {
856  amrex::Error("Cloud Chamber: initial_relative_humidity must lie in [0,1]");
857  }
858  }
859  } else if (init_mode == "legacy_theta_qv") {
860  config.physical_initialization = false;
861  const InitializationContract contract {
863  false,
864  false,
865  false,
866  pp_prob.contains("initial_temperature_bottom") ||
867  pp_prob.contains("initial_temperature_top") ||
868  pp_prob.contains("initial_relative_humidity") ||
869  pp_prob.contains("temperature_perturbation_amplitude")};
870  const std::string contract_error = initialization_contract_error(contract);
871  if (!contract_error.empty()) {
872  amrex::Error(contract_error);
873  }
874  pp_prob.get("theta_bottom", config.theta_bottom);
875  pp_prob.get("theta_top", config.theta_top);
876  pp_prob.query("theta_perturbation_amplitude",
877  config.theta_perturbation_amplitude);
878  } else {
879  amrex::Error("Cloud Chamber: prob.thermodynamic_initialization must be physical_temperature_rh or legacy_theta_qv");
880  }
881  std::string perturbation_mode = "deterministic_sine";
882  pp_prob.query("perturbation_mode", perturbation_mode);
883  if (amrex::toLower(perturbation_mode) != "deterministic_sine") {
884  amrex::Error("Cloud Chamber: prob.perturbation_mode must be deterministic_sine");
885  }
886  if (!config.physical_initialization) {
887  require_finite("prob.theta_bottom", config.theta_bottom);
888  require_finite("prob.theta_top", config.theta_top);
889  require_finite("prob.theta_perturbation_amplitude",
890  config.theta_perturbation_amplitude);
891  if (config.theta_bottom <= amrex::Real(0.0) ||
892  config.theta_top <= amrex::Real(0.0)) {
893  amrex::Error("Cloud Chamber: theta profile values must be positive");
894  }
895  }
896 
897  if (config.has_moisture) {
898  if (!config.physical_initialization) {
899  pp_prob.get("qv_bottom", config.qv_bottom);
900  pp_prob.get("qv_top", config.qv_top);
901  require_finite("prob.qv_bottom", config.qv_bottom);
902  require_finite("prob.qv_top", config.qv_top);
903  if (config.qv_bottom < amrex::Real(0.0) ||
904  config.qv_top < amrex::Real(0.0)) {
905  amrex::Error("Cloud Chamber: qv profile values must be nonnegative");
906  }
907  }
908  }
909 
912  for (int n = 0; n < 2 * AMREX_SPACEDIM; ++n) {
913  const std::string face = face_name(faces[n]);
914  amrex::ParmParse pp_face(face.c_str());
915  reject_unsupported_face_keys(pp_face, face);
916  std::string type;
917  if (!pp_face.query("type", type) ||
918  amrex::toLower(type) != "noslipwall") {
919  amrex::Error("Cloud Chamber: " + face + ".type must be NoSlipWall");
920  }
921  WallSpec& wall = config.walls[n];
922  wall.face = faces[n];
923  WallTransferContract transfer_contract;
924  std::string transfer = "resolved_molecular";
925  if (pp_face.query("wall_transfer_model", transfer)) {
926  transfer_contract.legacy_aggregate_specified = true;
927  transfer = amrex::toLower(transfer);
928  if (transfer != "resolved_molecular") {
929  amrex::Error("Cloud Chamber: only resolved_molecular wall transfer is supported");
930  }
931  }
932  if (pp_face.query("momentum_transfer_model", transfer_contract.momentum_model)) {
933  transfer_contract.momentum_model_specified = true;
934  transfer_contract.momentum_model = amrex::toLower(transfer_contract.momentum_model);
935  }
936  if (pp_face.query("heat_transfer_model", transfer_contract.heat_model)) {
937  transfer_contract.heat_model_specified = true;
938  transfer_contract.heat_model = amrex::toLower(transfer_contract.heat_model);
939  }
940  if (pp_face.query("vapor_transfer_model", transfer_contract.vapor_model)) {
941  transfer_contract.vapor_model_specified = true;
942  transfer_contract.vapor_model = amrex::toLower(transfer_contract.vapor_model);
943  }
944  if (pp_face.query("coefficient_source", transfer_contract.coefficient_source)) {
945  transfer_contract.coefficient_source_specified = true;
946  transfer_contract.coefficient_source = amrex::toLower(transfer_contract.coefficient_source);
947  }
948  if (pp_face.query("C_D", transfer_contract.momentum_coefficient)) {
949  transfer_contract.momentum_coefficient_specified = true;
950  require_finite(face + ".C_D", transfer_contract.momentum_coefficient);
951  }
952  if (pp_face.query("C_H", transfer_contract.heat_coefficient)) {
953  transfer_contract.heat_coefficient_specified = true;
954  require_finite(face + ".C_H", transfer_contract.heat_coefficient);
955  }
956  if (pp_face.query("C_E", transfer_contract.vapor_coefficient)) {
957  transfer_contract.vapor_coefficient_specified = true;
958  require_finite(face + ".C_E", transfer_contract.vapor_coefficient);
959  }
960  if (pp_face.query("z0_m", transfer_contract.z0_m)) {
961  transfer_contract.z0_m_specified = true;
962  require_finite(face + ".z0_m", transfer_contract.z0_m);
963  }
964  if (pp_face.query("z0_h", transfer_contract.z0_h)) {
965  transfer_contract.z0_h_specified = true;
966  require_finite(face + ".z0_h", transfer_contract.z0_h);
967  }
968  if (pp_face.query("z0_q", transfer_contract.z0_q)) {
969  transfer_contract.z0_q_specified = true;
970  require_finite(face + ".z0_q", transfer_contract.z0_q);
971  }
972  const std::string transfer_error =
973  wall_transfer_contract_error(transfer_contract, face);
974  if (!transfer_error.empty()) {
975  amrex::Error(transfer_error);
976  }
977  const bool any_channel_key =
978  transfer_contract.momentum_model_specified ||
979  transfer_contract.heat_model_specified ||
980  transfer_contract.vapor_model_specified ||
981  transfer_contract.coefficient_source_specified ||
982  transfer_contract.momentum_coefficient_specified ||
983  transfer_contract.heat_coefficient_specified ||
984  transfer_contract.vapor_coefficient_specified ||
985  transfer_contract.z0_m_specified ||
986  transfer_contract.z0_h_specified ||
987  transfer_contract.z0_q_specified;
988  if (!config.physical_initialization && any_channel_key) {
989  amrex::Error("Cloud Chamber: per-channel wall transfer keys require physical temperature initialization");
990  }
991  if (config.physical_initialization) {
992  if (pp_face.contains("theta") || pp_face.contains("qv") ||
993  pp_face.contains("theta_grad") || pp_face.contains("qv_grad")) {
994  amrex::Error("Cloud Chamber: physical walls cannot combine temperature/moisture with legacy theta/qv wall keys");
995  }
996  if (!pp_face.query("temperature", wall.wall.thermal.temperature_K)) {
997  amrex::Error("Cloud Chamber: missing required physical " + face + ".temperature");
998  }
999  require_finite(face + ".temperature", wall.wall.thermal.temperature_K);
1000  if (wall.wall.thermal.temperature_K <= amrex::Real(0.0)) {
1001  amrex::Error("Cloud Chamber: " + face + ".temperature must be positive Kelvin");
1002  }
1003  std::string moisture;
1004  pp_face.get("moisture", moisture);
1005  moisture = amrex::toLower(moisture);
1006  if (moisture == "dry") {
1008  } else if (moisture == "wet") {
1010  if (!config.has_moisture) {
1011  amrex::Error("Cloud Chamber: wet walls require an active moisture model");
1012  }
1013  } else {
1014  amrex::Error("Cloud Chamber: " + face + ".moisture must be dry or wet");
1015  }
1016  const std::string coupled_most_error =
1018  transfer_contract, face,
1019  wall.wall.moisture ==
1021  if (!coupled_most_error.empty()) {
1022  amrex::Error(coupled_most_error);
1023  }
1025  if (transfer_contract.momentum_model == "law_of_wall_momentum") {
1026  transfer_contract.momentum_model = "neutral_roughness_log";
1027  }
1028  wall.wall.momentum.model =
1029  transfer_contract.momentum_model == "neutral_roughness_log" ?
1031  (transfer_contract.momentum_model == "bulk_aero" ?
1034  wall.wall.heat.model =
1035  transfer_contract.heat_model == "bulk_aero" ?
1037  (transfer_contract.heat_model == "neutral_roughness_log" ?
1040  wall.wall.vapor.model =
1041  transfer_contract.vapor_model == "bulk_aero" ?
1043  (transfer_contract.vapor_model == "neutral_roughness_log" ?
1046  wall.wall.momentum.z0_m = transfer_contract.z0_m;
1047  wall.wall.momentum.C_D = transfer_contract.momentum_coefficient;
1048  wall.wall.heat.z0 = transfer_contract.z0_h;
1049  wall.wall.vapor.z0 = transfer_contract.z0_q;
1051  wall.wall.momentum.provider = transfer_contract.coefficient_source == "most" ?
1054  }
1056  wall.wall.heat.provider = transfer_contract.coefficient_source == "most" ?
1059  wall.wall.heat.coefficient = transfer_contract.heat_coefficient;
1060  }
1062  wall.wall.vapor.provider = transfer_contract.coefficient_source == "most" ?
1065  wall.wall.vapor.coefficient = transfer_contract.vapor_coefficient;
1066  }
1067  } else {
1068  if (!pp_face.query("theta", wall.wall.thermal.temperature_K)) {
1069  amrex::Error("Cloud Chamber: missing required " + face + ".theta");
1070  }
1072  wall.wall.moisture = config.has_moisture ?
1075  if (config.has_moisture && !pp_face.contains("qv")) {
1076  amrex::Error("Cloud Chamber: legacy cloudy walls require " + face + ".qv");
1077  }
1078  }
1079  for (int d = 0; d < AMREX_SPACEDIM; ++d) {
1080  if (pp_face.contains("velocity")) {
1081  amrex::Error("Cloud Chamber: wall velocity metadata is not active; use NoSlipWall with zero velocity");
1082  }
1083  wall.wall.wall_velocity[d] = amrex::Real(0.0);
1084  }
1085  }
1086  for (int d = 0; d < AMREX_SPACEDIM; ++d) {
1087  const amrex::Real length = config.prob_hi[d] - config.prob_lo[d];
1088  if (!(length > amrex::Real(0.0)) || !finite(length)) {
1089  amrex::Error("Cloud Chamber: every rectangular dimension must be finite and positive");
1090  }
1091  }
1092  return config;
1093 }
1094 
1095 } // namespace erf_cloud_chamber
1096 
1097 #endif
constexpr amrex::Real p_0
Definition: ERF_Constants.H:69
constexpr amrex::Real RdoRv
Definition: ERF_Constants.H:60
auto probhi
Definition: ERF_InitCustomPertVels_ABL.H:37
auto problo
Definition: ERF_InitCustomPertVels_ABL.H:36
const Real lx
Definition: ERF_InitCustomPertVels_CloudChamber.H:10
const int nx
Definition: ERF_InitCustomPertVels_CloudChamber.H:14
const int ny
Definition: ERF_InitCustomPertVels_CloudChamber.H:15
const Real v_scale
Definition: ERF_InitCustomPertVels_CloudChamber.H:16
const Real ly
Definition: ERF_InitCustomPertVels_CloudChamber.H:11
InitType init_type
Definition: ERF_InitCustomPertVels_EkmanSpiral.H:8
Real z0
Definition: ERF_InitCustomPertVels_ScalarAdvDiff.H:12
const Real dy
Definition: ERF_InitCustomPert_ABL.H:45
ParmParse pp_prob("prob")
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
const Real length
Definition: ERF_InitCustomPert_AnelasticWallDiffusion.H:16
ParmParse pp("prob")
const auto config
Definition: ERF_InitCustomPert_CloudChamber.H:37
amrex::Real p_inf
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:5
Real U_0
Definition: ERF_InitCustomPert_TurbulentInflow.H:18
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real erf_esatw(amrex::Real t, bool use_empirical=false)
Definition: ERF_MicrophysicsUtils.H:159
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ None
Definition: ERF_LSM_Types.H:6
Definition: ERF_CloudChamber.H:19
void require_zero_periodicity()
Definition: ERF_CloudChamber.H:616
WallFace
Definition: ERF_CloudChamber.H:28
std::string wall_coupled_most_roughness_error(const WallTransferContract &contract, const std::string &face, bool wet_wall)
Definition: ERF_CloudChamber.H:591
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real initial_u_velocity_perturbation(amrex::Real x, amrex::Real y, amrex::Real z, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &lo, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &length, amrex::Real U_0, int nx, int ny) noexcept
Evaluate the x-face component of the Cloud Chamber initial velocity.
Definition: ERF_CloudChamber.H:262
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real deterministic_perturbation(amrex::Real x, amrex::Real y, amrex::Real z, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &lo, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &length, amrex::Real amplitude) noexcept
Compute a deterministic 3D sine perturbation.
Definition: ERF_CloudChamber.H:221
std::string wall_transfer_contract_error(const WallTransferContract &contract, const std::string &face)
Definition: ERF_CloudChamber.H:425
std::string wall_roughness_geometry_error(const erf_wall_thermodynamics::Boundary &walls, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &dx)
Definition: ERF_CloudChamber.H:670
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real staggered_velocity_v_scale(amrex::Real lx, amrex::Real ly, amrex::Real dx, amrex::Real dy, int nx, int ny) noexcept
Discrete derivative ratio for the staggered initial velocity field.
Definition: ERF_CloudChamber.H:244
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real qv_at(const Config &config, amrex::Real z) noexcept
Compute the water vapor mixing ratio at a given height.
Definition: ERF_CloudChamber.H:347
Config parse_config(const amrex::Real *problo, const amrex::Real *probhi, const MoistureType selected_moisture_model)
Definition: ERF_CloudChamber.H:737
bool supports_moisture_model(const MoistureType moisture_type) noexcept
Return whether a moisture type is supported by the Cloud Chamber.
Definition: ERF_CloudChamber.H:166
std::string face_name(WallFace face)
Definition: ERF_CloudChamber.H:387
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real vapor_mixing_ratio_from_relative_humidity(amrex::Real temperature_K, amrex::Real pressure_Pa, amrex::Real relative_humidity) noexcept
Convert relative humidity to water vapor mixing ratio.
Definition: ERF_CloudChamber.H:364
void require_finite(const std::string &key, amrex::Real value)
Definition: ERF_CloudChamber.H:380
void validate_wall_roughness_geometry(const erf_wall_thermodynamics::Boundary &walls, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &dx)
Definition: ERF_CloudChamber.H:729
InitializationMode
Definition: ERF_CloudChamber.H:110
void reject_unsupported_face_keys(amrex::ParmParse &pp, const std::string &face)
Definition: ERF_CloudChamber.H:656
void require_no_unsupported_geometry()
Definition: ERF_CloudChamber.H:631
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real theta_at(const Config &config, amrex::Real x, amrex::Real y, amrex::Real z) noexcept
Compute the potential temperature at a given location.
Definition: ERF_CloudChamber.H:319
std::string initialization_contract_error(const InitializationContract &contract)
Validate the initialization contract and return an error message if violated.
Definition: ERF_CloudChamber.H:133
bool finite(amrex::Real value) noexcept
Definition: ERF_CloudChamber.H:375
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real linear_profile(amrex::Real bottom, amrex::Real top, amrex::Real coordinate, amrex::Real lower, amrex::Real length) noexcept
Compute a linear profile between two values.
Definition: ERF_CloudChamber.H:201
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real initial_w_velocity_perturbation() noexcept
The Cloud Chamber initial vertical velocity perturbation.
Definition: ERF_CloudChamber.H:303
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real initial_v_velocity_perturbation(amrex::Real x, amrex::Real y, amrex::Real z, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &lo, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &length, amrex::Real U_0, amrex::Real v_scale, int nx, int ny) noexcept
Evaluate the y-face component of the Cloud Chamber initial velocity.
Definition: ERF_CloudChamber.H:282
amrex::GpuArray< FaceWall, 2 *AMREX_SPACEDIM > Boundary
Definition: ERF_WallThermodynamics.H:70
real(c_double), parameter, private pi
Definition: ERF_module_mp_morr_two_moment.F90:100
static MeshType mesh_type
Vertical mesh representation.
Definition: ERF_DataStruct.H:2604
Configuration parameters for cloud chamber initialization.
Definition: ERF_CloudChamber.H:41
amrex::Real qv_top
Definition: ERF_CloudChamber.H:53
amrex::GpuArray< WallSpec, 2 *AMREX_SPACEDIM > walls
Definition: ERF_CloudChamber.H:56
erf_wall_thermodynamics::Boundary wall_boundary() const noexcept
Collect thermodynamic boundary conditions for all chamber walls.
Definition: ERF_CloudChamber.H:62
bool active
Definition: ERF_CloudChamber.H:42
bool has_moisture
Definition: ERF_CloudChamber.H:43
amrex::Real initial_temperature_top
Definition: ERF_CloudChamber.H:46
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > prob_hi
Definition: ERF_CloudChamber.H:55
bool has_wall_rate_channel() const noexcept
Definition: ERF_CloudChamber.H:84
amrex::Real qv_bottom
Definition: ERF_CloudChamber.H:52
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > prob_lo
Definition: ERF_CloudChamber.H:54
amrex::Real initial_temperature_bottom
Definition: ERF_CloudChamber.H:45
amrex::Real temperature_perturbation_amplitude
Definition: ERF_CloudChamber.H:48
amrex::Real theta_perturbation_amplitude
Definition: ERF_CloudChamber.H:51
amrex::Real theta_bottom
Definition: ERF_CloudChamber.H:49
amrex::Real initial_relative_humidity
Definition: ERF_CloudChamber.H:47
bool has_bulk_scalar_wall() const noexcept
Definition: ERF_CloudChamber.H:71
amrex::Real theta_top
Definition: ERF_CloudChamber.H:50
bool physical_initialization
Definition: ERF_CloudChamber.H:44
Contract used to validate initialization parameter consistency.
Definition: ERF_CloudChamber.H:118
InitializationMode mode
Definition: ERF_CloudChamber.H:119
bool has_initial_relative_humidity
Definition: ERF_CloudChamber.H:122
bool has_physical_profile_keys
Definition: ERF_CloudChamber.H:124
bool has_moisture
Definition: ERF_CloudChamber.H:121
bool has_legacy_profile_keys
Definition: ERF_CloudChamber.H:123
Metadata describing a chamber wall.
Definition: ERF_CloudChamber.H:33
erf_wall_thermodynamics::FaceWall wall
Definition: ERF_CloudChamber.H:35
WallFace face
Definition: ERF_CloudChamber.H:34
Definition: ERF_CloudChamber.H:400
amrex::Real z0_m
Definition: ERF_CloudChamber.H:419
std::string heat_model
Definition: ERF_CloudChamber.H:413
bool momentum_model_specified
Definition: ERF_CloudChamber.H:402
amrex::Real z0_q
Definition: ERF_CloudChamber.H:421
bool z0_h_specified
Definition: ERF_CloudChamber.H:410
bool coefficient_source_specified
Definition: ERF_CloudChamber.H:405
std::string coefficient_source
Definition: ERF_CloudChamber.H:415
std::string vapor_model
Definition: ERF_CloudChamber.H:414
bool z0_q_specified
Definition: ERF_CloudChamber.H:411
std::string momentum_model
Definition: ERF_CloudChamber.H:412
amrex::Real z0_h
Definition: ERF_CloudChamber.H:420
bool legacy_aggregate_specified
Definition: ERF_CloudChamber.H:401
bool vapor_model_specified
Definition: ERF_CloudChamber.H:404
amrex::Real momentum_coefficient
Definition: ERF_CloudChamber.H:416
bool heat_coefficient_specified
Definition: ERF_CloudChamber.H:407
amrex::Real heat_coefficient
Definition: ERF_CloudChamber.H:417
bool heat_model_specified
Definition: ERF_CloudChamber.H:403
bool momentum_coefficient_specified
Definition: ERF_CloudChamber.H:406
bool z0_m_specified
Definition: ERF_CloudChamber.H:409
amrex::Real vapor_coefficient
Definition: ERF_CloudChamber.H:418
bool vapor_coefficient_specified
Definition: ERF_CloudChamber.H:408
Definition: ERF_WallThermodynamics.H:61
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > wall_velocity
Definition: ERF_WallThermodynamics.H:67
ScalarTransfer vapor
Definition: ERF_WallThermodynamics.H:66
ScalarTransfer heat
Definition: ERF_WallThermodynamics.H:65
MoistureMode moisture
Definition: ERF_WallThermodynamics.H:63
MomentumTransfer momentum
Definition: ERF_WallThermodynamics.H:64
ThermalBoundary thermal
Definition: ERF_WallThermodynamics.H:62
amrex::Real z0_m
Definition: ERF_WallThermodynamics.H:51
CoefficientProvider provider
Definition: ERF_WallThermodynamics.H:49
amrex::Real C_D
Definition: ERF_WallThermodynamics.H:50
MomentumModel model
Definition: ERF_WallThermodynamics.H:48
amrex::Real coefficient
Definition: ERF_WallThermodynamics.H:57
CoefficientProvider provider
Definition: ERF_WallThermodynamics.H:56
amrex::Real z0
Definition: ERF_WallThermodynamics.H:58
ScalarModel model
Definition: ERF_WallThermodynamics.H:55
amrex::Real temperature_K
Definition: ERF_WallThermodynamics.H:44
ThermalMode mode
Definition: ERF_WallThermodynamics.H:43