compute_wind_speed_height.py
#!/usr/bin/env python ''' Copyright 2019 ECMWF. This software is licensed under the terms of the Apache Licence Version 2.0 which can be obtained at http://www.apache.org/licenses/LICENSE-2.0 In applying this licence, ECMWF does not waive the privileges and immunities granted to it by virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction. ************************************************************************** Function : compute_wind_speed_height Author (date) : Cristian Simarro (20/11/2015) modified: Xavi Abellan (03/12/2018) - compatibilty with Python 3 Category : COMPUTATION OneLineDesc : Computes the u/v components of the wind at certain height Description : Computes computes the u/v components of the wind at certain height. First, it calculates the geopotential of each model level. Once the requested height is found between two model levels, the program will vertically interpolate the u/v component values. Based on code from Nils Wedi, the IFS documentation: https://www.ecmwf.int/en/forecasts/documentation-and-support/changes-ecmwf-model/ifs-documentation part III. Dynamics and numerical procedures optimised implementation by Dominique Lucas. ported to Python by Cristian Simarro Parameters : -w wind - height in meters you want to know u/v components tq.grib - grib file with all the levelist of t and q uv.grib - grib file with all the levelists of u/v zlnsp.grib - grib file with levelist 1 for params z and lnsp -o output (optional) - name of the output file (default='uv_out_<wind>.grib') Return Value : output (default='uv_out_<wind>.grib') A fieldset the u/v components values for at the specified height Dependencies : None Example Usage : compute_wind_speed_height.py tq.grib zlnsp.grib uv.grib -w 100 ''' from __future__ import print_function import sys import argparse import numpy as np from eccodes import (codes_index_new_from_file, codes_index_get, codes_get, codes_index_select, codes_new_from_index, codes_set, codes_index_add_file, codes_get_array, codes_get_values, codes_index_release, codes_release, codes_set_values, codes_write) R_D = 287.06 R_G = 9.80665 def parse_args(): ''' Parse program arguments using ArgumentParser''' parser = argparse.ArgumentParser( description='Python tool to calculate the wind at certain height') parser.add_argument('-w', '--height', required=True, type=int, help='height to calculate the wind components') parser.add_argument('-o', '--output', help='name of the output file') parser.add_argument('t_q', metavar='tq.grib', type=str, help=('grib file with temperature(t) and humidity(q)', 'for the model levels')) parser.add_argument('z_lnsp', metavar='zlnsp.grib', type=str, help=('grib file with geopotential(z) and Logarithm', 'of surface pressure(lnsp) for the ml=1')) parser.add_argument('u_v', metavar='uv.grib', type=str, help=('grib file with u and v component of wind for', 'the model levels')) args = parser.parse_args() if not args.output: args.output = 'uv_out_%sm.grib' % args.height return args def main(): '''Main function''' args = parse_args() print('Arguments: %s' % ", ".join( ['%s: %s' % (k, v) for k, v in vars(args).items()])) fout = open(args.output, 'wb') index_keys = ['date', 'time', 'shortName', 'level', 'step'] idx = codes_index_new_from_file(args.z_lnsp, index_keys) codes_index_add_file(idx, args.t_q) if 'u_v' in args: codes_index_add_file(idx, args.u_v) # iterate date for date in codes_index_get(idx, 'date'): codes_index_select(idx, 'date', date) # iterate step for time in codes_index_get(idx, 'time'): codes_index_select(idx, 'time', time) values = get_initial_values(idx, keep_sample=True) if 'height' in args: values['height'] = args.height values['gh'] = args.height * R_G + values['z'] if 'levelist' in args: values['levelist'] = args.levelist # iterate step all but geopotential z which is always step 0 (an) for step in codes_index_get(idx, 'step'): codes_index_select(idx, 'step', step) # surface pressure try: values['sp'] = get_surface_pressure(idx) production_step(idx, values, fout) except WrongStepError: if step != '0': raise try: codes_release(values['sample']) except KeyError: pass codes_index_release(idx) fout.close() def get_initial_values(idx, keep_sample=False): '''Get the values of surface z, pv and number of levels ''' codes_index_select(idx, 'level', 1) codes_index_select(idx, 'step', 0) codes_index_select(idx, 'shortName', 'z') gid = codes_new_from_index(idx) values = {} # surface geopotential values['z'] = codes_get_values(gid) values['pv'] = codes_get_array(gid, 'pv') values['nlevels'] = codes_get(gid, 'NV', int) // 2 - 1 check_max_level(idx, values) if keep_sample: values['sample'] = gid else: codes_release(gid) return values def check_max_level(idx, values): '''Make sure we have all the levels required''' # how many levels are we computing? max_level = max(codes_index_get(idx, 'level', int)) if max_level != values['nlevels']: print('%s [WARN] total levels should be: %d but it is %d' % (sys.argv[0], values['nlevels'], max_level), file=sys.stderr) values['nlevels'] = max_level def get_surface_pressure(idx): '''Get the surface pressure for date-time-step''' codes_index_select(idx, 'level', 1) codes_index_select(idx, 'shortName', 'lnsp') gid = codes_new_from_index(idx) if gid is None: raise WrongStepError() if codes_get(gid, 'gridType', str) == 'sh': print('%s [ERROR] fields must be gridded, not spectral' % sys.argv[0], file=sys.stderr) sys.exit(1) # surface pressure sfc_p = np.exp(codes_get_values(gid)) codes_release(gid) return sfc_p def get_ph_levs(values, level): '''Return the presure at a given level and the next''' a_coef = values['pv'][0:values['nlevels'] + 1] b_coef = values['pv'][values['nlevels'] + 1:] ph_lev = a_coef[level - 1] + (b_coef[level - 1] * values['sp']) ph_levplusone = a_coef[level] + (b_coef[level] * values['sp']) return ph_lev, ph_levplusone def compute_z_level(idx, lev, values, z_h): '''Compute z at half & full level for the given level, based on t/q/sp''' # select the levelist and retrieve the vaules of t and q # t_level: values for t # q_level: values for q codes_index_select(idx, 'level', lev) codes_index_select(idx, 'shortName', 't') gid = codes_new_from_index(idx) t_level = codes_get_values(gid) codes_release(gid) codes_index_select(idx, 'shortName', 'q') gid = codes_new_from_index(idx) q_level = codes_get_values(gid) codes_release(gid) # compute moist temperature t_level = t_level * (1. + 0.609133 * q_level) # compute the pressures (on half-levels) ph_lev, ph_levplusone = get_ph_levs(values, lev) if lev == 1: dlog_p = np.log(ph_levplusone / 0.1) alpha = np.log(2) else: dlog_p = np.log(ph_levplusone / ph_lev) alpha = 1. - ((ph_lev / (ph_levplusone - ph_lev)) * dlog_p) t_level = t_level * R_D # z_f is the geopotential of this full level # integrate from previous (lower) half-level z_h to the # full level z_f = z_h + (t_level * alpha) # z_h is the geopotential of 'half-levels' # integrate z_h to next half level z_h = z_h + (t_level * dlog_p) return z_h, z_f def production_step(idx, values, fout): '''Produce u/v interpolated from ML for a given height''' # We want to integrate up into the atmosphere, starting at the # ground so we start at the lowest level (highest number) and # keep accumulating the height as we go. # See the IFS documentation, part III # For speed and file I/O, we perform the computations with # numpy vectors instead of fieldsets. out = {} params = ('u', 'v') # we need to get z and lnsp from the first level to do the # calculations z_h = values['z'] # height in geopotential z_f_prev = z_h # initialize values for the output for param in params: out[param] = np.zeros(values['sp'].size) found = [False for i in range(values['sp'].size)] for lev in list(reversed(list(range(1, values['nlevels'] + 1)))): z_h, z_f = compute_z_level(idx, lev, values, z_h) # retrieve u/v params for the current level for param in params: codes_index_select(idx, 'level', lev) # 136 codes_index_select(idx, 'shortName', param) gid = codes_new_from_index(idx) values[param] = codes_get_values(gid) codes_release(gid) if lev < values['nlevels']: codes_index_select(idx, 'level', lev + 1) # 137 gid = codes_new_from_index(idx) values['prev' + param] = codes_get_values(gid) codes_release(gid) else: values['prev' + param] = np.zeros(values['sp'].size) # search if the provided wind height converted to # geopotential (my_z) is between the current level (z_f) # and the previous one (z_f_prev) for i in range(z_f_prev.size): if found[i]: continue if values['gh'][i] >= z_f_prev[i] and values['gh'][i] < z_f[i]: found[i] = True # when found, interpolate vertically to get the # value and store it in out[param] to be written # at the end for param in params: res = (((float(values[param][i]) * (values['gh'][i] - z_f_prev[i])) + (float(values['prev' + param][i]) * (z_f[i] - values['gh'][i]))) / (z_f[i] - z_f_prev[i])) out[param][i] = res # update z_f_prev z_f_prev = z_f # simple error check for i in range(values['sp'].size): if not found[i]: print('point ', i, 'not found...') # write the values in the fout file for param in params: codes_set(values['sample'], 'shortName', param) codes_set(values['sample'], 'typeOfLevel', 'heightAboveGround') codes_set(values['sample'], 'level', values['height']) codes_set_values(values['sample'], out[param]) codes_write(values['sample'], fout) class WrongStepError(Exception): ''' Exception capturing wrong step''' pass if __name__ == '__main__': main()