743 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
749 amrex::Error(
"Cloud Chamber: geometry.prob_hi must exceed geometry.prob_lo in every direction");
753 amrex::ParmParse pp_erf(
"erf");
755 if (!pp_erf.query(
"anelastic", anelastic) || anelastic != 1) {
756 amrex::Error(
"Cloud Chamber: erf.anelastic = 1 is required");
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");
763 amrex::ParmParse pp_amr(
"amr");
765 pp_amr.query(
"max_level", max_level);
766 if (max_level != 0) {
767 amrex::Error(
"Cloud Chamber: amr.max_level must be 0");
772 amrex::ParmParse
pp_prob(
"prob");
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");
783 amrex::Error(
"Cloud Chamber: selected moisture model is unsupported; use an Eulerian model from the supported moisture allowlist");
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");
789 if (!
config.has_moisture &&
791 amrex::Error(
"Cloud Chamber: qv_bottom/qv_top require an active moisture model");
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;
801 if (amrex::toLower(
init_type) !=
"constantdensity") {
802 amrex::Error(
"Cloud Chamber: physical initialization requires the existing HSE-backed erf.init_type = ConstantDensity path");
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");
809 bool vert_implicit =
false;
810 pp_erf.query(
"vert_implicit", vert_implicit);
812 amrex::Error(
"Cloud Chamber: physical wall transfer requires erf.vert_implicit = false");
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,
821 has_initial_relative_humidity,
822 has_legacy_profile_keys,
826 std::string contract_error = physical_contract_error(
false);
827 if (!contract_error.empty()) {
828 amrex::Error(contract_error);
830 if (
config.has_moisture) {
831 pp_prob.get(
"initial_relative_humidity",
config.initial_relative_humidity);
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") ||
840 contract_error = physical_contract_error(has_legacy_profile_keys);
841 if (!contract_error.empty()) {
842 amrex::Error(contract_error);
847 config.temperature_perturbation_amplitude);
850 amrex::Error(
"Cloud Chamber: physical initial temperatures must be positive");
852 if (
config.has_moisture) {
856 amrex::Error(
"Cloud Chamber: initial_relative_humidity must lie in [0,1]");
859 }
else if (init_mode ==
"legacy_theta_qv") {
860 config.physical_initialization =
false;
861 const InitializationContract contract {
862 InitializationMode::LegacyThetaQv,
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")};
871 if (!contract_error.empty()) {
872 amrex::Error(contract_error);
876 pp_prob.query(
"theta_perturbation_amplitude",
877 config.theta_perturbation_amplitude);
879 amrex::Error(
"Cloud Chamber: prob.thermodynamic_initialization must be physical_temperature_rh or legacy_theta_qv");
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");
886 if (!
config.physical_initialization) {
890 config.theta_perturbation_amplitude);
893 amrex::Error(
"Cloud Chamber: theta profile values must be positive");
897 if (
config.has_moisture) {
898 if (!
config.physical_initialization) {
905 amrex::Error(
"Cloud Chamber: qv profile values must be nonnegative");
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());
917 if (!pp_face.query(
"type", type) ||
918 amrex::toLower(type) !=
"noslipwall") {
919 amrex::Error(
"Cloud Chamber: " + face +
".type must be NoSlipWall");
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");
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);
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);
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);
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);
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);
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);
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);
960 if (pp_face.query(
"z0_m", transfer_contract.z0_m)) {
961 transfer_contract.z0_m_specified =
true;
964 if (pp_face.query(
"z0_h", transfer_contract.z0_h)) {
965 transfer_contract.z0_h_specified =
true;
968 if (pp_face.query(
"z0_q", transfer_contract.z0_q)) {
969 transfer_contract.z0_q_specified =
true;
972 const std::string transfer_error =
974 if (!transfer_error.empty()) {
975 amrex::Error(transfer_error);
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");
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");
996 if (!pp_face.query(
"temperature", wall.wall.thermal.temperature_K)) {
997 amrex::Error(
"Cloud Chamber: missing required physical " + face +
".temperature");
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");
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");
1014 amrex::Error(
"Cloud Chamber: " + face +
".moisture must be dry or wet");
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);
1025 if (transfer_contract.momentum_model ==
"law_of_wall_momentum") {
1026 transfer_contract.momentum_model =
"neutral_roughness_log";
1028 wall.wall.momentum.model =
1029 transfer_contract.momentum_model ==
"neutral_roughness_log" ?
1031 (transfer_contract.momentum_model ==
"bulk_aero" ?
1034 wall.wall.heat.model =
1035 transfer_contract.heat_model ==
"bulk_aero" ?
1037 (transfer_contract.heat_model ==
"neutral_roughness_log" ?
1040 wall.wall.vapor.model =
1041 transfer_contract.vapor_model ==
"bulk_aero" ?
1043 (transfer_contract.vapor_model ==
"neutral_roughness_log" ?
1046 wall.wall.momentum.z0_m = transfer_contract.z0_m;
1047 wall.wall.momentum.C_D = transfer_contract.momentum_coefficient;
1048 wall.wall.heat.z0 = transfer_contract.z0_h;
1049 wall.wall.vapor.z0 = transfer_contract.z0_q;
1051 wall.wall.momentum.provider = transfer_contract.coefficient_source ==
"most" ?
1056 wall.wall.heat.provider = transfer_contract.coefficient_source ==
"most" ?
1059 wall.wall.heat.coefficient = transfer_contract.heat_coefficient;
1062 wall.wall.vapor.provider = transfer_contract.coefficient_source ==
"most" ?
1065 wall.wall.vapor.coefficient = transfer_contract.vapor_coefficient;
1068 if (!pp_face.query(
"theta", wall.wall.thermal.temperature_K)) {
1069 amrex::Error(
"Cloud Chamber: missing required " + face +
".theta");
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");
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");
1086 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
1089 amrex::Error(
"Cloud Chamber: every rectangular dimension must be finite and positive");
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
@ FixedPhysicalTemperature