Spaces:
Sleeping
Sleeping
File size: 4,926 Bytes
87602e0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | """Sounding and storm-scale diagnostic calculations."""
from __future__ import annotations
import numpy as np
def bunkers_storm_motion(
z: np.ndarray,
u: np.ndarray,
v: np.ndarray,
z_top_km: float = 6.0,
deviation_ms: float = 7.5,
) -> dict:
"""Bunkers et al. (2000) right- and left-mover storm motion.
Uses the 0–``z_top_km`` mean wind plus ±``deviation_ms`` m/s
perpendicular to the 0–``z_top_km`` shear vector.
Returns dict: rm_u, rm_v (right mover), lm_u, lm_v (left mover),
mean_u, mean_v.
"""
z = np.asarray(z, dtype=float)
u = np.asarray(u, dtype=float)
v = np.asarray(v, dtype=float)
mask = z <= z_top_km * 1000.0
if mask.sum() < 2:
return {"rm_u": 0.0, "rm_v": 0.0, "lm_u": 0.0, "lm_v": 0.0,
"mean_u": 0.0, "mean_v": 0.0}
mean_u = float(np.trapezoid(u[mask], z[mask]) / (z[mask][-1] - z[mask][0]))
mean_v = float(np.trapezoid(v[mask], z[mask]) / (z[mask][-1] - z[mask][0]))
shr_u = float(u[mask][-1] - u[mask][0])
shr_v = float(v[mask][-1] - v[mask][0])
mag = max(np.hypot(shr_u, shr_v), 1e-6)
# Right mover: + deviation perpendicular to shear (rotated +90°)
rm_u = mean_u + deviation_ms * shr_v / mag
rm_v = mean_v - deviation_ms * shr_u / mag
# Left mover: − deviation
lm_u = mean_u - deviation_ms * shr_v / mag
lm_v = mean_v + deviation_ms * shr_u / mag
return {
"rm_u": rm_u, "rm_v": rm_v,
"lm_u": lm_u, "lm_v": lm_v,
"mean_u": mean_u, "mean_v": mean_v,
}
def srh(
z: np.ndarray,
u: np.ndarray,
v: np.ndarray,
storm_u: float,
storm_v: float,
z_bot_m: float,
z_top_m: float,
) -> float:
"""Storm-Relative Helicity (m² s⁻²) for the specified layer.
Uses the discrete hodograph-area formula:
SRH = Σ [(u_{n+1} − cu)(v_n − cv) − (u_n − cu)(v_{n+1} − cv)]
"""
z = np.asarray(z, dtype=float)
u = np.asarray(u, dtype=float)
v = np.asarray(v, dtype=float)
mask = (z >= z_bot_m) & (z <= z_top_m)
u_l = u[mask] - storm_u
v_l = v[mask] - storm_v
if len(u_l) < 2:
return 0.0
return float(np.sum((u_l[1:] - u_l[:-1]) * (v_l[1:] + v_l[:-1]) -
(v_l[1:] - v_l[:-1]) * (u_l[1:] + u_l[:-1])) * 0.5)
def updraft_helicity(
w3d: np.ndarray,
zeta3d: np.ndarray,
z: np.ndarray,
z_bot_m: float,
z_top_m: float,
) -> float:
"""Updraft Helicity UH = ∫ w ζ_z dz over the updraft core center column."""
z = np.asarray(z, dtype=float)
mask = (z >= z_bot_m) & (z <= z_top_m)
if mask.sum() < 2:
return 0.0
cx = w3d.shape[0] // 2
cy = w3d.shape[1] // 2
w_col = w3d[cx, cy, mask]
z_col = zeta3d[cx, cy, mask]
return float(np.trapezoid(w_col * z_col, z[mask]))
def bulk_wind_shear(z: np.ndarray, u: np.ndarray, v: np.ndarray,
z_bot_m: float, z_top_m: float) -> float:
"""Bulk wind shear magnitude (m/s) in a layer."""
z = np.asarray(z, dtype=float)
u = np.asarray(u, dtype=float)
v = np.asarray(v, dtype=float)
u_bot = float(np.interp(z_bot_m, z, u))
v_bot = float(np.interp(z_bot_m, z, v))
u_top = float(np.interp(z_top_m, z, u))
v_top = float(np.interp(z_top_m, z, v))
return float(np.hypot(u_top - u_bot, v_top - v_bot))
def collect_diagnostics(
z: np.ndarray,
snd: dict,
parcel: dict,
u_hodo: np.ndarray,
v_hodo: np.ndarray,
z_hodo: np.ndarray,
w3d: np.ndarray,
zeta3d: np.ndarray,
) -> dict:
"""Compute and return all sounding/storm-scale diagnostics as a flat dict."""
z_hodo = np.asarray(z_hodo, dtype=float) * 1000.0 # km → m
u_env = np.interp(z, z_hodo, np.asarray(u_hodo, dtype=float))
v_env = np.interp(z, z_hodo, np.asarray(v_hodo, dtype=float))
bunk = bunkers_storm_motion(z_hodo, u_hodo, v_hodo)
srh_02 = srh(z_hodo, u_hodo, v_hodo, bunk["rm_u"], bunk["rm_v"], 0.0, 2000.0)
srh_25 = srh(z_hodo, u_hodo, v_hodo, bunk["rm_u"], bunk["rm_v"], 2000.0, 5000.0)
uh_02 = updraft_helicity(w3d, zeta3d, z, 0.0, 2000.0)
uh_25 = updraft_helicity(w3d, zeta3d, z, 2000.0, 5000.0)
bws_06 = bulk_wind_shear(z_hodo, u_hodo, v_hodo, 0.0, 6000.0)
cx = w3d.shape[0] // 2
cy = w3d.shape[1] // 2
w_max = float(np.max(w3d[cx, cy, :]))
return {
"CAPE": parcel["CAPE"],
"CIN": parcel["CIN"],
"LCL_m": parcel["LCL_m"],
"LFC_m": parcel["LFC_m"],
"EL_m": parcel["EL_m"],
"z_top_m": parcel.get("z_top_m", parcel["EL_m"]),
"SRH_02": srh_02,
"SRH_25": srh_25,
"UH_02": uh_02,
"UH_25": uh_25,
"BWS_06": bws_06,
"w_max": w_max,
"storm_u": bunk["rm_u"],
"storm_v": bunk["rm_v"],
"lm_u": bunk["lm_u"],
"lm_v": bunk["lm_v"],
"mean_u": bunk["mean_u"],
"mean_v": bunk["mean_v"],
}
|