GABLS4 LES Intercomparison Study for Stable Boundary Layers: Sensitivity with respect to SGS Models

[1]:
from IPython.display import display, Markdown
from datetime import datetime, timezone
display(Markdown(f"*Last run: {datetime.now(timezone.utc).strftime('%B %d, %Y at %H:%M UTC')}*"))

Last run: June 24, 2026 at 09:27 UTC

For case setup and physical parameters, see the Description notebook.

SGS models compared: LASDD-SM, LASDD-WL, LAD-SM, LAD-WL; grid: \(256^3\) (SP). Profiles shown at 5 UTC (simulation hour 5) and 17 UTC (simulation hour 17). The simulation starts at 0 UTC; the Dome C site is at 75.1°S.

Setup

The next cells load Python packages, locate the simulation outputs, and define the vertical grid and snapshot times.

[13]:
import os
import re
import glob
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path

Output directories

[14]:
from pathlib import Path

def find_repo_root(start=None):
    path = Path(start or ('__file__' in globals() and __file__) or Path.cwd()).resolve()
    for candidate in (path, *path.parents):
        if (candidate / 'examples').is_dir() and (candidate / 'docs').is_dir():
            return candidate
    raise FileNotFoundError('Could not locate jaxalfa repository root')

BaseDir = find_repo_root()

def read_config(run_dir):
    cfg = {}
    exec((run_dir / 'Config.py').read_text(), cfg)
    return cfg


optRes = 3  # 64x64x64: 1, 128x128x128: 2, 256x256x256: 3, 384x384x384: 4

_res = {1: '64x64x64', 2: '128x128x128', 3: '256x256x256', 4: '384x384x384'}
res_str = _res[optRes]

OutputDir1 = BaseDir / f'examples_A6000ada/SBL_GABLS4/runs/{res_str}_LASDD_SM_SP/output'
OutputDir2 = BaseDir / f'examples_A6000ada/SBL_GABLS4/runs/{res_str}_LASDD_WL_SP/output'
OutputDir3 = BaseDir / f'examples_A6000ada/SBL_GABLS4/runs/{res_str}_LAD_SM_SP/output'
OutputDir4 = BaseDir / f'examples_A6000ada/SBL_GABLS4/runs/{res_str}_LAD_WL_SP/output'

Case configuration

[15]:
# Use the first available run directory to read shared parameters.
_ref_dir = next(
    (d.parent for d in [OutputDir1, OutputDir2, OutputDir3, OutputDir4]
     if (d.parent / 'Config.py').exists()),
    OutputDir1.parent
)
cfg = read_config(_ref_dir)

nz                 = int(cfg['nz'])
l_z                = float(cfg['l_z'])
dt                 = float(cfg['dt'])
OutputInterval_sec = float(cfg['OutputInterval_sec'])
z_damping          = float(cfg.get('z_damping', 700.0))

print(f'nz={nz}, l_z={l_z:g} m, OutputInterval={OutputInterval_sec:g} s')
print(f'Damping layer above z={z_damping:g} m')
nz=256, l_z=1000 m, OutputInterval=600 s
Damping layer above z=700 m

Vertical grid and snapshot indices

[16]:
# Half levels — U, V, TH, u2, v2, TH2
z   = np.array([(k + 0.5) * l_z / (nz - 1) for k in range(nz)])

# Full (staggered) levels — w, uw, vw, wTH, txz, tyz, qz
z_w = np.array([k * l_z / (nz - 1) for k in range(nz)])

# GABLS4 simulation starts at 0 UTC.
# 5 UTC  = simulation hour  5 h = 18 000 s
# 17 UTC = simulation hour 17 h = 61 200 s
T_snap1_s =  5.0 * 3600   # 5 UTC
T_snap2_s = 17.0 * 3600   # 17 UTC

snap1_index = int(T_snap1_s / OutputInterval_sec) - 1   # 29
snap2_index = int(T_snap2_s / OutputInterval_sec) - 1   # 101

