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>
35 amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>
velocity = {};
54 amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>
prob_lo = {};
55 amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>
prob_hi = {};
56 amrex::GpuArray<WallSpec, 2 * AMREX_SPACEDIM>
walls = {};
65 for (
int n = 0; n < 2 * AMREX_SPACEDIM; ++n) {
66 boundary[n] =
walls[n].thermodynamics;
96 switch (contract.
mode) {
100 return "Cloud Chamber: physical SatAdj initialization requires prob.initial_relative_humidity";
103 return "Cloud Chamber: prob.initial_relative_humidity is only used with erf.moisture_model = SatAdj";
106 return "Cloud Chamber: physical_temperature_rh cannot be combined with legacy theta/qv profile keys";
111 return "Cloud Chamber: legacy_theta_qv cannot be combined with physical temperature/RH profile keys";
129 AMREX_GPU_HOST_DEVICE
135 return bottom + (top - bottom) * (coordinate - lower) /
length;
149 AMREX_GPU_HOST_DEVICE
152 const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>& lo,
153 const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>&
length,
160 return amplitude * sx * sy * sz;
172 AMREX_GPU_HOST_DEVICE
177 const amrex::GpuArray<amrex::Real, AMREX_SPACEDIM>
length = {
182 config.initial_temperature_bottom :
config.theta_bottom;
186 config.temperature_perturbation_amplitude :
config.theta_perturbation_amplitude;
200 AMREX_GPU_HOST_DEVICE
217 AMREX_GPU_HOST_DEVICE
226 const amrex::Real ev_Pa = relative_humidity * es_Pa;
227 return RdoRv * ev_Pa / (pressure_Pa - ev_Pa);
232 return std::isfinite(
static_cast<double>(value));
238 amrex::Error(
"Cloud Chamber: " + key +
" must be finite");
257 amrex::ParmParse pp_geom(
"geometry");
259 pp_geom.getarr(
"is_periodic",
periodic);
260 if (
periodic.size() != AMREX_SPACEDIM) {
261 amrex::Error(
"Cloud Chamber: geometry.is_periodic must provide three values");
263 for (
int d = 0; d < AMREX_SPACEDIM; ++d) {
265 amrex::Error(
"Cloud Chamber: geometry.is_periodic must be 0 0 0");
272 amrex::ParmParse pp_erf(
"erf");
274 if (pp_erf.query(
"terrain_type", value) &&
275 amrex::toLower(value) !=
"none") {
276 amrex::Error(
"Cloud Chamber: erf.terrain_type must be None; terrain and embedded boundaries are out of scope");
279 if (pp_erf.query(
"buildings_type", value) &&
280 amrex::toLower(value) !=
"none") {
281 amrex::Error(
"Cloud Chamber: erf.buildings_type must be None; immersed buildings are out of scope");
284 if (pp_erf.query(
"mesh_type", value) &&
285 amrex::toLower(value) !=
"constantdz") {
286 amrex::Error(
"Cloud Chamber: erf.mesh_type must be ConstantDz");
291 const std::string& face)
293 if (
pp.contains(
"physical_temperature") ||
pp.contains(
"relative_humidity") ||
294 pp.contains(
"rh") ||
pp.contains(
"roughness")) {
295 amrex::Error(
"Cloud Chamber: " + face +
296 " must use temperature and moisture for physical walls; "
297 "roughness and legacy aliases are unsupported");
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) {
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;
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");
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");
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");
492 amrex::Error(
"Cloud Chamber: missing required physical " + face +
".temperature");
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");
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
constexpr amrex::Real RdoRv
Definition: ERF_Constants.H:57
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 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:255
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
Compute a deterministic 3D sine perturbation.
Definition: ERF_CloudChamber.H:151
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.
Definition: ERF_CloudChamber.H:202
std::string face_name(WallFace face)
Definition: ERF_CloudChamber.H:242
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.
Definition: ERF_CloudChamber.H:219
Config parse_config(const amrex::Real *problo, const amrex::Real *probhi)
Definition: ERF_CloudChamber.H:301
void require_finite(const std::string &key, amrex::Real value)
Definition: ERF_CloudChamber.H:235
InitializationMode
Definition: ERF_CloudChamber.H:72
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
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.
Definition: ERF_CloudChamber.H:174
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
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:131
@ 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
Configuration parameters for cloud chamber initialization.
Definition: ERF_CloudChamber.H:41
amrex::Real qv_top
Definition: ERF_CloudChamber.H:53
amrex::GpuArray< WallSpec, 2 *AMREX_SPACEDIM > walls
Definition: ERF_CloudChamber.H:56
erf_wall_thermodynamics::Boundary wall_boundary() const noexcept
Collect thermodynamic boundary conditions for all chamber walls.
Definition: ERF_CloudChamber.H:62
bool active
Definition: ERF_CloudChamber.H:42
amrex::Real initial_temperature_top
Definition: ERF_CloudChamber.H:46
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > prob_hi
Definition: ERF_CloudChamber.H:55
bool cloudy
Definition: ERF_CloudChamber.H:43
amrex::Real qv_bottom
Definition: ERF_CloudChamber.H:52
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > prob_lo
Definition: ERF_CloudChamber.H:54
amrex::Real initial_temperature_bottom
Definition: ERF_CloudChamber.H:45
amrex::Real temperature_perturbation_amplitude
Definition: ERF_CloudChamber.H:48
amrex::Real theta_perturbation_amplitude
Definition: ERF_CloudChamber.H:51
amrex::Real theta_bottom
Definition: ERF_CloudChamber.H:49
amrex::Real initial_relative_humidity
Definition: ERF_CloudChamber.H:47
amrex::Real theta_top
Definition: ERF_CloudChamber.H:50
bool physical_initialization
Definition: ERF_CloudChamber.H:44
Contract used to validate initialization parameter consistency.
Definition: ERF_CloudChamber.H:80
InitializationMode mode
Definition: ERF_CloudChamber.H:81
bool has_initial_relative_humidity
Definition: ERF_CloudChamber.H:83
bool has_physical_profile_keys
Definition: ERF_CloudChamber.H:85
bool has_legacy_profile_keys
Definition: ERF_CloudChamber.H:84
bool cloudy
Definition: ERF_CloudChamber.H:82
Metadata describing a chamber wall.
Definition: ERF_CloudChamber.H:32
erf_wall_thermodynamics::Face thermodynamics
Definition: ERF_CloudChamber.H:34
WallFace face
Definition: ERF_CloudChamber.H:33
amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > velocity
Definition: ERF_CloudChamber.H:35
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