ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_RichardsonNumber.H File Reference
#include <AMReX_GpuQualifiers.H>
#include <AMReX_REAL.H>
#include <cmath>
#include "ERF_Constants.H"
#include "ERF_IndexDefines.H"
#include "ERF_DataStruct.H"
#include "ERF_MoistUtils.H"
Include dependency graph for ERF_RichardsonNumber.H:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Functions

AMREX_GPU_DEVICE AMREX_FORCE_INLINE bool IsSaturated (int i, int j, int k, const amrex::Array4< const amrex::Real > &cell_data, int qc_index)
 
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeN2 (int i, int j, int k, amrex::Real dzInv, amrex::Real const_grav, const amrex::Array4< const amrex::Real > &cell_data, const MoistureComponentIndices &moisture_indices)
 
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeVerticalShear2 (int i, int j, int k, amrex::Real dzInv, const amrex::Array4< const amrex::Real > &u, const amrex::Array4< const amrex::Real > &v)
 
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeRichardson (amrex::Real N2_moist, amrex::Real S2_vert)
 
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real StabilityFunction (amrex::Real Ri, amrex::Real Ri_crit)
 

Detailed Description

Functions for computing moist Richardson number and Mason (1989) stability function for Smagorinsky turbulence closure with conditional instability correction.

PHYSICAL BACKGROUND:

In cloud-resolving simulations, standard Smagorinsky models overpredict turbulent mixing in conditionally unstable regions (cloud updrafts) where buoyancy-driven instability occurs. This module implements the moist Richardson number correction following Mason (1989) and Stevens et al. (2005) for marine stratocumulus.

KEY EQUATIONS:

  1. Moist Brunt-Väisälä frequency: N²_moist = (g/θ_v) * ∂θ_v/∂z (unsaturated) N²_moist = (g/θ_l) * ∂θ_l/∂z (saturated, in clouds)
  2. Moist Richardson number: Ri_moist = N²_moist / S²_vert where S²_vert = (∂u/∂z)² + (∂v/∂z)²
  3. Mason (1989) stability function: f(Ri) = 0 if Ri ≥ Ri_crit (complete suppression) = sqrt(1 - Ri/Ri_crit) if 0 < Ri < Ri_crit (partial suppression) = 1 if Ri ≤ 0 (no suppression, unstable/neutral)
  4. Corrected vertical eddy viscosity: μ_t,v = ρ(C_s·Δ)²|S| · f(Ri_moist)

PHYSICAL INTERPRETATION:

  • In cloud updrafts: ∂θ_l/∂z < 0 → Ri < 0 → f(Ri) = 1.0 → full mixing (correct!)
  • In stable regions: ∂θ_v/∂z > 0 → Ri > 0 → f(Ri) < 1.0 → reduced mixing
  • In neutral regions: Ri ≈ 0 → f(Ri) ≈ 1.0 → standard Smagorinsky

CUDA-CONSERVATIVE DESIGN:

  • All functions are explicit device functions (not lambdas)
  • Use simple scalar parameters (no struct captures in device context)
  • Geometry passed via local scalars (dzInv), not complex objects
  • Reuse existing ERF GetThetav/GetThetal from ERF_MoistUtils.H

REFERENCES:

  • Mason, P. J. (1989): Large-eddy simulation of the convective atmospheric boundary layer. J. Atmos. Sci., 46, 1492-1516.
  • Stevens et al. (2005): Evaluation of large-eddy simulations via observations of nocturnal marine stratocumulus. Mon. Wea. Rev., 133, 1443-1462.
  • Smagorinsky, J. (1963): General circulation experiments with the primitive equations. Mon. Wea. Rev., 91, 99-164.

Function Documentation

◆ ComputeN2()

AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeN2 ( int  i,
int  j,
int  k,
amrex::Real  dzInv,
amrex::Real  const_grav,
const amrex::Array4< const amrex::Real > &  cell_data,
const MoistureComponentIndices moisture_indices 
)

Compute moist Brunt-Väisälä frequency squared (N²_moist).

Parameters
[in]i,j,kcell indices
[in]dzInvinverse vertical grid spacing (1/Δz) [1/m]
[in]const_gravgravitational acceleration g [m/s²]
[in]cell_dataconserved state
[in]moisture_indicesmoisture species indices
Returns
N²_moist [1/s²]

PHYSICS:

Uses centered finite differences: ∂/∂z ≈ (f_{k+1} - f_{k-1}) / (2Δz)