print(f'5 UTC  (sim h  5): file index {snap1_index}')
print(f'17 UTC (sim h 17): file index {snap2_index}')
5 UTC  (sim h  5): file index 29
17 UTC (sim h 17): file index 101

Snapshot loader

[17]:
def LoadSnapshot(stat_files, snap_index, nz_expected):
    if len(stat_files) == 0 or snap_index >= len(stat_files):
        print(f'No data at index {snap_index}; plotting NaN placeholders for nz={nz_expected}.')
        nan = np.full(nz_expected, np.nan)
        return tuple(nan.copy() for _ in range(15))
    with np.load(stat_files[snap_index]) as d:
        return (
            d['U'].copy(),   d['V'].copy(),   d['TH'].copy(),
            d['u2'].copy(),  d['v2'].copy(),  d['w2'].copy(),
            d['TH2'].copy(),
            d['uv'].copy(),  d['uw'].copy(),  d['vw'].copy(),
            d['txy'].copy(), d['txz'].copy(), d['tyz'].copy(),
            d['wTH'].copy(), d['qz'].copy()
        )

Available statistics files

[18]:
def get_stat_files(output_dir):
    files = sorted(
        glob.glob(str(output_dir / 'ALFA_Statistics_Iteration_*.npz')),
        key=lambda x: int(re.search(r'Iteration_(\d+)', x).group(1))
    )
    return files

StatFiles1 = get_stat_files(OutputDir1)
StatFiles2 = get_stat_files(OutputDir2)
StatFiles3 = get_stat_files(OutputDir3)
StatFiles4 = get_stat_files(OutputDir4)

print(f'LASDD-SM : {len(StatFiles1)} files')
print(f'LASDD-WL : {len(StatFiles2)} files')
print(f'LAD-SM   : {len(StatFiles3)} files')
print(f'LAD-WL   : {len(StatFiles4)} files')
LASDD-SM : 144 files
LASDD-WL : 144 files
LAD-SM   : 0 files
LAD-WL   : 0 files

Load snapshots

[19]:
# ---- 5 UTC (simulation hour 5) ----
(U1_a, V1_a, TH1_a, u2_1a, v2_1a, w2_1a, TH2_1a,
 uv_1a, uw_1a, vw_1a, txy_1a, txz_1a, tyz_1a,
 wTH_1a, qz_1a) = LoadSnapshot(StatFiles1, snap1_index, nz)

(U2_a, V2_a, TH2_a, u2_2a, v2_2a, w2_2a, TH2_2a,
 uv_2a, uw_2a, vw_2a, txy_2a, txz_2a, tyz_2a,
 wTH_2a, qz_2a) = LoadSnapshot(StatFiles2, snap1_index, nz)

(U3_a, V3_a, TH3_a, u2_3a, v2_3a, w2_3a, TH2_3a,
 uv_3a, uw_3a, vw_3a, txy_3a, txz_3a, tyz_3a,
 wTH_3a, qz_3a) = LoadSnapshot(StatFiles3, snap1_index, nz)

(U4_a, V4_a, TH4_a, u2_4a, v2_4a, w2_4a, TH2_4a,
 uv_4a, uw_4a, vw_4a, txy_4a, txz_4a, tyz_4a,
 wTH_4a, qz_4a) = LoadSnapshot(StatFiles4, snap1_index, nz)

# ---- 17 UTC (simulation hour 17) ----
(U1_b, V1_b, TH1_b, u2_1b, v2_1b, w2_1b, TH2_1b,
 uv_1b, uw_1b, vw_1b, txy_1b, txz_1b, tyz_1b,
 wTH_1b, qz_1b) = LoadSnapshot(StatFiles1, snap2_index, nz)

(U2_b, V2_b, TH2_b, u2_2b, v2_2b, w2_2b, TH2_2b,
 uv_2b, uw_2b, vw_2b, txy_2b, txz_2b, tyz_2b,
 wTH_2b, qz_2b) = LoadSnapshot(StatFiles2, snap2_index, nz)

(U3_b, V3_b, TH3_b, u2_3b, v2_3b, w2_3b, TH2_3b,
 uv_3b, uw_3b, vw_3b, txy_3b, txz_3b, tyz_3b,
 wTH_3b, qz_3b) = LoadSnapshot(StatFiles3, snap2_index, nz)

(U4_b, V4_b, TH4_b, u2_4b, v2_4b, w2_4b, TH2_4b,
 uv_4b, uw_4b, vw_4b, txy_4b, txz_4b, tyz_4b,
 wTH_4b, qz_4b) = LoadSnapshot(StatFiles4, snap2_index, nz)

# ---- Derived quantities ----
S1_a = np.sqrt(U1_a**2 + V1_a**2);  S1_b = np.sqrt(U1_b**2 + V1_b**2)
S2_a = np.sqrt(U2_a**2 + V2_a**2);  S2_b = np.sqrt(U2_b**2 + V2_b**2)
S3_a = np.sqrt(U3_a**2 + V3_a**2);  S3_b = np.sqrt(U3_b**2 + V3_b**2)
S4_a = np.sqrt(U4_a**2 + V4_a**2);  S4_b = np.sqrt(U4_b**2 + V4_b**2)

uw_tot_1a = uw_1a + txz_1a;  uw_tot_1b = uw_1b + txz_1b
vw_tot_1a = vw_1a + tyz_1a;  vw_tot_1b = vw_1b + tyz_1b
wT_tot_1a = wTH_1a + qz_1a;  wT_tot_1b = wTH_1b + qz_1b

uw_tot_2a = uw_2a + txz_2a;  uw_tot_2b = uw_2b + txz_2b
vw_tot_2a = vw_2a + tyz_2a;  vw_tot_2b = vw_2b + tyz_2b
wT_tot_2a = wTH_2a + qz_2a;  wT_tot_2b = wTH_2b + qz_2b

uw_tot_3a = uw_3a + txz_3a;  uw_tot_3b = uw_3b + txz_3b
vw_tot_3a = vw_3a + tyz_3a;  vw_tot_3b = vw_3b + tyz_3b
wT_tot_3a = wTH_3a + qz_3a;  wT_tot_3b = wTH_3b + qz_3b

uw_tot_4a = uw_4a + txz_4a;  uw_tot_4b = uw_4b + txz_4b
vw_tot_4a = vw_4a + tyz_4a;  vw_tot_4b = vw_4b + tyz_4b
wT_tot_4a = wTH_4a + qz_4a;  wT_tot_4b = wTH_4b + qz_4b

print('Snapshots loaded.')
No data at index 29; plotting NaN placeholders for nz=256.
No data at index 29; plotting NaN placeholders for nz=256.
No data at index 101; plotting NaN placeholders for nz=256.
No data at index 101; plotting NaN placeholders for nz=256.
Snapshots loaded.
[20]:
plt.rcParams.update({
    'text.usetex'    : True,
    'font.size'      : 14,
    'axes.labelsize' : 16,
    'xtick.labelsize': 12,
    'ytick.labelsize': 12,
})

z_max = z_damping   # show only below the damping layer
z_max_a = 500.0     # 5 UTC profiles
z_max_b = 120.0     # 17 UTC profiles
z_maxs  = [z_max_a, z_max_b]

run_styles = {
    'LASDD-SM': {'color': 'red',     'linestyle': '-'},
    'LASDD-WL': {'color': 'blue',    'linestyle': '-'},
    'LAD-SM'  : {'color': 'green',   'linestyle': '-'},
    'LAD-WL'  : {'color': 'magenta', 'linestyle': '-'},
}

def prf(ax, x, z, xlabel, run_label):
    s = run_styles[run_label]
    ax.plot(x, z, color=s['color'], linestyle=s['linestyle'], linewidth=2, label=run_label)
    ax.set_xlabel(xlabel)
    ax.set_ylabel(r'$z$ (m)')
    ax.set_ylim(0, z_max)

_res_label = {1: r'$64^3$', 2: r'$128^3$', 3: r'$256^3$', 4: r'$384^3$'}

Mean Wind and Hodograph

Horizontal mean wind components (\(U\), \(V\)), wind-speed magnitude, and hodograph at the two target times. Top row: 5 UTC (simulation hour 5); bottom row: 17 UTC (simulation hour 17).

[21]:
fig, axs = plt.subplots(2, 4, figsize=(18, 10), constrained_layout=True)

for row, (tag, U1,V1,S1, U2,V2,S2, U3,V3,S3, U4,V4,S4) in enumerate([
    ('5 UTC',  U1_a,V1_a,S1_a, U2_a,V2_a,S2_a, U3_a,V3_a,S3_a, U4_a,V4_a,S4_a),
    ('17 UTC', U1_b,V1_b,S1_b, U2_b,V2_b,S2_b, U3_b,V3_b,S3_b, U4_b,V4_b,S4_b),
]):
    z_max = z_maxs[row]
    prf(axs[row,0], U1, z, r'$U$ (m/s)', 'LASDD-SM')
    prf(axs[row,0], U2, z, r'$U$ (m/s)', 'LASDD-WL')
    prf(axs[row,0], U3, z, r'$U$ (m/s)', 'LAD-SM')
    prf(axs[row,0], U4, z, r'$U$ (m/s)', 'LAD-WL')

    prf(axs[row,1], V1, z, r'$V$ (m/s)', 'LASDD-SM')
    prf(axs[row,1], V2, z, r'$V$ (m/s)', 'LASDD-WL')
    prf(axs[row,1], V3, z, r'$V$ (m/s)', 'LAD-SM')
    prf(axs[row,1], V4, z, r'$V$ (m/s)', 'LAD-WL')

    prf(axs[row,2], S1, z, r'Wind Speed (m/s)', 'LASDD-SM')
    prf(axs[row,2], S2, z, r'Wind Speed (m/s)', 'LASDD-WL')
    prf(axs[row,2], S3, z, r'Wind Speed (m/s)', 'LAD-SM')
    prf(axs[row,2], S4, z, r'Wind Speed (m/s)', 'LAD-WL')

    for lbl, U, V in [('LASDD-SM',U1,V1),('LASDD-WL',U2,V2),('LAD-SM',U3,V3),('LAD-WL',U4,V4)]:
        s = run_styles[lbl]
        axs[row,3].plot(U, V, color=s['color'], linestyle='-', marker='o',
                        linewidth=2, markersize=3, label=lbl)
    axs[row,3].set_xlabel(r'$U$ (m/s)')
    axs[row,3].set_ylabel(r'$V$ (m/s)')
    axs[row,3].set_title('Hodograph')
    axs[row,3].set_aspect('equal')

    for ax in axs[row, :3]:
        ax.grid(); ax.legend(frameon=False)
    axs[row,3].grid(); axs[row,3].legend(frameon=False)
    axs[row,0].set_title(f'{tag}')

fig.suptitle(
    f'Mean Wind Profiles and Hodograph: SGS sensitivity, {_res_label[optRes]}, SP',
    fontsize=18
)
plt.show()
../../../_images/examples_SBL_GABLS4_notebooks_SBL_GABLS4_SGSModelSensitivity_19_0.png

Mean Potential Temperature and Temperature Variance

Horizontally averaged potential-temperature profile and resolved temperature variance. Top: 5 UTC; bottom: 17 UTC.

[22]:
fig, axs = plt.subplots(2, 2, figsize=(10, 10), constrained_layout=True)

for row, (tag, TH1,TH2_,TH3,TH4, TH2v1,TH2v2,TH2v3,TH2v4) in enumerate([
    ('5 UTC',  TH1_a,TH2_a,TH3_a,TH4_a, TH2_1a,TH2_2a,TH2_3a,TH2_4a),
    ('17 UTC', TH1_b,TH2_b,TH3_b,TH4_b, TH2_1b,TH2_2b,TH2_3b,TH2_4b),
]):
    z_max = z_maxs[row]
    prf(axs[row,0], TH1,  z, r'$\langle\theta\rangle$ (K)', 'LASDD-SM')
    prf(axs[row,0], TH2_, z, r'$\langle\theta\rangle$ (K)', 'LASDD-WL')
    prf(axs[row,0], TH3,  z, r'$\langle\theta\rangle$ (K)', 'LAD-SM')
    prf(axs[row,0], TH4,  z, r'$\langle\theta\rangle$ (K)', 'LAD-WL')
    axs[row,0].set_title(f'Mean Potential Temperature — {tag}')

    prf(axs[row,1], TH2v1, z, r"$\langle\theta'^2\rangle$ (K$^2$)", 'LASDD-SM')
    prf(axs[row,1], TH2v2, z, r"$\langle\theta'^2\rangle$ (K$^2$)", 'LASDD-WL')
    prf(axs[row,1], TH2v3, z, r"$\langle\theta'^2\rangle$ (K$^2$)", 'LAD-SM')
    prf(axs[row,1], TH2v4, z, r"$\langle\theta'^2\rangle$ (K$^2$)", 'LAD-WL')
    axs[row,1].set_title(f'Temperature Variance — {tag}')

    for ax in axs[row]:
        ax.grid(); ax.legend(frameon=False)

fig.suptitle(
    f'Potential Temperature Statistics: SGS sensitivity, {_res_label[optRes]}, SP',
    fontsize=18
)
plt.show()
../../../_images/examples_SBL_GABLS4_notebooks_SBL_GABLS4_SGSModelSensitivity_21_0.png

Resolved Velocity Variances

Resolved variance profiles of \(u\), \(v\), and \(w\). Top: 5 UTC; bottom: 17 UTC.

[23]:
fig, axs = plt.subplots(2, 3, figsize=(15, 10), constrained_layout=True)

for row, (tag, u2_1,v2_1,w2_1, u2_2,v2_2,w2_2, u2_3,v2_3,w2_3, u2_4,v2_4,w2_4) in enumerate([
    ('5 UTC',  u2_1a,v2_1a,w2_1a, u2_2a,v2_2a,w2_2a, u2_3a,v2_3a,w2_3a, u2_4a,v2_4a,w2_4a),
    ('17 UTC', u2_1b,v2_1b,w2_1b, u2_2b,v2_2b,w2_2b, u2_3b,v2_3b,w2_3b, u2_4b,v2_4b,w2_4b),
]):
    z_max = z_maxs[row]
    prf(axs[row,0], u2_1, z,   r'Resolved $\sigma_u^2$ (m$^2$/s$^2$)', 'LASDD-SM')
    prf(axs[row,0], u2_2, z,   r'Resolved $\sigma_u^2$ (m$^2$/s$^2$)', 'LASDD-WL')
    prf(axs[row,0], u2_3, z,   r'Resolved $\sigma_u^2$ (m$^2$/s$^2$)', 'LAD-SM')
    prf(axs[row,0], u2_4, z,   r'Resolved $\sigma_u^2$ (m$^2$/s$^2$)', 'LAD-WL')

    prf(axs[row,1], v2_1, z,   r'Resolved $\sigma_v^2$ (m$^2$/s$^2$)', 'LASDD-SM')
    prf(axs[row,1], v2_2, z,   r'Resolved $\sigma_v^2$ (m$^2$/s$^2$)', 'LASDD-WL')
    prf(axs[row,1], v2_3, z,   r'Resolved $\sigma_v^2$ (m$^2$/s$^2$)', 'LAD-SM')
    prf(axs[row,1], v2_4, z,   r'Resolved $\sigma_v^2$ (m$^2$/s$^2$)', 'LAD-WL')

    prf(axs[row,2], w2_1, z_w, r'Resolved $\sigma_w^2$ (m$^2$/s$^2$)', 'LASDD-SM')
    prf(axs[row,2], w2_2, z_w, r'Resolved $\sigma_w^2$ (m$^2$/s$^2$)', 'LASDD-WL')
    prf(axs[row,2], w2_3, z_w, r'Resolved $\sigma_w^2$ (m$^2$/s$^2$)', 'LAD-SM')
    prf(axs[row,2], w2_4, z_w, r'Resolved $\sigma_w^2$ (m$^2$/s$^2$)', 'LAD-WL')

    for ax in axs[row]:
        ax.grid(); ax.legend(frameon=False)
    axs[row,0].set_title(tag)

fig.suptitle(
    f'Resolved Velocity Variances: SGS sensitivity, {_res_label[optRes]}, SP',
    fontsize=18
)
plt.show()
../../../_images/examples_SBL_GABLS4_notebooks_SBL_GABLS4_SGSModelSensitivity_23_0.png

Total Momentum and Heat Fluxes

Total (resolved + SGS) momentum and sensible heat fluxes. Top: 5 UTC; bottom: 17 UTC.

[24]:
fig, axs = plt.subplots(2, 3, figsize=(15, 10), constrained_layout=True)

for row, (tag,
          uw1,vw1,wt1, uw2,vw2,wt2, uw3,vw3,wt3, uw4,vw4,wt4) in enumerate([
    ('5 UTC',
     uw_tot_1a,vw_tot_1a,wT_tot_1a,
     uw_tot_2a,vw_tot_2a,wT_tot_2a,
     uw_tot_3a,vw_tot_3a,wT_tot_3a,
     uw_tot_4a,vw_tot_4a,wT_tot_4a),
    ('17 UTC',
     uw_tot_1b,vw_tot_1b,wT_tot_1b,
     uw_tot_2b,vw_tot_2b,wT_tot_2b,
     uw_tot_3b,vw_tot_3b,wT_tot_3b,
     uw_tot_4b,vw_tot_4b,wT_tot_4b),
]):
    z_max = z_maxs[row]
    prf(axs[row,0], uw1, z_w, r"Total $\langle u'w'\rangle$ (m$^2$/s$^2$)", 'LASDD-SM')
    prf(axs[row,0], uw2, z_w, r"Total $\langle u'w'\rangle$ (m$^2$/s$^2$)", 'LASDD-WL')
    prf(axs[row,0], uw3, z_w, r"Total $\langle u'w'\rangle$ (m$^2$/s$^2$)", 'LAD-SM')
    prf(axs[row,0], uw4, z_w, r"Total $\langle u'w'\rangle$ (m$^2$/s$^2$)", 'LAD-WL')

    prf(axs[row,1], vw1, z_w, r"Total $\langle v'w'\rangle$ (m$^2$/s$^2$)", 'LASDD-SM')
    prf(axs[row,1], vw2, z_w, r"Total $\langle v'w'\rangle$ (m$^2$/s$^2$)", 'LASDD-WL')
    prf(axs[row,1], vw3, z_w, r"Total $\langle v'w'\rangle$ (m$^2$/s$^2$)", 'LAD-SM')
    prf(axs[row,1], vw4, z_w, r"Total $\langle v'w'\rangle$ (m$^2$/s$^2$)", 'LAD-WL')

    prf(axs[row,2], wt1, z_w, r"Total $\langle w'\Theta'\rangle$ (K m/s)", 'LASDD-SM')
    prf(axs[row,2], wt2, z_w, r"Total $\langle w'\Theta'\rangle$ (K m/s)", 'LASDD-WL')
    prf(axs[row,2], wt3, z_w, r"Total $\langle w'\Theta'\rangle$ (K m/s)", 'LAD-SM')
    prf(axs[row,2], wt4, z_w, r"Total $\langle w'\Theta'\rangle$ (K m/s)", 'LAD-WL')

    for ax in axs[row]:
        ax.grid(); ax.legend(frameon=False)
    axs[row,0].set_title(tag)

fig.suptitle(
    f'Total (Resolved + SGS) Fluxes: SGS sensitivity, {_res_label[optRes]}, SP',
    fontsize=18
)
plt.show()
../../../_images/examples_SBL_GABLS4_notebooks_SBL_GABLS4_SGSModelSensitivity_25_0.png