ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
erf_cloud_chamber Namespace Reference

Classes

struct  WallSpec
 Metadata describing a chamber wall. More...
 
struct  Config
 Configuration parameters for cloud chamber initialization. More...
 
struct  InitializationContract
 Contract used to validate initialization parameter consistency. More...
 
struct  WallTransferContract
 

Enumerations

enum class  WallFace : unsigned char {
  XLo , XHi , YLo , YHi ,
  ZLo , ZHi
}
 
enum class  InitializationMode : unsigned char { PhysicalTemperatureRH , LegacyThetaQv }
 

Functions

std::string initialization_contract_error (const InitializationContract &contract)
 Validate the initialization contract and return an error message if violated. More...
 
bool supports_moisture_model (const MoistureType moisture_type) noexcept
 Return whether a moisture type is supported by the Cloud Chamber. More...
 
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. More...
 
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. More...
 
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. More...
 
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. More...
 
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. More...
 
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real initial_w_velocity_perturbation () noexcept
 The Cloud Chamber initial vertical velocity perturbation. More...
 
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. More...
 
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. More...
 
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. More...
 
bool finite (amrex::Real value) noexcept
 
void require_finite (const std::string &key, amrex::Real value)
 
std::string face_name (WallFace face)
 
std::string wall_transfer_contract_error (const WallTransferContract &contract, const std::string &face)
 
std::string wall_coupled_most_roughness_error (const WallTransferContract &contract, const std::string &face, bool wet_wall)
 
void require_zero_periodicity ()
 
void require_no_unsupported_geometry ()
 
void reject_unsupported_face_keys (amrex::ParmParse &pp, const std::string &face)
 
std::string wall_roughness_geometry_error (const erf_wall_thermodynamics::Boundary &walls, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &dx)
 
void validate_wall_roughness_geometry (const erf_wall_thermodynamics::Boundary &walls, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &dx)
 
Config parse_config (const amrex::Real *problo, const amrex::Real *probhi, const MoistureType selected_moisture_model)
 

Enumeration Type Documentation

◆ InitializationMode

enum erf_cloud_chamber::InitializationMode : unsigned char
strong
Enumerator
PhysicalTemperatureRH 
LegacyThetaQv 

◆ WallFace

enum erf_cloud_chamber::WallFace : unsigned char
strong

Cloud Chamber wall metadata. This is deliberately a small, trivially copyable description: it can be captured by an AMReX device kernel without virtual dispatch. The per-channel contract supports resolved molecular, fixed bulk-aerodynamic, and neutral roughness-log transfer, with MOST coefficient generation on the supported horizontal faces.

Enumerator
XLo 
XHi 
YLo 
YHi 
ZLo 
ZHi 

Function Documentation

◆ deterministic_perturbation()

AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real erf_cloud_chamber::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.

Parameters
[in]xx-coordinate.
[in]yy-coordinate.
[in]zz-coordinate.
[in]loLower boundary coordinates.
[in]lengthDomain lengths.
[in]amplitudePerturbation amplitude.
Returns
Perturbation value at the given coordinates.
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 }
const Real length
Definition: ERF_InitCustomPert_AnelasticWallDiffusion.H:16
amrex::Real Real
Definition: ERF_ShocInterface.H:19
real(c_double), parameter, private pi
Definition: ERF_module_mp_morr_two_moment.F90:100

Referenced by ParallelFor(), and theta_at().

Here is the caller graph for this function:

◆ face_name()

std::string erf_cloud_chamber::face_name ( WallFace  face)
inline
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 }

Referenced by parse_config(), and wall_roughness_geometry_error().

Here is the caller graph for this function:

◆ finite()

◆ initial_u_velocity_perturbation()

AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real erf_cloud_chamber::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.

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 }
const int nx
Definition: ERF_InitCustomPertVels_CloudChamber.H:14
const int ny
Definition: ERF_InitCustomPertVels_CloudChamber.H:15
Real U_0
Definition: ERF_InitCustomPert_TurbulentInflow.H:18

Referenced by ParallelFor().

Here is the caller graph for this function:

◆ initial_v_velocity_perturbation()

AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real erf_cloud_chamber::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.

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 }
const Real v_scale
Definition: ERF_InitCustomPertVels_CloudChamber.H:16

Referenced by ParallelFor().

Here is the caller graph for this function:

◆ initial_w_velocity_perturbation()

AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real erf_cloud_chamber::initial_w_velocity_perturbation ( )
noexcept

The Cloud Chamber initial vertical velocity perturbation.

304 {
305  return amrex::Real(0.0);
306 }

Referenced by ParallelFor().

Here is the caller graph for this function:

◆ initialization_contract_error()

std::string erf_cloud_chamber::initialization_contract_error ( const InitializationContract &  contract)
inline

Validate the initialization contract and return an error message if violated.

Parameters
[in]contractThe contract to validate.
Returns
Error message string, empty if valid.
134 {
135  switch (contract.mode) {
136  case InitializationMode::PhysicalTemperatureRH:
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;
148  case InitializationMode::LegacyThetaQv:
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 }

Referenced by parse_config().

Here is the caller graph for this function:

◆ linear_profile()

AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real erf_cloud_chamber::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.

Parameters
[in]bottomValue at the lower boundary.
[in]topValue at the upper boundary.
[in]coordinateCurrent coordinate.
[in]lowerLower boundary coordinate.
[in]lengthLength of the domain.
Returns
Interpolated value.
204 {
205  return bottom + (top - bottom) * (coordinate - lower) / length;
206 }

Referenced by ParallelFor(), qv_at(), and theta_at().

Here is the caller graph for this function:

◆ parse_config()

Config erf_cloud_chamber::parse_config ( const amrex::Real *  problo,
const amrex::Real *  probhi,
const MoistureType  selected_moisture_model 
)
inline
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 {
819  InitializationMode::PhysicalTemperatureRH,
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 {
862  InitializationMode::LegacyThetaQv,
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 
910  const WallFace faces[] = {WallFace::XLo, WallFace::XHi, WallFace::YLo,
911  WallFace::YHi, WallFace::ZLo, WallFace::ZHi};
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;
1050  if (wall.wall.momentum.model == erf_wall_thermodynamics::MomentumModel::BulkAero) {
1051  wall.wall.momentum.provider = transfer_contract.coefficient_source == "most" ?
1054  }
1055  if (wall.wall.heat.model == erf_wall_thermodynamics::ScalarModel::BulkAero) {
1056  wall.wall.heat.provider = transfer_contract.coefficient_source == "most" ?
1059  wall.wall.heat.coefficient = transfer_contract.heat_coefficient;
1060  }
1061  if (wall.wall.vapor.model == erf_wall_thermodynamics::ScalarModel::BulkAero) {
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 }
constexpr amrex::Real p_0
Definition: ERF_Constants.H:69
auto probhi
Definition: ERF_InitCustomPertVels_ABL.H:37
auto problo
Definition: ERF_InitCustomPertVels_ABL.H:36
InitType init_type
Definition: ERF_InitCustomPertVels_EkmanSpiral.H:8
ParmParse pp_prob("prob")
const auto config
Definition: ERF_InitCustomPert_CloudChamber.H:37
amrex::Real p_inf
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:5
@ None
Definition: ERF_LSM_Types.H:6
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
std::string wall_transfer_contract_error(const WallTransferContract &contract, const std::string &face)
Definition: ERF_CloudChamber.H:425
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
void require_finite(const std::string &key, amrex::Real value)
Definition: ERF_CloudChamber.H:380
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
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

Referenced by ERF::ERF_shared().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ qv_at()

AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real erf_cloud_chamber::qv_at ( const Config &  config,
amrex::Real  z 
)
noexcept

Compute the water vapor mixing ratio at a given height.

Parameters
[in]configConfiguration parameters.
[in]zVertical coordinate.
Returns
Vapor mixing ratio at the specified height.
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 }
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

Referenced by ParallelFor().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ reject_unsupported_face_keys()

void erf_cloud_chamber::reject_unsupported_face_keys ( amrex::ParmParse &  pp,
const std::string &  face 
)
inline
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 }
ParmParse pp("prob")

Referenced by parse_config().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ require_finite()

void erf_cloud_chamber::require_finite ( const std::string &  key,
amrex::Real  value 
)
inline
381 {
382  if (!finite(value)) {
383  amrex::Error("Cloud Chamber: " + key + " must be finite");
384  }
385 }

Referenced by parse_config().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ require_no_unsupported_geometry()

void erf_cloud_chamber::require_no_unsupported_geometry ( )
inline
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 }
static MeshType mesh_type
Vertical mesh representation.
Definition: ERF_DataStruct.H:2604