Unsaturated: N² = (g/θ_v) · ∂θ_v/∂z where θ_v = θ(1 + 0.61q_v - q_c) accounts for vapor buoyancy and condensate loading

Saturated: N² = (g/θ_l) · ∂θ_l/∂z where θ_l = θ - (L_v/c_p·Π)·q_c is the liquid water potential temperature

CONDITIONAL INSTABILITY:

In cloud updrafts, ∂θ_l/∂z can be negative due to release of latent heat during ascent. This gives N² < 0, which is physically correct for convective instability and should enhance (not suppress) mixing.

UNITS:

Input: dzInv [1/m], const_grav [m/s²], θ [K] Output: N² [1/s²]

131 {
132  const int rho_comp = Rho_comp;
133  const int rhoTheta_comp = RhoTheta_comp;
134  const int qv_index = moisture_indices.qv;
135  const int qc_index = moisture_indices.qc;
136 
137  amrex::Real rho = cell_data(i, j, k, rho_comp);
138 
139  bool saturated = IsSaturated(i, j, k, cell_data, qc_index);
140 
141  amrex::Real inv_theta;
142  amrex::Real dtheta_dz;
143 
144  if (saturated && qc_index >= 0) {
145  amrex::Real theta_l = GetThetal(i, j, k , cell_data, moisture_indices);
146  amrex::Real theta_l_kp1= GetThetal(i, j, k+1, cell_data, moisture_indices);
147  amrex::Real theta_l_km1= GetThetal(i, j, k-1, cell_data, moisture_indices);
148 
149  inv_theta = 1.0 / theta_l;
150  dtheta_dz = 0.5 * (theta_l_kp1 - theta_l_km1) * dzInv;
151 
152  } else if (qv_index >= 0) {
153  amrex::Real theta_v = GetThetav(i, j, k , cell_data, moisture_indices);
154  amrex::Real theta_v_kp1= GetThetav(i, j, k+1, cell_data, moisture_indices);
155  amrex::Real theta_v_km1= GetThetav(i, j, k-1, cell_data, moisture_indices);
156 
157  inv_theta = 1.0 / theta_v;
158  dtheta_dz = 0.5 * (theta_v_kp1 - theta_v_km1) * dzInv;
159 
160  } else {
161  amrex::Real theta = cell_data(i, j, k, rhoTheta_comp) / rho;
162  amrex::Real theta_kp1 = cell_data(i, j, k+1, rhoTheta_comp) / cell_data(i, j, k+1, rho_comp);
163  amrex::Real theta_km1 = cell_data(i, j, k-1, rhoTheta_comp) / cell_data(i, j, k-1, rho_comp);
164 
165  inv_theta = 1.0 / theta;
166  dtheta_dz = 0.5 * (theta_kp1 - theta_km1) * dzInv;
167  }
168 
169  return const_grav * inv_theta * dtheta_dz;
170 }
#define Rho_comp
Definition: ERF_IndexDefines.H:36
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:37
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real GetThetal(int i, int j, int k, const amrex::Array4< amrex::Real const > &cell_data, const MoistureComponentIndices &moisture_indices)
Definition: ERF_MoistUtils.H:101
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real GetThetav(int i, int j, int k, const amrex::Array4< amrex::Real const > &cell_data, const MoistureComponentIndices &moisture_indices)
Definition: ERF_MoistUtils.H:72
AMREX_GPU_DEVICE AMREX_FORCE_INLINE bool IsSaturated(int i, int j, int k, const amrex::Array4< const amrex::Real > &cell_data, int qc_index)
Definition: ERF_RichardsonNumber.H:80
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ theta
Definition: ERF_MM5.H:20
@ rho
Definition: ERF_Kessler.H:22
int qv
Definition: ERF_DataStruct.H:100
int qc
Definition: ERF_DataStruct.H:101

Referenced by ComputeTurbulentViscosityLES().

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

◆ ComputeRichardson()

AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeRichardson ( amrex::Real  N2_moist,
amrex::Real  S2_vert 
)

Compute moist Richardson number.

Parameters
[in]N2_moistmoist Brunt-Väisälä frequency squared [1/s²]
[in]S2_vertvertical wind shear squared [1/s²]
Returns
Ri_moist = N²_moist / S²_vert [dimensionless]
231 {
232  amrex::Real S2_safe = amrex::max(S2_vert, 1.0e-10);
233  return N2_moist / S2_safe;
234 }

