Created
August 15, 2021 03:17
-
-
Save jthielen/42ba37b464fa10755bad30946fa7fba6 to your computer and use it in GitHub Desktop.
WRF Post-Process to CF Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
from itertools import product | |
from multiprocessing import Pool, Process | |
import numpy as np | |
import xarray as xr | |
import metpy.calc as mpcalc | |
from datetime import datetime | |
import cartopy.crs as ccrs | |
import cartopy.feature as cfeature | |
import pyproj | |
from metpy.plots.mapping import CFProjection | |
def generate_cf_projection_from_wrf(wrf_dataset): | |
""" | |
Create a MetPy CFProjection object from the attributes of a WRF dataset (as an | |
xarray Dataset). | |
Right now only works with LCC | |
""" | |
cf_attrs = { | |
'grid_mapping_name': 'lambert_conformal_conic', | |
'earth_radius': 6370000, | |
'standard_parallel': [wrf_dataset.TRUELAT1, wrf_dataset.TRUELAT2], | |
'longitude_of_central_meridian': wrf_dataset.STAND_LON, | |
'latitude_of_projection_origin': wrf_dataset.MOAD_CEN_LAT | |
} | |
return CFProjection(cf_attrs) | |
def generate_dimension_coordinates_from_wrf(wrf_dataset): | |
""" | |
Create coordinates corresponding to the bottom_top, south_north, and west_east | |
dimensions in a WRF dataset (as an xarray Dataset). | |
Must already have a MetPy crs coordinate added. Cannot be a moving domain. | |
""" | |
# Obtain CRSs | |
data_crs = wrf_dataset['metpy_crs'].metpy.cartopy_crs | |
latlon_crs = ccrs.Geodetic(globe=ccrs.Globe(ellipse='WGS84')) | |
# Obtain grid parameters | |
dx, dy = wrf_dataset.DX, wrf_dataset.DY | |
nx, ny = (wrf_dataset.dims[i] for i in ['west_east', 'south_north']) | |
# Compute easting and northing of domain center | |
e, n = data_crs.transform_point(wrf_dataset.CEN_LON, wrf_dataset.CEN_LAT, latlon_crs) | |
# Find easting and northing of lower-left corner of domain | |
x0 = -(nx - 1) / 2. * dx + e | |
y0 = -(ny - 1) / 2. * dy + n | |
# Build horizontal grid values | |
x, y = np.arange(nx) * dx + x0, np.arange(ny) * dy + y0 | |
# Build DataArrays | |
eta_attrs = wrf_dataset['ZNU'].attrs | |
eta_attrs['axis'] = 'Z' | |
eta_attrs['standard_name'] = 'atmosphere_hybrid_sigma_pressure_coordinate' | |
bottom_top = xr.DataArray(wrf_dataset['ZNU'][0], dims='bottom_top', attrs=eta_attrs) | |
south_north = xr.DataArray(y, dims='south_north', attrs={'axis': 'Y', 'units': 'm', | |
'standard_name': 'projection_y_coordinate'}) | |
west_east = xr.DataArray(x, dims='west_east', attrs={'axis': 'X', 'units': 'm', | |
'standard_name': 'projection_x_coordinate'}) | |
return bottom_top, south_north, west_east | |
def process_run(run_date, config, domain): | |
"""Function to process an individual run.""" | |
print('\n------------------\n') | |
print(run_date, config, domain) | |
print('\n------------------\n') | |
ds = xr.open_dataset(run_date.strftime('/glade/scratch/jthielen/' | |
'{config}/%Y%m%d/wrfout_{domain}_%Y-%m-%d_12:00:00'.format(config=config, domain=domain))) | |
ds = ds.assign_coords(metpy_crs=generate_cf_projection_from_wrf(ds)) | |
ds['bottom_top'], ds['south_north'], ds['west_east'] = generate_dimension_coordinates_from_wrf(ds) | |
ds = ds.swap_dims({'Time': 'XTIME'}).rename({'XTIME': 'time'}) | |
ds = ds.assign_coords(latitude=ds['XLAT'][0].drop(['time', 'XLAT', 'XLONG']), | |
longitude=ds['XLONG'][0].drop(['time', 'XLAT', 'XLONG'])).drop( | |
['XLAT', 'XLONG', 'XLAT_U', 'XLAT_V', 'XLONG_U', 'XLONG_V']) | |
eta_attrs = ds['ZNW'].attrs | |
eta_attrs['axis'] = 'Z' | |
eta_attrs['standard_name'] = 'atmosphere_hybrid_sigma_pressure_coordinate' | |
ds['bottom_top_stag'] = xr.DataArray(ds['ZNW'][0], dims='bottom_top_stag', attrs=eta_attrs) | |
print(ds.coords) | |
ds_output = xr.Dataset(coords=ds.coords) | |
print('land_use_category') | |
ds_output['land_use_category'] = ds['LU_INDEX'][0].drop('time') | |
print('u') | |
ds_output['u_at_10m'] = ds['U10'] | |
ds_output['u_at_10m'].attrs = {'units': 'm/s', 'standard_name': 'x_wind'} | |
ds_output['u'] = ((ds['U'][..., :-1] + ds['U'][..., 1:]) / 2).rename({'west_east_stag': 'west_east'}) # Destagger | |
ds_output['u'].attrs = {'units': 'm/s', 'standard_name': 'x_wind'} | |
print('v') | |
ds_output['v_at_10m'] = ds['V10'] | |
ds_output['v_at_10m'].attrs = {'units': 'm/s', 'standard_name': 'y_wind'} | |
ds_output['v'] = ((ds['V'][..., :-1, :] + ds['V'][..., 1:, :]) / 2).rename({'south_north_stag': 'south_north'}) # Destagger | |
ds_output['v'].attrs = {'units': 'm/s', 'standard_name': 'y_wind'} | |
print('w') | |
ds_output['w'] = ds['W'] | |
ds_output['w'].attrs = {'units': 'm/s', 'standard_name': 'upward_air_velocity'} | |
print('geopotential') | |
ds_output['geopotential'] = ds['PHB'] + ds['PH'] | |
ds_output['geopotential'].attrs = {'units': 'meter^2 / second^2', 'standard_name': 'geopotential'} | |
print('potential_temperature') | |
ds_output['potential_temperature_at_2m'] = ds['TH2'] | |
ds_output['potential_temperature_at_2m'].attrs = {'units': 'K', 'standard_name': 'air_potential_temperature'} | |
ds_output['potential_temperature'] = ds['T'] + 300 | |
ds_output['potential_temperature'].attrs = {'units': 'K', 'standard_name': 'air_potential_temperature'} | |
print('temperature') | |
ds_output['temperature_at_2m'] = ds['T2'] | |
ds_output['temperature_at_2m'].attrs = {'units': 'K', 'standard_name': 'air_temperature'} | |
print('pressure') | |
ds_output['pressure_at_surface'] = ds['PSFC'] | |
ds_output['pressure_at_surface'].attrs = {'units': 'Pa', 'standard_name': 'air_pressure'} | |
ds_output['pressure'] = ds['P'] + ds['PB'] | |
ds_output['pressure'].attrs = {'units': 'Pa', 'standard_name': 'air_pressure'} | |
print('turbulence_kinetic_energy') | |
ds_output['turbulence_kinetic_energy'] = ds['QKE'] / 2 | |
ds_output['turbulence_kinetic_energy'].attrs = {'units': 'm**2 / s**2', 'long_name': 'turbulent_kinetic_energy_from_mynn'} | |
print('length_scale') | |
ds_output['length_scale'] = ds['EL_PBL'] | |
ds_output['length_scale'].attrs = {'units': 'm', 'long_name': 'length_scale_from_pbl'} | |
print('terrain_height') | |
ds_output['terrain_height'] = ds['HGT'][0].drop('time') | |
ds_output['terrain_height'].attrs = {'units': 'm', 'standard_name': 'surface_altitude'} | |
print('friction_velocity') | |
ds_output['friction_velocity'] = ds['UST'] | |
ds_output['friction_velocity'].attrs = {'units': 'm/s', 'long_name': 'u*_in_similarity_theory'} | |
print('pbl_height') | |
ds_output['pbl_height'] = ds['PBLH'] | |
ds_output['pbl_height'].attrs = {'units': 'm', 'standard_name': 'atmosphere_boundary_layer_thickness'} | |
print('mixing_ratio') | |
ds_output['mixing_ratio_at_2m'] = ds['Q2'] | |
ds_output['mixing_ratio_at_2m'].attrs = {'units': '', 'standard_name': 'humidity_mixing_ratio'} | |
ds_output['mixing_ratio'] = ds['QVAPOR'] | |
ds_output['mixing_ratio'].attrs = {'units': '', 'standard_name': 'humidity_mixing_ratio'} | |
print('vegetation_category') | |
ds_output['vegetation_category'] = ds['IVGTYP'][0].drop('time') | |
ds_output['vegetation_category'].attrs = {'long_name': 'dominant_vegetation_category'} | |
print('soil_category') | |
ds_output['soil_category'] = ds['ISLTYP'][0].drop('time') | |
ds_output['soil_category'].attrs = {'long_name': 'dominant_soil_category'} | |
print('vegetation_fraction') | |
ds_output['vegetation_fraction'] = ds['VEGFRA'] | |
ds_output['vegetation_fraction'].attrs = {'standard_name': 'vegetation_area_fraction'} | |
print('leaf_area') | |
ds_output['leaf_area'] = ds['LAI'] | |
ds_output['leaf_area'].attrs = {'standard_name': 'leaf_area_index'} | |
print('precipitation_accumulation') | |
ds_output['precipitation_accumulation'] = ds['RAINNC'] | |
ds_output['precipitation_accumulation'].attrs = {'units': 'mm', 'standard_name': 'integral_of_lwe_precipitation_rate_wrt_time'} | |
print('cloud_fraction') | |
ds_output['cloud_fraction'] = ds['CLDFRA'] | |
ds_output['cloud_fraction'].attrs = {'units': '', 'standard_name': 'cloud_area_fraction_in_atmosphere_layer'} | |
print('surface_soil_temperature') | |
ds_output['surface_soil_temperature'] = ds['TMN'] | |
ds_output['surface_soil_temperature'].attrs = {'units': 'K', 'long_name': 'surface_soil_temperature'} | |
print('surface_heat_flux') | |
ds_output['surface_heat_flux'] = ds['HFX'] | |
ds_output['surface_heat_flux'].attrs = {'units': 'W m**-2', 'standard_name': 'surface_upward_heat_flux_in_air'} | |
print('surface_moisture_flux') | |
ds_output['surface_moisture_flux'] = ds['QFX'] | |
ds_output['surface_moisture_flux'].attrs = {'units': 'W m**-2', 'standard_name': 'surface_upward_heat_flux_in_air'} | |
print('surface_latent_heat_flux') | |
ds_output['surface_latent_heat_flux'] = ds['LH'] | |
ds_output['surface_latent_heat_flux'].attrs = {'units': 'W m**-2', 'standard_name': 'surface_upward_latent_heat_flux_in_air'} | |
print('accumulated_surface_heat_flux') | |
ds_output['accumulated_surface_heat_flux'] = ds['ACHFX'] | |
ds_output['accumulated_surface_heat_flux'].attrs = {'units': 'J m**-2', 'standard_name': 'integral_of_surface_upward_heat_flux_in_air_wrt_time'} | |
print('accumulated_surface_latent_heat_flux') | |
ds_output['accumulated_surface_latent_heat_flux'] = ds['ACLHF'] | |
ds_output['accumulated_surface_latent_heat_flux'].attrs = {'units': 'J m**-2', 'standard_name': 'integral_of_surface_upward_latent_heat_flux_in_air_wrf_time'} | |
print('potential_temperature_variance') | |
ds_output['potential_temperature_variance'] = ds['TSQ'] | |
ds_output['potential_temperature_variance'].attrs = {'units': 'K**2', 'long_name': 'variance_of_air_potential_temperature'} | |
print('bulk_richardson_number') | |
ds_output['bulk_richardson_number'] = ds['BR'] | |
ds_output['bulk_richardson_number'].attrs = {'units': '1', 'long_name': 'bulk_richardson_number'} | |
print('z_over_l') | |
ds_output['z_over_l'] = ds['ZOL'] | |
ds_output['z_over_l'].attrs = {'units': '1', 'long_name': 'dimensionless_most_z_over_l'} | |
print('skin_temperature') | |
ds_output['skin_temperature'] = ds['TSK'] | |
ds_output['skin_temperature'].attrs = {'units': 'K', 'long_name': 'surface_skin_temperature'} | |
print('surface_regime') | |
ds_output['surface_regime'] = ds['REGIME'] | |
ds_output['surface_regime'].attrs = {'long_name': 'surface_stability_regime_class'} | |
print('roughness_length') | |
ds_output['roughness_length'] = ds['ZNT'] | |
ds_output['roughness_length'].attrs = {'units': 'm', 'long_name': 'time_varying_roughness_length'} | |
print('timestep') | |
ds_output['timestep'] = ds['ITIMESTEP'] | |
ds_output['timestep'].attrs = {'long_name': 'model_time_index'} | |
for varname in ds_output: | |
ds_output[varname].attrs['grid_mapping'] = 'lambert_conformal' | |
ds_output['lambert_conformal'] = xr.DataArray(0, name='lambert_conformal', | |
attrs=ds_output['u'].metpy.crs.to_dict()) | |
ds_output.attrs['domain'] = domain | |
ds_output.attrs['IBC'] = config | |
print(ds_output) | |
ds_output = ds_output.drop('metpy_crs') | |
ds_output.to_netcdf(run_date.strftime('/glade/scratch/jthielen/cf_post/wrf_{config}_{domain}_%Y%m%d_1200_UTC.nc'.format(config=config, domain=domain))) | |
ds.close() | |
ds_output.close() | |
del ds | |
del ds_output | |
# Configuration for loop | |
""" | |
run_dates = [datetime(2017, 11, 19), | |
datetime(2017, 11, 23), | |
datetime(2017, 11, 26), | |
datetime(2017, 12, 7)] | |
configs = ['namanl', 'namfcst', 'gfsanl', 'gfsfcst'] | |
domains = ['d01', 'd02'] | |
""" | |
run_dates = [ | |
datetime(2017, 12, 2), | |
] | |
configs = ['prototype_3', 'prototype_4'] | |
#configs = ['baseline_4.2', 'baseline_4.2_mixlength_2', 'proto2_jahn_zl_and_sun'] | |
#configs = ['proto1_jahn_zl_and_sun'] | |
#domains = ['d01', 'd02'] | |
domains = ['d02'] | |
if __name__ == '__main__': | |
workers = 4 | |
p = Pool(workers) | |
p.starmap(process_run, product(run_dates, configs, domains), chunksize=workers) | |
print('All done.') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment