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