Referenced by parse_config().

Here is the caller graph for this function:

◆ require_zero_periodicity()

void erf_cloud_chamber::require_zero_periodicity ( )
inline
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 }

Referenced by parse_config().

Here is the caller graph for this function:

◆ staggered_velocity_v_scale()

AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real erf_cloud_chamber::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.

The ratio makes the finite-volume x/y divergence cancel on rectangular grids. A one-cell horizontal direction has an identically zero sine pattern, so the perturbation is zero rather than dividing by a zero discrete derivative factor.

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 }
const Real lx
Definition: ERF_InitCustomPertVels_CloudChamber.H:10
const Real ly
Definition: ERF_InitCustomPertVels_CloudChamber.H:11
const Real dy
Definition: ERF_InitCustomPert_ABL.H:45
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44

◆ supports_moisture_model()

bool erf_cloud_chamber::supports_moisture_model ( const MoistureType  moisture_type)
inlinenoexcept

Return whether a moisture type is supported by the Cloud Chamber.

This is intentionally an allowlist. In particular, particle-based SuperDroplets and spectral SBM remain outside the chamber's supported moisture configurations even though they are valid ERF moisture types.

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 }

Referenced by parse_config().

Here is the caller graph for this function:

◆ theta_at()

AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real erf_cloud_chamber::theta_at ( const Config &  config,
amrex::Real  x,
amrex::Real  y,
amrex::Real  z 
)
noexcept

Compute the potential temperature at a given location.

Parameters
[in]configConfiguration parameters.
[in]xx-coordinate.
[in]yy-coordinate.
[in]zz-coordinate.
Returns
Potential temperature at the specified location.
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]) +
334  deterministic_perturbation(x, y, z, config.prob_lo, length,
335  amplitude);
336 }
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

Referenced by ParallelFor().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ validate_wall_roughness_geometry()

void erf_cloud_chamber::validate_wall_roughness_geometry ( const erf_wall_thermodynamics::Boundary &  walls,
const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &  dx 
)
inline
732 {
733  const auto error = wall_roughness_geometry_error(walls, dx);
734  if (!error.empty()) { amrex::Error(error); }
735 }
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

Referenced by ERF::ERF_shared().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ vapor_mixing_ratio_from_relative_humidity()

AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real erf_cloud_chamber::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.

Parameters
[in]temperature_KTemperature in Kelvin.
[in]pressure_PaPressure in Pascals.
[in]relative_humidityRelative humidity [0,1].
Returns
Water vapor mixing ratio.
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 }
constexpr amrex::Real RdoRv
Definition: ERF_Constants.H:60
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real erf_esatw(amrex::Real t, bool use_empirical=false)
Definition: ERF_MicrophysicsUtils.H:159

Referenced by ParallelFor().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ wall_coupled_most_roughness_error()

std::string erf_cloud_chamber::wall_coupled_most_roughness_error ( const WallTransferContract &  contract,
const std::string &  face,
bool  wet_wall 
)
inline
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 }

Referenced by parse_config().

Here is the caller graph for this function:

◆ wall_roughness_geometry_error()

std::string erf_cloud_chamber::wall_roughness_geometry_error ( const erf_wall_thermodynamics::Boundary &  walls,
const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &  dx 
)
inline
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 }
Real z0
Definition: ERF_InitCustomPertVels_ScalarAdvDiff.H:12

Referenced by validate_wall_roughness_geometry().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ wall_transfer_contract_error()

std::string erf_cloud_chamber::wall_transfer_contract_error ( const WallTransferContract &  contract,
const std::string &  face 
)
inline
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 ||
447  contract.momentum_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 }

Referenced by parse_config().

Here is the call graph for this function:
Here is the caller graph for this function: