704 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
710 amrex::Error(
"Cloud Chamber: geometry.prob_hi must exceed geometry.prob_lo in every direction");
714 amrex::ParmParse pp_erf(
"erf");
716 if (!pp_erf.query(
"anelastic", anelastic) || anelastic != 1) {
717 amrex::Error(
"Cloud Chamber: erf.anelastic = 1 is required");
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");
724 amrex::ParmParse pp_amr(
"amr");
726 pp_amr.query(
"max_level", max_level);
727 if (max_level != 0) {
728 amrex::Error(
"Cloud Chamber: amr.max_level must be 0");
733 amrex::ParmParse
pp_prob(
"prob");
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");
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");
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");
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;
762 if (amrex::toLower(
init_type) !=
"constantdensity") {
763 amrex::Error(
"Cloud Chamber: physical initialization requires the existing HSE-backed erf.init_type = ConstantDensity path");
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");
770 bool vert_implicit =
false;
771 pp_erf.query(
"vert_implicit", vert_implicit);
773 amrex::Error(
"Cloud Chamber: physical wall transfer requires erf.vert_implicit = false");
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 {
780 InitializationMode::PhysicalTemperatureRH,
782 has_initial_relative_humidity,
783 has_legacy_profile_keys,
787 std::string contract_error = physical_contract_error(
false);
788 if (!contract_error.empty()) {
789 amrex::Error(contract_error);
792 pp_prob.get(
"initial_relative_humidity",
config.initial_relative_humidity);
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") ||
801 contract_error = physical_contract_error(has_legacy_profile_keys);
802 if (!contract_error.empty()) {
803 amrex::Error(contract_error);
808 config.temperature_perturbation_amplitude);
811 amrex::Error(
"Cloud Chamber: physical initial temperatures must be positive");
817 amrex::Error(
"Cloud Chamber: initial_relative_humidity must lie in [0,1]");
820 }
else if (init_mode ==
"legacy_theta_qv") {
821 config.physical_initialization =
false;
822 const InitializationContract contract {
823 InitializationMode::LegacyThetaQv,
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")};
832 if (!contract_error.empty()) {
833 amrex::Error(contract_error);
837 pp_prob.query(
"theta_perturbation_amplitude",
838 config.theta_perturbation_amplitude);
840 amrex::Error(
"Cloud Chamber: prob.thermodynamic_initialization must be physical_temperature_rh or legacy_theta_qv");
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");
847 if (!
config.physical_initialization) {
851 config.theta_perturbation_amplitude);
854 amrex::Error(
"Cloud Chamber: theta profile values must be positive");
858 if (moisture_specified) {
859 if (!
config.physical_initialization) {
866 amrex::Error(
"Cloud Chamber: qv profile values must be nonnegative");
871 const WallFace faces[] = {WallFace::XLo, WallFace::XHi, WallFace::YLo,
872 WallFace::YHi, WallFace::ZLo, WallFace::ZHi};
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());
878 if (!pp_face.query(
"type", type) ||
879 amrex::toLower(type) !=
"noslipwall") {
880 amrex::Error(
"Cloud Chamber: " + face +
".type must be NoSlipWall");
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");
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);
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);
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);
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);
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);
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);
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);
921 if (pp_face.query(
"z0_m", transfer_contract.z0_m)) {
922 transfer_contract.z0_m_specified =
true;
925 if (pp_face.query(
"z0_h", transfer_contract.z0_h)) {
926 transfer_contract.z0_h_specified =
true;
929 if (pp_face.query(
"z0_q", transfer_contract.z0_q)) {
930 transfer_contract.z0_q_specified =
true;
933 const std::string transfer_error =
935 if (!transfer_error.empty()) {
936 amrex::Error(transfer_error);
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");
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");
957 if (!pp_face.query(
"temperature", wall.wall.thermal.temperature_K)) {
958 amrex::Error(
"Cloud Chamber: missing required physical " + face +
".temperature");
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");
964 std::string moisture;
965 pp_face.get(
"moisture", moisture);
966 moisture = amrex::toLower(moisture);
967 if (moisture ==
"dry") {
969 }
else if (moisture ==
"wet") {
972 amrex::Error(
"Cloud Chamber: wet walls require SatAdj moisture");
975 amrex::Error(
"Cloud Chamber: " + face +
".moisture must be dry or wet");
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);
986 if (transfer_contract.momentum_model ==
"law_of_wall_momentum") {
987 transfer_contract.momentum_model =
"neutral_roughness_log";
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" ?
1017 wall.wall.heat.provider = transfer_contract.coefficient_source ==
"most" ?
1020 wall.wall.heat.coefficient = transfer_contract.heat_coefficient;
1023 wall.wall.vapor.provider = transfer_contract.coefficient_source ==
"most" ?
1026 wall.wall.vapor.coefficient = transfer_contract.vapor_coefficient;
1029 if (!pp_face.query(
"theta", wall.wall.thermal.temperature_K)) {
1030 amrex::Error(
"Cloud Chamber: missing required " + face +
".theta");
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");
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");
1047 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
1050 amrex::Error(
"Cloud Chamber: every rectangular dimension must be finite and positive");
constexpr amrex::Real p_0
Definition: ERF_Constants.H:53
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: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
std::string wall_transfer_contract_error(const WallTransferContract &contract, const std::string &face)
Definition: ERF_CloudChamber.H:392
std::string face_name(WallFace face)
Definition: ERF_CloudChamber.H:354
void require_finite(const std::string &key, amrex::Real value)
Definition: ERF_CloudChamber.H:347
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
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
@ FixedPhysicalTemperature