1 #ifndef ERF_CLOUD_CHAMBER_H_
2 #define ERF_CLOUD_CHAMBER_H_
4 #include <AMReX_Array.H>
5 #include <AMReX_ParmParse.H>
6 #include <AMReX_REAL.H>
32 amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>
velocity = {};
48 amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>
prob_lo = {};
49 amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>
prob_hi = {};
50 amrex::GpuArray<WallSpec, 2 * AMREX_SPACEDIM>
walls = {};
55 for (
int n = 0; n < 2 * AMREX_SPACEDIM; ++n) {
56 boundary[n] =
walls[n].thermodynamics;
78 switch (contract.
mode) {
82 return "Cloud Chamber: physical SatAdj initialization requires prob.initial_relative_humidity";
85 return "Cloud Chamber: prob.initial_relative_humidity is only used with erf.moisture_model = SatAdj";
88 return "Cloud Chamber: physical_temperature_rh cannot be combined with legacy theta/qv profile keys";
93 return "Cloud Chamber: legacy_theta_qv cannot be combined with physical temperature/RH profile keys";
102 AMREX_GPU_HOST_DEVICE
108 return bottom + (top - bottom) * (coordinate - lower) /
length;
112 AMREX_GPU_HOST_DEVICE
115 const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>& lo,
116 const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>&
length,
123 return amplitude * sx * sy * sz;
127 AMREX_GPU_HOST_DEVICE
132 const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>
length = {
137 config.initial_temperature_bottom :
config.theta_bottom;
141 config.temperature_perturbation_amplitude :
config.theta_perturbation_amplitude;
149 AMREX_GPU_HOST_DEVICE
159 AMREX_GPU_HOST_DEVICE
168 const amrex::Real ev_Pa = relative_humidity * es_Pa;
169 return RdoRv * ev_Pa / (pressure_Pa - ev_Pa);
174 return std::isfinite(
static_cast<double>(
value));
180 amrex::Error(
"Cloud Chamber: " + key +
" must be finite");
199 amrex::ParmParse pp_geom(
"geometry");
201 pp_geom.getarr(
"is_periodic",
periodic);
202 if (
periodic.size() != AMREX_SPACEDIM) {
203 amrex::Error(
"Cloud Chamber: geometry.is_periodic must provide three values");
205 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
207 amrex::Error(
"Cloud Chamber: geometry.is_periodic must be 0 0 0");
214 amrex::ParmParse pp_erf(
"erf");
216 if (pp_erf.query(
"terrain_type",
value) &&
217 amrex::toLower(
value) !=
"none") {
218 amrex::Error(
"Cloud Chamber: erf.terrain_type must be None; terrain and embedded boundaries are out of scope");
221 if (pp_erf.query(
"buildings_type",
value) &&
222 amrex::toLower(
value) !=
"none") {
223 amrex::Error(
"Cloud Chamber: erf.buildings_type must be None; immersed buildings are out of scope");
226 if (pp_erf.query(
"mesh_type",
value) &&
227 amrex::toLower(
value) !=
"constantdz") {
228 amrex::Error(
"Cloud Chamber: erf.mesh_type must be ConstantDz");
233 const std::string& face)
235 if (
pp.contains(
"physical_temperature") ||
pp.contains(
"relative_humidity") ||
236 pp.contains(
"rh") ||
pp.contains(
"roughness")) {
237 amrex::Error(
"Cloud Chamber: " + face +
238 " must use temperature and moisture for physical walls; "
239 "roughness and legacy aliases are unsupported");
248 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
254 amrex::Error(
"Cloud Chamber: geometry.prob_hi must exceed geometry.prob_lo in every direction");
258 amrex::ParmParse pp_erf(
"erf");
260 if (!pp_erf.query(
"anelastic", anelastic) || anelastic != 1) {
261 amrex::Error(
"Cloud Chamber: erf.anelastic = 1 is required");
263 bool use_gravity =
false;
264 if (!pp_erf.query(
"use_gravity", use_gravity) || !use_gravity) {
265 amrex::Error(
"Cloud Chamber: erf.use_gravity = true is required");
268 amrex::ParmParse pp_amr(
"amr");
270 pp_amr.query(
"max_level", max_level);
271 if (max_level != 0) {
272 amrex::Error(
"Cloud Chamber: amr.max_level must be 0");
277 amrex::ParmParse
pp_prob(
"prob");
282 if (std::abs(
p_inf -
p_0) > pressure_tolerance) {
283 amrex::Error(
"Cloud Chamber: prob.p_inf must match ERF's p_0 = 100000 Pa within 1e-6 relative tolerance");
287 std::string moisture_model;
288 const bool moisture_specified = pp_erf.query(
"moisture_model", moisture_model);
289 if (moisture_specified) {
290 moisture_model = amrex::toLower(moisture_model);
291 if (moisture_model !=
"satadj") {
292 amrex::Error(
"Cloud Chamber: moisture_model must be omitted for dry mode or set to SatAdj");
295 }
else if (
pp_prob.contains(
"qv_bottom") ||
pp_prob.contains(
"qv_top")) {
296 amrex::Error(
"Cloud Chamber: qv_bottom/qv_top require erf.moisture_model = SatAdj");
299 std::string init_mode;
300 pp_prob.get(
"thermodynamic_initialization", init_mode);
301 init_mode = amrex::toLower(init_mode);
302 if (init_mode ==
"physical_temperature_rh") {
303 config.physical_initialization =
true;
306 if (amrex::toLower(
init_type) !=
"constantdensity") {
307 amrex::Error(
"Cloud Chamber: physical initialization requires the existing HSE-backed erf.init_type = ConstantDensity path");
309 std::string molec_diff_type;
310 pp_erf.get(
"molec_diff_type", molec_diff_type);
311 if (amrex::toLower(molec_diff_type) !=
"constantalpha") {
312 amrex::Error(
"Cloud Chamber: physical wall transfer requires erf.molec_diff_type = ConstantAlpha");
314 bool vert_implicit =
false;
315 pp_erf.query(
"vert_implicit", vert_implicit);
317 amrex::Error(
"Cloud Chamber: physical wall transfer requires erf.vert_implicit = false");
319 pp_prob.get(
"initial_temperature_bottom",
config.initial_temperature_bottom);
320 pp_prob.get(
"initial_temperature_top",
config.initial_temperature_top);
321 const bool has_initial_relative_humidity =
pp_prob.contains(
"initial_relative_humidity");
322 auto physical_contract_error = [&](
bool has_legacy_profile_keys) {
326 has_initial_relative_humidity,
327 has_legacy_profile_keys,
331 std::string contract_error = physical_contract_error(
false);
332 if (!contract_error.empty()) {
333 amrex::Error(contract_error);
336 pp_prob.get(
"initial_relative_humidity",
config.initial_relative_humidity);
338 pp_prob.query(
"temperature_perturbation_amplitude",
339 config.temperature_perturbation_amplitude);
340 const bool has_legacy_profile_keys =
pp_prob.contains(
"theta_bottom") ||
341 pp_prob.contains(
"theta_top") ||
342 pp_prob.contains(
"theta_perturbation_amplitude") ||
343 pp_prob.contains(
"qv_bottom") ||
345 contract_error = physical_contract_error(has_legacy_profile_keys);
346 if (!contract_error.empty()) {
347 amrex::Error(contract_error);
352 config.temperature_perturbation_amplitude);
355 amrex::Error(
"Cloud Chamber: physical initial temperatures must be positive");
361 amrex::Error(
"Cloud Chamber: initial_relative_humidity must lie in [0,1]");
364 }
else if (init_mode ==
"legacy_theta_qv") {
365 config.physical_initialization =
false;
371 pp_prob.contains(
"initial_temperature_bottom") ||
372 pp_prob.contains(
"initial_temperature_top") ||
373 pp_prob.contains(
"initial_relative_humidity") ||
374 pp_prob.contains(
"temperature_perturbation_amplitude")};
376 if (!contract_error.empty()) {
377 amrex::Error(contract_error);
381 pp_prob.query(
"theta_perturbation_amplitude",
382 config.theta_perturbation_amplitude);
384 amrex::Error(
"Cloud Chamber: prob.thermodynamic_initialization must be physical_temperature_rh or legacy_theta_qv");
386 std::string perturbation_mode =
"deterministic_sine";
387 pp_prob.query(
"perturbation_mode", perturbation_mode);
388 if (amrex::toLower(perturbation_mode) !=
"deterministic_sine") {
389 amrex::Error(
"Cloud Chamber: prob.perturbation_mode must be deterministic_sine");
391 if (!
config.physical_initialization) {
395 config.theta_perturbation_amplitude);
398 amrex::Error(
"Cloud Chamber: theta profile values must be positive");
402 if (moisture_specified) {
403 if (!
config.physical_initialization) {
410 amrex::Error(
"Cloud Chamber: qv profile values must be nonnegative");
417 for (
int n = 0; n < 2 * AMREX_SPACEDIM; ++n) {
418 const std::string face =
face_name(faces[n]);
419 amrex::ParmParse pp_face(face.c_str());
422 if (!pp_face.query(
"type", type) ||
423 amrex::toLower(type) !=
"noslipwall") {
424 amrex::Error(
"Cloud Chamber: " + face +
".type must be NoSlipWall");
427 wall.
face = faces[n];
428 if (
config.physical_initialization) {
429 if (pp_face.contains(
"theta") || pp_face.contains(
"qv") ||
430 pp_face.contains(
"theta_grad") || pp_face.contains(
"qv_grad")) {
431 amrex::Error(
"Cloud Chamber: physical walls cannot combine temperature/moisture with legacy theta/qv wall keys");
434 amrex::Error(
"Cloud Chamber: missing required physical " + face +
".temperature");
438 amrex::Error(
"Cloud Chamber: " + face +
".temperature must be positive Kelvin");
440 std::string moisture;
441 pp_face.get(
"moisture", moisture);
442 moisture = amrex::toLower(moisture);
443 if (moisture ==
"dry") {
445 }
else if (moisture ==
"wet") {
448 amrex::Error(
"Cloud Chamber: wet walls require SatAdj moisture");
451 amrex::Error(
"Cloud Chamber: " + face +
".moisture must be dry or wet");
453 std::string transfer =
"resolved_molecular";
454 pp_face.query(
"wall_transfer_model", transfer);
455 transfer = amrex::toLower(transfer);
456 if (transfer !=
"resolved_molecular") {
457 amrex::Error(
"Cloud Chamber: only resolved_molecular wall transfer is supported");
462 if (!pp_face.query(
"theta",
config.walls[n].thermodynamics.physical_temperature_K)) {
463 amrex::Error(
"Cloud Chamber: missing required " + face +
".theta");
469 if (
config.cloudy && !pp_face.contains(
"qv")) {
470 amrex::Error(
"Cloud Chamber: legacy cloudy walls require " + face +
".qv");
473 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
474 if (pp_face.contains(
"velocity")) {
475 amrex::Error(
"Cloud Chamber: wall velocity metadata is not active; use NoSlipWall with zero velocity");
480 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
483 amrex::Error(
"Cloud Chamber: every rectangular dimension must be finite and positive");
constexpr amrex::Real p_0
Definition: ERF_Constants.H:61
constexpr amrex::Real RdoRv
Definition: ERF_Constants.H:57
Real value
Definition: ERF_HurricaneDiagnostics.cpp:30
auto probhi
Definition: ERF_InitCustomPertVels_ABL.H:21
auto problo
Definition: ERF_InitCustomPertVels_ABL.H:20
InitType init_type
Definition: ERF_InitCustomPertVels_EkmanSpiral.H:3
ParmParse pp_prob("prob")
const Real length
Definition: ERF_InitCustomPert_AnelasticWallDiffusion.H:14
const auto config
Definition: ERF_InitCustomPert_CloudChamber.H:37
amrex::Real p_inf
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:5
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real erf_esatw(amrex::Real t, bool use_empirical=false)
Definition: ERF_MicrophysicsUtils.H:123
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Definition: ERF_CloudChamber.H:18
void require_zero_periodicity()
Definition: ERF_CloudChamber.H:197
WallFace
Definition: ERF_CloudChamber.H:27
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
Definition: ERF_CloudChamber.H:114
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real qv_at(const Config &config, amrex::Real z) noexcept
Definition: ERF_CloudChamber.H:151
std::string face_name(WallFace face)
Definition: ERF_CloudChamber.H:184
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
Definition: ERF_CloudChamber.H:161
Config parse_config(const amrex::Real *problo, const amrex::Real *probhi)
Definition: ERF_CloudChamber.H:243
void require_finite(const std::string &key, amrex::Real value)
Definition: ERF_CloudChamber.H:177
InitializationMode
Definition: ERF_CloudChamber.H:62
void reject_unsupported_face_keys(amrex::ParmParse &pp, const std::string &face)
Definition: ERF_CloudChamber.H:232
void require_no_unsupported_geometry()
Definition: ERF_CloudChamber.H:212
AMREX_FORCE_INLINE AMREX_GPU_HOST_DEVICE amrex::Real theta_at(const Config &config, amrex::Real x, amrex::Real y, amrex::Real z) noexcept
Definition: ERF_CloudChamber.H:129
std::string initialization_contract_error(const InitializationContract &contract)
Definition: ERF_CloudChamber.H:76
bool finite(amrex::Real value) noexcept
Definition: ERF_CloudChamber.H:172
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
Definition: ERF_CloudChamber.H:104
@ FixedPhysicalTemperature
amrex::GpuArray< Face, 2 *AMREX_SPACEDIM > Boundary
Definition: ERF_WallThermodynamics.H:34
real(c_double), parameter, private pi
Definition: ERF_module_mp_morr_two_moment.F90:100
Definition: ERF_CloudChamber.H:35
amrex::Real qv_top
Definition: ERF_CloudChamber.H:47
amrex::GpuArray< WallSpec, 2 *AMREX_SPACEDIM > walls
Definition: ERF_CloudChamber.H:50
erf_wall_thermodynamics::Boundary wall_boundary() const noexcept
Definition: ERF_CloudChamber.H:52
bool active
Definition: ERF_CloudChamber.H:36
amrex::Real initial_temperature_top
Definition: ERF_CloudChamber.H:40
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > prob_hi
Definition: ERF_CloudChamber.H:49
bool cloudy
Definition: ERF_CloudChamber.H:37
amrex::Real qv_bottom
Definition: ERF_CloudChamber.H:46
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > prob_lo
Definition: ERF_CloudChamber.H:48
amrex::Real initial_temperature_bottom
Definition: ERF_CloudChamber.H:39
amrex::Real temperature_perturbation_amplitude
Definition: ERF_CloudChamber.H:42
amrex::Real theta_perturbation_amplitude
Definition: ERF_CloudChamber.H:45
amrex::Real theta_bottom
Definition: ERF_CloudChamber.H:43
amrex::Real initial_relative_humidity
Definition: ERF_CloudChamber.H:41
amrex::Real theta_top
Definition: ERF_CloudChamber.H:44
bool physical_initialization
Definition: ERF_CloudChamber.H:38
Definition: ERF_CloudChamber.H:67
InitializationMode mode
Definition: ERF_CloudChamber.H:68
bool has_initial_relative_humidity
Definition: ERF_CloudChamber.H:70
bool has_physical_profile_keys
Definition: ERF_CloudChamber.H:72
bool has_legacy_profile_keys
Definition: ERF_CloudChamber.H:71
bool cloudy
Definition: ERF_CloudChamber.H:69
Definition: ERF_CloudChamber.H:29
erf_wall_thermodynamics::Face thermodynamics
Definition: ERF_CloudChamber.H:31
WallFace face
Definition: ERF_CloudChamber.H:30
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > velocity
Definition: ERF_CloudChamber.H:32
Definition: ERF_WallThermodynamics.H:27
MoistureMode moisture_mode
Definition: ERF_WallThermodynamics.H:29
ThermalMode thermal_mode
Definition: ERF_WallThermodynamics.H:28
TransferModel transfer_model
Definition: ERF_WallThermodynamics.H:30
amrex::Real physical_temperature_K
Definition: ERF_WallThermodynamics.H:31