Referenced by ComputeTurbulentViscosityLES().

Here is the caller graph for this function:

◆ ComputeVerticalShear2()

AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeVerticalShear2 ( int  i,
int  j,
int  k,
amrex::Real  dzInv,
const amrex::Array4< const amrex::Real > &  u,
const amrex::Array4< const amrex::Real > &  v 
)

Compute vertical shear squared (S²_vert).

Parameters
[in]i,j,kcell indices
[in]dzInvinverse vertical grid spacing (1/Δz) [1/m]
[in]uface-centered x-velocity (u) array with ngrow≥1 in z
[in]vface-centered y-velocity (v) array with ngrow≥1 in z
Returns
S²_vert = (∂u/∂z)² + (∂v/∂z)² [1/s²]

DISCRETIZATION:

ERF uses Arakawa C-grid: u defined at (i+1/2, j, k) (x-faces) v defined at (i, j+1/2, k) (y-faces) w defined at (i, j, k+1/2) (z-faces) cell centers at (i, j, k)

To get shear at cell center (i,j,k), interpolate velocities to center then difference: ∂u/∂z|_{i,j,k} ≈ (u_{i,j,k+1} - u_{i,j,k-1}) / (2Δz) where u_{i,j,k} = 0.5 * (u_{i-1/2,j,k} + u_{i+1/2,j,k})

UNITS:

Input: dzInv [1/m], u, v [m/s] Output: S²_vert [1/s²]

205 {
206  amrex::Real u_c_km1 = 0.5 * (u(i, j, k-1) + u(i+1, j, k-1));
207  amrex::Real u_c_kp1 = 0.5 * (u(i, j, k+1) + u(i+1, j, k+1));
208 
209  amrex::Real v_c_km1 = 0.5 * (v(i, j, k-1) + v(i, j+1, k-1));
210  amrex::Real v_c_kp1 = 0.5 * (v(i, j, k+1) + v(i, j+1, k+1));
211 
212  amrex::Real du_dz = 0.5 * (u_c_kp1 - u_c_km1) * dzInv;
213  amrex::Real dv_dz = 0.5 * (v_c_kp1 - v_c_km1) * dzInv;
214 
215  amrex::Real S2_vert = du_dz * du_dz + dv_dz * dv_dz;
216 
217  return S2_vert;
218 }

Referenced by ComputeTurbulentViscosityLES().

Here is the caller graph for this function:

◆ IsSaturated()

AMREX_GPU_DEVICE AMREX_FORCE_INLINE bool IsSaturated ( int  i,
int  j,
int  k,
const amrex::Array4< const amrex::Real > &  cell_data,
int  qc_index 
)

Check if grid cell is saturated based on cloud liquid water content.

Parameters
[in]i,j,kcell indices
[in]cell_dataconserved state (ρ, ρθ, ρq_v, ρq_c, etc.)
[in]moisture_indicesindices for moisture species
Returns
true if saturated (q_c > 1e-8 kg/kg), false otherwise

PHYSICAL JUSTIFICATION: Threshold of 1e-8 kg/kg (0.01 g/kg) is typical for distinguishing cloudy from clear regions in LES. Below this threshold, condensate is negligible and air is effectively unsaturated.

83 {
84  if (qc_index >= 0) {
85  amrex::Real rho = cell_data(i, j, k, Rho_comp);
86  amrex::Real qc = cell_data(i, j, k, qc_index) / rho;
87  return (qc > 1.0e-8); // 0.01 g/kg threshold
88  }
89  return false;
90 }
@ qc
Definition: ERF_SatAdj.H:36

Referenced by ComputeN2().

Here is the caller graph for this function:

◆ StabilityFunction()

AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real StabilityFunction ( amrex::Real  Ri,
amrex::Real  Ri_crit 
)

Mason (1989) stability function for turbulent mixing suppression.

Parameters
[in]Rigradient Richardson number [dimensionless]
[in]Ri_critcritical Richardson number (default 0.25)
Returns
f(Ri) ∈ [0, 1] [dimensionless]
247 {
248  if (Ri >= Ri_crit) {
249  return 0.0;
250  } else if (Ri > 0.0) {
251  return std::sqrt(1.0 - Ri / Ri_crit);
252  } else {
253  return 1.0;
254  }
255 }

Referenced by ComputeTurbulentViscosityLES().

Here is the caller graph for this function: