ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_TwoStreamColumn.H File Reference

Per-column two-stream radiation kernels. More...

#include <algorithm>
#include <cmath>
#include <type_traits>
#include <AMReX_Array4.H>
#include <AMReX_Box.H>
#include <AMReX_Geometry.H>
#include <AMReX_GpuControl.H>
#include <AMReX_Math.H>
#include <AMReX_REAL.H>
#include <ERF_Constants.H>
#include <ERF_EOS.H>
#include <ERF_IndexDefines.H>
#include <ERF_RadStruct.H>
#include <ERF_TwoStreamSW.H>
#include <ERF_TwoStreamLW.H>
#include <ERF_PrognosticCloudFraction.H>
#include <ERF_AerosolOpticalDepth.H>
#include <ERF_OrbCosZenith.H>
#include <ERF_SurfaceTemperature.H>
#include <ERF_TerrainMetrics.H>
Include dependency graph for ERF_TwoStreamColumn.H:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Classes

struct  TwoStreamParams
 Trivially copyable subset of RadChoice used by the column kernels. More...
 

Namespaces

 TwoStreamScratch
 Per-column scratch the sweep keeps in a caller-provided Array4.
 

Functions

TwoStreamParams make_two_stream_params (const RadChoice &rc, const amrex::Real rdOcp)
 Copy the column-kernel parameters out of a RadChoice. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_temperature_from_rhotheta (amrex::Real rho_theta, amrex::Real rho, amrex::Real qv=0.0)
 GPU-safe helper to compute absolute temperature from (rho, rho*theta). More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool valid_surface_temperature (amrex::Real value)
 Check whether a heterogeneous absolute surface temperature is valid. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real resolve_surface_albedo_sw (int i, int j, const amrex::Array4< const amrex::Real > *hetero_alb_sw, const TwoStreamParams &rad_choice, bool has_hetero_alb)
 Resolve per-column shortwave surface albedo from hetero field or fallback. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real resolve_surface_emissivity_lw (int i, int j, const amrex::Array4< const amrex::Real > *hetero_emiss_lw, const TwoStreamParams &rad_choice, bool has_hetero_emiss)
 Resolve per-column longwave surface emissivity from hetero field or fallback. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real resolve_surface_temp_k (int i, int j, const amrex::Array4< const amrex::Real > *lsm_t_sfc, bool has_lsm_t_sfc, const amrex::Array4< const amrex::Real > *seb_t_sfc, bool has_seb_t_sfc, const amrex::Array4< const amrex::Real > *surface_layer_theta, bool has_surface_layer, const TwoStreamParams &rad_choice, bool &from_surface_layer)
 Resolve the per-column surface-temperature boundary condition. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool is_cloud_level (amrex::Real z_center, const TwoStreamParams &rad_choice)
 GPU-safe helper to determine whether a layer falls within the cloud band [cloud_base_height_m, cloud_top_height_m]. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_qv_from_state (int i, int j, int k, const amrex::Array4< const amrex::Real > &state_arr)
 Water-vapor mixing ratio qv = RhoQv / Rho at (i,j,k). More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_inverse_exner (int i, int j, int k, const amrex::Array4< const amrex::Real > &state_arr)
 Inverse Exner function 1/pi at (i,j,k) from the state, with pi = (p / p_0)^(R_d/c_p) evaluated by getExnergivenRTh(). Used to convert a temperature tendency dT/dt into the potential-temperature tendency dtheta/dt = (dT/dt) / pi that the RhoTheta source term expects. Returns 1 for unphysical input. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_qc_from_state (int i, int j, int k, const amrex::Array4< const amrex::Real > &state_arr)
 Cloud-water mixing ratio qc = RhoQc / Rho at (i,j,k). More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_tau_dynamic (amrex::Real tau_base, amrex::Real qv, amrex::Real qc, amrex::Real coeff_qv, amrex::Real coeff_qc)
 Moisture-dependent (dynamic) per-layer optical depth. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real tau_layer_value (amrex::Real z_center, amrex::Real tau_base, const TwoStreamParams &rad_choice, bool apply_cloud)
 GPU-safe helper to compute the per-layer optical depth at level k, given the base (clear-sky) optical depth and cloud-layer parameters. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void select_scattering_props (amrex::Real z_center, const TwoStreamParams &rad_choice, bool apply_cloud, amrex::Real &omega, amrex::Real &g)
 GPU-safe helper to select the single-scattering albedo and asymmetry factor to use for level k's diffuse SW calculation. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_cloud_fraction_prognostic (int i, int j, int k, const amrex::Array4< const amrex::Real > &state_arr, const TwoStreamParams &rad_choice)
 GPU-safe helper to diagnose prognostic cloud fraction from per-level relative humidity and cloud liquid water. More...
 
amrex::Box two_stream_scratch_box (const amrex::Box &bx)
 Box of the scratch FArrayBox a sweep over box bx needs. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_layer_tau (int i, int j, int k, amrex::Real dz_layer, amrex::Real z_center, const amrex::Array4< const amrex::Real > &state_arr, amrex::Real tau_base, bool is_sw, bool cloudy, const TwoStreamParams &rad_choice)
 GPU-safe helper to assemble the total per-layer optical depth at level k for either the SW or the LW band. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void diagnose_layer_optics (int i, int j, int k, amrex::Real dz_layer, amrex::Real z_center, const amrex::Array4< const amrex::Real > &state_arr, amrex::Real tau_base, bool is_sw, bool cloudy, const TwoStreamParams &rad_choice, amrex::Real &tau, amrex::Real &omega, amrex::Real &g)
 Optical depth, single-scattering albedo and asymmetry factor of layer k for one band, for either optical-depth model. More...
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void vertical_two_stream_sweep (int i, int j, const amrex::Box &bx, amrex::Real dz_uniform, const amrex::Array4< const amrex::Real > &state_arr, const TwoStreamParams &rad_choice, bool cloudy, const amrex::Array4< amrex::Real > &qheating_arr, amrex::Real &max_heating_rate, amrex::Real &sw_surface_flux, amrex::Real &sw_up_toa, amrex::Real &lw_net_surface, amrex::Real &lw_up_toa, amrex::Real &sw_down_toa, const amrex::Array4< const amrex::Real > &z_phys_nd, const amrex::Array4< amrex::Real > &scratch, bool has_hetero_alb_sw=false, const amrex::Array4< const amrex::Real > *hetero_alb_sw=nullptr, bool has_hetero_emiss_lw=false, const amrex::Array4< const amrex::Real > *hetero_emiss_lw=nullptr, bool has_lsm_t_sfc=false, const amrex::Array4< const amrex::Real > *lsm_t_sfc=nullptr, bool has_seb_t_sfc=false, const amrex::Array4< const amrex::Real > *seb_t_sfc=nullptr, bool has_surface_layer=false, const amrex::Array4< const amrex::Real > *surface_layer_theta=nullptr, bool has_latlon=false, const amrex::Array4< const amrex::Real > *lat_arr=nullptr, const amrex::Array4< const amrex::Real > *lon_arr=nullptr, const amrex::Array4< amrex::Real > *rad_flux_out=nullptr)
 GPU-safe per-column vertical integration kernel for two-stream radiation, computing either the clear-sky or cloudy-column fluxes and per-level heating rates, depending on the cloudy flag. More...
 

Variables

constexpr int TwoStreamScratch::DZ = 0
 
constexpr int TwoStreamScratch::Z = 1
 
constexpr int TwoStreamScratch::TAU_SW = 2
 
constexpr int TwoStreamScratch::F_DIR = 3
 
constexpr int TwoStreamScratch::A = 4
 
constexpr int TwoStreamScratch::S = 5
 
constexpr int TwoStreamScratch::LW_UP = 6
 
constexpr int TwoStreamScratch::LW_DN = 7
 
constexpr int TwoStreamScratch::TAU_LW = 8
 
constexpr int TwoStreamScratch::NCOMP = 9
 

Detailed Description

Per-column two-stream radiation kernels.

Everything needed to evaluate one (i,j) column of the two-stream model: temperature from the equation of state, per-column surface properties, per-layer optical depth assembly, and vertical_two_stream_sweep(), which performs the SW and LW sweeps and writes per-level heating rates.

Vertical orientation follows ERF: k = kmin is the surface layer and k = kmax the top layer. SW sweeps downward from kmax to kmin; LW sweeps downward (TOA -> surface) and then upward (surface -> TOA) on layer interfaces. Layer temperature is obtained from rho*theta through the Exner function.

The functions are header-only and GPU-safe so that the column physics can be exercised directly by unit tests (Tests/Unit/Radiation) as well as by the level driver in ERF_TwoStreamRadiation.cpp (TwoStreamRadiation::advance).

Function Documentation

◆ diagnose_cloud_fraction_prognostic()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_cloud_fraction_prognostic ( int  i,
int  j,
int  k,
const amrex::Array4< const amrex::Real > &  state_arr,
const TwoStreamParams rad_choice 
)

GPU-safe helper to diagnose prognostic cloud fraction from per-level relative humidity and cloud liquid water.

Computes cloud fraction from RH and qc using: cf_rh(k) = linear ramp from 0 at rh_min to 1 at rh_max cf_qc(k) = qc_scale * qc(k) cf(k) = min(1, cf_rh + cf_qc) [saturated blend]

Temperature comes from the equation of state (Exner function) and pressure from getPgivenRTh(), both evaluated with the local qv. Returns 0 when cloud_fraction_prog_enable is false.

Parameters
[in]i,j,kGrid indices
[in]state_arrState array proxy (contains Rho, RhoTheta, qv, qc)
[in]rad_choiceRadiation parameters (prognostic cloud fraction settings)
Returns
Diagnosed cloud fraction [0, 1] if enabled; 0 if disabled
555 {
556  if (!rad_choice.cloud_fraction_prog_enable) {
557  return 0.0;
558  }
559 
560  const amrex::Real qv = get_qv_from_state(i, j, k, state_arr);
561  const amrex::Real qc = get_qc_from_state(i, j, k, state_arr);
562 
563  amrex::Real rho = state_arr(i, j, k, Rho_comp);
564  amrex::Real rho_theta = state_arr(i, j, k, RhoTheta_comp);
565  // Unreachable from TwoStreamRadiation::advance, which refuses a state with
566  // a non-finite or non-positive density or rho*theta before the sweep;
567  // kept so a direct kernel caller (unit tests) cannot divide by zero.
568  if (rho <= 0.0 || !amrex::Math::isfinite(rho)) rho = 1.0;
569  if (rho_theta <= 0.0 || !amrex::Math::isfinite(rho_theta)) rho_theta = 288.15;
570 
571  // Temperature (via the Exner function) and pressure from the equation of state
573  amrex::Real P = getPgivenRTh(rho_theta, qv);
574  if (P <= 0.0 || !amrex::Math::isfinite(P)) P = p_0;
575 
577 
579  rh, qc,
580  rad_choice.cloud_fraction_rh_min,
581  rad_choice.cloud_fraction_rh_max,
582  rad_choice.cloud_fraction_qc_scale);
583 }
constexpr amrex::Real p_0
Definition: ERF_Constants.H:53
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real getPgivenRTh(const amrex::Real rhotheta, const amrex::Real qv=amrex::Real(0))
Definition: ERF_EOS.H:81
#define Rho_comp
Definition: ERF_IndexDefines.H:39
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:40
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_relative_humidity(amrex::Real qv, amrex::Real T, amrex::Real P)
Compute relative humidity from water vapor mixing ratio.
Definition: ERF_PrognosticCloudFraction.H:36
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_cloud_fraction_from_rh_qc(amrex::Real rh, amrex::Real qc, amrex::Real rh_min, amrex::Real rh_max, amrex::Real qc_scale)
Diagnose cloud fraction from relative humidity and cloud water.
Definition: ERF_PrognosticCloudFraction.H:98
amrex::Real Real
Definition: ERF_ShocInterface.H:19
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_qv_from_state(int i, int j, int k, const amrex::Array4< const amrex::Real > &state_arr)
Water-vapor mixing ratio qv = RhoQv / Rho at (i,j,k).
Definition: ERF_TwoStreamColumn.H:386
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_qc_from_state(int i, int j, int k, const amrex::Array4< const amrex::Real > &state_arr)
Cloud-water mixing ratio qc = RhoQc / Rho at (i,j,k).
Definition: ERF_TwoStreamColumn.H:422
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_temperature_from_rhotheta(amrex::Real rho_theta, amrex::Real rho, amrex::Real qv=0.0)
GPU-safe helper to compute absolute temperature from (rho, rho*theta).
Definition: ERF_TwoStreamColumn.H:200
@ P
Definition: ERF_IndexDefines.H:204
@ rho
Definition: ERF_Kessler.H:25
@ qv
Definition: ERF_Kessler.H:31
@ qc
Definition: ERF_SatAdj.H:42
@ T
Definition: ERF_IndexDefines.H:128
amrex::Real cloud_fraction_qc_scale
Definition: ERF_TwoStreamColumn.H:89
amrex::Real cloud_fraction_rh_max
Definition: ERF_TwoStreamColumn.H:88
bool cloud_fraction_prog_enable
Definition: ERF_TwoStreamColumn.H:86
amrex::Real cloud_fraction_rh_min
Definition: ERF_TwoStreamColumn.H:87

Referenced by diagnose_layer_optics(), and diagnose_layer_tau().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ diagnose_layer_optics()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void diagnose_layer_optics ( int  i,
int  j,
int  k,
amrex::Real  dz_layer,
amrex::Real  z_center,
const amrex::Array4< const amrex::Real > &  state_arr,
amrex::Real  tau_base,
bool  is_sw,
bool  cloudy,
const TwoStreamParams rad_choice,
amrex::Real tau,
amrex::Real omega,
amrex::Real g 
)

Optical depth, single-scattering albedo and asymmetry factor of layer k for one band, for either optical-depth model.

Per-layer model (default): the optical depth comes from diagnose_layer_tau() and the scattering properties from the clear-sky or cloud-band inputs through select_scattering_props(), exactly as before.

Mass model (tau_model = mass), shortwave: each constituent contributes an extinction optical depth from the layer mass path, dry absorption rho dz sw_kabs_dry (omega = 0) Rayleigh rho dz sw_kscat_dry (omega = 1, g = 0) water vapor rho dz sw_kabs_vapor qv (omega = 0) cloud water rho dz sw_kext_cloud qc (sw_cloud_omega, sw_cloud_g) plus the prescribed cloud-band enhancement (with the cloud-band scattering inputs) and the absorbing moisture-coefficient and aerosol additions. The layer properties are the extinction-weighted mixtures tau = sum tau_i, omega = sum omega_i tau_i / tau, g = sum g_i omega_i tau_i / sum omega_i tau_i. Longwave uses diagnose_layer_tau(), whose clear-sky base is the mass path rho dz (lw_kabs_dry + lw_kabs_vapor qv + lw_kabs_cloud qc) in this model.

