Scheduled Downtime
On Friday 21 April 2023 @ 5pm MT, this website will be down for maintenance and expected to return online the morning of 24 April 2023 at the latest

AFWA visibility logic from fortran to python

William.Hatheway

Active member
Just wanted to share my attempt at pulling out the AFWA visibility diagnostic from fortran into python code.

Please test and see what you think.



Python:
import numpy as np
from scipy.ndimage import gaussian_filter
from netCDF4 import Dataset
import wrf

def compute_afwa_visibility_miles(wrf_path):
    """
    AFWA surface visibility diagnostic from a WRF output.
    Returns:
        vis_miles  – visibility in miles
        alpha      – AFWA haze shape parameter
    """

    nc = Dataset(wrf_path)

    # --- Surface fields ---
    psfc = wrf.getvar(nc, "PSFC").values
    t2   = wrf.getvar(nc, "T2").values
    rh2  = wrf.getvar(nc, "rh2").values
    q2   = wrf.getvar(nc, "Q2").values

    # Hydrometeors (lowest model level)
    rain = wrf.getvar(nc, "QRAIN")[0,:,:].values
    snow = wrf.getvar(nc, "QSNOW")[0,:,:].values
    grau = wrf.getvar(nc, "QGRAUP")[0,:,:].values

    # Winds
    u10 = wrf.getvar(nc, "U10").values
    v10 = wrf.getvar(nc, "V10").values
    wind10 = np.sqrt(u10*u10 + v10*v10)

    # Precipitable water
    try:
        pw = wrf.getvar(nc, "pw")
        pw = pw[0,:,:].values if "Time" in pw.dims else pw.values
    except:
        pw = np.full_like(psfc, 20.0)

    # 125 m wind (fallback to 10 m)
    try:
        hgt = wrf.getvar(nc, "height_agl")[0,:,:,:]
        ua  = wrf.getvar(nc, "ua")[0,:,:,:]
        va  = wrf.getvar(nc, "va")[0,:,:,:]
        u125 = wrf.interplevel(ua, hgt, 125.0).values
        v125 = wrf.interplevel(va, hgt, 125.0).values
        wind125 = np.sqrt(u125*u125 + v125*v125)
    except:
        wind125 = wind10

    # -------------------------
    # AFWA VISIBILITY EQUATIONS
    # -------------------------
    R = 287.05
    rho = psfc / (R * t2)

    br = 1.1   * (1000*rho*(rain+grau))**0.75
    bs = 10.36 * (1000*rho*snow)**0.78
    ext  = (br + bs) / 1000.0

    visfactor = 3.912
    vis_hyd = np.where(ext > 0, visfactor / ext, 9.9e5)

    qsafe = np.where(q2 > 0, q2, np.nan)
    mask  = ~np.isnan(qsafe)

    vis_haze = np.full_like(psfc, 9.9e5)
    vis_haze_local = 1500.0 * (105 - rh2[mask]) * (5.0 / np.minimum(1000*qsafe[mask], 5.0))
    vis_haze[mask] = vis_haze_local

    alpha = np.full_like(psfc, 3.6)
    if np.any(mask):
        a_local = (
            0.1
            + pw[mask]/25.0
            + wind125[mask]/3.0
            + (100 - rh2[mask])/10.0
            + 1.0/(1000*qsafe[mask])
        )
        alpha[mask] = np.minimum(a_local, 3.6)

    # Final: min(hydrometeor, haze)
    vis_m = np.where(vis_hyd < vis_haze, vis_hyd, vis_haze)
    vis_m = gaussian_filter(vis_m, sigma=1)

    # Convert to miles
    vis_miles = vis_m * 0.000621371

    return vis_miles, alpha




Python:
import numpy as np
from scipy.ndimage import gaussian_filter
from netCDF4 import Dataset
import wrf

def compute_afwa_visibility_km(wrf_path):
    """
    AFWA surface visibility diagnostic from a WRF output.
    Returns:
        vis_km  – visibility in kilometers
        alpha   – AFWA haze shape parameter
    """

    nc = Dataset(wrf_path)

    # --- Surface fields ---
    psfc = wrf.getvar(nc, "PSFC").values
    t2   = wrf.getvar(nc, "T2").values
    rh2  = wrf.getvar(nc, "rh2").values
    q2   = wrf.getvar(nc, "Q2").values

    # Hydrometeors (lowest model level)
    rain = wrf.getvar(nc, "QRAIN")[0,:,:].values
    snow = wrf.getvar(nc, "QSNOW")[0,:,:].values
    grau = wrf.getvar(nc, "QGRAUP")[0,:,:].values

    # Winds
    u10 = wrf.getvar(nc, "U10").values
    v10 = wrf.getvar(nc, "V10").values
    wind10 = np.sqrt(u10*u10 + v10*v10)

    # Precipitable water
    try:
        pw = wrf.getvar(nc, "pw")
        pw = pw[0,:,:].values if "Time" in pw.dims else pw.values
    except:
        pw = np.full_like(psfc, 20.0)

    # 125 m wind (fallback to 10 m)
    try:
        hgt = wrf.getvar(nc, "height_agl")[0,:,:,:]
        ua  = wrf.getvar(nc, "ua")[0,:,:,:]
        va  = wrf.getvar(nc, "va")[0,:,:,:]
        u125 = wrf.interplevel(ua, hgt, 125.0).values
        v125 = wrf.interplevel(va, hgt, 125.0).values
        wind125 = np.sqrt(u125*u125 + v125*v125)
    except:
        wind125 = wind10

    # -------------------------
    # AFWA VISIBILITY EQUATIONS
    # -------------------------
    R = 287.05
    rho = psfc / (R * t2)

    br = 1.1   * (1000*rho*(rain+grau))**0.75
    bs = 10.36 * (1000*rho*snow)**0.78
    ext  = (br + bs) / 1000.0

    visfactor = 3.912
    vis_hyd = np.where(ext > 0, visfactor / ext, 9.9e5)

    qsafe = np.where(q2 > 0, q2, np.nan)
    mask  = ~np.isnan(qsafe)

    vis_haze = np.full_like(psfc, 9.9e5)
    vis_haze_local = 1500.0 * (105 - rh2[mask]) * (5.0 / np.minimum(1000*qsafe[mask], 5.0))
    vis_haze[mask] = vis_haze_local

    alpha = np.full_like(psfc, 3.6)
    if np.any(mask):
        a_local = (
            0.1
            + pw[mask]/25.0
            + wind125[mask]/3.0
            + (100 - rh2[mask])/10.0
            + 1.0/(1000*qsafe[mask])
        )
        alpha[mask] = np.minimum(a_local, 3.6)

    # Final: min(hydrometeor, haze)
    vis_m = np.where(vis_hyd < vis_haze, vis_haze, vis_hyd)
    vis_m = gaussian_filter(vis_m, sigma=1)

    # Convert to km
    vis_km = vis_m / 1000.0

    return vis_km, alpha
 
Top