306 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
312 amrex::Error(
"Cloud Chamber: geometry.prob_hi must exceed geometry.prob_lo in every direction");
316 amrex::ParmParse pp_erf(
"erf");
318 if (!pp_erf.query(
"anelastic", anelastic) || anelastic != 1) {
319 amrex::Error(
"Cloud Chamber: erf.anelastic = 1 is required");
321 bool use_gravity =
false;
322 if (!pp_erf.query(
"use_gravity", use_gravity) || !use_gravity) {
323 amrex::Error(
"Cloud Chamber: erf.use_gravity = true is required");
326 amrex::ParmParse pp_amr(
"amr");
328 pp_amr.query(
"max_level", max_level);
329 if (max_level != 0) {
330 amrex::Error(
"Cloud Chamber: amr.max_level must be 0");
335 amrex::ParmParse
pp_prob(
"prob");
340 if (std::abs(
p_inf -
p_0) > pressure_tolerance) {
341 amrex::Error(
"Cloud Chamber: prob.p_inf must match ERF's p_0 = 100000 Pa within 1e-6 relative tolerance");
345 std::string moisture_model;
346 const bool moisture_specified = pp_erf.query(
"moisture_model", moisture_model);
347 if (moisture_specified) {
348 moisture_model = amrex::toLower(moisture_model);
349 if (moisture_model !=
"satadj") {
350 amrex::Error(
"Cloud Chamber: moisture_model must be omitted for dry mode or set to SatAdj");
353 }
else if (
pp_prob.contains(
"qv_bottom") ||
pp_prob.contains(
"qv_top")) {
354 amrex::Error(
"Cloud Chamber: qv_bottom/qv_top require erf.moisture_model = SatAdj");
357 std::string init_mode;
358 pp_prob.get(
"thermodynamic_initialization", init_mode);
359 init_mode = amrex::toLower(init_mode);
360 if (init_mode ==
"physical_temperature_rh") {
361 config.physical_initialization =
true;
364 if (amrex::toLower(
init_type) !=
"constantdensity") {
365 amrex::Error(
"Cloud Chamber: physical initialization requires the existing HSE-backed erf.init_type = ConstantDensity path");
367 std::string molec_diff_type;
368 pp_erf.get(
"molec_diff_type", molec_diff_type);
369 if (amrex::toLower(molec_diff_type) !=
"constantalpha") {
370 amrex::Error(
"Cloud Chamber: physical wall transfer requires erf.molec_diff_type = ConstantAlpha");
372 bool vert_implicit =
false;
373 pp_erf.query(
"vert_implicit", vert_implicit);
375 amrex::Error(
"Cloud Chamber: physical wall transfer requires erf.vert_implicit = false");
377 pp_prob.get(
"initial_temperature_bottom",
config.initial_temperature_bottom);
378 pp_prob.get(
"initial_temperature_top",
config.initial_temperature_top);
379 const bool has_initial_relative_humidity =
pp_prob.contains(
"initial_relative_humidity");
380 auto physical_contract_error = [&](
bool has_legacy_profile_keys) {
381 const InitializationContract contract {
382 InitializationMode::PhysicalTemperatureRH,
384 has_initial_relative_humidity,
385 has_legacy_profile_keys,
389 std::string contract_error = physical_contract_error(
false);
390 if (!contract_error.empty()) {
391 amrex::Error(contract_error);
394 pp_prob.get(
"initial_relative_humidity",
config.initial_relative_humidity);
396 pp_prob.query(
"temperature_perturbation_amplitude",
397 config.temperature_perturbation_amplitude);
398 const bool has_legacy_profile_keys =
pp_prob.contains(
"theta_bottom") ||
399 pp_prob.contains(
"theta_top") ||
400 pp_prob.contains(
"theta_perturbation_amplitude") ||
401 pp_prob.contains(
"qv_bottom") ||
403 contract_error = physical_contract_error(has_legacy_profile_keys);
404 if (!contract_error.empty()) {
405 amrex::Error(contract_error);
410 config.temperature_perturbation_amplitude);
413 amrex::Error(
"Cloud Chamber: physical initial temperatures must be positive");
419 amrex::Error(
"Cloud Chamber: initial_relative_humidity must lie in [0,1]");
422 }
else if (init_mode ==
"legacy_theta_qv") {
423 config.physical_initialization =
false;
424 const InitializationContract contract {
425 InitializationMode::LegacyThetaQv,
429 pp_prob.contains(
"initial_temperature_bottom") ||
430 pp_prob.contains(
"initial_temperature_top") ||
431 pp_prob.contains(
"initial_relative_humidity") ||
432 pp_prob.contains(
"temperature_perturbation_amplitude")};
434 if (!contract_error.empty()) {
435 amrex::Error(contract_error);
439 pp_prob.query(
"theta_perturbation_amplitude",
440 config.theta_perturbation_amplitude);
442 amrex::Error(
"Cloud Chamber: prob.thermodynamic_initialization must be physical_temperature_rh or legacy_theta_qv");
444 std::string perturbation_mode =
"deterministic_sine";
445 pp_prob.query(
"perturbation_mode", perturbation_mode);
446 if (amrex::toLower(perturbation_mode) !=
"deterministic_sine") {
447 amrex::Error(
"Cloud Chamber: prob.perturbation_mode must be deterministic_sine");
449 if (!
config.physical_initialization) {
453 config.theta_perturbation_amplitude);
456 amrex::Error(
"Cloud Chamber: theta profile values must be positive");
460 if (moisture_specified) {
461 if (!
config.physical_initialization) {
468 amrex::Error(
"Cloud Chamber: qv profile values must be nonnegative");
473 const WallFace faces[] = {WallFace::XLo, WallFace::XHi, WallFace::YLo,
474 WallFace::YHi, WallFace::ZLo, WallFace::ZHi};
475 for (
int n = 0; n < 2 * AMREX_SPACEDIM; ++n) {
476 const std::string face =
face_name(faces[n]);
477 amrex::ParmParse pp_face(face.c_str());
480 if (!pp_face.query(
"type", type) ||
481 amrex::toLower(type) !=
"noslipwall") {
482 amrex::Error(
"Cloud Chamber: " + face +
".type must be NoSlipWall");
484 WallSpec& wall =
config.walls[n];
485 wall.face = faces[n];
486 if (
config.physical_initialization) {
487 if (pp_face.contains(
"theta") || pp_face.contains(
"qv") ||
488 pp_face.contains(
"theta_grad") || pp_face.contains(
"qv_grad")) {
489 amrex::Error(
"Cloud Chamber: physical walls cannot combine temperature/moisture with legacy theta/qv wall keys");
491 if (!pp_face.query(
"temperature", wall.thermodynamics.physical_temperature_K)) {
492 amrex::Error(
"Cloud Chamber: missing required physical " + face +
".temperature");
494 require_finite(face +
".temperature", wall.thermodynamics.physical_temperature_K);
495 if (wall.thermodynamics.physical_temperature_K <=
amrex::Real(0.0)) {
496 amrex::Error(
"Cloud Chamber: " + face +
".temperature must be positive Kelvin");
498 std::string moisture;
499 pp_face.get(
"moisture", moisture);
500 moisture = amrex::toLower(moisture);
501 if (moisture ==
"dry") {
503 }
else if (moisture ==
"wet") {
506 amrex::Error(
"Cloud Chamber: wet walls require SatAdj moisture");
509 amrex::Error(
"Cloud Chamber: " + face +
".moisture must be dry or wet");
511 std::string transfer =
"resolved_molecular";
512 pp_face.query(
"wall_transfer_model", transfer);
513 transfer = amrex::toLower(transfer);
514 if (transfer !=
"resolved_molecular") {
515 amrex::Error(
"Cloud Chamber: only resolved_molecular wall transfer is supported");
520 if (!pp_face.query(
"theta",
config.walls[n].thermodynamics.physical_temperature_K)) {
521 amrex::Error(
"Cloud Chamber: missing required " + face +
".theta");
524 wall.thermodynamics.moisture_mode =
config.cloudy ?
527 if (
config.cloudy && !pp_face.contains(
"qv")) {
528 amrex::Error(
"Cloud Chamber: legacy cloudy walls require " + face +
".qv");
531 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
532 if (pp_face.contains(
"velocity")) {
533 amrex::Error(
"Cloud Chamber: wall velocity metadata is not active; use NoSlipWall with zero velocity");
538 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
541 amrex::Error(
"Cloud Chamber: every rectangular dimension must be finite and positive");
constexpr amrex::Real p_0
Definition: ERF_Constants.H:61
auto probhi
Definition: ERF_InitCustomPertVels_ABL.H:37
auto problo
Definition: ERF_InitCustomPertVels_ABL.H:36
InitType init_type
Definition: ERF_InitCustomPertVels_EkmanSpiral.H:3
ParmParse pp_prob("prob")
const auto config
Definition: ERF_InitCustomPert_CloudChamber.H:37
amrex::Real p_inf
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:5
void require_zero_periodicity()
Definition: ERF_CloudChamber.H:255
WallFace
Definition: ERF_CloudChamber.H:27
std::string face_name(WallFace face)
Definition: ERF_CloudChamber.H:242
void require_finite(const std::string &key, amrex::Real value)
Definition: ERF_CloudChamber.H:235
void reject_unsupported_face_keys(amrex::ParmParse &pp, const std::string &face)
Definition: ERF_CloudChamber.H:290
void require_no_unsupported_geometry()
Definition: ERF_CloudChamber.H:270
std::string initialization_contract_error(const InitializationContract &contract)
Validate the initialization contract and return an error message if violated.
Definition: ERF_CloudChamber.H:94
bool finite(amrex::Real value) noexcept
Definition: ERF_CloudChamber.H:230
@ FixedPhysicalTemperature