Parameters
[in]i,j,kGrid indices (kmin is the surface layer).
[in]dz_layerThickness of this layer [m].
[in]z_centerHeight of the layer center above the surface [m].
[in]state_arrState array (read-only).
[in]tau_basePer-layer-model clear-sky optical depth for this band.
[in]is_swtrue for the shortwave band, false for longwave.
[in]cloudytrue for the cloudy-column evaluation.
[in]rad_choiceColumn-kernel parameters.
[out]tauOptical depth of the layer [unitless].
[out]omegaSingle-scattering albedo of the layer (0 for LW).
[out]gAsymmetry factor of the layer (0 for LW).
754 {
755  omega = 0.0;
756  g = 0.0;
757  if (!is_sw || rad_choice.tau_model != TauModel::Mass) {
758  tau = diagnose_layer_tau(i, j, k, dz_layer, z_center, state_arr, tau_base, is_sw, cloudy, rad_choice);
759  if (is_sw) {
760  select_scattering_props(z_center, rad_choice, cloudy, omega, g);
761  }
762  return;
763  }
764 
765  // Shortwave mass model: constituent extinction optical depths.
766  amrex::Real rho = state_arr(i, j, k, Rho_comp);
767  if (!(rho > 0.0) || !amrex::Math::isfinite(rho)) rho = 0.0;
768  const amrex::Real qv = get_qv_from_state(i, j, k, state_arr);
769  const amrex::Real qc = get_qc_from_state(i, j, k, state_arr);
770  const amrex::Real path = rho * dz_layer;
771 
772  const amrex::Real tau_dry_abs = path * rad_choice.sw_kabs_dry;
773  const amrex::Real tau_rayleigh = path * rad_choice.sw_kscat_dry;
774  const amrex::Real tau_vapor = path * rad_choice.sw_kabs_vapor * qv;
775  const amrex::Real tau_cloud = path * rad_choice.sw_kext_cloud * qc;
776 
777  amrex::Real ext = tau_dry_abs + tau_rayleigh + tau_vapor + tau_cloud;
778  amrex::Real sca = tau_rayleigh + rad_choice.sw_cloud_omega * tau_cloud;
779  amrex::Real gsca = rad_choice.sw_cloud_g * rad_choice.sw_cloud_omega * tau_cloud;
780 
781  // Prescribed cloud band (optionally scaled by the prognostic cloud
782  // fraction) with the cloud-band scattering inputs.
783  amrex::Real band = tau_layer_value(z_center, 0.0, rad_choice, cloudy);
784  if (rad_choice.cloud_fraction_prog_enable && cloudy &&
785  rad_choice.tau_profile_type == TauProfileType::CloudLayer &&
786  is_cloud_level(z_center, rad_choice)) {
787  band = diagnose_cloud_fraction_prognostic(i, j, k, state_arr, rad_choice) * rad_choice.cloud_tau_per_layer;
788  }
789  if (band > 0.0) {
790  ext += band;
791  sca += rad_choice.cloud_single_scattering_albedo * band;
792  gsca += rad_choice.cloud_asymmetry_factor * rad_choice.cloud_single_scattering_albedo * band;
793  }
794 
795  // Absorbing additions (moisture coefficients, aerosol): everything that
796  // diagnose_layer_tau() adds on top of a zero base, minus the band.
797  const amrex::Real additions = diagnose_layer_tau(i, j, k, dz_layer, z_center, state_arr,
798  0.0, /*is_sw=*/true, cloudy, rad_choice) - band;
799  if (additions > 0.0) ext += additions;
800 
801  if (!amrex::Math::isfinite(ext) || ext <= 0.0) {
802  tau = 0.0;
803  return;
804  }
805  tau = ext;
806  omega = sca / ext;
807  if (omega > 1.0) omega = 1.0;
808  if (omega < 0.0) omega = 0.0;
809  g = (sca > 0.0) ? gsca / sca : 0.0;
810  if (g > 1.0) g = 1.0;
811  if (g < -1.0) g = -1.0;
812 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_cloud_fraction_prognostic(int i, int j, int k, const amrex::Array4< const amrex::Real > &state_arr, const TwoStreamParams &rad_choice)
GPU-safe helper to diagnose prognostic cloud fraction from per-level relative humidity and cloud liqu...
Definition: ERF_TwoStreamColumn.H:551
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real tau_layer_value(amrex::Real z_center, amrex::Real tau_base, const TwoStreamParams &rad_choice, bool apply_cloud)
GPU-safe helper to compute the per-layer optical depth at level k, given the base (clear-sky) optical...
Definition: ERF_TwoStreamColumn.H:480
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool is_cloud_level(amrex::Real z_center, const TwoStreamParams &rad_choice)
GPU-safe helper to determine whether a layer falls within the cloud band [cloud_base_height_m,...
Definition: ERF_TwoStreamColumn.H:373
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void select_scattering_props(amrex::Real z_center, const TwoStreamParams &rad_choice, bool apply_cloud, amrex::Real &omega, amrex::Real &g)
GPU-safe helper to select the single-scattering albedo and asymmetry factor to use for level k's diff...
Definition: ERF_TwoStreamColumn.H:515
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_layer_tau(int i, int j, int k, amrex::Real dz_layer, amrex::Real z_center, const amrex::Array4< const amrex::Real > &state_arr, amrex::Real tau_base, bool is_sw, bool cloudy, const TwoStreamParams &rad_choice)
GPU-safe helper to assemble the total per-layer optical depth at level k for either the SW or the LW ...
Definition: ERF_TwoStreamColumn.H:645
@ omega
Definition: ERF_Morrison.H:55
real(c_double), parameter g
Definition: ERF_module_model_constants.F90:19
amrex::Real sw_cloud_omega
Definition: ERF_TwoStreamColumn.H:102
amrex::Real cloud_single_scattering_albedo
Definition: ERF_TwoStreamColumn.H:70
amrex::Real sw_kabs_vapor
Definition: ERF_TwoStreamColumn.H:100
TauModel tau_model
Definition: ERF_TwoStreamColumn.H:97
amrex::Real cloud_tau_per_layer
Definition: ERF_TwoStreamColumn.H:66
TauProfileType tau_profile_type
Definition: ERF_TwoStreamColumn.H:63
amrex::Real sw_kext_cloud
Definition: ERF_TwoStreamColumn.H:101
amrex::Real sw_kabs_dry
Definition: ERF_TwoStreamColumn.H:98
amrex::Real sw_kscat_dry
Definition: ERF_TwoStreamColumn.H:99
amrex::Real cloud_asymmetry_factor
Definition: ERF_TwoStreamColumn.H:71
amrex::Real sw_cloud_g
Definition: ERF_TwoStreamColumn.H:103

Referenced by vertical_two_stream_sweep().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ diagnose_layer_tau()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_layer_tau ( int  i,
int  j,
int  k,
amrex::Real  dz_layer,
amrex::Real  z_center,
const amrex::Array4< const amrex::Real > &  state_arr,
amrex::Real  tau_base,
bool  is_sw,
bool  cloudy,
const TwoStreamParams rad_choice 
)

GPU-safe helper to assemble the total per-layer optical depth at level k for either the SW or the LW band.

The contributions are applied in the same order for both bands:

  1. clear-sky base value, plus the cloud-layer enhancement when the column is evaluated as "cloudy" and the level lies inside the cloud band;
  2. optional moisture-dependent (dynamic) term from qv and qc;
  3. optional prognostic cloud fraction, which replaces the cloud-band enhancement by cf(k) * cloud_tau_per_layer;
  4. optional prescribed aerosol term (constant, exponential or table).
Parameters
[in]i,j,kGrid indices (k increases upward; kmin is the surface layer).
[in]dz_layerThickness of this layer [m].
[in]z_centerHeight of the layer center above the surface [m] (cloud-band detection and the aerosol profile).
[in]state_arrState array (read-only).
[in]tau_baseClear-sky optical depth per layer for this band (for LW, replaced by the mass-path value when lw_mass_absorption_enable or tau_model = mass is set).
[in]is_swtrue for the shortwave band, false for longwave.
[in]cloudytrue for the cloudy-column evaluation.
[in]rad_choiceRadiation parameters.
Returns
Optical depth of layer k [unitless].
651 {
652  // Longwave option: gray optical depth from the layer mass path,
653  // tau = rho dz (k_dry + k_vapor qv + k_cloud qc),
654  // which makes the column optical depth independent of the vertical
655  // resolution and gives water vapor and cloud water a real greenhouse
656  // effect. Replaces the fixed tau_lw_per_layer as the clear-sky base.
657  amrex::Real base = tau_base;
658  if (!is_sw && (rad_choice.lw_mass_absorption_enable || rad_choice.tau_model == TauModel::Mass)) {
659  amrex::Real rho = state_arr(i, j, k, Rho_comp);
660  if (!(rho > 0.0) || !amrex::Math::isfinite(rho)) rho = 0.0;
661  const amrex::Real qv = get_qv_from_state(i, j, k, state_arr);
662  const amrex::Real qc = get_qc_from_state(i, j, k, state_arr);
663  base = rho * dz_layer * (rad_choice.lw_kabs_dry
664  + rad_choice.lw_kabs_vapor * qv
665  + rad_choice.lw_kabs_cloud * qc);
666  if (!amrex::Math::isfinite(base) || base < 0.0) base = 0.0;
667  }
668 
669  // Base (clear-sky) value, plus cloud-band enhancement for cloudy columns.
670  amrex::Real tau = tau_layer_value(z_center, base, rad_choice, cloudy);
671 
672  // Dynamic (moisture-dependent) contribution.
673  const bool dynamic_enabled = is_sw ? rad_choice.tau_sw_dynamic_enable
674  : rad_choice.tau_lw_dynamic_enable;
675  if (dynamic_enabled) {
676  const amrex::Real qv = get_qv_from_state(i, j, k, state_arr);
677  const amrex::Real qc = get_qc_from_state(i, j, k, state_arr);
678  tau = is_sw ? diagnose_tau_dynamic(tau, qv, qc, rad_choice.tau_sw_coeff_qv, rad_choice.tau_sw_coeff_qc)
679  : diagnose_tau_dynamic(tau, qv, qc, rad_choice.tau_lw_coeff_qv, rad_choice.tau_lw_coeff_qc);
680  }
681 
682  // Prognostic cloud fraction: scale the cloud-band enhancement by cf(k).
683  if (rad_choice.cloud_fraction_prog_enable && cloudy &&
684  rad_choice.tau_profile_type == TauProfileType::CloudLayer &&
685  is_cloud_level(z_center, rad_choice)) {
686  // tau already holds the full cloud-band enhancement (this is a cloudy
687  // column inside the band) plus the dynamic term; scale only the
688  // enhancement by cf(k) and leave the dynamic term in place.
689  amrex::Real cf_prog = diagnose_cloud_fraction_prognostic(i, j, k, state_arr, rad_choice);
690  tau += (cf_prog - 1.0) * rad_choice.cloud_tau_per_layer;
691  }
692 
693  // Prescribed bulk aerosol contribution (added on top of everything above).
694  if (rad_choice.aerosol_enable) {
695  amrex::Real tau_aerosol = 0.0;
696  if (rad_choice.aerosol_profile_type == AerosolProfileType::Constant) {
697  tau_aerosol = diagnose_tau_aerosol_constant(rad_choice.aerosol_tau_per_layer);
698  } else if (rad_choice.aerosol_profile_type == AerosolProfileType::Exponential) {
699  tau_aerosol = diagnose_tau_aerosol_exponential(z_center, dz_layer,
700  rad_choice.aerosol_tau_surface,
701  rad_choice.aerosol_scale_height_m);
702  } else if (rad_choice.aerosol_profile_type == AerosolProfileType::Table) {
703  tau_aerosol = diagnose_tau_aerosol_table(k);
704  }
705  tau += tau_aerosol;
706  }
707 
708  return tau;
709 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_tau_aerosol_constant(amrex::Real tau_aerosol_const)
Diagnose aerosol optical depth for Constant profile.
Definition: ERF_AerosolOpticalDepth.H:42
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_tau_aerosol_exponential(amrex::Real z_level, amrex::Real dz, amrex::Real tau_surface, amrex::Real scale_height_m)
Diagnose aerosol optical depth for Exponential profile.
Definition: ERF_AerosolOpticalDepth.H:77
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_tau_aerosol_table(int k)
Diagnose aerosol optical depth for the Table profile.
Definition: ERF_AerosolOpticalDepth.H:123
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_tau_dynamic(amrex::Real tau_base, amrex::Real qv, amrex::Real qc, amrex::Real coeff_qv, amrex::Real coeff_qc)
Moisture-dependent (dynamic) per-layer optical depth.
Definition: ERF_TwoStreamColumn.H:450
bool lw_mass_absorption_enable
Definition: ERF_TwoStreamColumn.H:105
amrex::Real lw_kabs_cloud
Definition: ERF_TwoStreamColumn.H:108
amrex::Real aerosol_tau_per_layer
Definition: ERF_TwoStreamColumn.H:93
bool tau_sw_dynamic_enable
Definition: ERF_TwoStreamColumn.H:79
bool aerosol_enable
Definition: ERF_TwoStreamColumn.H:91
amrex::Real aerosol_tau_surface
Definition: ERF_TwoStreamColumn.H:95
amrex::Real tau_sw_coeff_qc
Definition: ERF_TwoStreamColumn.H:82
amrex::Real aerosol_scale_height_m
Definition: ERF_TwoStreamColumn.H:94
amrex::Real lw_kabs_vapor
Definition: ERF_TwoStreamColumn.H:107
AerosolProfileType aerosol_profile_type
Definition: ERF_TwoStreamColumn.H:92
amrex::Real tau_sw_coeff_qv
Definition: ERF_TwoStreamColumn.H:81
bool tau_lw_dynamic_enable
Definition: ERF_TwoStreamColumn.H:80
amrex::Real lw_kabs_dry
Definition: ERF_TwoStreamColumn.H:106

Referenced by diagnose_layer_optics(), and vertical_two_stream_sweep().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ diagnose_tau_dynamic()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real diagnose_tau_dynamic ( amrex::Real  tau_base,
amrex::Real  qv,
amrex::Real  qc,
amrex::Real  coeff_qv,
amrex::Real  coeff_qc 
)

Moisture-dependent (dynamic) per-layer optical depth.

tau = tau_base + coeff_qv * qv + coeff_qc * qc

clamped to [0, 100]. With both coefficients zero this returns tau_base unchanged, so the static configuration is reproduced exactly. Used for both the SW and the LW band with the respective coefficient pair.

Parameters
[in]tau_baseOptical depth before the moisture contribution [unitless].
[in]qvWater-vapor mixing ratio [kg/kg].
[in]qcCloud-water mixing ratio [kg/kg].
[in]coeff_qvOptical depth per unit qv [unitless per kg/kg].
[in]coeff_qcOptical depth per unit qc [unitless per kg/kg].
Returns
Optical depth for this layer [unitless].
452 {
453  if (!(qv > 0.0) || !amrex::Math::isfinite(qv)) qv = 0.0;
454  if (!(qc > 0.0) || !amrex::Math::isfinite(qc)) qc = 0.0;
455  amrex::Real tau = tau_base + coeff_qv * qv + coeff_qc * qc;
456  if (!amrex::Math::isfinite(tau)) return tau_base;
457  if (tau < 0.0) tau = 0.0;
458  if (tau > 100.0) tau = 100.0;
459  return tau;
460 }

Referenced by diagnose_layer_tau().

Here is the caller graph for this function:

◆ get_inverse_exner()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_inverse_exner ( int  i,
int  j,
int  k,
const amrex::Array4< const amrex::Real > &  state_arr 
)

Inverse Exner function 1/pi at (i,j,k) from the state, with pi = (p / p_0)^(R_d/c_p) evaluated by getExnergivenRTh(). Used to convert a temperature tendency dT/dt into the potential-temperature tendency dtheta/dt = (dT/dt) / pi that the RhoTheta source term expects. Returns 1 for unphysical input.

407 {
408  amrex::Real rho_theta = state_arr(i, j, k, RhoTheta_comp);
409  if (!(rho_theta > 0.0) || !amrex::Math::isfinite(rho_theta)) return 1.0;
410  const amrex::Real qv = get_qv_from_state(i, j, k, state_arr);
411  const amrex::Real pi = getExnergivenRTh(rho_theta, RdoCp, qv);
412  return (amrex::Math::isfinite(pi) && pi > 0.0) ? 1.0 / pi : 1.0;
413 }
constexpr amrex::Real RdoCp
Definition: ERF_Constants.H:41
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real getExnergivenRTh(const amrex::Real rhotheta, const amrex::Real rdOcp, const amrex::Real qv=amrex::Real(0))
Definition: ERF_EOS.H:156
real(c_double), parameter, private pi
Definition: ERF_module_mp_morr_two_moment.F90:100

Referenced by vertical_two_stream_sweep().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_qc_from_state()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_qc_from_state ( int  i,
int  j,
int  k,
const amrex::Array4< const amrex::Real > &  state_arr 
)

Cloud-water mixing ratio qc = RhoQc / Rho at (i,j,k).

Returns 0 when the state carries no cloud-water component, or when the stored values are non-finite or negative.

424 {
425  if (state_arr.nComp() <= RhoQ2_comp) return 0.0;
426  amrex::Real rho = state_arr(i, j, k, Rho_comp);
427  amrex::Real rho_qc = state_arr(i, j, k, RhoQ2_comp);
428  if (!(rho > 0.0) || !amrex::Math::isfinite(rho) || !amrex::Math::isfinite(rho_qc)) return 0.0;
429  amrex::Real qc = rho_qc / rho;
430  return (amrex::Math::isfinite(qc) && qc > 0.0) ? qc : 0.0;
431 }
#define RhoQ2_comp
Definition: ERF_IndexDefines.H:46

Referenced by diagnose_cloud_fraction_prognostic(), diagnose_layer_optics(), and diagnose_layer_tau().

Here is the caller graph for this function:

◆ get_qv_from_state()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_qv_from_state ( int  i,
int  j,
int  k,
const amrex::Array4< const amrex::Real > &  state_arr 
)

Water-vapor mixing ratio qv = RhoQv / Rho at (i,j,k).

Returns 0 when the state carries no moisture components, or when the stored values are non-finite or negative.

388 {
389  if (state_arr.nComp() <= RhoQ1_comp) return 0.0;
390  amrex::Real rho = state_arr(i, j, k, Rho_comp);
391  amrex::Real rho_qv = state_arr(i, j, k, RhoQ1_comp);
392  if (!(rho > 0.0) || !amrex::Math::isfinite(rho) || !amrex::Math::isfinite(rho_qv)) return 0.0;
393  amrex::Real qv = rho_qv / rho;
394  return (amrex::Math::isfinite(qv) && qv > 0.0) ? qv : 0.0;
395 }
#define RhoQ1_comp
Definition: ERF_IndexDefines.H:45

Referenced by diagnose_cloud_fraction_prognostic(), diagnose_layer_optics(), diagnose_layer_tau(), get_inverse_exner(), and vertical_two_stream_sweep().

Here is the caller graph for this function:

◆ get_temperature_from_rhotheta()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_temperature_from_rhotheta ( amrex::Real  rho_theta,
amrex::Real  rho,
amrex::Real  qv = 0.0 
)

GPU-safe helper to compute absolute temperature from (rho, rho*theta).

ERF stores dry density and dry potential temperature. The pressure follows from the equation of state, p = p_0 * (R_d * rho * theta_m / p_0)^gamma, theta_m = theta * (1 + R_v/R_d * qv), and the absolute temperature is recovered through the Exner function, T = theta * (p / p_0)^(R_d / c_p), which getTgivenRandRTh() evaluates as p / (R_d * rho * (1 + R_v/R_d * qv)).

Parameters
[in]rho_thetaRhoTheta component [K·kg/m^3]
[in]rhoDensity [kg/m^3]
[in]qvWater-vapor mixing ratio [kg/kg] (0 for dry air)
Returns
Temperature [K], clamped to [100, 400]; 288.15 for unphysical input.
203 {
204  if (!(rho > 0.0) || !amrex::Math::isfinite(rho) ||
205  !(rho_theta > 0.0) || !amrex::Math::isfinite(rho_theta)) {
206  return 288.15; // Defensive: fallback to standard T
207  }
208  if (!(qv >= 0.0) || !amrex::Math::isfinite(qv)) {
209  qv = 0.0;
210  }
211 
212  amrex::Real T = getTgivenRandRTh(rho, rho_theta, qv);
213  if (!amrex::Math::isfinite(T) || T <= 0.0) {
214  return 288.15; // Defensive: fallback
215  }
216 
217  // Defensive clipping to a sensible terrestrial range
218  T = std::max(T, amrex::Real(100.0));
219  T = std::min(T, amrex::Real(400.0));
220  return T;
221 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real getTgivenRandRTh(const amrex::Real rho, const amrex::Real rhotheta, const amrex::Real qv=amrex::Real(0))
Definition: ERF_EOS.H:46

Referenced by diagnose_cloud_fraction_prognostic(), and vertical_two_stream_sweep().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ is_cloud_level()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool is_cloud_level ( amrex::Real  z_center,
const TwoStreamParams rad_choice 
)

GPU-safe helper to determine whether a layer falls within the cloud band [cloud_base_height_m, cloud_top_height_m].

Parameters
[in]z_centerHeight of the layer center above the surface [m].
[in]rad_choiceRadiation parameters.
Returns
true if this layer is inside the configured cloud band.
374 {
375  return (z_center >= rad_choice.cloud_base_height_m &&
376  z_center <= rad_choice.cloud_top_height_m);
377 }
amrex::Real cloud_top_height_m
Definition: ERF_TwoStreamColumn.H:65
amrex::Real cloud_base_height_m
Definition: ERF_TwoStreamColumn.H:64

Referenced by diagnose_layer_optics(), diagnose_layer_tau(), select_scattering_props(), and tau_layer_value().

Here is the caller graph for this function:

◆ make_two_stream_params()

TwoStreamParams make_two_stream_params ( const RadChoice rc,
const amrex::Real  rdOcp 
)
inline

Copy the column-kernel parameters out of a RadChoice.

129 {
131  p.sw_enabled = rc.sw_enabled;
132  p.lw_enabled = rc.lw_enabled;
133  p.tau_per_layer = rc.tau_per_layer;
134  p.tau_lw_per_layer = rc.tau_lw_per_layer;
135  p.tau_profile_type = rc.tau_profile_type;
136  p.cloud_base_height_m = rc.cloud_base_height_m;
137  p.cloud_top_height_m = rc.cloud_top_height_m;
138  p.cloud_tau_per_layer = rc.cloud_tau_per_layer;
139  p.single_scattering_albedo = rc.single_scattering_albedo;
140  p.asymmetry_factor = rc.asymmetry_factor;
141  p.cloud_single_scattering_albedo = rc.cloud_single_scattering_albedo;
142  p.cloud_asymmetry_factor = rc.cloud_asymmetry_factor;
143  p.surface_albedo_sw = rc.surface_albedo_sw;
144  p.surface_albedo_sw_diffuse = rc.surface_albedo_sw_diffuse;
145  p.surface_emissivity_lw = rc.surface_emissivity_lw;
146  p.t_sfc_default = rc.rad_t_sfc;
147  p.rdOcp = rdOcp;
148  p.tau_sw_dynamic_enable = rc.tau_sw_dynamic_enable;
149  p.tau_lw_dynamic_enable = rc.tau_lw_dynamic_enable;
150  p.tau_sw_coeff_qv = rc.tau_sw_coeff_qv;
151  p.tau_sw_coeff_qc = rc.tau_sw_coeff_qc;
152  p.tau_lw_coeff_qv = rc.tau_lw_coeff_qv;
153  p.tau_lw_coeff_qc = rc.tau_lw_coeff_qc;
154  p.cloud_fraction_prog_enable = rc.cloud_fraction_prog_enable;
155  p.cloud_fraction_rh_min = rc.cloud_fraction_rh_min;
156  p.cloud_fraction_rh_max = rc.cloud_fraction_rh_max;
157  p.cloud_fraction_qc_scale = rc.cloud_fraction_qc_scale;
158  p.aerosol_enable = rc.aerosol_enable;
159  p.aerosol_profile_type = rc.aerosol_profile_type;
160  p.aerosol_tau_per_layer = rc.aerosol_tau_per_layer;
161  p.aerosol_scale_height_m = rc.aerosol_scale_height_m;
162  p.aerosol_tau_surface = rc.aerosol_tau_surface;
163  p.tau_model = rc.tau_model;
164  p.sw_kabs_dry = rc.sw_kabs_dry;
165  p.sw_kscat_dry = rc.sw_kscat_dry;
166  p.sw_kabs_vapor = rc.sw_kabs_vapor;
167  p.sw_kext_cloud = rc.sw_kext_cloud;
168  p.sw_cloud_omega = rc.sw_cloud_omega;
169  p.sw_cloud_g = rc.sw_cloud_g;
170  p.lw_mass_absorption_enable = rc.lw_mass_absorption_enable;
171  p.lw_kabs_dry = rc.lw_kabs_dry;
172  p.lw_kabs_vapor = rc.lw_kabs_vapor;
173  p.lw_kabs_cloud = rc.lw_kabs_cloud;
174  // Sun: a fixed cosine when given; the date-dependent quantities (calday,
175  // declination and the irradiance of the date) are filled by the driver.
176  p.solar_dynamic = !(rc.fixed_solar_zenith_angle > 0.0);
177  p.cos_zenith_fixed = rc.fixed_solar_zenith_angle;
178  p.S0 = (rc.fixed_total_solar_irradiance >= 0.0) ? rc.fixed_total_solar_irradiance : 1360.9;
179  p.lat_cons_rad = rc.rad_cons_lat * PI / 180.0;
180  p.lon_cons_rad = rc.rad_cons_lon * PI / 180.0;
181  return p;
182 }
const Real rdOcp
Definition: ERF_InitCustomPert_ABL.H:72
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:39
@ p
Definition: ERF_WSM6.H:280
amrex::Real lw_kabs_dry
Dry-air gray LW mass absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:188
bool aerosol_enable
Enable prescribed bulk aerosol/turbidity optical depth. When true, aerosol optical depth is added on ...
Definition: ERF_RadStruct.H:577
TauModel tau_model
Optical depth model: "per_layer" (default, fixed tau per layer) or "mass" (from the layer mass path i...
Definition: ERF_RadStruct.H:155
amrex::Real sw_kabs_vapor
Mass model, SW: water-vapor gray absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:164
amrex::Real cloud_top_height_m
Cloud layer top height [m]. Only used when tau_profile_type == CloudLayer. Must be >= cloud_base_heig...
Definition: ERF_RadStruct.H:276
amrex::Real aerosol_tau_surface
Total-column aerosol optical depth at surface [dimensionless]. Used for AerosolProfileType::Exponenti...
Definition: ERF_RadStruct.H:609
amrex::Real fixed_solar_zenith_angle
erf.fixed_solar_zenith_angle: the COSINE of the solar zenith angle (mu0), as RRTMGP takes it,...
Definition: ERF_RadStruct.H:120
bool sw_enabled
Enable shortwave (solar) radiation computation. Only used by the two-stream model.
Definition: ERF_RadStruct.H:82
amrex::Real cloud_asymmetry_factor
Cloud scattering asymmetry factor in [-1, 1], used instead of asymmetry_factor for levels where the c...
Definition: ERF_RadStruct.H:331
amrex::Real tau_lw_per_layer
Longwave optical depth per layer (constant for all layers). Used in gray-gas LW two-stream solver....
Definition: ERF_RadStruct.H:102
amrex::Real surface_albedo_sw
Shortwave surface albedo fallback [0, 1] for per-column heterogeneous-surface-property resolution....
Definition: ERF_RadStruct.H:340
amrex::Real surface_emissivity_lw
Longwave surface emissivity fallback [0, 1] for per-column heterogeneous-surface-property resolution....
Definition: ERF_RadStruct.H:355
amrex::Real sw_kext_cloud
Mass model, SW: cloud-water extinction coefficient m^2/kg.
Definition: ERF_RadStruct.H:167
amrex::Real tau_sw_coeff_qv
Shortwave optical depth coefficient for water vapor [dimensionless]. Dynamic SW tau = tau_sw_coeff_qv...
Definition: ERF_RadStruct.H:513
amrex::Real fixed_total_solar_irradiance
erf.fixed_total_solar_irradiance: top-of-atmosphere irradiance [W/m^2] when >= 0. Otherwise (default)...
Definition: ERF_RadStruct.H:127
bool tau_lw_dynamic_enable
Enable dynamic longwave optical depth diagnosis from moisture/clouds. When true, LW optical depth is ...
Definition: ERF_RadStruct.H:504
amrex::Real tau_sw_coeff_qc
Shortwave optical depth coefficient for cloud liquid water [dimensionless]. Default 0....
Definition: ERF_RadStruct.H:521
amrex::Real aerosol_tau_per_layer
Constant aerosol optical depth per layer [dimensionless]. Used for AerosolProfileType::Constant profi...
Definition: ERF_RadStruct.H:593
amrex::Real single_scattering_albedo
Clear-sky (background gas) single-scattering albedo in [0, 1] for the SW diffuse (scattering) two-str...
Definition: ERF_RadStruct.H:302
bool tau_sw_dynamic_enable
Definition: ERF_RadStruct.H:505
AerosolProfileType aerosol_profile_type
Aerosol optical depth profile type: Constant, Exponential, or Table.
Definition: ERF_RadStruct.H:586
amrex::Real rad_t_sfc
erf.rad_t_sfc: surface temperature [K] used as the longwave boundary condition wherever no land-surfa...
Definition: ERF_RadStruct.H:135
amrex::Real tau_lw_coeff_qc
Longwave optical depth coefficient for cloud liquid water [dimensionless]. Default 0....
Definition: ERF_RadStruct.H:537
amrex::Real cloud_single_scattering_albedo
Cloud single-scattering albedo in [0, 1], used instead of single_scattering_albedo for levels where t...
Definition: ERF_RadStruct.H:322
amrex::Real lw_kabs_cloud
Cloud-water LW mass absorption coefficient m^2/kg.
Definition: ERF_RadStruct.H:194
amrex::Real sw_cloud_omega
Mass model, SW: cloud-water single-scattering albedo.
Definition: ERF_RadStruct.H:170
amrex::Real sw_kscat_dry
Mass model, SW: dry-air (Rayleigh) scattering coefficient [m^2/kg], omega = 1, g = 0.
Definition: ERF_RadStruct.H:161
amrex::Real sw_kabs_dry
Mass model, SW: dry-air gray absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:158
amrex::Real asymmetry_factor
Clear-sky (background gas) scattering asymmetry factor in [-1, 1] (0 = isotropic scattering,...
Definition: ERF_RadStruct.H:310
amrex::Real cloud_fraction_qc_scale
Cloud water [kg/kg] at which the liquid-water term alone gives a cloud fraction of 1: cf += min(1,...
Definition: ERF_RadStruct.H:569
bool cloud_fraction_prog_enable
Enable prognostic cloud fraction diagnosis from RH/qc. When true, cloud fraction is computed per-leve...
Definition: ERF_RadStruct.H:545
bool lw_enabled
Enable longwave (thermal) radiation computation. Only used by the two-stream model.
Definition: ERF_RadStruct.H:88
amrex::Real surface_albedo_sw_diffuse
Shortwave surface albedo for diffuse light [0,1]. A negative value (default) uses surface_albedo_sw f...
Definition: ERF_RadStruct.H:346
amrex::Real cloud_base_height_m
Cloud layer base height [m]. Only used when tau_profile_type == CloudLayer.
Definition: ERF_RadStruct.H:270
amrex::Real lw_kabs_vapor
Water-vapor gray LW mass absorption coefficient [m^2/kg].
Definition: ERF_RadStruct.H:191
amrex::Real tau_per_layer
Shortwave optical depth per layer (constant for all layers). Used in Beer-Lambert direct-beam formula...
Definition: ERF_RadStruct.H:95
amrex::Real tau_lw_coeff_qv
Longwave optical depth coefficient for water vapor [dimensionless]. Dynamic LW tau = tau_lw_coeff_qv ...
Definition: ERF_RadStruct.H:529
amrex::Real cloud_fraction_rh_max
Maximum relative humidity threshold for cloud fraction diagnosis [0, 1]. Must be >= cloud_fraction_rh...
Definition: ERF_RadStruct.H:561
amrex::Real sw_cloud_g
Mass model, SW: cloud-water asymmetry factor.
Definition: ERF_RadStruct.H:173
amrex::Real rad_cons_lon
Definition: ERF_RadStruct.H:139
bool lw_mass_absorption_enable
Gray longwave optical depth from the layer mass path instead of the fixed tau_lw_per_layer (also sele...
Definition: ERF_RadStruct.H:185
amrex::Real cloud_tau_per_layer
Additional optical depth per layer contributed by the cloud, added on top of the clear-sky tau_per_la...
Definition: ERF_RadStruct.H:284
amrex::Real aerosol_scale_height_m
Scale height for exponential aerosol profile [m]. Controls decay rate: tau_aerosol(k) = aerosol_tau_s...
Definition: ERF_RadStruct.H:601
amrex::Real rad_cons_lat
erf.rad_cons_lat / erf.rad_cons_lon: site latitude and longitude [degrees] when the grid has no lat/l...
Definition: ERF_RadStruct.H:138
amrex::Real cloud_fraction_rh_min
Minimum relative humidity threshold for cloud fraction diagnosis [0, 1]. Cloud fraction ramps from 0 ...
Definition: ERF_RadStruct.H:553
TauProfileType tau_profile_type
Optical depth profile type: "constant" (default, the thickness-independent tau) or "cloud_layer" (add...
Definition: ERF_RadStruct.H:264
Trivially copyable subset of RadChoice used by the column kernels.
Definition: ERF_TwoStreamColumn.H:57

Referenced by TwoStreamRadiation::advance().

Here is the caller graph for this function:

◆ resolve_surface_albedo_sw()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real resolve_surface_albedo_sw ( int  i,
int  j,
const amrex::Array4< const amrex::Real > *  hetero_alb_sw,
const TwoStreamParams rad_choice,
bool  has_hetero_alb 
)

Resolve per-column shortwave surface albedo from hetero field or fallback.

Precedence:

  1. If hetero_alb_sw array available and value at (i,j) is finite ∈ [0,1], use it
  2. Otherwise, use rad_choice.surface_albedo_sw (already clamped by init_params)
  3. Hard default: 0.3
Parameters
[in]i,jColumn index
[in]hetero_alb_swHeterogeneous SW albedo field (may be nullptr)
[in]rad_choiceRadiation parameters with fallback surface_albedo_sw
[in]has_hetero_albtrue if hetero_alb_sw is available
Returns
SW albedo in [0, 1]
250 {
251  amrex::Real alb = rad_choice.surface_albedo_sw; // Default fallback (already clamped)
252 
253  if (has_hetero_alb && hetero_alb_sw != nullptr && hetero_alb_sw->contains(i, j, 0)) {
254  amrex::Real hetero_val = (*hetero_alb_sw)(i, j, 0, 0);
255  if (amrex::Math::isfinite(hetero_val) && hetero_val >= 0.0 && hetero_val <= 1.0) {
256  alb = hetero_val;
257  }
258  }
259 
260  return alb;
261 }
amrex::Real surface_albedo_sw
Definition: ERF_TwoStreamColumn.H:73

Referenced by vertical_two_stream_sweep().

Here is the caller graph for this function:

◆ resolve_surface_emissivity_lw()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real resolve_surface_emissivity_lw ( int  i,
int  j,
const amrex::Array4< const amrex::Real > *  hetero_emiss_lw,
const TwoStreamParams rad_choice,
bool  has_hetero_emiss 
)

Resolve per-column longwave surface emissivity from hetero field or fallback.

Precedence:

  1. If hetero_emiss_lw array available and value at (i,j) is finite ∈ [0,1], use it
  2. Otherwise, use rad_choice.surface_emissivity_lw (already clamped by init_params)
  3. Hard default: 0.99
Parameters
[in]i,jColumn index
[in]hetero_emiss_lwHeterogeneous LW emissivity field (may be nullptr)
[in]rad_choiceRadiation parameters with fallback surface_emissivity_lw
[in]has_hetero_emisstrue if hetero_emiss_lw is available
Returns
LW emissivity in [0, 1]
283 {
284  amrex::Real emiss = rad_choice.surface_emissivity_lw; // Default fallback (already clamped)
285 
286  if (has_hetero_emiss && hetero_emiss_lw != nullptr && hetero_emiss_lw->contains(i, j, 0)) {
287  amrex::Real hetero_val = (*hetero_emiss_lw)(i, j, 0, 0); // Assume single component
288  if (amrex::Math::isfinite(hetero_val) && hetero_val >= 0.0 && hetero_val <= 1.0) {
289  emiss = hetero_val;
290  }
291  }
292 
293  return emiss;
294 }
amrex::Real surface_emissivity_lw
Definition: ERF_TwoStreamColumn.H:75

Referenced by vertical_two_stream_sweep().

Here is the caller graph for this function:

◆ resolve_surface_temp_k()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real resolve_surface_temp_k ( int  i,
int  j,
const amrex::Array4< const amrex::Real > *  lsm_t_sfc,
bool  has_lsm_t_sfc,
const amrex::Array4< const amrex::Real > *  seb_t_sfc,
bool  has_seb_t_sfc,
const amrex::Array4< const amrex::Real > *  surface_layer_theta,
bool  has_surface_layer,
const TwoStreamParams rad_choice,
bool &  from_surface_layer 
)

Resolve the per-column surface-temperature boundary condition.

The candidates remain separate until this per-cell resolver runs. This is important for an LSM field that exists globally but contains an undefined value in an individual column.

Precedence for each column:

  1. valid LSM absolute temperature;
  2. valid prognostic SEB absolute temperature;
  3. valid SurfaceLayer potential temperature (marked for Exner conversion);
  4. the scalar absolute-temperature fallback, erf.rad_t_sfc.

Only the SurfaceLayer candidate is converted by the caller. The LSM and prognostic SEB candidates already satisfy the absolute-temperature contract.

Parameters
[in]i,jColumn index
[in]lsm_t_sfcLSM absolute-temperature field (may be nullptr)
[in]has_lsm_t_sfctrue if the LSM field is available
[in]seb_t_sfcprognostic SEB absolute-temperature field (may be nullptr)
[in]has_seb_t_sfctrue if the prognostic SEB field is available
[in]surface_layer_thetaSurfaceLayer potential-temperature field (may be nullptr)
[in]has_surface_layertrue if the SurfaceLayer field is available
[in]rad_choiceRadiation parameters with the fallback t_sfc_default
[out]from_surface_layertrue when the returned value is SurfaceLayer theta
Returns
Surface temperature candidate [K]
334 {
335  amrex::Real t_surf = rad_choice.t_sfc_default; // erf.rad_t_sfc (already validated)
336  from_surface_layer = false;
337 
338  if (has_lsm_t_sfc && lsm_t_sfc != nullptr && lsm_t_sfc->contains(i, j, 0)) {
339  const amrex::Real lsm_value = (*lsm_t_sfc)(i, j, 0, 0);
340  if (valid_surface_temperature(lsm_value)) {
341  return lsm_value;
342  }
343  }
344 
345  if (has_seb_t_sfc && seb_t_sfc != nullptr && seb_t_sfc->contains(i, j, 0)) {
346  const amrex::Real seb_value = (*seb_t_sfc)(i, j, 0, 0);
347  if (valid_surface_temperature(seb_value)) {
348  return seb_value;
349  }
350  }
351 
352  if (has_surface_layer && surface_layer_theta != nullptr &&
353  surface_layer_theta->contains(i, j, 0)) {
354  const amrex::Real theta = (*surface_layer_theta)(i, j, 0, 0);
356  from_surface_layer = true;
357  return theta;
358  }
359  }
360 
361  return t_surf;
362 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool valid_surface_temperature(amrex::Real value)
Check whether a heterogeneous absolute surface temperature is valid.
Definition: ERF_TwoStreamColumn.H:225
@ theta
Definition: ERF_SLM.H:19
amrex::Real t_sfc_default
Definition: ERF_TwoStreamColumn.H:76

Referenced by vertical_two_stream_sweep().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ select_scattering_props()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void select_scattering_props ( amrex::Real  z_center,
const TwoStreamParams rad_choice,
bool  apply_cloud,
amrex::Real omega,
amrex::Real g 
)

GPU-safe helper to select the single-scattering albedo and asymmetry factor to use for level k's diffuse SW calculation.

When this column evaluation applies the cloud-layer enhancement (apply_cloud == true, tau_profile_type == CloudLayer, and level k falls within the cloud band), the cloud scattering properties (cloud_single_scattering_albedo, cloud_asymmetry_factor) are used. Otherwise, the clear-sky scattering properties (single_scattering_albedo, asymmetry_factor) are used. Both default to 0.0, so by default this function always yields omega == 0.0 and the layers neither scatter the direct beam nor reflect diffuse light; the diffuse field then consists of the surface-reflected beam only.

Parameters
[in]z_centerHeight of the layer center above the surface [m].
[in]rad_choiceRadiation parameters.
[in]apply_cloudSame flag passed to tau_layer_value(); true for the cloudy-column evaluation, false for the clear-sky column evaluation.
[out]omegaSelected single-scattering albedo for this level.
[out]gSelected asymmetry factor for this level.
518 {
519  bool use_cloud_props = apply_cloud &&
520  rad_choice.tau_profile_type == TauProfileType::CloudLayer &&
521  is_cloud_level(z_center, rad_choice);
522 
523  if (use_cloud_props) {
525  g = rad_choice.cloud_asymmetry_factor;
526  } else {
527  omega = rad_choice.single_scattering_albedo;
528  g = rad_choice.asymmetry_factor;
529  }
530 }
amrex::Real single_scattering_albedo
Definition: ERF_TwoStreamColumn.H:68
amrex::Real asymmetry_factor
Definition: ERF_TwoStreamColumn.H:69

Referenced by diagnose_layer_optics().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ tau_layer_value()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real tau_layer_value ( amrex::Real  z_center,
amrex::Real  tau_base,
const TwoStreamParams rad_choice,
bool  apply_cloud 
)

GPU-safe helper to compute the per-layer optical depth at level k, given the base (clear-sky) optical depth and cloud-layer parameters.

When rad_choice.tau_profile_type == Constant, returns tau_base unchanged (byte-identical). When == CloudLayer, adds rad_choice.cloud_tau_per_layer whenever the level height falls within [cloud_base_height_m, cloud_top_height_m].

Parameters
[in]z_centerHeight of the layer center above the surface [m].
[in]tau_baseClear-sky optical depth per layer.
[in]rad_choiceRadiation parameters.
[in]apply_cloudIf false, always returns tau_base (used for the clear-sky column computation even when cloud_fraction > 0).
Returns
Optical depth for this layer [unitless].
483 {
484  if (!apply_cloud || rad_choice.tau_profile_type != TauProfileType::CloudLayer) {
485  return tau_base;
486  }
487  if (is_cloud_level(z_center, rad_choice)) {
488  return tau_base + rad_choice.cloud_tau_per_layer;
489  }
490  return tau_base;
491 }

Referenced by diagnose_layer_optics(), and diagnose_layer_tau().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ two_stream_scratch_box()

amrex::Box two_stream_scratch_box ( const amrex::Box &  bx)
inline

Box of the scratch FArrayBox a sweep over box bx needs.

613 {
614  amrex::Box sbx(bx);
615  sbx.growHi(2, 1);
616  return sbx;
617 }

Referenced by TwoStreamRadiation::advance().

Here is the caller graph for this function:

◆ valid_surface_temperature()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool valid_surface_temperature ( amrex::Real  value)

Check whether a heterogeneous absolute surface temperature is valid.

226 {
227  return amrex::Math::isfinite(value) && value > 0.0 && value < lsm_undefined;
228 }
constexpr amrex::Real lsm_undefined
Definition: ERF_Constants.H:26

Referenced by resolve_surface_temp_k().

Here is the caller graph for this function:

◆ vertical_two_stream_sweep()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void vertical_two_stream_sweep ( int  i,
int  j,
const amrex::Box &  bx,
amrex::Real  dz_uniform,
const amrex::Array4< const amrex::Real > &  state_arr,
const TwoStreamParams rad_choice,
bool  cloudy,
const amrex::Array4< amrex::Real > &  qheating_arr,
amrex::Real max_heating_rate,
amrex::Real sw_surface_flux,
amrex::Real sw_up_toa,
amrex::Real lw_net_surface,
amrex::Real lw_up_toa,
amrex::Real sw_down_toa,
const amrex::Array4< const amrex::Real > &  z_phys_nd,
const amrex::Array4< amrex::Real > &  scratch,
bool  has_hetero_alb_sw = false,
const amrex::Array4< const amrex::Real > *  hetero_alb_sw = nullptr,
bool  has_hetero_emiss_lw = false,
const amrex::Array4< const amrex::Real > *  hetero_emiss_lw = nullptr,
bool  has_lsm_t_sfc = false,
const amrex::Array4< const amrex::Real > *  lsm_t_sfc = nullptr,
bool  has_seb_t_sfc = false,
const amrex::Array4< const amrex::Real > *  seb_t_sfc = nullptr,
bool  has_surface_layer = false,
const amrex::Array4< const amrex::Real > *  surface_layer_theta = nullptr,
bool  has_latlon = false,
const amrex::Array4< const amrex::Real > *  lat_arr = nullptr,
const amrex::Array4< const amrex::Real > *  lon_arr = nullptr,
const amrex::Array4< amrex::Real > *  rad_flux_out = nullptr 
)

GPU-safe per-column vertical integration kernel for two-stream radiation, computing either the clear-sky or cloudy-column fluxes and per-level heating rates, depending on the cloudy flag.

Vertical orientation. ERF's vertical index increases upward: k = kmin is the layer adjacent to the surface and k = kmax is the layer adjacent to the top of the domain (TOA for this model). Layer k spans the interfaces m = k - kmin (bottom) and m = k - kmin + 1 (top), so interface m = 0 is the surface and m = nlev is the TOA.

Per (i,j) column:

  1. SW: Beer-Lambert direct beam on the interfaces, then the two-stream diffuse field (upward and downward streams) by the adding method with the surface albedo as the lower boundary condition; the per-level SW heating rate from the net-flux divergence is written to qheating_arr(i,j,k,0).
  2. LW: sweep downward from the TOA (F_down = 0) to the surface, then upward from the surface (F_up = eps * sigma * T_s^4 + (1 - eps) * F_down) to the TOA, storing both interface profiles. The per-level LW heating rate from the net-flux divergence is written to qheating_arr(i,j,k,1).
  3. Scalar diagnostics (max heating rate, surface fluxes) are returned for the reduction in the caller.

Layer temperature is obtained from (rho, rho*theta, qv) through the equation of state, i.e. including the Exner function, so LW emission uses absolute temperature rather than potential temperature.

Integrates per-column heterogeneous surface properties (albedo, emissivity, surface temperature) from optional fields with robust fallback to RadChoice scalar parameters.

Parameters
[in]i,jColumn indices
[in]bxComputational box (cell-centered, full vertical extent)
[in]dz_uniformUniform vertical cell size [m], used when z_phys_cc is unavailable. Passed in rather than taken from a Geometry because Geometry::CellSize() is host-only and this runs on device.
[in]state_arrArray proxy to state data (read-only)
[in]rad_choiceColumn-kernel parameters (TwoStreamParams, built from RadChoice)
[in]cloudyIf true and tau_profile_type == CloudLayer, apply the cloud-layer optical depth enhancement (and cloud scattering properties).
[out]qheating_arrComponent 0 receives the SW and component 1 the LW radiative tendency of potential temperature, dtheta/dt = (dT/dt) / pi [K/s], at every level k in [kmin, kmax]. This is the convention the RhoTheta source term (ERF_MakeSources.cpp) and the RRTMGP path use.
[out]max_heating_rateMaximum |Q_sw|+|Q_lw| of the stored theta tendencies in this column
[out]sw_surface_fluxSW absorbed by the surface, (1 - alb_dir) F_dir + (1 - alb_dif) F_dif [W/m^2]
[out]sw_up_toaUpwelling (reflected) SW at the top of the atmosphere [W/m^2]
[out]lw_net_surfaceNet LW (up - down) at the surface [W/m^2]
[out]lw_up_toaUpwelling LW at the top of the atmosphere (outgoing longwave) [W/m^2]
[out]sw_down_toaIncident SW at the top of the atmosphere, S0 cos(zenith) or zero at night [W/m^2]
[in]z_phys_ndOptional nodal physical heights (layer interfaces on a nonuniform grid)
[in]has_hetero_alb_swtrue if hetero_alb_sw is available
[in]hetero_alb_swOptional per-column SW surface albedo field
[in]has_hetero_emiss_lwtrue if hetero_emiss_lw is available
[in]hetero_emiss_lwOptional per-column LW surface emissivity field
[in]has_lsm_t_sfctrue if the LSM absolute-temperature field is available
[in]lsm_t_sfcOptional LSM absolute surface-temperature field [K]
[in]has_seb_t_sfctrue if the prognostic SEB absolute-temperature field is available
[in]seb_t_sfcOptional prognostic SEB absolute surface-temperature field [K]
[in]has_surface_layertrue if the SurfaceLayer potential-temperature field is available
[in]surface_layer_thetaOptional SurfaceLayer potential-temperature field [K]
[in]has_latlontrue if per-column latitude and longitude fields are available
[in]lat_arrOptional per-column latitude [degrees] (dynamic sun)
[in]lon_arrOptional per-column longitude [degrees, east positive] (dynamic sun)
[out]rad_flux_outOptional 4-component flux output in RRTMGP's level layout: at index k the lower interface of layer k, and at kmax + 1 the top-of-atmosphere interface (the array must extend one cell above the column); component 0 is the upward SW, 1 the downward SW (direct plus diffuse), 2 the upward LW and 3 the downward LW [W/m^2]
916 {
917  // Grid bounds: kmin is the surface layer, kmax the top layer.
918  const int kmin = bx.smallEnd(2);
919  const int kmax = bx.bigEnd(2);
920  const int nlev = kmax - kmin + 1;
921 
922  // The scratch box must hold nlev + 1 interface entries for this column,
923  // and so must the flux output when it is wanted.
924  AMREX_ASSERT(scratch.contains(i, j, kmin) && scratch.contains(i, j, kmax + 1));
925  AMREX_ASSERT(rad_flux_out == nullptr ||
926  (rad_flux_out->contains(i, j, kmin) && rad_flux_out->contains(i, j, kmax + 1)));
927 
928  // Per-column scratch accessors (see TwoStreamScratch). Layer fields are
929  // indexed by layer m in [0, nlev), interface fields by m in [0, nlev].
930  auto dz_level = [&] (int m) -> amrex::Real& { return scratch(i, j, kmin + m, TwoStreamScratch::DZ); };
931  auto z_level = [&] (int m) -> amrex::Real& { return scratch(i, j, kmin + m, TwoStreamScratch::Z); };
932  auto tau_sw_level = [&] (int m) -> amrex::Real& { return scratch(i, j, kmin + m, TwoStreamScratch::TAU_SW); };
933  auto F_dir = [&] (int m) -> amrex::Real& { return scratch(i, j, kmin + m, TwoStreamScratch::F_DIR); };
934  auto A = [&] (int m) -> amrex::Real& { return scratch(i, j, kmin + m, TwoStreamScratch::A); };
935  auto S = [&] (int m) -> amrex::Real& { return scratch(i, j, kmin + m, TwoStreamScratch::S); };
936  auto F_lw_up_iface = [&] (int m) -> amrex::Real& { return scratch(i, j, kmin + m, TwoStreamScratch::LW_UP); };
937  auto F_lw_down_iface = [&] (int m) -> amrex::Real& { return scratch(i, j, kmin + m, TwoStreamScratch::LW_DN); };
938  auto tau_lw_level = [&] (int m) -> amrex::Real& { return scratch(i, j, kmin + m, TwoStreamScratch::TAU_LW); };
939 
940  // Physical constants
941  const amrex::Real sigma = stefan_boltzmann; // Stefan-Boltzmann [W/(m^2·K^4)]
942  const amrex::Real cp_air = Cp_d; // Dry-air specific heat at constant pressure [J/(kg·K)]
943 
944  // ------------------------------------------------------------------
945  // Layer thickness dz_level(m) and layer-centre height z_level(m) above
946  // the surface. On a stretched or terrain-following grid both come from
947  // the interface heights: the height of face k over cell (i,j) is the
948  // mean of its four nodes in z_phys_nd, so the thickness is the distance
949  // between the layer's two interfaces (not the centre-to-centre spacing,
950  // which differs by the stretch ratio) and the top layer gets its own
951  // thickness. Without z_phys_nd the spacing is uniform. The heating
952  // divergence dF/(rho cp dz) and the mass path rho dz both use this
953  // thickness; z_level locates the cloud band and the aerosol profile.
954  // ------------------------------------------------------------------
955  if (z_phys_nd) {
956  auto z_face = [&] (int k) -> amrex::Real {
957  return 0.25 * (z_phys_nd(i, j, k) + z_phys_nd(i + 1, j, k) +
958  z_phys_nd(i, j + 1, k) + z_phys_nd(i + 1, j + 1, k));
959  };
960  const amrex::Real z_surface = z_face(kmin);
961  for (int m = 0; m < nlev; ++m) {
962  const amrex::Real z_lo = z_face(kmin + m);
963  const amrex::Real z_hi = z_face(kmin + m + 1);
964  const amrex::Real dz_computed = z_hi - z_lo;
965  dz_level(m) = (dz_computed > 0.0 && amrex::Math::isfinite(dz_computed)) ? dz_computed : dz_uniform;
966  z_level(m) = 0.5 * (z_lo + z_hi) - z_surface;
967  }
968  } else {
969  for (int m = 0; m < nlev; ++m) {
970  dz_level(m) = dz_uniform;
971  z_level(m) = (static_cast<amrex::Real>(m) + 0.5) * dz_uniform;
972  }
973  }
974 
975  // Cosine of the solar zenith angle: the fixed value of
976  // erf.fixed_solar_zenith_angle, or the sun's position over this column at
977  // the calendar time of the call, with the same formula RRTMGP uses
978  // (orbital_cos_zenith without the interval average). The latitude and
979  // longitude are the column's own where the grid carries them, else the
980  // erf.rad_cons_lat/lon constants.
981  amrex::Real cos_zenith;
982  if (rad_choice.solar_dynamic) {
983  const amrex::Real lat_rad = (has_latlon && lat_arr != nullptr && lat_arr->contains(i, j, 0))
984  ? (*lat_arr)(i, j, 0) * PI / 180.0 : rad_choice.lat_cons_rad;
985  const amrex::Real lon_rad = (has_latlon && lon_arr != nullptr && lon_arr->contains(i, j, 0))
986  ? (*lon_arr)(i, j, 0) * PI / 180.0 : rad_choice.lon_cons_rad;
987  cos_zenith = static_cast<amrex::Real>(
988  orbital_cos_zenith_instant(rad_choice.calday, lat_rad, lon_rad, rad_choice.declin));
989  } else {
990  cos_zenith = rad_choice.cos_zenith_fixed;
991  }
992 
993  // Top-of-atmosphere irradiance of this call (fixed, or 1360.9 W/m^2
994  // times the Earth-Sun distance factor of the date; set by the driver).
995  const amrex::Real S0 = rad_choice.S0;
996  const amrex::Real tau_sw_base = rad_choice.tau_per_layer;
997  const amrex::Real tau_lw_base = rad_choice.tau_lw_per_layer;
998 
999  amrex::Real local_max_heating = 0.0;
1000 
1001  // Zero-initialize this column's heating rate output (covers the
1002  // sw_enabled=false and lw_enabled=false cases), and the interface fluxes
1003  // when they are wanted (a disabled band or night leaves zeros).
1004  for (int k = kmin; k <= kmax; ++k) {
1005  qheating_arr(i, j, k, 0) = 0.0;
1006  qheating_arr(i, j, k, 1) = 0.0;
1007  }
1008  if (rad_flux_out != nullptr) {
1009  for (int k = kmin; k <= kmax + 1; ++k) {
1010  for (int comp = 0; comp < 4; ++comp) { (*rad_flux_out)(i, j, k, comp) = 0.0; }
1011  }
1012  }
1013  sw_down_toa = (rad_choice.sw_enabled && cos_zenith > 0.0) ? S0 * cos_zenith : 0.0;
1014 
1015  // ========================================================================
1016  // SHORTWAVE. Interfaces m = 0 (surface) .. nlev (TOA); layer m lies
1017  // between interfaces m and m+1.
1018  // 1. Direct beam F_dir(m) by Beer-Lambert from the TOA downward.
1019  // 2. Adding method, upward pass: A(m) is the albedo of everything below
1020  // interface m for diffuse light, S(m) the upward diffuse flux at m
1021  // produced by the direct beam illuminating everything below m. The
1022  // surface starts the recursion with A(0) = diffuse albedo and
1023  // S(0) = direct albedo * F_dir(0).
1024  // 3. Downward pass from the TOA (no incident diffuse flux): diffuse
1025  // downward d(m), diffuse upward u(m) = A(m) d(m) + S(m), and the net
1026  // flux F_dir + d - u whose divergence gives the heating rate.
1027  // ========================================================================
1028  amrex::Real tau_sw_cum = 0.0; // Column SW optical depth accumulated from the top
1029  amrex::Real sw_surface_absorbed = 0.0; // absorbed direct + diffuse at the surface
1030  amrex::Real sw_up_at_toa = 0.0; // u(nlev)
1031  // Direct-beam albedo (per column, LSM field or fallback) and diffuse albedo
1032  // (surface_albedo_sw_diffuse, or the direct value when negative).
1033  const amrex::Real alb_dir = resolve_surface_albedo_sw(i, j, hetero_alb_sw, rad_choice, has_hetero_alb_sw);
1034  const amrex::Real alb_dif = (rad_choice.surface_albedo_sw_diffuse >= 0.0)
1035  ? rad_choice.surface_albedo_sw_diffuse : alb_dir;
1036 
1037  if (rad_choice.sw_enabled && cos_zenith > 0.0) {
1038 
1039  // 1. Per-layer optical depth and the direct beam on the interfaces.
1040  F_dir(nlev) = S0 * cos_zenith;
1041  for (int k = kmax; k >= kmin; --k) {
1042  const int m = k - kmin;
1043  amrex::Real omega_m = 0.0, g_m = 0.0;
1044  diagnose_layer_optics(i, j, k, dz_level(m), z_level(m), state_arr, tau_sw_base,
1045  /*is_sw=*/true, cloudy, rad_choice, tau_sw_level(m), omega_m, g_m);
1046  tau_sw_cum += tau_sw_level(m);
1047  F_dir(m) = compute_sw_direct_flux(tau_sw_cum, S0, cos_zenith);
1048  }
1049 
1050  // Two-stream properties of layer m (evaluated in both passes).
1051  auto layer_props = [&](int m) {
1052  amrex::Real tau_m = 0.0, omega = 0.0, g = 0.0;
1053  diagnose_layer_optics(i, j, kmin + m, dz_level(m), z_level(m), state_arr, tau_sw_base,
1054  /*is_sw=*/true, cloudy, rad_choice, tau_m, omega, g);
1055  return compute_sw_layer_two_stream(tau_sw_level(m), omega, g, cos_zenith);
1056  };
1057 
1058  // 2. Upward pass of the adding method.
1059  A(0) = alb_dif;
1060  S(0) = alb_dir * F_dir(0);
1061  for (int m = 0; m < nlev; ++m) {
1062  const TwoStreamLayerSW L = layer_props(m);
1063  amrex::Real denom = 1.0 - L.R_dif * A(m);
1064  if (denom < 1.0e-12) denom = 1.0e-12;
1065  A(m + 1) = L.R_dif + L.T_dif * L.T_dif * A(m) / denom;
1066  S(m + 1) = L.R_dir * F_dir(m + 1)
1067  + L.T_dif * (S(m) + A(m) * L.T_dir * F_dir(m + 1)) / denom;
1068  }
1069 
1070  // 3. Downward pass: fluxes on the interfaces and layer heating rates.
1071  amrex::Real d_above = 0.0; // No diffuse flux incident at the TOA
1072  amrex::Real u_above = S(nlev);
1073  sw_up_at_toa = u_above;
1074  amrex::Real F_net_above = F_dir(nlev) + d_above - u_above;
1075  if (rad_flux_out != nullptr) {
1076  (*rad_flux_out)(i, j, kmax + 1, 0) = u_above; // SW up at the top of the atmosphere
1077  (*rad_flux_out)(i, j, kmax + 1, 1) = F_dir(nlev); // SW down at the top: the incident beam
1078  }
1079  for (int k = kmax; k >= kmin; --k) {
1080  const int m = k - kmin;
1081  const TwoStreamLayerSW L = layer_props(m);
1082  amrex::Real denom = 1.0 - L.R_dif * A(m);
1083  if (denom < 1.0e-12) denom = 1.0e-12;
1084 
1085  const amrex::Real d_m = (L.T_dif * d_above + L.T_dir * F_dir(m + 1) + L.R_dif * S(m)) / denom;
1086  const amrex::Real u_m = A(m) * d_m + S(m);
1087  const amrex::Real F_net_m = F_dir(m) + d_m - u_m;
1088  if (rad_flux_out != nullptr) {
1089  (*rad_flux_out)(i, j, k, 0) = u_m; // SW up at the lower interface of layer k
1090  (*rad_flux_out)(i, j, k, 1) = F_dir(m) + d_m; // SW down, direct plus diffuse
1091  }
1092 
1093  amrex::Real rho = state_arr(i, j, k, Rho_comp);
1094  if (rho <= 0.0 || !amrex::Math::isfinite(rho)) rho = 1.0;
1095 
1096  // dT/dt from the flux divergence, stored as dtheta/dt = (dT/dt)/pi
1097  // (the RhoTheta source term and RRTMGP use the same convention).
1098  const amrex::Real Q_sw = compute_sw_heating_rate(F_net_above, F_net_m,
1099  dz_level(m), rho, cp_air)
1100  * get_inverse_exner(i, j, k, state_arr);
1101  qheating_arr(i, j, k, 0) = Q_sw;
1102  local_max_heating = std::max(local_max_heating, std::abs(Q_sw));
1103 
1104  d_above = d_m;
1105  F_net_above = F_net_m;
1106  if (m == 0) {
1107  sw_surface_absorbed = (1.0 - alb_dir) * F_dir(0) + (1.0 - alb_dif) * d_m;
1108  }
1109  }
1110  }
1111 
1112  // ========================================================================
1113  // LONGWAVE: interface flux profiles, index m = 0 at the surface and
1114  // m = nlev at the TOA.
1115  // ========================================================================
1116 
1117  if (rad_choice.lw_enabled) {
1118  // Downward sweep: TOA -> surface. Also caches the per-layer LW optical
1119  // depth for the upward sweep.
1120  F_lw_down_iface(nlev) = 0.0; // No incoming LW from space
1121  for (int k = kmax; k >= kmin; --k) {
1122  const int m = k - kmin;
1123 
1124  amrex::Real rho = state_arr(i, j, k, Rho_comp);
1125  amrex::Real rho_theta = state_arr(i, j, k, RhoTheta_comp);
1126  if (rho <= 0.0 || !amrex::Math::isfinite(rho)) rho = 1.0;
1127  if (rho_theta <= 0.0 || !amrex::Math::isfinite(rho_theta)) rho_theta = 288.15;
1128  amrex::Real qv = get_qv_from_state(i, j, k, state_arr);
1129  amrex::Real T_layer = get_temperature_from_rhotheta(rho_theta, rho, qv);
1130 
1131  tau_lw_level(m) = diagnose_layer_tau(i, j, k, dz_level(m), z_level(m),
1132  state_arr, tau_lw_base, /*is_sw=*/false, cloudy,
1133  rad_choice);
1134 
1135  F_lw_down_iface(m) = compute_lw_flux_down(F_lw_down_iface(m + 1), T_layer, sigma, tau_lw_level(m));
1136  }
1137 
1138  // Upward sweep: surface -> TOA. The surface emits eps * sigma * T_s^4
1139  // and reflects the fraction (1 - eps) of the downwelling flux that
1140  // reaches it (gray surface, Kirchhoff's law).
1141  {
1142  bool t_sfc_from_surface_layer = false;
1143  amrex::Real t_surface = resolve_surface_temp_k(
1144  i, j, lsm_t_sfc, has_lsm_t_sfc, seb_t_sfc, has_seb_t_sfc,
1145  surface_layer_theta, has_surface_layer, rad_choice,
1146  t_sfc_from_surface_layer);
1147  // The surface layer carries a potential temperature (MOST works in
1148  // theta); the emission needs the temperature, so convert with the
1149  // physical surface pressure diagnosed from the lowest atmospheric
1150  // cell.
1151  if (t_sfc_from_surface_layer) {
1152  const amrex::Real rho = state_arr(i, j, kmin, Rho_comp);
1153  const amrex::Real rho_theta = state_arr(i, j, kmin, RhoTheta_comp);
1154  const amrex::Real qv = get_qv_from_state(i, j, kmin, state_arr);
1155  const amrex::Real delta_z = z_level(0);
1157  rho, rho_theta, qv, delta_z);
1159  t_surface, pressure, rad_choice.rdOcp, t_surface)) {
1160  t_surface = rad_choice.t_sfc_default;
1161  }
1162  }
1163  amrex::Real emiss_lw = resolve_surface_emissivity_lw(i, j, hetero_emiss_lw, rad_choice, has_hetero_emiss_lw);
1164  F_lw_up_iface(0) = emiss_lw * compute_thermal_intensity(t_surface, sigma)
1165  + (1.0 - emiss_lw) * F_lw_down_iface(0);
1166  }
1167  for (int k = kmin; k <= kmax; ++k) {
1168  const int m = k - kmin;
1169 
1170  amrex::Real rho = state_arr(i, j, k, Rho_comp);
1171  amrex::Real rho_theta = state_arr(i, j, k, RhoTheta_comp);
1172  if (rho <= 0.0 || !amrex::Math::isfinite(rho)) rho = 1.0;
1173  if (rho_theta <= 0.0 || !amrex::Math::isfinite(rho_theta)) rho_theta = 288.15;
1174  amrex::Real qv = get_qv_from_state(i, j, k, state_arr);
1175  amrex::Real T_layer = get_temperature_from_rhotheta(rho_theta, rho, qv);
1176 
1177  F_lw_up_iface(m + 1) = compute_lw_flux_up(F_lw_up_iface(m), T_layer, sigma, tau_lw_level(m));
1178  }
1179 
1180  // Per-level LW heating rate from the net-flux divergence across
1181  // each layer (bottom interface m, top interface m+1).
1182  for (int k = kmin; k <= kmax; ++k) {
1183  const int m = k - kmin;
1184  amrex::Real rho = state_arr(i, j, k, Rho_comp);
1185  if (rho <= 0.0 || !amrex::Math::isfinite(rho)) rho = 1.0;
1186  if (rad_flux_out != nullptr) {
1187  (*rad_flux_out)(i, j, k, 2) = F_lw_up_iface(m); // LW up at the lower interface of layer k
1188  (*rad_flux_out)(i, j, k, 3) = F_lw_down_iface(m); // LW down
1189  if (k == kmax) {
1190  (*rad_flux_out)(i, j, kmax + 1, 2) = F_lw_up_iface(nlev); // outgoing LW at the top
1191  (*rad_flux_out)(i, j, kmax + 1, 3) = F_lw_down_iface(nlev); // zero: nothing comes in from space
1192  }
1193  }
1194 
1195  amrex::Real F_net_top = F_lw_up_iface(m + 1) - F_lw_down_iface(m + 1);
1196  amrex::Real F_net_bot = F_lw_up_iface(m) - F_lw_down_iface(m);
1197 
1198  // dT/dt from the net-flux divergence, stored as dtheta/dt = (dT/dt)/pi.
1199  amrex::Real Q_lw = compute_lw_heating_rate(F_net_top, F_net_bot, dz_level(m), rho, cp_air)
1200  * get_inverse_exner(i, j, k, state_arr);
1201  qheating_arr(i, j, k, 1) = Q_lw;
1202 
1203  amrex::Real Q_sw_here = qheating_arr(i, j, k, 0);
1204  local_max_heating = std::max(local_max_heating, std::abs(Q_sw_here) + std::abs(Q_lw));
1205  }
1206  }
1207 
1208  // ========================================================================
1209  // SURFACE AND DIAGNOSTICS
1210  // ========================================================================
1211  if (rad_choice.sw_enabled) {
1212  // Absorbed direct plus diffuse (see the SW block above)
1213  sw_surface_flux = sw_surface_absorbed;
1214  sw_up_toa = sw_up_at_toa;
1215  } else {
1216  sw_surface_flux = 0.0;
1217  sw_up_toa = 0.0;
1218  }
1219 
1220  if (rad_choice.lw_enabled) {
1221  amrex::Real F_lw_up_sfc = F_lw_up_iface(0);
1222  amrex::Real F_lw_down_sfc = F_lw_down_iface(0);
1223  lw_up_toa = F_lw_up_iface(nlev);
1224  lw_net_surface = F_lw_up_sfc - F_lw_down_sfc;
1225  } else {
1226  lw_net_surface = 0.0;
1227  lw_up_toa = 0.0;
1228  }
1229 
1230  max_heating_rate = local_max_heating;
1231 }
if(l_use_mynn &&start_comp<=RhoKE_comp &&end_comp >=RhoKE_comp)
Definition: ERF_AddQKESources.H:2
constexpr amrex::Real Cp_d
Definition: ERF_Constants.H:36
amrex::Real sigma
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:11
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE double orbital_cos_zenith_instant(double jday, double lat, double lon, double declin)
Definition: ERF_OrbCosZenith.H:507
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real get_inverse_exner(int i, int j, int k, const amrex::Array4< const amrex::Real > &state_arr)
Inverse Exner function 1/pi at (i,j,k) from the state, with pi = (p / p_0)^(R_d/c_p) evaluated by get...
Definition: ERF_TwoStreamColumn.H:405
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real resolve_surface_albedo_sw(int i, int j, const amrex::Array4< const amrex::Real > *hetero_alb_sw, const TwoStreamParams &rad_choice, bool has_hetero_alb)
Resolve per-column shortwave surface albedo from hetero field or fallback.
Definition: ERF_TwoStreamColumn.H:245
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void diagnose_layer_optics(int i, int j, int k, amrex::Real dz_layer, amrex::Real z_center, const amrex::Array4< const amrex::Real > &state_arr, amrex::Real tau_base, bool is_sw, bool cloudy, const TwoStreamParams &rad_choice, amrex::Real &tau, amrex::Real &omega, amrex::Real &g)
Optical depth, single-scattering albedo and asymmetry factor of layer k for one band,...
Definition: ERF_TwoStreamColumn.H:747
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real resolve_surface_emissivity_lw(int i, int j, const amrex::Array4< const amrex::Real > *hetero_emiss_lw, const TwoStreamParams &rad_choice, bool has_hetero_emiss)
Resolve per-column longwave surface emissivity from hetero field or fallback.
Definition: ERF_TwoStreamColumn.H:278
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real resolve_surface_temp_k(int i, int j, const amrex::Array4< const amrex::Real > *lsm_t_sfc, bool has_lsm_t_sfc, const amrex::Array4< const amrex::Real > *seb_t_sfc, bool has_seb_t_sfc, const amrex::Array4< const amrex::Real > *surface_layer_theta, bool has_surface_layer, const TwoStreamParams &rad_choice, bool &from_surface_layer)
Resolve the per-column surface-temperature boundary condition.
Definition: ERF_TwoStreamColumn.H:324
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_lw_flux_down(amrex::Real F_down_above, amrex::Real T_layer, amrex::Real sigma, amrex::Real tau_lw)
Compute downwelling LW flux in one layer of a two-stream sweep.
Definition: ERF_TwoStreamLW.H:188
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_thermal_intensity(amrex::Real T, amrex::Real sigma)
Compute thermal (LW) intensity for a given temperature.
Definition: ERF_TwoStreamLW.H:93
constexpr amrex::Real stefan_boltzmann
Stefan-Boltzmann constant [W/(m^2 K^4)] used by the two-stream LW model.
Definition: ERF_TwoStreamLW.H:11
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_lw_heating_rate(amrex::Real F_net_top, amrex::Real F_net_bot, amrex::Real dz, amrex::Real rho, amrex::Real cp)
Compute LW heating rate from net flux divergence.
Definition: ERF_TwoStreamLW.H:236
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_lw_flux_up(amrex::Real F_up_below, amrex::Real T_layer, amrex::Real sigma, amrex::Real tau_lw)
Compute upwelling LW flux in one layer of a two-stream sweep.
Definition: ERF_TwoStreamLW.H:156
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_sw_direct_flux(amrex::Real tau_cumulative, amrex::Real S0, amrex::Real cos_zenith)
Compute Beer-Lambert direct-beam flux at a given optical depth.
Definition: ERF_TwoStreamSW.H:72
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real compute_sw_heating_rate(amrex::Real flux_top, amrex::Real flux_bot, amrex::Real dz, amrex::Real rho, amrex::Real cp)
Compute the shortwave heating rate of a layer from the net (downward positive) flux at its top and bo...
Definition: ERF_TwoStreamSW.H:101
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE TwoStreamLayerSW compute_sw_layer_two_stream(amrex::Real tau, amrex::Real omega, amrex::Real g, amrex::Real cos_zenith)
Compute the two-stream layer solution for shortwave radiation.
Definition: ERF_TwoStreamSW.H:176
constexpr int F_DIR
Definition: ERF_TwoStreamColumn.H:602
constexpr int A
Definition: ERF_TwoStreamColumn.H:603
constexpr int Z
Definition: ERF_TwoStreamColumn.H:600
constexpr int TAU_LW
Definition: ERF_TwoStreamColumn.H:607
constexpr int LW_UP
Definition: ERF_TwoStreamColumn.H:605
constexpr int LW_DN
Definition: ERF_TwoStreamColumn.H:606
constexpr int S
Definition: ERF_TwoStreamColumn.H:604
constexpr int DZ
Definition: ERF_TwoStreamColumn.H:599
constexpr int TAU_SW
Definition: ERF_TwoStreamColumn.H:601
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real pressure_at_surface(const amrex::Real rho, const amrex::Real rho_theta, const amrex::Real qv, const amrex::Real delta_z)
Definition: ERF_SurfaceTemperature.H:30
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool theta_to_temperature(const amrex::Real theta, const amrex::Real pressure, const amrex::Real rdOcp, amrex::Real &temperature)
Definition: ERF_SurfaceTemperature.H:54
Two-stream reflectance and transmittance of one homogeneous layer.
Definition: ERF_TwoStreamSW.H:132
amrex::Real T_dir
Definition: ERF_TwoStreamSW.H:136
amrex::Real T_dif
Definition: ERF_TwoStreamSW.H:134
amrex::Real R_dif
Definition: ERF_TwoStreamSW.H:133
amrex::Real R_dir
Definition: ERF_TwoStreamSW.H:135
amrex::Real cos_zenith_fixed
Definition: ERF_TwoStreamColumn.H:114
bool sw_enabled
Definition: ERF_TwoStreamColumn.H:58
amrex::Real tau_lw_per_layer
Definition: ERF_TwoStreamColumn.H:61
amrex::Real S0
Definition: ERF_TwoStreamColumn.H:115
amrex::Real lat_cons_rad
Definition: ERF_TwoStreamColumn.H:118
bool lw_enabled
Definition: ERF_TwoStreamColumn.H:59
amrex::Real surface_albedo_sw_diffuse
Definition: ERF_TwoStreamColumn.H:74
amrex::Real rdOcp
Definition: ERF_TwoStreamColumn.H:77
amrex::Real tau_per_layer
Definition: ERF_TwoStreamColumn.H:60
amrex::Real calday
Definition: ERF_TwoStreamColumn.H:116
amrex::Real declin
Definition: ERF_TwoStreamColumn.H:117
amrex::Real lon_cons_rad
Definition: ERF_TwoStreamColumn.H:119
bool solar_dynamic
Definition: ERF_TwoStreamColumn.H:113

Referenced by TwoStreamRadiation::advance().

Here is the call graph for this function:
Here is the caller graph for this function: